@llui/agent 0.0.31 → 0.0.34
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 +82 -1
- package/dist/client/agentConfirm.d.ts +48 -18
- package/dist/client/agentConfirm.d.ts.map +1 -1
- package/dist/client/agentConfirm.js +28 -25
- package/dist/client/agentConfirm.js.map +1 -1
- package/dist/client/agentConnect.d.ts +95 -34
- package/dist/client/agentConnect.d.ts.map +1 -1
- package/dist/client/agentConnect.js +81 -47
- package/dist/client/agentConnect.js.map +1 -1
- package/dist/client/agentLog.d.ts +31 -14
- package/dist/client/agentLog.d.ts.map +1 -1
- package/dist/client/agentLog.js +39 -20
- package/dist/client/agentLog.js.map +1 -1
- package/dist/client/effect-handler.d.ts +23 -0
- package/dist/client/effect-handler.d.ts.map +1 -1
- package/dist/client/effect-handler.js +185 -126
- package/dist/client/effect-handler.js.map +1 -1
- package/dist/client/effects.d.ts +13 -2
- package/dist/client/effects.d.ts.map +1 -1
- package/dist/client/effects.js.map +1 -1
- package/dist/client/factory.d.ts +55 -3
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +30 -5
- package/dist/client/factory.js.map +1 -1
- package/dist/client/rpc/describe-visible-content.d.ts +18 -5
- package/dist/client/rpc/describe-visible-content.d.ts.map +1 -1
- package/dist/client/rpc/describe-visible-content.js +112 -7
- package/dist/client/rpc/describe-visible-content.js.map +1 -1
- package/dist/client/rpc/list-actions.d.ts +52 -2
- package/dist/client/rpc/list-actions.d.ts.map +1 -1
- package/dist/client/rpc/list-actions.js +187 -5
- package/dist/client/rpc/list-actions.js.map +1 -1
- package/dist/client/rpc/query-state.d.ts +32 -0
- package/dist/client/rpc/query-state.d.ts.map +1 -0
- package/dist/client/rpc/query-state.js +82 -0
- package/dist/client/rpc/query-state.js.map +1 -0
- package/dist/client/rpc/send-message.d.ts +2 -0
- package/dist/client/rpc/send-message.d.ts.map +1 -1
- package/dist/client/rpc/send-message.js +119 -9
- package/dist/client/rpc/send-message.js.map +1 -1
- package/dist/client/rpc/would-dispatch.d.ts +66 -0
- package/dist/client/rpc/would-dispatch.d.ts.map +1 -0
- package/dist/client/rpc/would-dispatch.js +21 -0
- package/dist/client/rpc/would-dispatch.js.map +1 -0
- package/dist/client/ws-client.d.ts +3 -1
- package/dist/client/ws-client.d.ts.map +1 -1
- package/dist/client/ws-client.js +29 -0
- package/dist/client/ws-client.js.map +1 -1
- package/dist/codecs.d.ts +107 -0
- package/dist/codecs.d.ts.map +1 -0
- package/dist/codecs.js +166 -0
- package/dist/codecs.js.map +1 -0
- package/dist/protocol.d.ts +155 -6
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +7 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server/lap/forward.d.ts +13 -0
- package/dist/server/lap/forward.d.ts.map +1 -1
- package/dist/server/lap/forward.js +74 -0
- package/dist/server/lap/forward.js.map +1 -1
- package/dist/server/lap/router.d.ts.map +1 -1
- package/dist/server/lap/router.js +7 -1
- package/dist/server/lap/router.js.map +1 -1
- package/dist/server/ws/pairing-registry.d.ts +22 -6
- package/dist/server/ws/pairing-registry.d.ts.map +1 -1
- package/dist/server/ws/pairing-registry.js +49 -0
- package/dist/server/ws/pairing-registry.js.map +1 -1
- package/dist/state-diff.d.ts +52 -0
- package/dist/state-diff.d.ts.map +1 -0
- package/dist/state-diff.js +119 -0
- package/dist/state-diff.js.map +1 -0
- package/package.json +11 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AppHandle } from '@llui/dom'\nimport type { AgentEffect } from './effects.js'\nimport type { AgentConfirmState } from './agentConfirm.js'\nimport type {\n AgentDocs,\n AgentContext,\n LapDrainMeta,\n MessageAnnotations,\n MessageSchemaEntry,\n} from '../protocol.js'\nimport { attachWsClient, type WsLike, type RpcHosts } from './ws-client.js'\nimport { createEffectHandler } from './effect-handler.js'\n\ntype ComponentMetadata = {\n __msgSchema?: unknown\n __stateSchema?: unknown\n __msgAnnotations?: Record<string, MessageAnnotations>\n __bindingDescriptors?: Array<{ variant: string }>\n __schemaHash?: string\n name: string\n agentAffordances?: (state: unknown) => Array<{ type: string; [k: string]: unknown }>\n agentDocs?: AgentDocs\n agentContext?: (state: unknown) => AgentContext\n}\n\nexport type CreateAgentClientOpts<State, Msg> = {\n handle: AppHandle\n def: ComponentMetadata\n appVersion?: string\n rootElement: Element | null\n slices: {\n getConnect: (s: State) => unknown\n getConfirm: (s: State) => AgentConfirmState\n wrapConnectMsg: (m: unknown) => Msg\n wrapConfirmMsg: (m: unknown) => Msg\n /**\n * Optional: wrap an agentLog msg so the client-side activity feed\n * mirrors what Claude is doing. If omitted, outbound log-append\n * frames still go to the server, but the local agent.log slice\n * stays empty (the UI won't show activity).\n */\n wrapLogMsg?: (m: unknown) => Msg\n }\n}\n\nexport type AgentClient = {\n effectHandler: (effect: AgentEffect) => Promise<void>\n start(): void\n stop(): void\n}\n\nexport function createAgentClient<State, Msg>(\n opts: CreateAgentClientOpts<State, Msg>,\n): AgentClient {\n let ws: WebSocket | null = null\n let wsClient: ReturnType<typeof attachWsClient> | null = null\n let confirmPollTimer: ReturnType<typeof setInterval> | null = null\n let stateSubscription: (() => void) | null = null\n const resolvedConfirms = new Set<string>()\n\n // Drain-error buffer: populated by persistent `window.error` and\n // `window.unhandledrejection` listeners installed on `start()`. The\n // send-message drain loop consumes and clears it per call so the\n // envelope surfaces only errors that fired during that window.\n const drainErrors: LapDrainMeta['errors'] = []\n let errorListenersInstalled = false\n let onErrorEvt: ((e: ErrorEvent) => void) | null = null\n let onRejectionEvt: ((e: PromiseRejectionEvent) => void) | null = null\n\n function installErrorListeners(): void {\n if (errorListenersInstalled) return\n if (typeof window === 'undefined') return\n onErrorEvt = (e: ErrorEvent) => {\n drainErrors.push({\n kind: 'error',\n message: e.message ?? String(e.error ?? 'unknown error'),\n stack: e.error instanceof Error ? e.error.stack : undefined,\n })\n }\n onRejectionEvt = (e: PromiseRejectionEvent) => {\n const r = e.reason\n const message = r instanceof Error ? r.message : typeof r === 'string' ? r : safeStringify(r)\n drainErrors.push({\n kind: 'unhandledrejection',\n message,\n stack: r instanceof Error ? r.stack : undefined,\n })\n }\n window.addEventListener('error', onErrorEvt)\n window.addEventListener('unhandledrejection', onRejectionEvt)\n errorListenersInstalled = true\n }\n\n function removeErrorListeners(): void {\n if (!errorListenersInstalled) return\n if (typeof window === 'undefined') return\n if (onErrorEvt) window.removeEventListener('error', onErrorEvt)\n if (onRejectionEvt) window.removeEventListener('unhandledrejection', onRejectionEvt)\n onErrorEvt = null\n onRejectionEvt = null\n errorListenersInstalled = false\n }\n\n const rpcHost: RpcHosts = {\n getState: () => opts.handle.getState(),\n send: (m) => opts.handle.send(m),\n flush: () => opts.handle.flush(),\n subscribe: (listener) => opts.handle.subscribe(() => listener()),\n getAndClearDrainErrors: () => drainErrors.splice(0, drainErrors.length),\n getMsgAnnotations: () => opts.def.__msgAnnotations ?? null,\n getBindingDescriptors: () => opts.def.__bindingDescriptors ?? null,\n getAgentAffordances: () => opts.def.agentAffordances ?? null,\n getAgentContext: () => opts.def.agentContext ?? null,\n getRootElement: () => opts.rootElement,\n proposeConfirm: (entry) => {\n opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }))\n },\n }\n\n const helloBuilder = () => ({\n t: 'hello' as const,\n appName: opts.def.name,\n appVersion: opts.appVersion ?? '0.0.0',\n msgSchema: (opts.def.__msgSchema ?? {}) as Record<string, MessageSchemaEntry>,\n stateSchema: (opts.def.__stateSchema ?? {}) as object,\n affordancesSample: opts.def.agentAffordances\n ? opts.def.agentAffordances(opts.handle.getState())\n : [],\n docs: opts.def.agentDocs ?? null,\n schemaHash: opts.def.__schemaHash ?? '',\n })\n\n const effectHandler = createEffectHandler({\n send: (m) => opts.handle.send(m),\n wrapAgentConnect: (m) => opts.slices.wrapConnectMsg(m),\n forward: (payload) => opts.handle.send(payload),\n openWs: (token, wsUrl) => {\n if (ws) ws.close()\n ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}`)\n wsClient = attachWsClient(ws as unknown as WsLike, rpcHost, helloBuilder, {\n onActivated: () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'ActivatedByClaude' }))\n },\n onLogEntry: opts.slices.wrapLogMsg\n ? (entry) => {\n opts.handle.send(opts.slices.wrapLogMsg!({ type: 'Append', entry }))\n }\n : undefined,\n })\n ws.addEventListener('open', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsOpened' }))\n })\n ws.addEventListener('close', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsClosed' }))\n })\n },\n closeWs: () => {\n wsClient?.close()\n ws = null\n wsClient = null\n },\n })\n\n const pollConfirms = () => {\n const state = opts.handle.getState() as State\n const confirm = opts.slices.getConfirm(state)\n for (const entry of confirm.pending) {\n if (entry.status === 'pending') continue\n if (resolvedConfirms.has(entry.id)) continue\n resolvedConfirms.add(entry.id)\n if (entry.status === 'approved') {\n wsClient?.resolveConfirm(entry.id, 'confirmed', opts.handle.getState())\n } else if (entry.status === 'rejected') {\n wsClient?.resolveConfirm(entry.id, 'user-cancelled')\n }\n }\n }\n\n return {\n effectHandler,\n start() {\n if (!confirmPollTimer) confirmPollTimer = setInterval(pollConfirms, 200)\n if (!stateSubscription) {\n stateSubscription = opts.handle.subscribe((state) => {\n wsClient?.emitStateUpdate('/', state)\n })\n }\n installErrorListeners()\n },\n stop() {\n if (confirmPollTimer) clearInterval(confirmPollTimer)\n confirmPollTimer = null\n if (stateSubscription) {\n stateSubscription()\n stateSubscription = null\n }\n removeErrorListeners()\n drainErrors.length = 0\n wsClient?.close()\n },\n }\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v)\n } catch {\n return String(v)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAA8B,MAAM,gBAAgB,CAAA;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAsB,MAAM,cAAc,CAAA;AA8FnG,MAAM,UAAU,iBAAiB,CAC/B,IAAuC;IAEvC,IAAI,EAAE,GAAqB,IAAI,CAAA;IAC/B,IAAI,QAAQ,GAA6C,IAAI,CAAA;IAC7D,IAAI,gBAAgB,GAA0C,IAAI,CAAA;IAClE,IAAI,iBAAiB,GAAwB,IAAI,CAAA;IACjD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAA;IAE1C,iEAAiE;IACjE,oEAAoE;IACpE,iEAAiE;IACjE,+DAA+D;IAC/D,MAAM,WAAW,GAA2B,EAAE,CAAA;IAC9C,IAAI,uBAAuB,GAAG,KAAK,CAAA;IACnC,IAAI,UAAU,GAAqC,IAAI,CAAA;IACvD,IAAI,cAAc,GAAgD,IAAI,CAAA;IAEtE,SAAS,qBAAqB;QAC5B,IAAI,uBAAuB;YAAE,OAAM;QACnC,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAM;QACzC,UAAU,GAAG,CAAC,CAAa,EAAE,EAAE;YAC7B,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,eAAe,CAAC;gBACxD,KAAK,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;aAC5D,CAAC,CAAA;QACJ,CAAC,CAAA;QACD,cAAc,GAAG,CAAC,CAAwB,EAAE,EAAE;YAC5C,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;YAClB,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAA;YAC7F,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,oBAAoB;gBAC1B,OAAO;gBACP,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;aAChD,CAAC,CAAA;QACJ,CAAC,CAAA;QACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;QAC5C,MAAM,CAAC,gBAAgB,CAAC,oBAAoB,EAAE,cAAc,CAAC,CAAA;QAC7D,uBAAuB,GAAG,IAAI,CAAA;IAChC,CAAC;IAED,SAAS,oBAAoB;QAC3B,IAAI,CAAC,uBAAuB;YAAE,OAAM;QACpC,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAM;QACzC,IAAI,UAAU;YAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;QAC/D,IAAI,cAAc;YAAE,MAAM,CAAC,mBAAmB,CAAC,oBAAoB,EAAE,cAAc,CAAC,CAAA;QACpF,UAAU,GAAG,IAAI,CAAA;QACjB,cAAc,GAAG,IAAI,CAAA;QACrB,uBAAuB,GAAG,KAAK,CAAA;IACjC,CAAC;IAED,oEAAoE;IACpE,kEAAkE;IAClE,mEAAmE;IACnE,iEAAiE;IACjE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,iBAAiB,EAAE,CAAA;IAEjD,MAAM,OAAO,GAAa;QACxB,QAAQ,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC;QAC7D,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACxD,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;QAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;QAChE,sBAAsB,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC;QACvE,iBAAiB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI;QAC1D,kEAAkE;QAClE,kEAAkE;QAClE,0DAA0D;QAC1D,mDAAmD;QACnD,YAAY,EAAE,GAAG,EAAE,CAAE,IAAI,CAAC,GAAG,CAAC,WAA0C,IAAI,IAAI;QAChF,+DAA+D;QAC/D,kEAAkE;QAClE,2CAA2C;QAC3C,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAChD,gEAAgE;QAChE,oEAAoE;QACpE,gEAAgE;QAChE,gEAAgE;QAChE,kEAAkE;QAClE,SAAS;QACT,qBAAqB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE;QAChE,mBAAmB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI;QAC5D,eAAe,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI;QACpD,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW;QACtC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE;YACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;QAC1E,CAAC;KACF,CAAA;IAED,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,CAAC;QAC1B,CAAC,EAAE,OAAgB;QACnB,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI;QACtB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,OAAO;QACtC,SAAS,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAuC;QAC7E,WAAW,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAW;QACrD,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,gBAAgB;YAC1C,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACnD,CAAC,CAAC,EAAE;QACN,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI;QAChC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE;KACxC,CAAC,CAAA;IAEF,MAAM,aAAa,GAAG,mBAAmB,CAAC;QACxC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAChC,gBAAgB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;QACtD,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;QAC/C,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,IAAI,EAAE;gBAAE,EAAE,CAAC,KAAK,EAAE,CAAA;YAClB,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,KAAK,UAAU,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACjE,QAAQ,GAAG,cAAc,CAAC,EAAuB,EAAE,OAAO,EAAE,YAAY,EAAE;gBACxE,WAAW,EAAE,GAAG,EAAE;oBAChB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAA;gBAC7E,CAAC;gBACD,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;oBAChC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;wBACR,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAW,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;oBACtE,CAAC;oBACH,CAAC,CAAC,SAAS;aACd,CAAC,CAAA;YACF,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;gBAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;YACpE,CAAC,CAAC,CAAA;YACF,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBAChC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;YACpE,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,EAAE,GAAG,EAAE;YACZ,QAAQ,EAAE,KAAK,EAAE,CAAA;YACjB,EAAE,GAAG,IAAI,CAAA;YACT,QAAQ,GAAG,IAAI,CAAA;QACjB,CAAC;KACF,CAAC,CAAA;IAEF,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAW,CAAA;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QAC7C,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;gBAAE,SAAQ;YACxC,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAAE,SAAQ;YAC5C,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAChC,QAAQ,EAAE,cAAc,CACtB,KAAK,CAAC,EAAE,EACR,WAAW,EACX,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,CAC9C,CAAA;YACH,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACvC,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAA;YACtD,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,OAAO;QACL,aAAa;QACb,KAAK;YACH,IAAI,CAAC,gBAAgB;gBAAE,gBAAgB,GAAG,WAAW,CAAC,YAAY,EAAE,GAAG,CAAC,CAAA;YACxE,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACvB,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;oBAClD,0DAA0D;oBAC1D,0DAA0D;oBAC1D,iCAAiC;oBACjC,QAAQ,EAAE,eAAe,CAAC,GAAG,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;gBAC9D,CAAC,CAAC,CAAA;YACJ,CAAC;YACD,qBAAqB,EAAE,CAAA;QACzB,CAAC;QACD,IAAI;YACF,IAAI,gBAAgB;gBAAE,aAAa,CAAC,gBAAgB,CAAC,CAAA;YACrD,gBAAgB,GAAG,IAAI,CAAA;YACvB,IAAI,iBAAiB,EAAE,CAAC;gBACtB,iBAAiB,EAAE,CAAA;gBACnB,iBAAiB,GAAG,IAAI,CAAA;YAC1B,CAAC;YACD,oBAAoB,EAAE,CAAA;YACtB,WAAW,CAAC,MAAM,GAAG,CAAC,CAAA;YACtB,QAAQ,EAAE,KAAK,EAAE,CAAA;QACnB,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,CAAU;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["import type { AppHandle } from '@llui/dom'\nimport type { AgentEffect } from './effects.js'\nimport type { AgentConfirmState } from './agentConfirm.js'\nimport type {\n AgentDocs,\n AgentContext,\n LapDrainMeta,\n MessageAnnotations,\n MessageSchemaEntry,\n} from '../protocol.js'\nimport { attachWsClient, type WsLike, type RpcHosts } from './ws-client.js'\nimport { createEffectHandler } from './effect-handler.js'\nimport { makeDefaultCodecs, encodeForWire, decodeFromWire, type CodecRegistry } from '../codecs.js'\n\n/**\n * The shape the compiler emits as `__msgSchema`. Mirrors `MsgField`\n * from `@llui/vite-plugin/src/msg-schema.ts`. Three coexisting forms:\n *\n * 1. Bare primitive: `'string' | 'number' | 'boolean' | 'unknown'`\n * and bare enum: `{enum: [...]}`. Compact form for unannotated\n * required fields.\n * 2. Bare nested types: `{kind: 'object', shape}` for inline /\n * followed-via-typeIndex shapes; `{kind: 'array', element}` for\n * `T[]` / `readonly T[]` / `Array<T>`. The synthesizer recurses\n * to build copy-paste-ready nested examples.\n * 3. Rich descriptor: wraps any of the above with `{optional?,\n * priority?, hint?}` carrying TS optionality and `@should` hints.\n */\nexport type MsgSchemaBareType =\n | string\n | { enum: string[] }\n | { kind: 'object'; shape: Record<string, MsgSchemaField> }\n | { kind: 'array'; element: MsgSchemaBareType }\n\nexport type MsgSchemaField =\n | MsgSchemaBareType\n | {\n type: MsgSchemaBareType\n optional?: boolean\n priority?: 'should'\n hint?: string\n }\n\nexport type MsgSchemaShape = {\n discriminant: string\n variants: Record<string, Record<string, MsgSchemaField>>\n}\n\ntype ComponentMetadata = {\n __msgSchema?: unknown\n __stateSchema?: unknown\n __msgAnnotations?: Record<string, MessageAnnotations>\n __schemaHash?: string\n name: string\n agentAffordances?: (state: unknown) => Array<{ type: string; [k: string]: unknown }>\n agentDocs?: AgentDocs\n agentContext?: (state: unknown) => AgentContext\n}\n\nexport type CreateAgentClientOpts<State, Msg> = {\n handle: AppHandle\n def: ComponentMetadata\n appVersion?: string\n rootElement: Element | null\n slices: {\n getConnect: (s: State) => unknown\n getConfirm: (s: State) => AgentConfirmState\n wrapConnectMsg: (m: unknown) => Msg\n wrapConfirmMsg: (m: unknown) => Msg\n /**\n * Optional: wrap an agentLog msg so the client-side activity feed\n * mirrors what Claude is doing. If omitted, outbound log-append\n * frames still go to the server, but the local agent.log slice\n * stays empty (the UI won't show activity).\n */\n wrapLogMsg?: (m: unknown) => Msg\n }\n /**\n * Codec registry for non-JSON-safe values (Date, Blob, Map, …)\n * crossing the LAP boundary. Defaults to `makeDefaultCodecs()`\n * which ships `iso-date` and `epoch-millis`. Provide a custom\n * registry to register additional codecs (e.g. `base64-blob` for\n * file uploads). See `@llui/agent/codecs` for the convention.\n */\n codecs?: CodecRegistry\n /**\n * Base path for agent HTTP endpoints. Default: `'/agent'` (matches\n * the canonical paths in `@llui/vite-plugin`'s dev middleware and\n * `@llui/agent/server`). The mint URL, resume URLs, and revoke URL\n * derive from this so consumers don't have to keep them in sync.\n *\n * Override when:\n * - **Cross-origin agent server**: pass the full base, e.g.\n * `'https://api.example.com/agent'` or `'http://localhost:8787/agent'`.\n * - **`@cloudflare/vite-plugin` in dev**: pass `'/cdn-cgi/agent'`\n * because cloudflare-vite shadows non-`/cdn-cgi/*` routes.\n */\n agentBasePath?: string\n}\n\nexport type AgentClient = {\n effectHandler: (effect: AgentEffect) => Promise<void>\n start(): void\n stop(): void\n}\n\nexport function createAgentClient<State, Msg>(\n opts: CreateAgentClientOpts<State, Msg>,\n): AgentClient {\n let ws: WebSocket | null = null\n let wsClient: ReturnType<typeof attachWsClient> | null = null\n let confirmPollTimer: ReturnType<typeof setInterval> | null = null\n let stateSubscription: (() => void) | null = null\n const resolvedConfirms = new Set<string>()\n\n // Drain-error buffer: populated by persistent `window.error` and\n // `window.unhandledrejection` listeners installed on `start()`. The\n // send-message drain loop consumes and clears it per call so the\n // envelope surfaces only errors that fired during that window.\n const drainErrors: LapDrainMeta['errors'] = []\n let errorListenersInstalled = false\n let onErrorEvt: ((e: ErrorEvent) => void) | null = null\n let onRejectionEvt: ((e: PromiseRejectionEvent) => void) | null = null\n\n function installErrorListeners(): void {\n if (errorListenersInstalled) return\n if (typeof window === 'undefined') return\n onErrorEvt = (e: ErrorEvent) => {\n drainErrors.push({\n kind: 'error',\n message: e.message ?? String(e.error ?? 'unknown error'),\n stack: e.error instanceof Error ? e.error.stack : undefined,\n })\n }\n onRejectionEvt = (e: PromiseRejectionEvent) => {\n const r = e.reason\n const message = r instanceof Error ? r.message : typeof r === 'string' ? r : safeStringify(r)\n drainErrors.push({\n kind: 'unhandledrejection',\n message,\n stack: r instanceof Error ? r.stack : undefined,\n })\n }\n window.addEventListener('error', onErrorEvt)\n window.addEventListener('unhandledrejection', onRejectionEvt)\n errorListenersInstalled = true\n }\n\n function removeErrorListeners(): void {\n if (!errorListenersInstalled) return\n if (typeof window === 'undefined') return\n if (onErrorEvt) window.removeEventListener('error', onErrorEvt)\n if (onRejectionEvt) window.removeEventListener('unhandledrejection', onRejectionEvt)\n onErrorEvt = null\n onRejectionEvt = null\n errorListenersInstalled = false\n }\n\n // Codec registry handles non-JSON-safe values (Date, etc.) crossing\n // the LAP boundary. `getState` encodes outgoing snapshots; `send`\n // decodes incoming agent messages before they hit the reducer. The\n // tagged-value convention is documented in `@llui/agent/codecs`.\n const codecs = opts.codecs ?? makeDefaultCodecs()\n\n const rpcHost: RpcHosts = {\n getState: () => encodeForWire(opts.handle.getState(), codecs),\n send: (m) => opts.handle.send(decodeFromWire(m, codecs)),\n flush: () => opts.handle.flush(),\n subscribe: (listener) => opts.handle.subscribe(() => listener()),\n getAndClearDrainErrors: () => drainErrors.splice(0, drainErrors.length),\n getMsgAnnotations: () => opts.def.__msgAnnotations ?? null,\n // The compiler-injected message schema. Used by `list_actions` to\n // synthesize payload examples for `@agentOnly` variants that have\n // no live UI binding — the agent should still see them as\n // affordances even though no human can click them.\n getMsgSchema: () => (opts.def.__msgSchema as MsgSchemaShape | undefined) ?? null,\n // Run the reducer in isolation for `would_dispatch`. Wraps the\n // AppHandle's same-named method so the host doesn't need a direct\n // reference to the live ComponentInstance.\n runReducer: (msg) => opts.handle.runReducer(msg),\n // Live binding descriptors: read from the runtime registry that\n // tracks which Msg variants are dispatchable from currently-mounted\n // event handlers. Empty array when the app wasn't compiled with\n // agent metadata (no tagger pass) or has no view bindings yet —\n // both produce the same \"no live affordances\" signal at the agent\n // layer.\n getBindingDescriptors: () => opts.handle.getBindingDescriptors(),\n getAgentAffordances: () => opts.def.agentAffordances ?? null,\n getAgentContext: () => opts.def.agentContext ?? null,\n getRootElement: () => opts.rootElement,\n proposeConfirm: (entry) => {\n opts.handle.send(opts.slices.wrapConfirmMsg({ type: 'Propose', entry }))\n },\n }\n\n const helloBuilder = () => ({\n t: 'hello' as const,\n appName: opts.def.name,\n appVersion: opts.appVersion ?? '0.0.0',\n msgSchema: (opts.def.__msgSchema ?? {}) as Record<string, MessageSchemaEntry>,\n stateSchema: (opts.def.__stateSchema ?? {}) as object,\n affordancesSample: opts.def.agentAffordances\n ? opts.def.agentAffordances(opts.handle.getState())\n : [],\n docs: opts.def.agentDocs ?? null,\n schemaHash: opts.def.__schemaHash ?? '',\n })\n\n const effectHandler = createEffectHandler({\n send: (m) => opts.handle.send(m),\n wrapAgentConnect: (m) => opts.slices.wrapConnectMsg(m),\n forward: (payload) => opts.handle.send(payload),\n agentBasePath: opts.agentBasePath,\n openWs: (token, wsUrl) => {\n if (ws) ws.close()\n ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}`)\n wsClient = attachWsClient(ws as unknown as WsLike, rpcHost, helloBuilder, {\n onActivated: () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'ActivatedByClaude' }))\n },\n onLogEntry: opts.slices.wrapLogMsg\n ? (entry) => {\n opts.handle.send(opts.slices.wrapLogMsg!({ type: 'Append', entry }))\n }\n : undefined,\n })\n ws.addEventListener('open', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsOpened' }))\n })\n ws.addEventListener('close', () => {\n opts.handle.send(opts.slices.wrapConnectMsg({ type: 'WsClosed' }))\n })\n },\n closeWs: () => {\n wsClient?.close()\n ws = null\n wsClient = null\n },\n })\n\n const pollConfirms = () => {\n const state = opts.handle.getState() as State\n const confirm = opts.slices.getConfirm(state)\n for (const entry of confirm.pending) {\n if (entry.status === 'pending') continue\n if (resolvedConfirms.has(entry.id)) continue\n resolvedConfirms.add(entry.id)\n if (entry.status === 'approved') {\n wsClient?.resolveConfirm(\n entry.id,\n 'confirmed',\n encodeForWire(opts.handle.getState(), codecs),\n )\n } else if (entry.status === 'rejected') {\n wsClient?.resolveConfirm(entry.id, 'user-cancelled')\n }\n }\n }\n\n return {\n effectHandler,\n start() {\n if (!confirmPollTimer) confirmPollTimer = setInterval(pollConfirms, 200)\n if (!stateSubscription) {\n stateSubscription = opts.handle.subscribe((state) => {\n // Same codec convention as `getState`: outgoing snapshots\n // pass through the encoder so non-JSON-safe values (Date,\n // etc.) become tagged-wire form.\n wsClient?.emitStateUpdate('/', encodeForWire(state, codecs))\n })\n }\n installErrorListeners()\n },\n stop() {\n if (confirmPollTimer) clearInterval(confirmPollTimer)\n confirmPollTimer = null\n if (stateSubscription) {\n stateSubscription()\n stateSubscription = null\n }\n removeErrorListeners()\n drainErrors.length = 0\n wsClient?.close()\n },\n }\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v)\n } catch {\n return String(v)\n }\n}\n"]}
|
|
@@ -1,22 +1,35 @@
|
|
|
1
|
-
import type { OutlineNode } from '../../protocol.js';
|
|
1
|
+
import type { MessageAnnotations, OutlineNode } from '../../protocol.js';
|
|
2
2
|
export type DescribeVisibleArgs = Record<string, never>;
|
|
3
3
|
export type DescribeVisibleResult = {
|
|
4
4
|
outline: OutlineNode[];
|
|
5
|
+
/**
|
|
6
|
+
* `'data-agent'` when the outline was scoped to author-tagged zones.
|
|
7
|
+
* `'fallback'` when the app has no `data-agent` attributes and the
|
|
8
|
+
* walker fell back to a generic semantic-element pass over the root.
|
|
9
|
+
* `'truncated'` when the fallback outline hit the node-count cap and
|
|
10
|
+
* stopped early; the caller can ask follow-up questions through
|
|
11
|
+
* `query_dom` or by inspecting state directly.
|
|
12
|
+
*/
|
|
13
|
+
source: 'data-agent' | 'fallback' | 'truncated';
|
|
5
14
|
};
|
|
6
15
|
export type DescribeVisibleHost = {
|
|
7
16
|
getRootElement(): Element | null;
|
|
8
17
|
getBindingDescriptors(): Array<{
|
|
9
18
|
variant: string;
|
|
10
19
|
}> | null;
|
|
11
|
-
getMsgAnnotations(): Record<string,
|
|
12
|
-
intent: string | null;
|
|
13
|
-
humanOnly: boolean;
|
|
14
|
-
}> | null;
|
|
20
|
+
getMsgAnnotations(): Record<string, MessageAnnotations> | null;
|
|
15
21
|
};
|
|
16
22
|
/**
|
|
17
23
|
* Walk data-agent-tagged subtrees and produce a structured outline.
|
|
18
24
|
* Buttons cross-reference __bindingDescriptors so Claude can tie
|
|
19
25
|
* visible text to variant names.
|
|
26
|
+
*
|
|
27
|
+
* If the app has no `data-agent` tags, fall back to a depth-limited
|
|
28
|
+
* walk of the entire root element. This makes the tool useful for apps
|
|
29
|
+
* that haven't (yet) tagged their views — typical first-pass dogfood
|
|
30
|
+
* targets — instead of returning an empty outline that conveys
|
|
31
|
+
* nothing. The fallback path sets `source: 'fallback'` so the caller
|
|
32
|
+
* can tell the outline is best-effort.
|
|
20
33
|
*/
|
|
21
34
|
export declare function handleDescribeVisibleContent(host: DescribeVisibleHost): DescribeVisibleResult;
|
|
22
35
|
//# sourceMappingURL=describe-visible-content.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-visible-content.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"describe-visible-content.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAExE,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AACvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB;;;;;;;OAOG;IACH,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,WAAW,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,IAAI,OAAO,GAAG,IAAI,CAAA;IAChC,qBAAqB,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;IAC1D,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;CAC/D,CAAA;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAqB7F"}
|
|
@@ -1,20 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard caps for the fallback walk. The author-tagged path doesn't need
|
|
3
|
+
* caps — by definition the author chose what to expose. The fallback is
|
|
4
|
+
* walking arbitrary DOM, so it has to bound the work.
|
|
5
|
+
*
|
|
6
|
+
* 200 nodes covers ~3–5 visible screens of typical app UI; depth 8 is
|
|
7
|
+
* deep enough for nested content trees but stops us from descending
|
|
8
|
+
* into giant virtualised lists or syntax-highlighted code blocks.
|
|
9
|
+
*/
|
|
10
|
+
const FALLBACK_MAX_NODES = 200;
|
|
11
|
+
const FALLBACK_MAX_DEPTH = 8;
|
|
1
12
|
/**
|
|
2
13
|
* Walk data-agent-tagged subtrees and produce a structured outline.
|
|
3
14
|
* Buttons cross-reference __bindingDescriptors so Claude can tie
|
|
4
15
|
* visible text to variant names.
|
|
16
|
+
*
|
|
17
|
+
* If the app has no `data-agent` tags, fall back to a depth-limited
|
|
18
|
+
* walk of the entire root element. This makes the tool useful for apps
|
|
19
|
+
* that haven't (yet) tagged their views — typical first-pass dogfood
|
|
20
|
+
* targets — instead of returning an empty outline that conveys
|
|
21
|
+
* nothing. The fallback path sets `source: 'fallback'` so the caller
|
|
22
|
+
* can tell the outline is best-effort.
|
|
5
23
|
*/
|
|
6
24
|
export function handleDescribeVisibleContent(host) {
|
|
7
25
|
const root = host.getRootElement();
|
|
8
26
|
if (!root)
|
|
9
|
-
return { outline: [] };
|
|
10
|
-
const out = [];
|
|
27
|
+
return { outline: [], source: 'data-agent' };
|
|
11
28
|
const allZones = Array.from(root.querySelectorAll('[data-agent]'));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
if (allZones.length > 0) {
|
|
30
|
+
const out = [];
|
|
31
|
+
// Only walk top-level zones; skip zones that are descendants of other zones
|
|
32
|
+
const topLevel = allZones.filter((zone) => !allZones.some((other) => other !== zone && other.contains(zone)));
|
|
33
|
+
for (const zone of topLevel) {
|
|
34
|
+
walk(zone, out);
|
|
35
|
+
}
|
|
36
|
+
return { outline: out, source: 'data-agent' };
|
|
16
37
|
}
|
|
17
|
-
|
|
38
|
+
// Fallback: walk the entire root with caps. Useful for apps without
|
|
39
|
+
// any data-agent annotations — at minimum the agent gets headings,
|
|
40
|
+
// buttons, links, lists, and visible text it can use to orient.
|
|
41
|
+
const out = [];
|
|
42
|
+
const truncated = walkFallback(root, out, 0);
|
|
43
|
+
return { outline: out, source: truncated ? 'truncated' : 'fallback' };
|
|
18
44
|
}
|
|
19
45
|
function walk(el, out) {
|
|
20
46
|
const tag = el.tagName.toLowerCase();
|
|
@@ -63,4 +89,83 @@ function walk(el, out) {
|
|
|
63
89
|
walk(child, out);
|
|
64
90
|
}
|
|
65
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Depth- and count-limited walk for the fallback path. Returns true
|
|
94
|
+
* iff the cap was hit (at least one element was skipped). Same node
|
|
95
|
+
* vocabulary as `walk` but prunes more aggressively:
|
|
96
|
+
*
|
|
97
|
+
* - `<a>` without `href`, `<button>` without text — skipped (often
|
|
98
|
+
* icon-only chrome that adds noise without helping the agent).
|
|
99
|
+
* - Nested wrapper divs without semantic content — descended into
|
|
100
|
+
* but not emitted.
|
|
101
|
+
* - Standalone text nodes — only emitted when the parent has no
|
|
102
|
+
* semantic children (preserves the existing `walk` heuristic).
|
|
103
|
+
*/
|
|
104
|
+
function walkFallback(el, out, depth) {
|
|
105
|
+
if (out.length >= FALLBACK_MAX_NODES)
|
|
106
|
+
return true;
|
|
107
|
+
if (depth > FALLBACK_MAX_DEPTH)
|
|
108
|
+
return false;
|
|
109
|
+
const tag = el.tagName.toLowerCase();
|
|
110
|
+
// Skip script/style/noscript subtrees — never user-facing content.
|
|
111
|
+
if (tag === 'script' || tag === 'style' || tag === 'noscript' || tag === 'template')
|
|
112
|
+
return false;
|
|
113
|
+
const text = (el.textContent ?? '').trim();
|
|
114
|
+
if (/^h[1-6]$/.test(tag)) {
|
|
115
|
+
out.push({ kind: 'heading', level: Number(tag[1]), text });
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (tag === 'button') {
|
|
119
|
+
if (text.length > 0 || el.getAttribute('aria-label')) {
|
|
120
|
+
out.push({
|
|
121
|
+
kind: 'button',
|
|
122
|
+
text: text || el.getAttribute('aria-label') || '',
|
|
123
|
+
disabled: el.disabled,
|
|
124
|
+
actionVariant: el.getAttribute('data-agent') ?? null,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return out.length >= FALLBACK_MAX_NODES;
|
|
128
|
+
}
|
|
129
|
+
if (tag === 'a' && el.getAttribute('href')) {
|
|
130
|
+
out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' });
|
|
131
|
+
return out.length >= FALLBACK_MAX_NODES;
|
|
132
|
+
}
|
|
133
|
+
if (tag === 'input') {
|
|
134
|
+
const inputType = el.type ?? 'text';
|
|
135
|
+
// `hidden` inputs are almost always form-state plumbing; skip them.
|
|
136
|
+
if (inputType !== 'hidden') {
|
|
137
|
+
out.push({
|
|
138
|
+
kind: 'input',
|
|
139
|
+
label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,
|
|
140
|
+
value: el.value ?? null,
|
|
141
|
+
type: inputType,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return out.length >= FALLBACK_MAX_NODES;
|
|
145
|
+
}
|
|
146
|
+
if (tag === 'ul' || tag === 'ol') {
|
|
147
|
+
const items = [];
|
|
148
|
+
for (const child of Array.from(el.children)) {
|
|
149
|
+
if (child.tagName.toLowerCase() === 'li') {
|
|
150
|
+
const itemText = (child.textContent ?? '').trim();
|
|
151
|
+
if (itemText.length > 0)
|
|
152
|
+
items.push({ kind: 'item', text: itemText });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (items.length > 0)
|
|
156
|
+
out.push({ kind: 'list', items });
|
|
157
|
+
return out.length >= FALLBACK_MAX_NODES;
|
|
158
|
+
}
|
|
159
|
+
// Bare text leaf: only emit if the element has a non-empty text and
|
|
160
|
+
// no element children. Same rule as the tagged walker.
|
|
161
|
+
if (text.length > 0 && el.children.length === 0) {
|
|
162
|
+
out.push({ kind: 'text', text });
|
|
163
|
+
return out.length >= FALLBACK_MAX_NODES;
|
|
164
|
+
}
|
|
165
|
+
for (const child of Array.from(el.children)) {
|
|
166
|
+
if (walkFallback(child, out, depth + 1))
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
66
171
|
//# sourceMappingURL=describe-visible-content.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-visible-content.js","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OutlineNode } from '../../protocol.js'\n\nexport type DescribeVisibleArgs = Record<string, never>\nexport type DescribeVisibleResult = { outline: OutlineNode[] }\n\nexport type DescribeVisibleHost = {\n getRootElement(): Element | null\n getBindingDescriptors(): Array<{ variant: string }> | null\n getMsgAnnotations(): Record<string, { intent: string | null; humanOnly: boolean }> | null\n}\n\n/**\n * Walk data-agent-tagged subtrees and produce a structured outline.\n * Buttons cross-reference __bindingDescriptors so Claude can tie\n * visible text to variant names.\n */\nexport function handleDescribeVisibleContent(host: DescribeVisibleHost): DescribeVisibleResult {\n const root = host.getRootElement()\n if (!root) return { outline: [] }\n const out: OutlineNode[] = []\n const allZones = Array.from(root.querySelectorAll('[data-agent]'))\n // Only walk top-level zones; skip zones that are descendants of other zones\n const topLevel = allZones.filter(\n (zone) => !allZones.some((other) => other !== zone && other.contains(zone)),\n )\n for (const zone of topLevel) {\n walk(zone, out)\n }\n return { outline: out }\n}\n\nfunction walk(el: Element, out: OutlineNode[]): void {\n const tag = el.tagName.toLowerCase()\n const text = (el.textContent ?? '').trim()\n if (/^h[1-6]$/.test(tag)) {\n out.push({ kind: 'heading', level: Number(tag[1]), text })\n return\n }\n if (tag === 'button') {\n out.push({\n kind: 'button',\n text,\n disabled: (el as HTMLButtonElement).disabled,\n actionVariant: el.getAttribute('data-agent') ?? null,\n })\n return\n }\n if (tag === 'a' && el.getAttribute('href')) {\n out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' })\n return\n }\n if (tag === 'input') {\n out.push({\n kind: 'input',\n label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,\n value: (el as HTMLInputElement).value ?? null,\n type: (el as HTMLInputElement).type ?? 'text',\n })\n return\n }\n if (tag === 'ul' || tag === 'ol') {\n const items: OutlineNode[] = []\n for (const child of Array.from(el.children)) {\n if (child.tagName.toLowerCase() === 'li') {\n items.push({ kind: 'item', text: (child.textContent ?? '').trim() })\n }\n }\n out.push({ kind: 'list', items })\n return\n }\n if (text.length > 0 && el.children.length === 0) {\n out.push({ kind: 'text', text })\n return\n }\n for (const child of Array.from(el.children)) {\n walk(child, out)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"describe-visible-content.js","sourceRoot":"","sources":["../../../src/client/rpc/describe-visible-content.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageAnnotations, OutlineNode } from '../../protocol.js'\n\nexport type DescribeVisibleArgs = Record<string, never>\nexport type DescribeVisibleResult = {\n outline: OutlineNode[]\n /**\n * `'data-agent'` when the outline was scoped to author-tagged zones.\n * `'fallback'` when the app has no `data-agent` attributes and the\n * walker fell back to a generic semantic-element pass over the root.\n * `'truncated'` when the fallback outline hit the node-count cap and\n * stopped early; the caller can ask follow-up questions through\n * `query_dom` or by inspecting state directly.\n */\n source: 'data-agent' | 'fallback' | 'truncated'\n}\n\nexport type DescribeVisibleHost = {\n getRootElement(): Element | null\n getBindingDescriptors(): Array<{ variant: string }> | null\n getMsgAnnotations(): Record<string, MessageAnnotations> | null\n}\n\n/**\n * Hard caps for the fallback walk. The author-tagged path doesn't need\n * caps — by definition the author chose what to expose. The fallback is\n * walking arbitrary DOM, so it has to bound the work.\n *\n * 200 nodes covers ~3–5 visible screens of typical app UI; depth 8 is\n * deep enough for nested content trees but stops us from descending\n * into giant virtualised lists or syntax-highlighted code blocks.\n */\nconst FALLBACK_MAX_NODES = 200\nconst FALLBACK_MAX_DEPTH = 8\n\n/**\n * Walk data-agent-tagged subtrees and produce a structured outline.\n * Buttons cross-reference __bindingDescriptors so Claude can tie\n * visible text to variant names.\n *\n * If the app has no `data-agent` tags, fall back to a depth-limited\n * walk of the entire root element. This makes the tool useful for apps\n * that haven't (yet) tagged their views — typical first-pass dogfood\n * targets — instead of returning an empty outline that conveys\n * nothing. The fallback path sets `source: 'fallback'` so the caller\n * can tell the outline is best-effort.\n */\nexport function handleDescribeVisibleContent(host: DescribeVisibleHost): DescribeVisibleResult {\n const root = host.getRootElement()\n if (!root) return { outline: [], source: 'data-agent' }\n const allZones = Array.from(root.querySelectorAll('[data-agent]'))\n if (allZones.length > 0) {\n const out: OutlineNode[] = []\n // Only walk top-level zones; skip zones that are descendants of other zones\n const topLevel = allZones.filter(\n (zone) => !allZones.some((other) => other !== zone && other.contains(zone)),\n )\n for (const zone of topLevel) {\n walk(zone, out)\n }\n return { outline: out, source: 'data-agent' }\n }\n // Fallback: walk the entire root with caps. Useful for apps without\n // any data-agent annotations — at minimum the agent gets headings,\n // buttons, links, lists, and visible text it can use to orient.\n const out: OutlineNode[] = []\n const truncated = walkFallback(root, out, 0)\n return { outline: out, source: truncated ? 'truncated' : 'fallback' }\n}\n\nfunction walk(el: Element, out: OutlineNode[]): void {\n const tag = el.tagName.toLowerCase()\n const text = (el.textContent ?? '').trim()\n if (/^h[1-6]$/.test(tag)) {\n out.push({ kind: 'heading', level: Number(tag[1]), text })\n return\n }\n if (tag === 'button') {\n out.push({\n kind: 'button',\n text,\n disabled: (el as HTMLButtonElement).disabled,\n actionVariant: el.getAttribute('data-agent') ?? null,\n })\n return\n }\n if (tag === 'a' && el.getAttribute('href')) {\n out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' })\n return\n }\n if (tag === 'input') {\n out.push({\n kind: 'input',\n label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,\n value: (el as HTMLInputElement).value ?? null,\n type: (el as HTMLInputElement).type ?? 'text',\n })\n return\n }\n if (tag === 'ul' || tag === 'ol') {\n const items: OutlineNode[] = []\n for (const child of Array.from(el.children)) {\n if (child.tagName.toLowerCase() === 'li') {\n items.push({ kind: 'item', text: (child.textContent ?? '').trim() })\n }\n }\n out.push({ kind: 'list', items })\n return\n }\n if (text.length > 0 && el.children.length === 0) {\n out.push({ kind: 'text', text })\n return\n }\n for (const child of Array.from(el.children)) {\n walk(child, out)\n }\n}\n\n/**\n * Depth- and count-limited walk for the fallback path. Returns true\n * iff the cap was hit (at least one element was skipped). Same node\n * vocabulary as `walk` but prunes more aggressively:\n *\n * - `<a>` without `href`, `<button>` without text — skipped (often\n * icon-only chrome that adds noise without helping the agent).\n * - Nested wrapper divs without semantic content — descended into\n * but not emitted.\n * - Standalone text nodes — only emitted when the parent has no\n * semantic children (preserves the existing `walk` heuristic).\n */\nfunction walkFallback(el: Element, out: OutlineNode[], depth: number): boolean {\n if (out.length >= FALLBACK_MAX_NODES) return true\n if (depth > FALLBACK_MAX_DEPTH) return false\n const tag = el.tagName.toLowerCase()\n // Skip script/style/noscript subtrees — never user-facing content.\n if (tag === 'script' || tag === 'style' || tag === 'noscript' || tag === 'template') return false\n const text = (el.textContent ?? '').trim()\n if (/^h[1-6]$/.test(tag)) {\n out.push({ kind: 'heading', level: Number(tag[1]), text })\n return false\n }\n if (tag === 'button') {\n if (text.length > 0 || el.getAttribute('aria-label')) {\n out.push({\n kind: 'button',\n text: text || el.getAttribute('aria-label') || '',\n disabled: (el as HTMLButtonElement).disabled,\n actionVariant: el.getAttribute('data-agent') ?? null,\n })\n }\n return out.length >= FALLBACK_MAX_NODES\n }\n if (tag === 'a' && el.getAttribute('href')) {\n out.push({ kind: 'link', text, href: el.getAttribute('href') ?? '' })\n return out.length >= FALLBACK_MAX_NODES\n }\n if (tag === 'input') {\n const inputType = (el as HTMLInputElement).type ?? 'text'\n // `hidden` inputs are almost always form-state plumbing; skip them.\n if (inputType !== 'hidden') {\n out.push({\n kind: 'input',\n label: el.getAttribute('aria-label') ?? el.getAttribute('name') ?? null,\n value: (el as HTMLInputElement).value ?? null,\n type: inputType,\n })\n }\n return out.length >= FALLBACK_MAX_NODES\n }\n if (tag === 'ul' || tag === 'ol') {\n const items: OutlineNode[] = []\n for (const child of Array.from(el.children)) {\n if (child.tagName.toLowerCase() === 'li') {\n const itemText = (child.textContent ?? '').trim()\n if (itemText.length > 0) items.push({ kind: 'item', text: itemText })\n }\n }\n if (items.length > 0) out.push({ kind: 'list', items })\n return out.length >= FALLBACK_MAX_NODES\n }\n // Bare text leaf: only emit if the element has a non-empty text and\n // no element children. Same rule as the tagged walker.\n if (text.length > 0 && el.children.length === 0) {\n out.push({ kind: 'text', text })\n return out.length >= FALLBACK_MAX_NODES\n }\n for (const child of Array.from(el.children)) {\n if (walkFallback(child, out, depth + 1)) return true\n }\n return false\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MessageAnnotations } from '../../protocol.js';
|
|
2
|
+
import type { MsgSchemaShape } from '../factory.js';
|
|
2
3
|
type Binding = {
|
|
3
4
|
variant: string;
|
|
4
5
|
};
|
|
@@ -7,19 +8,68 @@ export type ListActionsHost = {
|
|
|
7
8
|
getState(): unknown;
|
|
8
9
|
getBindingDescriptors(): Binding[] | null;
|
|
9
10
|
getMsgAnnotations(): Annotations | null;
|
|
11
|
+
getMsgSchema(): MsgSchemaShape | null;
|
|
10
12
|
getAgentAffordances(): ((state: unknown) => Array<{
|
|
11
13
|
type: string;
|
|
12
14
|
[k: string]: unknown;
|
|
13
15
|
}>) | null;
|
|
14
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* `dispatchMode` on each action is `'shared'` (human can also click via
|
|
19
|
+
* a UI affordance) or `'agent-only'` (no UI binding — agent is the only
|
|
20
|
+
* dispatcher). `'human-only'` variants are filtered out before this
|
|
21
|
+
* point — they never reach the LLM.
|
|
22
|
+
*
|
|
23
|
+
* `source` distinguishes WHERE the affordance came from:
|
|
24
|
+
* - `'binding'` — a tagged event handler is currently mounted.
|
|
25
|
+
* - `'always-affordable'` — the app's `agentAffordances(state)` hook
|
|
26
|
+
* listed it as available right now.
|
|
27
|
+
* - `'schema'` — neither of the above; the variant is in the
|
|
28
|
+
* Msg union, annotated `@agentOnly`, and the payload is example-
|
|
29
|
+
* only (no live UI binding maps to it). Bulk-edit operations
|
|
30
|
+
* typically land here.
|
|
31
|
+
*/
|
|
15
32
|
export type ListActionsResult = {
|
|
16
33
|
actions: Array<{
|
|
17
34
|
variant: string;
|
|
18
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Human-readable phrase from `@intent("…")`, or `null` when the
|
|
37
|
+
* variant is unannotated. Mirror of LapActionsResponse.intent —
|
|
38
|
+
* callers should treat `null` as a documentation gap and not as
|
|
39
|
+
* "missing label, fall back to variant name".
|
|
40
|
+
*/
|
|
41
|
+
intent: string | null;
|
|
19
42
|
requiresConfirm: boolean;
|
|
20
|
-
|
|
43
|
+
dispatchMode: 'shared' | 'agent-only';
|
|
44
|
+
source: 'binding' | 'always-affordable' | 'schema';
|
|
21
45
|
selectorHint: string | null;
|
|
22
46
|
payloadHint: object | null;
|
|
47
|
+
/** Cautionary text from `@warning` JSDoc, or null. */
|
|
48
|
+
warning: string | null;
|
|
49
|
+
/** Concrete examples from `@example` JSDoc, in source order. */
|
|
50
|
+
examples: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Effect kinds this variant emits, from `@emits("k1", "k2")`.
|
|
53
|
+
* Empty when not annotated. Lets the agent know what side
|
|
54
|
+
* effects fire — useful for batching ("100 dispatches × cloud-
|
|
55
|
+
* save = bad") and for confirming destructive flows.
|
|
56
|
+
*/
|
|
57
|
+
emits: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Per-field guidance lifted from `@should("…")` JSDoc on payload
|
|
60
|
+
* fields. Path is dot/bracket notation rooted at the payload
|
|
61
|
+
* (e.g. `"cells"` for a top-level field, `"cells[].meta"` for an
|
|
62
|
+
* array element's nested field). Useful when the field is typed
|
|
63
|
+
* as `unknown` or as a polymorphic shape — the hint says "type
|
|
64
|
+
* matches the criterion's kind: number for quantity, …" so the
|
|
65
|
+
* agent doesn't have to guess from the bare schema.
|
|
66
|
+
*
|
|
67
|
+
* Empty when no field on this variant carries an `@should` hint.
|
|
68
|
+
*/
|
|
69
|
+
fieldHints: Array<{
|
|
70
|
+
path: string;
|
|
71
|
+
hint: string;
|
|
72
|
+
}>;
|
|
23
73
|
}>;
|
|
24
74
|
};
|
|
25
75
|
export declare function handleListActions(host: ListActionsHost): ListActionsResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-actions.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"list-actions.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,eAAe,CAAA;AAEnE,KAAK,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAClC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,IAAI,OAAO,CAAA;IACnB,qBAAqB,IAAI,OAAO,EAAE,GAAG,IAAI,CAAA;IACzC,iBAAiB,IAAI,WAAW,GAAG,IAAI,CAAA;IACvC,YAAY,IAAI,cAAc,GAAG,IAAI,CAAA;IACrC,mBAAmB,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,IAAI,CAAA;CAClG,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAA;QACf;;;;;WAKG;QACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,eAAe,EAAE,OAAO,CAAA;QACxB,YAAY,EAAE,QAAQ,GAAG,YAAY,CAAA;QACrC,MAAM,EAAE,SAAS,GAAG,mBAAmB,GAAG,QAAQ,CAAA;QAClD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;QAC1B,sDAAsD;QACtD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;QACtB,gEAAgE;QAChE,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB;;;;;WAKG;QACH,KAAK,EAAE,MAAM,EAAE,CAAA;QACf;;;;;;;;;;WAUG;QACH,UAAU,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAClD,CAAC,CAAA;CACH,CAAA;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAoG1E"}
|
|
@@ -3,36 +3,218 @@ export function handleListActions(host) {
|
|
|
3
3
|
const state = host.getState();
|
|
4
4
|
const descriptors = host.getBindingDescriptors() ?? [];
|
|
5
5
|
const affordances = host.getAgentAffordances()?.(state) ?? [];
|
|
6
|
+
const schema = host.getMsgSchema();
|
|
6
7
|
const out = [];
|
|
7
|
-
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
// From bindings — these have UI affordances by definition, so they're
|
|
10
|
+
// either 'shared' (default) or, in the malformed case where someone
|
|
11
|
+
// bound an `@agentOnly` Msg in a view, 'agent-only'. Either way the
|
|
12
|
+
// agent can dispatch them.
|
|
8
13
|
for (const d of descriptors) {
|
|
9
14
|
const ann = annotations[d.variant];
|
|
10
|
-
if (ann?.
|
|
15
|
+
if (ann?.dispatchMode === 'human-only')
|
|
11
16
|
continue;
|
|
17
|
+
seen.add(d.variant);
|
|
18
|
+
const variantSchema = schema?.variants[d.variant];
|
|
12
19
|
out.push({
|
|
13
20
|
variant: d.variant,
|
|
14
|
-
intent: ann?.intent ??
|
|
21
|
+
intent: ann?.intent ?? null,
|
|
15
22
|
requiresConfirm: ann?.requiresConfirm ?? false,
|
|
23
|
+
dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',
|
|
16
24
|
source: 'binding',
|
|
17
25
|
selectorHint: null,
|
|
18
26
|
payloadHint: null,
|
|
27
|
+
warning: ann?.warning ?? null,
|
|
28
|
+
examples: ann?.examples ?? [],
|
|
29
|
+
emits: ann?.emits ?? [],
|
|
30
|
+
fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],
|
|
19
31
|
});
|
|
20
32
|
}
|
|
21
33
|
// From always-affordable
|
|
22
34
|
for (const msg of affordances) {
|
|
23
35
|
const ann = annotations[msg.type];
|
|
24
|
-
if (ann?.
|
|
36
|
+
if (ann?.dispatchMode === 'human-only')
|
|
25
37
|
continue;
|
|
38
|
+
seen.add(msg.type);
|
|
26
39
|
const { type, ...rest } = msg;
|
|
40
|
+
const variantSchema = schema?.variants[type];
|
|
27
41
|
out.push({
|
|
28
42
|
variant: type,
|
|
29
|
-
intent: ann?.intent ??
|
|
43
|
+
intent: ann?.intent ?? null,
|
|
30
44
|
requiresConfirm: ann?.requiresConfirm ?? false,
|
|
45
|
+
dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',
|
|
31
46
|
source: 'always-affordable',
|
|
32
47
|
selectorHint: null,
|
|
33
48
|
payloadHint: Object.keys(rest).length > 0 ? rest : null,
|
|
49
|
+
warning: ann?.warning ?? null,
|
|
50
|
+
examples: ann?.examples ?? [],
|
|
51
|
+
emits: ann?.emits ?? [],
|
|
52
|
+
fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],
|
|
34
53
|
});
|
|
35
54
|
}
|
|
55
|
+
// From schema — variants that aren't already surfaced as bindings
|
|
56
|
+
// or always-affordable, and that the author intentionally documented
|
|
57
|
+
// for agent use. Two cases land here:
|
|
58
|
+
//
|
|
59
|
+
// 1. `@agentOnly` variants — the canonical "no UI button maps to
|
|
60
|
+
// this; the agent is the only dispatcher." Bulk edits, imports,
|
|
61
|
+
// admin operations.
|
|
62
|
+
// 2. `'shared'` variants with `@intent` but no live binding — the
|
|
63
|
+
// author wrote a description for the variant, signalling it's
|
|
64
|
+
// a real agent-callable dispatch even when the corresponding UI
|
|
65
|
+
// affordance is closed (e.g. `Matrix/SetQuantityValue` lives
|
|
66
|
+
// inside the cell editor; without this case, an agent that
|
|
67
|
+
// wants to set one cell is forced to use the bulk
|
|
68
|
+
// `Matrix/SetManyCells` with a 1-element array).
|
|
69
|
+
//
|
|
70
|
+
// `'shared'` variants WITHOUT `@intent` stay hidden — undocumented
|
|
71
|
+
// shared variants are usually internal (effect-result messages,
|
|
72
|
+
// router-internal acks) that aren't intended as agent affordances.
|
|
73
|
+
// `'human-only'` is always filtered.
|
|
74
|
+
if (schema) {
|
|
75
|
+
for (const [variant, fields] of Object.entries(schema.variants)) {
|
|
76
|
+
if (seen.has(variant))
|
|
77
|
+
continue;
|
|
78
|
+
const ann = annotations[variant];
|
|
79
|
+
if (ann?.dispatchMode === 'human-only')
|
|
80
|
+
continue;
|
|
81
|
+
const isAgentOnly = ann?.dispatchMode === 'agent-only';
|
|
82
|
+
const isDocumentedShared = ann?.dispatchMode !== 'agent-only' && Boolean(ann?.intent);
|
|
83
|
+
if (!isAgentOnly && !isDocumentedShared)
|
|
84
|
+
continue;
|
|
85
|
+
out.push({
|
|
86
|
+
variant,
|
|
87
|
+
intent: ann?.intent ?? null,
|
|
88
|
+
requiresConfirm: ann?.requiresConfirm ?? false,
|
|
89
|
+
dispatchMode: isAgentOnly ? 'agent-only' : 'shared',
|
|
90
|
+
source: 'schema',
|
|
91
|
+
selectorHint: null,
|
|
92
|
+
payloadHint: synthesizePayload(variant, fields),
|
|
93
|
+
warning: ann?.warning ?? null,
|
|
94
|
+
examples: ann?.examples ?? [],
|
|
95
|
+
emits: ann?.emits ?? [],
|
|
96
|
+
fieldHints: collectFieldHints(fields),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
36
100
|
return { actions: out };
|
|
37
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Build an example payload object the LLM can fill in. Required
|
|
104
|
+
* fields always appear; optional fields appear only when annotated
|
|
105
|
+
* `@should` (LLM is encouraged to fill them in). Fields without a
|
|
106
|
+
* concrete primitive type (`'unknown'`) emit `null` placeholders the
|
|
107
|
+
* LLM is expected to replace.
|
|
108
|
+
*
|
|
109
|
+
* The first key is `type` so the payload reads as a complete Msg
|
|
110
|
+
* shape — copy-paste-ready into `send_message`.
|
|
111
|
+
*/
|
|
112
|
+
function synthesizePayload(variant, fields) {
|
|
113
|
+
const out = { type: variant };
|
|
114
|
+
for (const [name, descriptor] of Object.entries(fields)) {
|
|
115
|
+
const optional = isOptional(descriptor);
|
|
116
|
+
const priority = isShould(descriptor);
|
|
117
|
+
// Skip optional fields unless they're @should-flagged. Required
|
|
118
|
+
// fields always appear.
|
|
119
|
+
if (optional && priority !== 'should')
|
|
120
|
+
continue;
|
|
121
|
+
out[name] = exampleValue(descriptor);
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Walk a variant's field tree and collect every `@should` hint into a
|
|
127
|
+
* flat list keyed by field path. Path conventions:
|
|
128
|
+
* - top-level field: `"cells"`
|
|
129
|
+
* - nested object property: `"cells.value"`
|
|
130
|
+
* - array element: `"cells[]"` for the element itself; descendants
|
|
131
|
+
* use `"cells[].meta"` and so on.
|
|
132
|
+
*
|
|
133
|
+
* Surfaces the same hints that show up nested inside
|
|
134
|
+
* `description.messages.variants[X].field.hint` so callers don't have
|
|
135
|
+
* to dig through the schema tree to find them.
|
|
136
|
+
*/
|
|
137
|
+
function collectFieldHints(fields) {
|
|
138
|
+
const out = [];
|
|
139
|
+
for (const [name, descriptor] of Object.entries(fields)) {
|
|
140
|
+
walkHint(name, descriptor, out);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
function walkHint(path, d, out) {
|
|
145
|
+
// Rich descriptor with a hint at this position.
|
|
146
|
+
if (typeof d === 'object' && d !== null && 'type' in d) {
|
|
147
|
+
if (typeof d.hint === 'string' && d.hint.length > 0) {
|
|
148
|
+
out.push({ path, hint: d.hint });
|
|
149
|
+
}
|
|
150
|
+
walkHintBare(path, d.type, out);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
walkHintBare(path, d, out);
|
|
154
|
+
}
|
|
155
|
+
function walkHintBare(path, t, out) {
|
|
156
|
+
if (t === null || typeof t !== 'object')
|
|
157
|
+
return;
|
|
158
|
+
const obj = t;
|
|
159
|
+
if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {
|
|
160
|
+
for (const [name, descriptor] of Object.entries(obj.shape)) {
|
|
161
|
+
walkHint(`${path}.${name}`, descriptor, out);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (obj.kind === 'array') {
|
|
166
|
+
walkHint(`${path}[]`, obj.element, out);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function isOptional(d) {
|
|
170
|
+
return typeof d === 'object' && 'type' in d && d.optional === true;
|
|
171
|
+
}
|
|
172
|
+
function isShould(d) {
|
|
173
|
+
return typeof d === 'object' && 'type' in d ? d.priority : undefined;
|
|
174
|
+
}
|
|
175
|
+
function exampleValue(d) {
|
|
176
|
+
// Unwrap rich descriptor to get the bare type for synthesis.
|
|
177
|
+
const t = typeof d === 'object' && 'type' in d
|
|
178
|
+
? d.type
|
|
179
|
+
: d;
|
|
180
|
+
return synthesizeBare(t);
|
|
181
|
+
}
|
|
182
|
+
function synthesizeBare(t) {
|
|
183
|
+
if (typeof t === 'string') {
|
|
184
|
+
if (t === 'string')
|
|
185
|
+
return '';
|
|
186
|
+
if (t === 'number')
|
|
187
|
+
return 0;
|
|
188
|
+
if (t === 'boolean')
|
|
189
|
+
return false;
|
|
190
|
+
return null; // 'unknown' or unrecognized keyword → placeholder
|
|
191
|
+
}
|
|
192
|
+
if (t === null || typeof t !== 'object')
|
|
193
|
+
return null;
|
|
194
|
+
const obj = t;
|
|
195
|
+
if ('enum' in obj && Array.isArray(obj.enum)) {
|
|
196
|
+
// First option doubles as the canonical example.
|
|
197
|
+
return obj.enum[0] ?? null;
|
|
198
|
+
}
|
|
199
|
+
if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {
|
|
200
|
+
// Recurse into the nested shape. Same optional-skip rule as the
|
|
201
|
+
// top-level synthesizer: required fields appear, optional ones
|
|
202
|
+
// appear only when @should-flagged.
|
|
203
|
+
const out = {};
|
|
204
|
+
for (const [name, descriptor] of Object.entries(obj.shape)) {
|
|
205
|
+
const isOpt = isOptional(descriptor);
|
|
206
|
+
const pri = isShould(descriptor);
|
|
207
|
+
if (isOpt && pri !== 'should')
|
|
208
|
+
continue;
|
|
209
|
+
out[name] = exampleValue(descriptor);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
if (obj.kind === 'array') {
|
|
214
|
+
// Wrap the synthesized element in a one-item array. Lets the LLM
|
|
215
|
+
// see the per-entry shape without us guessing at array length.
|
|
216
|
+
return [synthesizeBare(obj.element)];
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
38
220
|
//# sourceMappingURL=list-actions.js.map
|