Skip to content

Commit a306518

Browse files
committed
✨ feat: widget-builder builtin plugin with stlite console rendering, capability gating, and SessionTool protocol
- Add `builtin_plugins/widget_builder` as a bundled plugin: `SubmitWidgetTool`, extensible AST lint pipeline, `st-widget-builder` agent + skill, Jinja2 prompt template, and reusable Streamlit component library (PlantUMLCard, GitHub repo card, stock ticker, search result card) - Add `capabilities.py`: pure `parse_capabilities`, `filter_by_capabilities`, and `overlay_capabilities` helpers for frontmatter-driven capability gating across agents, skills, and plugins - Add `session_tools.py`: `SessionTool` Protocol and `SessionToolRegistry`; retrofit `ExitPlanModeTool` to satisfy the protocol; replace single `_exit_plan_mode_tool` slot in `ToolUseLoop` with a `_session_tools` list built from registry + allowed_tools - Extend `AgentRegistry.register()` with `capabilities=` and `plugin_root=` params; `get()` and `render_catalog()` accept `session_capabilities` for gated visibility; add `visible_for()` - Add `substitute_agent_body()` in `spawn_agent.py` for `${SESSION_ID}` / `${CLAUDE_PLUGIN_ROOT}` / bash-style `${VAR:-default}` expansion in agent bodies; registered agent types' model is now authoritative (ignores LLM-supplied model arg) - Auto-scan `builtin_plugins/` at plugin discovery; propagate `plugin_root` and capability tuples through `Orchestrator` → `AgentRegistry` / `SkillRegistry` / `SessionToolRegistry` - Parse `X-Meeseeks-Capabilities` request header in API backend; thread `session_capabilities` end-to-end through `Orchestrator` → `SpawnAgentTool` → `ToolUseLoop` - Add `StliteWidgetPanel`, `CheckAgentsCard`, `SpawnAgentCard`, `FiveLeafBloomLoader`, and `WidgetCard` to the console; wire `widget_ready` event parsing and render inline stlite widgets in the conversation timeline - Persist completed child handles in `AgentHypervisor` so `check_agents` surfaces results; add `list_visible()` helper; emit structured `check_agents` payload in tool card - Fix: skip CWD root injection for non-registry tools; thread session_capabilities into slash-command skill lookup; reject path traversal in `widget_id`; scope `submit_widget` schema to widget-builder sessions only; force full PWA refresh on service-worker update - Migrate docs screenshots to PNG; add Capabilities, Widgets, and core-orchestration docs pages; refresh plugins, skills, web-api, and landing pages
1 parent 7073d05 commit a306518

106 files changed

Lines changed: 7350 additions & 286 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
run: |
3030
python -m pip install --upgrade pip
3131
python -m pip install uv
32-
uv sync --group docs --extra tools --extra ha
32+
uv sync --group docs --extra ha
3333
3434
- name: Verify mkdocstrings imports
3535
run: .venv/bin/python -c "import meeseeks_core.common; print(meeseeks_core.common.__name__)"

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ https://github.com/user-attachments/assets/78754e8f-828a-4c54-9e97-29cbeacbc3bc
2323
2424
## Overview
2525

26-
Meeseeks is an AI assistant modeled as a conversation state machine. A top-level session binds an LLM to a filtered tool set via `bind_tools` and advances through a `submitted → running → terminal` lifecycle. Where parallelism is useful, the session issues `spawn_agent`; the hypervisor admits child sessions under a concurrency budget, constrains their tool scope, and resolves them into one of four terminal states — `completed`, `failed`, `cancelled`, or `rejected`. Transcripts are persisted per session, long histories are compacted, and summaries are retained across compactions.
26+
Meeseeks is an AI assistant modeled as a conversation state machine. A top-level session binds an LLM to a filtered tool set via `bind_tools` and advances through a `submitted → running → terminal` lifecycle. Where parallelism is useful, the session issues `spawn_agent`; the hypervisor admits child sessions under a concurrency budget, constrains their tool scope, and resolves them into one of four terminal states — `completed`, `failed`, `cancelled`, or `rejected`. Transcripts are persisted per session, long histories are compacted, and summaries are retained across compactions. The plugin and skill layers follow the Claude-ecosystem Agent Skills + Plugin standard, and the console renders interactive [stlite (Streamlit-in-WASM) widgets](docs/features-widgets.md) inline in the conversation timeline via the bundled `widget-builder` plugin.
2727

2828
### Meeseeks Console
2929

@@ -35,8 +35,8 @@ The web console provides a task orchestration frontend backed by the REST API. I
3535
<th>Console landing page</th>
3636
</tr>
3737
<tr>
38-
<td align="center"><img src="docs/meeseeks-console-02-tasks.jpg" alt="Meeseeks task detail page" height="360px"></td>
39-
<td align="center"><img src="docs/meeseeks-console-01-front.jpg" alt="Meeseeks console landing page" height="360px"></td>
38+
<td align="center"><img src="docs/meeseeks-console-02-tasks.png" alt="Meeseeks task detail page" height="360px"></td>
39+
<td align="center"><img src="docs/meeseeks-console-01-front.png" alt="Meeseeks console landing page" height="360px"></td>
4040
</tr>
4141
</table>
4242

