llui-agent 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +6 -5
- package/dist/bridge.js.map +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +5 -2
- package/dist/prompts.js.map +1 -1
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
|
|
|
17
17
|
}
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Restart Claude Desktop. The 11 LLui tools (`
|
|
20
|
+
Restart Claude Desktop. The 11 LLui tools (`connect_session`, `disconnect_session`, `describe_app`, `get_state`, `list_actions`, `send_message`, `get_confirm_result`, `wait_for_change`, `query_dom`, `describe_visible_content`, `describe_context`) now appear. Desktop exposes the bundled `llui-connect` MCP prompt as a slash command — see "Slash shortcuts" below.
|
|
21
21
|
|
|
22
22
|
## Install (Claude Code CLI)
|
|
23
23
|
|
|
@@ -50,9 +50,9 @@ Run `/mcp` inside CC to confirm the server connected (or start a new session). T
|
|
|
50
50
|
|
|
51
51
|
## Use
|
|
52
52
|
|
|
53
|
-
Open any LLui app built with `@llui/agent/client`. Click "Connect with Claude" in the app and copy the generated snippet. Paste it into Claude — the snippet is a natural-language instruction containing the URL and token. Claude reads it and calls `
|
|
53
|
+
Open any LLui app built with `@llui/agent/client`. Click "Connect with Claude" in the app and copy the generated snippet. Paste it into Claude — the snippet is a natural-language instruction containing the URL and token. Claude reads it and calls `connect_session` to bind. The same snippet works in Desktop and CC.
|
|
54
54
|
|
|
55
|
-
Each Claude chat is bound to ONE LLui app at a time. To switch, ask Claude to call `
|
|
55
|
+
Each Claude chat is bound to ONE LLui app at a time. To switch, ask Claude to call `disconnect_session` and paste a new snippet.
|
|
56
56
|
|
|
57
57
|
## Slash shortcuts (optional)
|
|
58
58
|
|
|
@@ -68,7 +68,7 @@ The `<server-name>` in CC is whatever you passed to `claude mcp add` — `llui`
|
|
|
68
68
|
## How it works
|
|
69
69
|
|
|
70
70
|
1. Your LLui app mints a per-browser-session token and renders a connect snippet — a one-line instruction containing the LAP URL and the bearer token.
|
|
71
|
-
2. You paste into Claude — Claude reads the snippet, calls `
|
|
71
|
+
2. You paste into Claude — Claude reads the snippet, calls `connect_session`, and the bridge records `{url, token}` for this chat.
|
|
72
72
|
3. The bridge pings `POST {url}/describe` to validate and cache the app's schema.
|
|
73
73
|
4. Subsequent Claude tool calls (`get_state`, `send_message`, etc.) forward to `{url}/<path>` with your token as a Bearer.
|
|
74
74
|
5. Sensitive actions (`@requiresConfirm` in the app's code) route through a confirmation prompt that only the user can approve.
|
package/dist/bridge.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type BridgeDeps = {
|
|
|
20
20
|
* Forwarded tools (`kind: 'forward'`) share a generic forwarder that
|
|
21
21
|
* looks up the binding, dispatches to LAP, and caches description
|
|
22
22
|
* payloads where applicable. The two meta tools
|
|
23
|
-
* (`
|
|
23
|
+
* (`connect_session`, `disconnect_session`) carry custom
|
|
24
24
|
* handlers that mutate the BindingMap directly.
|
|
25
25
|
*/
|
|
26
26
|
export declare function createBridgeServer(deps: BridgeDeps): McpServer;
|
package/dist/bridge.js
CHANGED
|
@@ -13,7 +13,7 @@ import { registerPrompts } from './prompts.js';
|
|
|
13
13
|
* Forwarded tools (`kind: 'forward'`) share a generic forwarder that
|
|
14
14
|
* looks up the binding, dispatches to LAP, and caches description
|
|
15
15
|
* payloads where applicable. The two meta tools
|
|
16
|
-
* (`
|
|
16
|
+
* (`connect_session`, `disconnect_session`) carry custom
|
|
17
17
|
* handlers that mutate the BindingMap directly.
|
|
18
18
|
*/
|
|
19
19
|
export function createBridgeServer(deps) {
|
|
@@ -26,10 +26,10 @@ export function createBridgeServer(deps) {
|
|
|
26
26
|
}
|
|
27
27
|
function registerToolDescriptor(server, deps, desc) {
|
|
28
28
|
if (desc.kind === 'meta') {
|
|
29
|
-
if (desc.name === '
|
|
29
|
+
if (desc.name === 'connect_session') {
|
|
30
30
|
registerConnectSession(server, deps, desc);
|
|
31
31
|
}
|
|
32
|
-
else if (desc.name === '
|
|
32
|
+
else if (desc.name === 'disconnect_session') {
|
|
33
33
|
registerDisconnectSession(server, deps, desc);
|
|
34
34
|
}
|
|
35
35
|
return;
|
|
@@ -79,8 +79,9 @@ function registerForwardedTool(server, deps, desc) {
|
|
|
79
79
|
const binding = deps.bindings.get(deps.sessionId);
|
|
80
80
|
if (!binding) {
|
|
81
81
|
return errorResult('not bound — ask the user to copy the connect snippet from the LLui app, ' +
|
|
82
|
-
|
|
83
|
-
'(In Claude
|
|
82
|
+
"or call the LLui MCP server's `connect_session` tool with the url and token they provide. " +
|
|
83
|
+
'(In Claude Code the tool is namespaced as `mcp__<server>__connect_session` and may be deferred. ' +
|
|
84
|
+
'In Claude Desktop, the snippet is also available as the slash command `/llui-connect`.)');
|
|
84
85
|
}
|
|
85
86
|
// describe_app can serve from cache when one is available.
|
|
86
87
|
if (desc.name === 'describe_app' && binding.describe) {
|
package/dist/bridge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { TOOL_DESCRIPTORS, type ToolDescriptor } from './tools.js'\nimport { BindingMap } from './binding.js'\nimport { forwardLap } from './forwarder.js'\nimport type { LapDescribeResponse, LapObserveResponse } from '@llui/agent/protocol'\nimport { registerPrompts } from './prompts.js'\n\nexport type BridgeDeps = {\n /** Injectable for tests. */\n fetch?: typeof fetch\n /** MCP session ID for this client. In stdio mode there's one session; derive from the Server instance. */\n sessionId: string\n /** Shared binding map (one BindingMap per process). */\n bindings: BindingMap\n /** Package version — set from package.json at boot. */\n version: string\n}\n\n/**\n * Builds the bridge's MCP server using the high-level `McpServer`\n * registrars. Each tool's Zod schema (declared once in `tools.ts`)\n * drives both runtime input validation and the JSON Schema published\n * to `tools/list` — eliminating the hand-written-schema-vs-handler\n * drift that the low-level `setRequestHandler` pattern is prone to.\n *\n * Forwarded tools (`kind: 'forward'`) share a generic forwarder that\n * looks up the binding, dispatches to LAP, and caches description\n * payloads where applicable. The two meta tools\n * (`llui_connect_session`, `llui_disconnect_session`) carry custom\n * handlers that mutate the BindingMap directly.\n */\nexport function createBridgeServer(deps: BridgeDeps): McpServer {\n const server = new McpServer(\n { name: 'llui-agent', version: deps.version },\n { capabilities: { tools: {}, prompts: {} } },\n )\n\n for (const desc of TOOL_DESCRIPTORS) {\n registerToolDescriptor(server, deps, desc)\n }\n\n registerPrompts(server)\n\n return server\n}\n\nfunction registerToolDescriptor(server: McpServer, deps: BridgeDeps, desc: ToolDescriptor): void {\n if (desc.kind === 'meta') {\n if (desc.name === 'llui_connect_session') {\n registerConnectSession(server, deps, desc)\n } else if (desc.name === 'llui_disconnect_session') {\n registerDisconnectSession(server, deps, desc)\n }\n return\n }\n registerForwardedTool(server, deps, desc)\n}\n\nfunction registerConnectSession(server: McpServer, deps: BridgeDeps, desc: ToolDescriptor): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async (args) => {\n const { url, token } = args as { url: string; token: string }\n deps.bindings.set(deps.sessionId, url, token)\n // Validate AND prefetch the bootstrap bundle in one call.\n // /observe returns {state, actions, description, context} —\n // exactly what the LLM needs to start acting. Without this,\n // Claude has to follow up with `observe` to get anything\n // usable, costing round-trips and creating a window where\n // the connect tool's \"you are now connected\" result is the\n // entire context the LLM has to reason about.\n const res = await forwardLap(url, token, '/observe', {}, { fetch: deps.fetch })\n if (!res.ok) {\n deps.bindings.clear(deps.sessionId)\n return errorResult(`connect failed: ${JSON.stringify(res.error)}`)\n }\n const observe = res.body as LapObserveResponse\n deps.bindings.setDescribe(deps.sessionId, observe.description)\n return okResult({\n status: 'connected',\n appName: observe.description.name,\n appVersion: observe.description.version,\n // Full observe payload — same shape the `observe` tool returns —\n // so a `describe_app` / `get_state` / `list_actions` /\n // `describe_context` follow-up is unnecessary on the first turn.\n state: observe.state,\n actions: observe.actions,\n description: observe.description,\n context: observe.context,\n })\n },\n )\n}\n\nfunction registerDisconnectSession(\n server: McpServer,\n deps: BridgeDeps,\n desc: ToolDescriptor,\n): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async () => {\n deps.bindings.clear(deps.sessionId)\n return okResult({ status: 'disconnected' })\n },\n )\n}\n\nfunction registerForwardedTool(\n server: McpServer,\n deps: BridgeDeps,\n desc: Extract<ToolDescriptor, { kind: 'forward' }>,\n): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async (args) => {\n const binding = deps.bindings.get(deps.sessionId)\n if (!binding) {\n return errorResult(\n 'not bound — ask the user to copy the connect snippet from the LLui app, ' +\n 'or call `llui_connect_session` with the url and token they provide. ' +\n '(In Claude Desktop only, the snippet is also available as the slash command `/llui-connect`.)',\n )\n }\n\n // describe_app can serve from cache when one is available.\n if (desc.name === 'describe_app' && binding.describe) {\n return okResult(binding.describe)\n }\n\n const res = await forwardLap(binding.url, binding.token, desc.lapPath, args ?? {}, {\n fetch: deps.fetch,\n })\n if (!res.ok) {\n return errorResult(\n `LAP ${desc.lapPath} failed: status=${res.status} ${JSON.stringify(res.error)}`,\n )\n }\n\n // Cache describe_app responses after the first call too.\n if (desc.name === 'describe_app') {\n deps.bindings.setDescribe(deps.sessionId, res.body as LapDescribeResponse)\n }\n\n // observe returns description on every call; cache it so a later\n // describe_app can short-circuit the LAP round-trip.\n if (desc.name === 'observe') {\n const obs = res.body as LapObserveResponse\n if (obs?.description) deps.bindings.setDescribe(deps.sessionId, obs.description)\n }\n\n return okResult(res.body)\n },\n )\n}\n\nfunction okResult(body: unknown): CallToolResult {\n // structuredContent is what current Claude clients (Desktop + CC)\n // consume preferentially when present — typed JSON instead of a\n // stringified blob. The `content` array stays as a `text` fallback\n // so older clients still see something sensible.\n return {\n structuredContent: body as Record<string, unknown>,\n content: [{ type: 'text', text: JSON.stringify(body) }],\n }\n}\n\nfunction errorResult(msg: string): CallToolResult {\n return {\n content: [{ type: 'text', text: msg }],\n isError: true,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { TOOL_DESCRIPTORS, type ToolDescriptor } from './tools.js'\nimport { BindingMap } from './binding.js'\nimport { forwardLap } from './forwarder.js'\nimport type { LapDescribeResponse, LapObserveResponse } from '@llui/agent/protocol'\nimport { registerPrompts } from './prompts.js'\n\nexport type BridgeDeps = {\n /** Injectable for tests. */\n fetch?: typeof fetch\n /** MCP session ID for this client. In stdio mode there's one session; derive from the Server instance. */\n sessionId: string\n /** Shared binding map (one BindingMap per process). */\n bindings: BindingMap\n /** Package version — set from package.json at boot. */\n version: string\n}\n\n/**\n * Builds the bridge's MCP server using the high-level `McpServer`\n * registrars. Each tool's Zod schema (declared once in `tools.ts`)\n * drives both runtime input validation and the JSON Schema published\n * to `tools/list` — eliminating the hand-written-schema-vs-handler\n * drift that the low-level `setRequestHandler` pattern is prone to.\n *\n * Forwarded tools (`kind: 'forward'`) share a generic forwarder that\n * looks up the binding, dispatches to LAP, and caches description\n * payloads where applicable. The two meta tools\n * (`connect_session`, `disconnect_session`) carry custom\n * handlers that mutate the BindingMap directly.\n */\nexport function createBridgeServer(deps: BridgeDeps): McpServer {\n const server = new McpServer(\n { name: 'llui-agent', version: deps.version },\n { capabilities: { tools: {}, prompts: {} } },\n )\n\n for (const desc of TOOL_DESCRIPTORS) {\n registerToolDescriptor(server, deps, desc)\n }\n\n registerPrompts(server)\n\n return server\n}\n\nfunction registerToolDescriptor(server: McpServer, deps: BridgeDeps, desc: ToolDescriptor): void {\n if (desc.kind === 'meta') {\n if (desc.name === 'connect_session') {\n registerConnectSession(server, deps, desc)\n } else if (desc.name === 'disconnect_session') {\n registerDisconnectSession(server, deps, desc)\n }\n return\n }\n registerForwardedTool(server, deps, desc)\n}\n\nfunction registerConnectSession(server: McpServer, deps: BridgeDeps, desc: ToolDescriptor): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async (args) => {\n const { url, token } = args as { url: string; token: string }\n deps.bindings.set(deps.sessionId, url, token)\n // Validate AND prefetch the bootstrap bundle in one call.\n // /observe returns {state, actions, description, context} —\n // exactly what the LLM needs to start acting. Without this,\n // Claude has to follow up with `observe` to get anything\n // usable, costing round-trips and creating a window where\n // the connect tool's \"you are now connected\" result is the\n // entire context the LLM has to reason about.\n const res = await forwardLap(url, token, '/observe', {}, { fetch: deps.fetch })\n if (!res.ok) {\n deps.bindings.clear(deps.sessionId)\n return errorResult(`connect failed: ${JSON.stringify(res.error)}`)\n }\n const observe = res.body as LapObserveResponse\n deps.bindings.setDescribe(deps.sessionId, observe.description)\n return okResult({\n status: 'connected',\n appName: observe.description.name,\n appVersion: observe.description.version,\n // Full observe payload — same shape the `observe` tool returns —\n // so a `describe_app` / `get_state` / `list_actions` /\n // `describe_context` follow-up is unnecessary on the first turn.\n state: observe.state,\n actions: observe.actions,\n description: observe.description,\n context: observe.context,\n })\n },\n )\n}\n\nfunction registerDisconnectSession(\n server: McpServer,\n deps: BridgeDeps,\n desc: ToolDescriptor,\n): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async () => {\n deps.bindings.clear(deps.sessionId)\n return okResult({ status: 'disconnected' })\n },\n )\n}\n\nfunction registerForwardedTool(\n server: McpServer,\n deps: BridgeDeps,\n desc: Extract<ToolDescriptor, { kind: 'forward' }>,\n): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async (args) => {\n const binding = deps.bindings.get(deps.sessionId)\n if (!binding) {\n return errorResult(\n 'not bound — ask the user to copy the connect snippet from the LLui app, ' +\n \"or call the LLui MCP server's `connect_session` tool with the url and token they provide. \" +\n '(In Claude Code the tool is namespaced as `mcp__<server>__connect_session` and may be deferred. ' +\n 'In Claude Desktop, the snippet is also available as the slash command `/llui-connect`.)',\n )\n }\n\n // describe_app can serve from cache when one is available.\n if (desc.name === 'describe_app' && binding.describe) {\n return okResult(binding.describe)\n }\n\n const res = await forwardLap(binding.url, binding.token, desc.lapPath, args ?? {}, {\n fetch: deps.fetch,\n })\n if (!res.ok) {\n return errorResult(\n `LAP ${desc.lapPath} failed: status=${res.status} ${JSON.stringify(res.error)}`,\n )\n }\n\n // Cache describe_app responses after the first call too.\n if (desc.name === 'describe_app') {\n deps.bindings.setDescribe(deps.sessionId, res.body as LapDescribeResponse)\n }\n\n // observe returns description on every call; cache it so a later\n // describe_app can short-circuit the LAP round-trip.\n if (desc.name === 'observe') {\n const obs = res.body as LapObserveResponse\n if (obs?.description) deps.bindings.setDescribe(deps.sessionId, obs.description)\n }\n\n return okResult(res.body)\n },\n )\n}\n\nfunction okResult(body: unknown): CallToolResult {\n // structuredContent is what current Claude clients (Desktop + CC)\n // consume preferentially when present — typed JSON instead of a\n // stringified blob. The `content` array stays as a `text` fallback\n // so older clients still see something sensible.\n return {\n structuredContent: body as Record<string, unknown>,\n content: [{ type: 'text', text: JSON.stringify(body) }],\n }\n}\n\nfunction errorResult(msg: string): CallToolResult {\n return {\n content: [{ type: 'text', text: msg }],\n isError: true,\n }\n}\n"]}
|
package/dist/prompts.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
5
5
|
* `/llui-connect <url> <token>`; CC: `/mcp__<server>__llui-connect …`).
|
|
6
6
|
* The prompt body Claude sees is the same natural-language instruction
|
|
7
7
|
* the LLui app shows in its connect snippet — so pasting either form
|
|
8
|
-
* lands the same `
|
|
8
|
+
* lands the same `connect_session` tool call.
|
|
9
9
|
*/
|
|
10
10
|
export declare function registerPrompts(server: McpServer): void;
|
|
11
11
|
//# sourceMappingURL=prompts.d.ts.map
|
package/dist/prompts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6BvD"}
|
package/dist/prompts.js
CHANGED
|
@@ -5,7 +5,7 @@ import { z } from 'zod';
|
|
|
5
5
|
* `/llui-connect <url> <token>`; CC: `/mcp__<server>__llui-connect …`).
|
|
6
6
|
* The prompt body Claude sees is the same natural-language instruction
|
|
7
7
|
* the LLui app shows in its connect snippet — so pasting either form
|
|
8
|
-
* lands the same `
|
|
8
|
+
* lands the same `connect_session` tool call.
|
|
9
9
|
*/
|
|
10
10
|
export function registerPrompts(server) {
|
|
11
11
|
server.registerPrompt('llui-connect', {
|
|
@@ -22,7 +22,10 @@ export function registerPrompts(server) {
|
|
|
22
22
|
content: {
|
|
23
23
|
type: 'text',
|
|
24
24
|
text: `Please connect this conversation to the LLui app at ${url}. ` +
|
|
25
|
-
`Call
|
|
25
|
+
`Call the LLui MCP server's \`connect_session\` tool ` +
|
|
26
|
+
`with url=${JSON.stringify(url)} and token=${JSON.stringify(token)}. ` +
|
|
27
|
+
`(In Claude Code the tool may be namespaced as ` +
|
|
28
|
+
`\`mcp__<server>__connect_session\` and deferred — load it via tool search if needed.)`,
|
|
26
29
|
},
|
|
27
30
|
},
|
|
28
31
|
],
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,WAAW,EACT,2FAA2F;QAC7F,UAAU,EAAE;YACV,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;SAC3C;KACF,EACD,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACnB,WAAW,EAAE,uBAAuB,GAAG,EAAE;QACzC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,uDAAuD,GAAG,IAAI;wBAC9D,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,WAAW,EACT,2FAA2F;QAC7F,UAAU,EAAE;YACV,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;SAC3C;KACF,EACD,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACnB,WAAW,EAAE,uBAAuB,GAAG,EAAE;QACzC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,uDAAuD,GAAG,IAAI;wBAC9D,sDAAsD;wBACtD,YAAY,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI;wBACtE,gDAAgD;wBAChD,uFAAuF;iBAC1F;aACF;SACF;KACF,CAAC,CACH,CAAA;AACH,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\n\n/**\n * Registers the bundled `llui-connect` MCP prompt. Both Claude Desktop\n * and Claude Code surface it as a slash command (Desktop:\n * `/llui-connect <url> <token>`; CC: `/mcp__<server>__llui-connect …`).\n * The prompt body Claude sees is the same natural-language instruction\n * the LLui app shows in its connect snippet — so pasting either form\n * lands the same `connect_session` tool call.\n */\nexport function registerPrompts(server: McpServer): void {\n server.registerPrompt(\n 'llui-connect',\n {\n description:\n 'Bind this Claude conversation to an LLui app. Paste the URL and token the app showed you.',\n argsSchema: {\n url: z.string().describe('LAP base URL'),\n token: z.string().describe('Bearer token'),\n },\n },\n ({ url, token }) => ({\n description: `Bind to LLui app at ${url}`,\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text:\n `Please connect this conversation to the LLui app at ${url}. ` +\n `Call the LLui MCP server's \\`connect_session\\` tool ` +\n `with url=${JSON.stringify(url)} and token=${JSON.stringify(token)}. ` +\n `(In Claude Code the tool may be namespaced as ` +\n `\\`mcp__<server>__connect_session\\` and deferred — load it via tool search if needed.)`,\n },\n },\n ],\n }),\n )\n}\n"]}
|
package/dist/tools.js
CHANGED
|
@@ -27,7 +27,7 @@ const empty = z.object({});
|
|
|
27
27
|
export const TOOL_DESCRIPTORS = [
|
|
28
28
|
{
|
|
29
29
|
kind: 'meta',
|
|
30
|
-
name: '
|
|
30
|
+
name: 'connect_session',
|
|
31
31
|
description: 'Bind this Claude conversation to a specific LLui app. Call ONCE per chat when the user pastes a connect snippet from the LLui app — the snippet contains the url and token to forward here. The result includes the full observe bundle ({state, actions, description, context}) so you have everything you need to start acting — no separate describe_app / get_state / list_actions / describe_context follow-up is required on the first turn. Use observe later when you want a refreshed snapshot.',
|
|
32
32
|
schema: z.object({
|
|
33
33
|
url: z.string().describe('LAP base URL (e.g. https://app.example/agent/lap/v1)'),
|
|
@@ -36,7 +36,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
kind: 'meta',
|
|
39
|
-
name: '
|
|
39
|
+
name: 'disconnect_session',
|
|
40
40
|
description: 'Clear the binding for this Claude conversation. Subsequent LLui tool calls will fail until rebind.',
|
|
41
41
|
schema: empty,
|
|
42
42
|
},
|
package/dist/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\n\n/**\n * Tool catalogue exposed to Claude through the MCP bridge. Two tiers:\n *\n * - **Efficient path (recommended)**: `observe` + `send_message`.\n * `observe` returns state + actions + description + context in a\n * single LAP call, replacing the old describe_app + get_state +\n * list_actions trio. `send_message` defaults to `waitFor:'drained'`,\n * blocking until the message queue goes idle (http/delay/debounce\n * round-trips complete) and returning the new state + actions +\n * drain meta. Together these cut the \"check state → act → check\n * state\" loop from 5 round-trips to 2.\n *\n * - **Legacy / specialized**: `describe_app`, `get_state`,\n * `list_actions`, `wait_for_change`. Kept for back-compat and niche\n * uses (e.g. scoped state reads via JSON pointer, external state\n * pushes). New integrations should prefer `observe`.\n *\n * Spec §8.\n *\n * The catalogue is the single source of truth — Zod schemas drive both\n * runtime input validation and the JSON Schema published in\n * `tools/list`. Forwarded tools also carry their LAP endpoint path so\n * `bridge.ts` can register one generic forwarder that loops over them.\n */\n\nconst empty = z.object({})\n\n/** Descriptor for a tool that forwards directly to the bound LAP server. */\nexport interface ForwardedToolDescriptor {\n kind: 'forward'\n name: string\n description: string\n /** Zod schema defining the tool's input shape. */\n schema: z.ZodObject<z.ZodRawShape>\n /** LAP endpoint path (relative to the binding's base URL). */\n lapPath: string\n}\n\n/** Descriptor for a tool whose handler is implemented in the bridge itself. */\nexport interface MetaToolDescriptor {\n kind: 'meta'\n name: string\n description: string\n schema: z.ZodObject<z.ZodRawShape>\n}\n\nexport type ToolDescriptor = ForwardedToolDescriptor | MetaToolDescriptor\n\nexport const TOOL_DESCRIPTORS: ToolDescriptor[] = [\n {\n kind: 'meta',\n name: 'llui_connect_session',\n description:\n 'Bind this Claude conversation to a specific LLui app. Call ONCE per chat when the user pastes a connect snippet from the LLui app — the snippet contains the url and token to forward here. The result includes the full observe bundle ({state, actions, description, context}) so you have everything you need to start acting — no separate describe_app / get_state / list_actions / describe_context follow-up is required on the first turn. Use observe later when you want a refreshed snapshot.',\n schema: z.object({\n url: z.string().describe('LAP base URL (e.g. https://app.example/agent/lap/v1)'),\n token: z.string().describe('Bearer token for LAP calls'),\n }),\n },\n {\n kind: 'meta',\n name: 'llui_disconnect_session',\n description:\n 'Clear the binding for this Claude conversation. Subsequent LLui tool calls will fail until rebind.',\n schema: empty,\n },\n {\n kind: 'forward',\n name: 'observe',\n description:\n 'Unified snapshot — returns {state, actions, description, context} in one call. Use this as the default \"what can I see, what can I do\" read; prefer it over describe_app + get_state + list_actions. Typical flow: observe → send_message → (repeat). The response includes the static app description (name, version, msgSchema, docs) on every call so first-time callers do not need a separate describe_app.',\n schema: empty,\n lapPath: '/observe',\n },\n {\n kind: 'forward',\n name: 'describe_app',\n description:\n \"Return the bound app's name, version, state/message schemas, annotations, and static docs. Legacy — prefer `observe`, which includes this as `description`.\",\n schema: empty,\n lapPath: '/describe',\n },\n {\n kind: 'forward',\n name: 'get_state',\n description:\n 'Return the current app state. Optional `path` (JSON-pointer) to narrow the slice. Legacy for full-state reads — prefer `observe`. Still useful for scoped reads via JSON pointer.',\n schema: z.object({\n path: z.string().optional().describe('Optional JSON-pointer, e.g. \"/user/name\"'),\n }),\n lapPath: '/state',\n },\n {\n kind: 'forward',\n name: 'query_state',\n description:\n 'Read a single slice of state via JSON-pointer path. Returns `{found: true, value}` on hit or `{found: false, detail}` on miss (missing key, walking through null, etc.). Cheaper than `observe` when checking one field. Path syntax: `\"\"` (whole state), `\"/auth/user\"`, `\"/items/0/id\"`, `\"/key~1with~1slash\"` (escaped `/`), `\"/key~0tilde\"` (escaped `~`).',\n schema: z.object({\n path: z.string().describe('JSON-pointer (RFC 6901) — `/auth/user` or `\"\"` for whole state'),\n }),\n lapPath: '/query-state',\n },\n {\n kind: 'forward',\n name: 'describe_recent_actions',\n description:\n 'Return the most recent log entries for this session (newest first). Each `dispatched` entry includes a `stateDiff` showing what changed. Useful for self-correction over multi-step flows — read your own past dispatches without re-querying full state. Filter by `kind` (e.g. `\"dispatched\"`) to skip read-only entries.',\n schema: z.object({\n n: z.number().int().positive().optional().describe('How many entries to return (default 10)'),\n kind: z\n .string()\n .optional()\n .describe('Filter to a specific kind (e.g. \"dispatched\", \"read\", \"error\")'),\n }),\n lapPath: '/recent-actions',\n },\n {\n kind: 'forward',\n name: 'would_dispatch',\n description:\n 'Predict what dispatching `msg` would do without committing it. Runs the reducer in isolation against current state and returns `{stateDiff, effects}`. Effects are listed but NOT executed — the cloud is not hit, analytics do not fire. Use this to weigh a candidate action before sending: \"if I dispatch X, will it change Y?\" Pure-reducer assumption: if the reducer branches on Date.now() / localStorage / random, prediction drifts from real dispatch by exactly that impurity.',\n schema: z.object({\n msg: z\n .object({ type: z.string() })\n .passthrough()\n .describe('The candidate message; must have a `type` string'),\n }),\n lapPath: '/would-dispatch',\n },\n {\n kind: 'forward',\n name: 'list_actions',\n description:\n 'Return the currently-affordable actions: visible UI bindings plus agent-affordable registry entries, filtered by annotation gates. Legacy — prefer `observe`, which includes this as `actions`.',\n schema: empty,\n lapPath: '/actions',\n },\n {\n kind: 'forward',\n name: 'send_message',\n description:\n 'Dispatch a message to the app. Blocks by default until the message queue goes idle (drain semantics — captures http/delay/debounce round-trips that feed back as messages). Returns {status, stateDiff, actions, drain} on dispatched, {status: \"pending-confirmation\", confirmId} when the variant is @requiresConfirm, or {status: \"rejected\", reason} on validation failures. By default the response carries `stateDiff` (a JSON-Patch-shaped delta) and not the full post-state — apply the diff to the snapshot you got from `connect`/`observe`. Pass `includeState: true` if you want the full snapshot back (rare; expensive on bandwidth and context for large states). `drain.timedOut: true` means the 5s cap was hit while messages were still arriving — follow up with `observe` to resync. `actions` in the response reflects the new state, so you normally do not need a separate `observe` after a send.',\n schema: z.object({\n msg: z\n .object({ type: z.string() })\n .passthrough()\n .describe('The message to dispatch; must have a `type` string'),\n reason: z\n .string()\n .optional()\n .describe('User-facing rationale (required for confirm-gated variants)'),\n waitFor: z\n .enum(['drained', 'idle', 'none'])\n .optional()\n .describe(\n '\"drained\" (default) waits for the message queue to go idle; \"idle\" flushes the update cycle only (no async effects); \"none\" is fire-and-forget.',\n ),\n drainQuietMs: z\n .number()\n .optional()\n .describe(\n 'Quiescence window for waitFor:\"drained\". Drain completes when no commit fires for this many ms. Default 100.',\n ),\n timeoutMs: z\n .number()\n .optional()\n .describe(\n 'Hard cap on total wait. Default 5000. For waitFor:\"drained\", this bounds how long the drain loop runs; for pending-confirmation, how long to wait for user approval.',\n ),\n includeState: z\n .boolean()\n .optional()\n .describe(\n 'Include the full post-drain `stateAfter` snapshot in the response. Default false — `stateDiff` is what callers normally need, and resending the full state on every dispatch wastes bandwidth and context. Set true only when you need a fresh snapshot back (e.g., after a long-running effect that may have produced changes the diff misses).',\n ),\n }),\n lapPath: '/message',\n },\n {\n kind: 'forward',\n name: 'get_confirm_result',\n description:\n 'Poll a pending-confirmation by confirmId. Returns confirmed / rejected / still-pending.',\n schema: z.object({\n confirmId: z.string(),\n timeoutMs: z.number().optional(),\n }),\n lapPath: '/confirm-result',\n },\n {\n kind: 'forward',\n name: 'wait_for_change',\n description:\n 'Long-poll for a state change. Returns changed / timeout. Specialized — use for external state pushes (WebSocket messages, timers) that arrive while Claude is idle. For the normal send-then-read loop, `send_message` with `waitFor:\"drained\"` already waits for effect round-trips.',\n schema: z.object({\n path: z\n .string()\n .optional()\n .describe('Optional JSON-pointer to narrow which state changes trigger resolution'),\n timeoutMs: z.number().optional(),\n }),\n lapPath: '/wait',\n },\n {\n kind: 'forward',\n name: 'query_dom',\n description: 'Read elements tagged with data-agent=\"<name>\" in the rendered UI.',\n schema: z.object({\n name: z.string(),\n multiple: z.boolean().optional(),\n }),\n lapPath: '/query-dom',\n },\n {\n kind: 'forward',\n name: 'describe_visible_content',\n description: 'Return a structured outline of the currently-visible data-agent-tagged subtrees.',\n schema: empty,\n lapPath: '/describe-visible',\n },\n {\n kind: 'forward',\n name: 'describe_context',\n description:\n 'Return the current per-state narrative docs (agentContext) — what the user is trying to do right now.',\n schema: empty,\n lapPath: '/context',\n },\n]\n"]}
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\n\n/**\n * Tool catalogue exposed to Claude through the MCP bridge. Two tiers:\n *\n * - **Efficient path (recommended)**: `observe` + `send_message`.\n * `observe` returns state + actions + description + context in a\n * single LAP call, replacing the old describe_app + get_state +\n * list_actions trio. `send_message` defaults to `waitFor:'drained'`,\n * blocking until the message queue goes idle (http/delay/debounce\n * round-trips complete) and returning the new state + actions +\n * drain meta. Together these cut the \"check state → act → check\n * state\" loop from 5 round-trips to 2.\n *\n * - **Legacy / specialized**: `describe_app`, `get_state`,\n * `list_actions`, `wait_for_change`. Kept for back-compat and niche\n * uses (e.g. scoped state reads via JSON pointer, external state\n * pushes). New integrations should prefer `observe`.\n *\n * Spec §8.\n *\n * The catalogue is the single source of truth — Zod schemas drive both\n * runtime input validation and the JSON Schema published in\n * `tools/list`. Forwarded tools also carry their LAP endpoint path so\n * `bridge.ts` can register one generic forwarder that loops over them.\n */\n\nconst empty = z.object({})\n\n/** Descriptor for a tool that forwards directly to the bound LAP server. */\nexport interface ForwardedToolDescriptor {\n kind: 'forward'\n name: string\n description: string\n /** Zod schema defining the tool's input shape. */\n schema: z.ZodObject<z.ZodRawShape>\n /** LAP endpoint path (relative to the binding's base URL). */\n lapPath: string\n}\n\n/** Descriptor for a tool whose handler is implemented in the bridge itself. */\nexport interface MetaToolDescriptor {\n kind: 'meta'\n name: string\n description: string\n schema: z.ZodObject<z.ZodRawShape>\n}\n\nexport type ToolDescriptor = ForwardedToolDescriptor | MetaToolDescriptor\n\nexport const TOOL_DESCRIPTORS: ToolDescriptor[] = [\n {\n kind: 'meta',\n name: 'connect_session',\n description:\n 'Bind this Claude conversation to a specific LLui app. Call ONCE per chat when the user pastes a connect snippet from the LLui app — the snippet contains the url and token to forward here. The result includes the full observe bundle ({state, actions, description, context}) so you have everything you need to start acting — no separate describe_app / get_state / list_actions / describe_context follow-up is required on the first turn. Use observe later when you want a refreshed snapshot.',\n schema: z.object({\n url: z.string().describe('LAP base URL (e.g. https://app.example/agent/lap/v1)'),\n token: z.string().describe('Bearer token for LAP calls'),\n }),\n },\n {\n kind: 'meta',\n name: 'disconnect_session',\n description:\n 'Clear the binding for this Claude conversation. Subsequent LLui tool calls will fail until rebind.',\n schema: empty,\n },\n {\n kind: 'forward',\n name: 'observe',\n description:\n 'Unified snapshot — returns {state, actions, description, context} in one call. Use this as the default \"what can I see, what can I do\" read; prefer it over describe_app + get_state + list_actions. Typical flow: observe → send_message → (repeat). The response includes the static app description (name, version, msgSchema, docs) on every call so first-time callers do not need a separate describe_app.',\n schema: empty,\n lapPath: '/observe',\n },\n {\n kind: 'forward',\n name: 'describe_app',\n description:\n \"Return the bound app's name, version, state/message schemas, annotations, and static docs. Legacy — prefer `observe`, which includes this as `description`.\",\n schema: empty,\n lapPath: '/describe',\n },\n {\n kind: 'forward',\n name: 'get_state',\n description:\n 'Return the current app state. Optional `path` (JSON-pointer) to narrow the slice. Legacy for full-state reads — prefer `observe`. Still useful for scoped reads via JSON pointer.',\n schema: z.object({\n path: z.string().optional().describe('Optional JSON-pointer, e.g. \"/user/name\"'),\n }),\n lapPath: '/state',\n },\n {\n kind: 'forward',\n name: 'query_state',\n description:\n 'Read a single slice of state via JSON-pointer path. Returns `{found: true, value}` on hit or `{found: false, detail}` on miss (missing key, walking through null, etc.). Cheaper than `observe` when checking one field. Path syntax: `\"\"` (whole state), `\"/auth/user\"`, `\"/items/0/id\"`, `\"/key~1with~1slash\"` (escaped `/`), `\"/key~0tilde\"` (escaped `~`).',\n schema: z.object({\n path: z.string().describe('JSON-pointer (RFC 6901) — `/auth/user` or `\"\"` for whole state'),\n }),\n lapPath: '/query-state',\n },\n {\n kind: 'forward',\n name: 'describe_recent_actions',\n description:\n 'Return the most recent log entries for this session (newest first). Each `dispatched` entry includes a `stateDiff` showing what changed. Useful for self-correction over multi-step flows — read your own past dispatches without re-querying full state. Filter by `kind` (e.g. `\"dispatched\"`) to skip read-only entries.',\n schema: z.object({\n n: z.number().int().positive().optional().describe('How many entries to return (default 10)'),\n kind: z\n .string()\n .optional()\n .describe('Filter to a specific kind (e.g. \"dispatched\", \"read\", \"error\")'),\n }),\n lapPath: '/recent-actions',\n },\n {\n kind: 'forward',\n name: 'would_dispatch',\n description:\n 'Predict what dispatching `msg` would do without committing it. Runs the reducer in isolation against current state and returns `{stateDiff, effects}`. Effects are listed but NOT executed — the cloud is not hit, analytics do not fire. Use this to weigh a candidate action before sending: \"if I dispatch X, will it change Y?\" Pure-reducer assumption: if the reducer branches on Date.now() / localStorage / random, prediction drifts from real dispatch by exactly that impurity.',\n schema: z.object({\n msg: z\n .object({ type: z.string() })\n .passthrough()\n .describe('The candidate message; must have a `type` string'),\n }),\n lapPath: '/would-dispatch',\n },\n {\n kind: 'forward',\n name: 'list_actions',\n description:\n 'Return the currently-affordable actions: visible UI bindings plus agent-affordable registry entries, filtered by annotation gates. Legacy — prefer `observe`, which includes this as `actions`.',\n schema: empty,\n lapPath: '/actions',\n },\n {\n kind: 'forward',\n name: 'send_message',\n description:\n 'Dispatch a message to the app. Blocks by default until the message queue goes idle (drain semantics — captures http/delay/debounce round-trips that feed back as messages). Returns {status, stateDiff, actions, drain} on dispatched, {status: \"pending-confirmation\", confirmId} when the variant is @requiresConfirm, or {status: \"rejected\", reason} on validation failures. By default the response carries `stateDiff` (a JSON-Patch-shaped delta) and not the full post-state — apply the diff to the snapshot you got from `connect`/`observe`. Pass `includeState: true` if you want the full snapshot back (rare; expensive on bandwidth and context for large states). `drain.timedOut: true` means the 5s cap was hit while messages were still arriving — follow up with `observe` to resync. `actions` in the response reflects the new state, so you normally do not need a separate `observe` after a send.',\n schema: z.object({\n msg: z\n .object({ type: z.string() })\n .passthrough()\n .describe('The message to dispatch; must have a `type` string'),\n reason: z\n .string()\n .optional()\n .describe('User-facing rationale (required for confirm-gated variants)'),\n waitFor: z\n .enum(['drained', 'idle', 'none'])\n .optional()\n .describe(\n '\"drained\" (default) waits for the message queue to go idle; \"idle\" flushes the update cycle only (no async effects); \"none\" is fire-and-forget.',\n ),\n drainQuietMs: z\n .number()\n .optional()\n .describe(\n 'Quiescence window for waitFor:\"drained\". Drain completes when no commit fires for this many ms. Default 100.',\n ),\n timeoutMs: z\n .number()\n .optional()\n .describe(\n 'Hard cap on total wait. Default 5000. For waitFor:\"drained\", this bounds how long the drain loop runs; for pending-confirmation, how long to wait for user approval.',\n ),\n includeState: z\n .boolean()\n .optional()\n .describe(\n 'Include the full post-drain `stateAfter` snapshot in the response. Default false — `stateDiff` is what callers normally need, and resending the full state on every dispatch wastes bandwidth and context. Set true only when you need a fresh snapshot back (e.g., after a long-running effect that may have produced changes the diff misses).',\n ),\n }),\n lapPath: '/message',\n },\n {\n kind: 'forward',\n name: 'get_confirm_result',\n description:\n 'Poll a pending-confirmation by confirmId. Returns confirmed / rejected / still-pending.',\n schema: z.object({\n confirmId: z.string(),\n timeoutMs: z.number().optional(),\n }),\n lapPath: '/confirm-result',\n },\n {\n kind: 'forward',\n name: 'wait_for_change',\n description:\n 'Long-poll for a state change. Returns changed / timeout. Specialized — use for external state pushes (WebSocket messages, timers) that arrive while Claude is idle. For the normal send-then-read loop, `send_message` with `waitFor:\"drained\"` already waits for effect round-trips.',\n schema: z.object({\n path: z\n .string()\n .optional()\n .describe('Optional JSON-pointer to narrow which state changes trigger resolution'),\n timeoutMs: z.number().optional(),\n }),\n lapPath: '/wait',\n },\n {\n kind: 'forward',\n name: 'query_dom',\n description: 'Read elements tagged with data-agent=\"<name>\" in the rendered UI.',\n schema: z.object({\n name: z.string(),\n multiple: z.boolean().optional(),\n }),\n lapPath: '/query-dom',\n },\n {\n kind: 'forward',\n name: 'describe_visible_content',\n description: 'Return a structured outline of the currently-visible data-agent-tagged subtrees.',\n schema: empty,\n lapPath: '/describe-visible',\n },\n {\n kind: 'forward',\n name: 'describe_context',\n description:\n 'Return the current per-state narrative docs (agentContext) — what the user is trying to do right now.',\n schema: empty,\n lapPath: '/context',\n },\n]\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llui-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"llui-agent": "./dist/cli.js"
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
23
23
|
"zod": "^4.0.0",
|
|
24
|
-
"@llui/agent": "0.0.
|
|
24
|
+
"@llui/agent": "0.0.35"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^22.0.0"
|