@@ -59,6 +59,12 @@ The web console provides a task orchestration frontend backed by the REST API. I
5959
<td align="center"><img src="docs/meeseeks-console-05-plugins.jpg" alt="Plugins page with installed plugins and marketplace listings" height="300px"></td>
6060
<td align="center"><img src="docs/meeseeks-console-06-projects.jpg" alt="Projects page showing virtual workspaces shared across sessions" height="300px"></td>
6161
</tr>
62+
<tr>
63+
<th colspan="2">Widgets inline in chat</th>
64+
</tr>
65+
<tr>
66+
<td colspan="2" align="center"><img src="docs/meeseeks-console-07-widgets.png" alt="Stock ticker and GitHub repo card widgets rendered inline in the Meeseeks Console" width="100%"></td>
67+
</tr>
6268
</table>
6369

6470
## Features
@@ -80,9 +86,10 @@ The web console provides a task orchestration frontend backed by the REST API. I
8086

8187
### Tooling and integrations
8288
- (✅) **Tool registry:** Discovers local tools and MCP tools via persistent connection pool with automatic reconnection and config change detection.
83-
- (✅) **Skills:** Supports the [Agent Skills](https://agentskills.io) open standard. Place `SKILL.md` files in `~/.claude/skills/` or `.claude/skills/` to teach the assistant reusable workflows. Skills can be invoked via `/skill-name` slash commands or auto-activated by the LLM.
89+
- (✅) **Skills:** Supports the [Agent Skills](https://agentskills.io) open standard. Place `SKILL.md` files in `~/.claude/skills/` or `.claude/skills/` to teach the assistant reusable workflows. Skills can be invoked via `/skill-name` slash commands or auto-activated by the LLM. `requires-capabilities` frontmatter gates a skill to sessions that advertise the matching capability bundle.
8490
- (✅) **Configurable file editing:** Two built-in edit mechanisms — Aider-style SEARCH/REPLACE blocks and per-file structured patch (`file_path` / `old_string` / `new_string`). Select via `agent.edit_tool` in config, or let the system auto-select based on model identity. Different models perform better with different formats; the choice is transparent to the rest of the stack.
85-
- (✅) **Plugin system:** Discover, install, and manage plugins from configured marketplaces. Plugins can provide agent definitions, skills, hooks, and MCP tool integrations. Managed via the CLI (`/plugins`), console UI, or REST API.
91+
- (✅) **Plugin system:** Discover, install, and manage plugins from configured marketplaces, alongside a built-in plugin scan path for first-party bundles. Plugins can provide agent definitions, skills, hooks, MCP tool integrations, and per-agent stateful session tools via the `SessionTool` protocol. `requires-capabilities` frontmatter plus the `X-Meeseeks-Capabilities` request header gate capability bundles to compatible sessions, and `${CLAUDE_PLUGIN_ROOT}` substitution lets plugins reference their own assets by absolute path. Managed via the CLI (`/plugins`), console UI, or REST API.
92+
- (✅) **Interactive widgets:** Inline [stlite (Streamlit-in-WASM) widgets](docs/features-widgets.md) rendered in the conversation timeline via the bundled `widget-builder` plugin. A sub-agent writes a two-file widget (`app.py` + `data.json`), calls `submit_widget`, and the console mounts the result in a sandboxed Web Worker — no server round-trip, no CORS. Ships with a component library (GitHubRepoCard, SearchResultCard, StockTickerCard) and an AST import-allowlist lint that returns line-numbered feedback to the generating agent.
8693
- (✅) **Native LSP integration:** Opt-in code intelligence via `lsp_tool` (pygls/lsprotocol). Supports diagnostics, go-to-definition, find-references, and hover. Built-in servers: pyright (Python), typescript-language-server (TS/JS), gopls (Go), rust-analyzer (Rust) — auto-discovered on the PATH. Passive diagnostics inject automatically after file edits. Configure via `agent.lsp` in config.
8794
- (✅) **Web IDE:** Opt-in per-session code-server containers for browser-based editing, accessible from the console via "Open in Web IDE".
8895
- (✅) **Local file + shell tools:** Built-in tools for file reads, directory listing, and shell commands (approval-gated).

apps/meeseeks_api/src/meeseeks_api/backend.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,16 @@ def post(self) -> tuple[dict, int]:
610610
if session_tag:
611611
runtime.session_store.tag_session(session_id, session_tag)
612612
context_payload = _build_context_payload(payload)
613+
# Capability header — clients may declare supported features (e.g. "stlite"
614+
# for the widget builder). Parse comma-separated values and persist in the
615+
# session context so the Orchestrator can conditionally enable agent types.
616+
capabilities_header = request.headers.get("X-Meeseeks-Capabilities", "")
617+
if capabilities_header:
618+
client_capabilities = [
619+
c.strip() for c in capabilities_header.split(",") if c.strip()
620+
]
621+
if client_capabilities:
622+
context_payload["client_capabilities"] = client_capabilities
613623
# Include project in context if provided
614624
try:
615625
project_cwd = _resolve_project_cwd(payload)
@@ -652,6 +662,14 @@ def post(self, session_id: str) -> tuple[dict, int]:
652662
return {"message": "Session is already running."}, 409
653663

654664
context_payload = _build_context_payload(request_data)
665+
# Capability header — same parsing as Sessions.post() for per-query declarations.
666+
capabilities_header = request.headers.get("X-Meeseeks-Capabilities", "")
667+
if capabilities_header:
668+
client_capabilities = [
669+
c.strip() for c in capabilities_header.split(",") if c.strip()
670+
]
671+
if client_capabilities:
672+
context_payload["client_capabilities"] = client_capabilities
655673
# Use model from context if provided, else config default
656674
if "model" not in context_payload:
657675
context_payload["model"] = get_config_value("llm", "default_model", default="unknown")
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""Tests for X-Meeseeks-Capabilities header parsing in the API."""
2+
3+
# mypy: ignore-errors
4+
from meeseeks_api import backend
5+
from meeseeks_core.session_store import SessionStore
6+
7+
8+
def _reset_backend(tmp_path, monkeypatch):
9+
backend.session_store = SessionStore(root_dir=str(tmp_path))
10+
backend.runtime = backend.SessionRuntime(session_store=backend.session_store)
11+
backend.notification_store = backend.NotificationStore(root_dir=str(tmp_path))
12+
backend.share_store = backend.ShareStore(root_dir=str(tmp_path))
13+
backend.notification_service = backend.NotificationService(
14+
backend.notification_store,
15+
backend.runtime.session_store,
16+
)
17+
18+
19+
def test_session_create_stores_client_capabilities(monkeypatch, tmp_path):
20+
"""X-Meeseeks-Capabilities header is parsed and persisted in context event."""
21+
_reset_backend(tmp_path, monkeypatch)
22+
client = backend.app.test_client()
23+
24+
resp = client.post(
25+
"/api/sessions",
26+
json={},
27+
headers={
28+
"X-API-KEY": backend.MASTER_API_TOKEN,
29+
"X-Meeseeks-Capabilities": "stlite, foo",
30+
},
31+
)
32+
assert resp.status_code == 200
33+
session_id = resp.get_json()["session_id"]
34+
35+
events = backend.session_store.load_transcript(session_id)
36+
ctx_events = [e for e in events if e.get("type") == "context"]
37+
caps = next(
38+
(
39+
e["payload"].get("client_capabilities")
40+
for e in ctx_events
41+
if "client_capabilities" in e.get("payload", {})
42+
),
43+
None,
44+
)
45+
assert caps == ["stlite", "foo"]
46+
47+
48+
def test_session_create_without_header_stores_no_capabilities(monkeypatch, tmp_path):
49+
"""Session creation without the capabilities header writes no client_capabilities."""
50+
_reset_backend(tmp_path, monkeypatch)
51+
client = backend.app.test_client()
52+
53+
resp = client.post(
54+
"/api/sessions",
55+
json={},
56+
headers={"X-API-KEY": backend.MASTER_API_TOKEN},
57+
)
58+
assert resp.status_code == 200
59+
session_id = resp.get_json()["session_id"]
60+
61+
events = backend.session_store.load_transcript(session_id)
62+
ctx_events = [e for e in events if e.get("type") == "context"]
63+
for e in ctx_events:
64+
assert "client_capabilities" not in e.get("payload", {})
65+
66+
67+
def test_session_create_strips_whitespace_from_capabilities(monkeypatch, tmp_path):
68+
"""Whitespace around capability tokens is stripped during parsing."""
69+
_reset_backend(tmp_path, monkeypatch)
70+
client = backend.app.test_client()
71+
72+
resp = client.post(
73+
"/api/sessions",
74+
json={},
75+
headers={
76+
"X-API-KEY": backend.MASTER_API_TOKEN,
77+
"X-Meeseeks-Capabilities": " stlite , other-feature ",
78+
},
79+
)
80+
assert resp.status_code == 200
81+
session_id = resp.get_json()["session_id"]
82+
83+
events = backend.session_store.load_transcript(session_id)
84+
ctx_events = [e for e in events if e.get("type") == "context"]
85+
caps = next(
86+
(
87+
e["payload"].get("client_capabilities")
88+
for e in ctx_events
89+
if "client_capabilities" in e.get("payload", {})
90+
),
91+
None,
92+
)
93+
assert caps == ["stlite", "other-feature"]

0 commit comments

Comments
 (0)