theokit 0.44.3 → 0.45.0
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/dist/{actions-virtual-module-IK2SDIZ3.js → actions-virtual-module-LLDDAQWY.js} +2 -2
- package/dist/{agent-OCCL5RUY.js → agent-WFULT3OB.js} +2 -2
- package/dist/{app-typed-client-4OH3JREB.js → app-typed-client-S3VR4ZA3.js} +2 -2
- package/dist/{build-25RSFP22.js → build-YQ6MXOV6.js} +2 -2
- package/dist/{chunk-NGEBYCOZ.js → chunk-5AMXBU2R.js} +6 -6
- package/dist/{chunk-Q5OT7LZG.js → chunk-6VDYQ4I6.js} +1 -1
- package/dist/{chunk-Q5OT7LZG.js.map → chunk-6VDYQ4I6.js.map} +1 -1
- package/dist/{chunk-DSOBIXXR.js → chunk-UYMCKHML.js} +2 -2
- package/dist/chunk-UYMCKHML.js.map +1 -0
- package/dist/{chunk-ZC2UESE4.js → chunk-VFU7DTUL.js} +2 -2
- package/dist/chunk-VFU7DTUL.js.map +1 -0
- package/dist/cli/index.js +5 -5
- package/dist/client/core.d.ts +2 -2
- package/dist/client/index.d.ts +1 -1
- package/dist/{dev-V6UBM2OL.js → dev-YNO26JS3.js} +4 -4
- package/dist/{internal-api-ALXVBL56.js → internal-api-NO24QAXC.js} +2 -2
- package/dist/{mcp-4JFODA5U.js → mcp-3S6A4A4Q.js} +2 -2
- package/dist/server/define/index.d.ts +2 -2
- package/dist/server/index.d.ts +1 -1
- package/dist/{start-2LNPPG74.js → start-TVT7BZ4V.js} +3 -3
- package/dist/{vite-plugin-X6YO3OUH.js → vite-plugin-K64KQZSU.js} +4 -4
- package/package.json +4 -7
- package/dist/chunk-DSOBIXXR.js.map +0 -1
- package/dist/chunk-ZC2UESE4.js.map +0 -1
- /package/dist/{actions-virtual-module-IK2SDIZ3.js.map → actions-virtual-module-LLDDAQWY.js.map} +0 -0
- /package/dist/{agent-OCCL5RUY.js.map → agent-WFULT3OB.js.map} +0 -0
- /package/dist/{app-typed-client-4OH3JREB.js.map → app-typed-client-S3VR4ZA3.js.map} +0 -0
- /package/dist/{build-25RSFP22.js.map → build-YQ6MXOV6.js.map} +0 -0
- /package/dist/{chunk-NGEBYCOZ.js.map → chunk-5AMXBU2R.js.map} +0 -0
- /package/dist/{dev-V6UBM2OL.js.map → dev-YNO26JS3.js.map} +0 -0
- /package/dist/{internal-api-ALXVBL56.js.map → internal-api-NO24QAXC.js.map} +0 -0
- /package/dist/{mcp-4JFODA5U.js.map → mcp-3S6A4A4Q.js.map} +0 -0
- /package/dist/{start-2LNPPG74.js.map → start-TVT7BZ4V.js.map} +0 -0
- /package/dist/{vite-plugin-X6YO3OUH.js.map → vite-plugin-K64KQZSU.js.map} +0 -0
|
@@ -178,7 +178,7 @@ async function agentCommand(name, message, deps = {}) {
|
|
|
178
178
|
async function createAgentSsrLoader(projectRoot) {
|
|
179
179
|
const { createServer } = await import("vite");
|
|
180
180
|
const react = (await import("@vitejs/plugin-react")).default;
|
|
181
|
-
const { theoPluginAsync } = await import("./vite-plugin-
|
|
181
|
+
const { theoPluginAsync } = await import("./vite-plugin-K64KQZSU.js");
|
|
182
182
|
const { loadConfig } = await import("./load-config-ICCNMO74.js");
|
|
183
183
|
const config = await loadConfig(projectRoot);
|
|
184
184
|
const theoPlugins = await theoPluginAsync({
|
|
@@ -212,4 +212,4 @@ export {
|
|
|
212
212
|
agentCommand,
|
|
213
213
|
createAgentSsrLoader
|
|
214
214
|
};
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-UYMCKHML.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/agent/run-terminal-agent.ts","../src/server/agent/render-terminal.ts","../src/cli/commands/agent.ts"],"sourcesContent":["/**\n * M5 (theokit-ai-first) — run a compiled agent in the terminal, reusing the M4 harness.\n *\n * The web `mountAgent` renders `streamAgentUIMessages` to an HTTP `Response` and resolves HITL\n * approvals via the approve route. This is its terminal sibling: SAME `compileAgentModule` + SAME\n * `streamAgentUIMessages` + SAME in-process approval registry — only the render surface (stdout) and\n * the approval input (a readline prompt) differ (ADR 0039 D2). In a single-process CLI the stream's\n * `awaitApproval` (→ `registry.register`) and the terminal prompt (→ `registry.resolve`) share the one\n * in-process registry — the M4 singleton's exact fit. No new runtime, no LLM call, no tool dispatch.\n */\nimport {\n compileAgentModule,\n streamAgentUIMessages,\n type HumanInTheLoopOptions,\n} from '@theokit/agents'\n\nimport { getApprovalRegistry, type ApprovalRegistry } from './approval-registry.js'\nimport {\n promptTerminalApproval,\n renderAgentStreamToTerminal,\n type TerminalOut,\n} from './render-terminal.js'\n\ninterface RunTerminalAgentInput {\n message: string\n /** Resume key; a fresh id per run when omitted. */\n sessionId?: string\n /** Injectable sink (defaults to `process.stdout` at the CLI entry). */\n stdout?: TerminalOut\n /** Injectable shared registry (defaults to the process singleton). */\n registry?: ApprovalRegistry\n /** Injectable approval prompt (defaults to the readline prompt over the process streams). */\n promptApproval?: (req: { toolName: string; timeoutMs?: number }) => Promise<boolean>\n /** Labels a fail-fast `AgentDefinitionError` (the file path). */\n source?: string\n}\n\n/**\n * Compile the module (M4, gathers `@Mixin` toolboxes), stream it, and render to the terminal. When the\n * agent has `@HumanInTheLoop`-gated tools, the terminal prompts and resolves the shared registry —\n * approve runs the tool, deny/timeout/non-TTY denies and the run continues.\n */\nexport async function runAgentInTerminal(\n mod: unknown,\n apiKey: string,\n input: RunTerminalAgentInput,\n): Promise<{ sawError: boolean }> {\n const compiled = compileAgentModule(mod, input.source)\n const stdout = input.stdout ?? process.stdout\n const registry = input.registry ?? getApprovalRegistry()\n const sessionId = input.sessionId ?? crypto.randomUUID()\n const promptApproval =\n input.promptApproval ??\n ((req: { toolName: string; timeoutMs?: number }) =>\n promptTerminalApproval(req, { input: process.stdin, output: process.stdout }, req.timeoutMs))\n\n // Mirror mount-agent's HITL wiring; absent ⇒ the non-HITL stream path (M2), unchanged.\n const gated = compiled.hitl\n const hitl =\n gated && gated.size > 0\n ? {\n gated,\n awaitApproval: (approvalId: string, opts: HumanInTheLoopOptions) =>\n registry.register(approvalId, {\n timeoutMs: opts.timeout ?? 300_000,\n onTimeout: opts.onTimeout ?? 'abort',\n }),\n }\n : undefined\n\n const chunks = streamAgentUIMessages(compiled, apiKey, {\n message: input.message,\n sessionId,\n hitl,\n })\n return renderAgentStreamToTerminal(chunks, {\n stdout,\n onApproval: async ({ approvalId, toolName }) => {\n // The renderer paused here (the SDK run is paused in its awaited pre_tool_call hook); prompt,\n // then resolve the SAME registry the stream registered into — the run resumes with the decision.\n // The prompt shares the gated tool's timeout so it can never outlive the registry-settled run.\n const timeoutMs = gated?.get(toolName)?.timeout ?? 300_000\n const approved = await promptApproval({ toolName, timeoutMs })\n registry.resolve(approvalId, approved)\n },\n })\n}\n","/**\n * M5 (theokit-ai-first) — the terminal render surface for the M4 agent harness.\n *\n * Consumes the M4 `UIMessageChunk` stream (`streamAgentUIMessages`) and writes it to the terminal:\n * streaming text, a dim reasoning line, `▸ tool(input)` / result cards, a checkpoint notice, errors.\n * On a HITL `tool-approval-request` it delegates to the injected `onApproval` (the entry wires that to\n * a readline prompt + the approval registry). This is the ONLY new code M5 adds — no runtime, no LLM\n * call, no tool dispatch (ADR 0039 D2). I/O is INJECTED (`stdout`, prompt streams) so the whole\n * surface is deterministically testable without a TTY (ADR 0039 D4 / ADR-M5a).\n */\nimport { createInterface } from 'node:readline'\nimport type { Readable, Writable } from 'node:stream'\n\nimport type { WireChunk as UIMessageChunk } from '@theokit/presenter/wire'\n\n/** ANSI helpers — applied only on a TTY so a captured (non-TTY) sink stays plain-text for tests. */\nconst ANSI = {\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n green: '\\x1b[32m',\n reset: '\\x1b[0m',\n}\nfunction paint(text: string, color: keyof typeof ANSI, tty: boolean | undefined): string {\n return tty ? `${ANSI[color]}${text}${ANSI.reset}` : text\n}\n\n/** A stdout-like sink; `isTTY` gates color (Node's `process.stdout` carries it, a test sink does not). */\nexport type TerminalOut = Writable & { isTTY?: boolean }\n\ninterface TerminalRenderOptions {\n stdout: TerminalOut\n /** Resolve a HITL approval (the entry prompts + resolves the registry). */\n onApproval: (req: { approvalId: string; toolName: string }) => Promise<void>\n}\n\n/** Render an `unknown` tool output/input for display (string as-is; other JSON-ish values stringified). */\nfunction display(value: unknown): string {\n return typeof value === 'string' ? value : JSON.stringify(value ?? {})\n}\n\n/**\n * Map a single (non-approval) `UIMessageChunk` to its terminal line(s), or `''` for chunks that render\n * nothing (`start`/`text-start`/`reasoning-start`/…). Pure — the async loop owns approval + I/O.\n * Narrows the `ai` discriminated union directly on `chunk.type` (no cast; G3).\n */\nfunction renderChunkLine(chunk: UIMessageChunk, tty: boolean | undefined): string {\n switch (chunk.type) {\n case 'text-delta':\n return chunk.delta\n case 'text-end':\n case 'finish':\n return '\\n'\n case 'reasoning-delta':\n return paint(chunk.delta, 'dim', tty)\n case 'reasoning-end':\n return '\\n'\n case 'tool-input-available':\n return paint(`\\n▸ ${chunk.toolName}(${display(chunk.input)})\\n`, 'cyan', tty)\n case 'tool-output-available':\n return paint(` ✓ ${display(chunk.output)}\\n`, 'green', tty)\n case 'tool-output-error':\n return paint(` ✗ ${chunk.errorText}\\n`, 'red', tty)\n case 'error':\n return paint(`\\n✗ ${chunk.errorText}\\n`, 'red', tty)\n default:\n // ai-sdk `data-*` transient parts (e.g. the M4 `data-checkpoint` resume handle).\n return chunk.type === 'data-checkpoint'\n ? paint('\\n⎇ checkpoint saved — resume with the same session id\\n', 'dim', tty)\n : ''\n }\n}\n\n/**\n * Render the `UIMessageChunk` stream to `stdout`. Sequential: one chunk → its line(s). The SDK run is\n * already paused inside the awaited `pre_tool_call` hook when a `tool-approval-request` arrives, so a\n * blocking `await onApproval(...)` here is correct — no frame buffer, no concurrency. Returns whether\n * an `error` chunk was seen so the caller can signal a non-zero exit (fail-loud, not silent exit 0).\n */\nexport async function renderAgentStreamToTerminal(\n chunks: AsyncIterable<UIMessageChunk>,\n opts: TerminalRenderOptions,\n): Promise<{ sawError: boolean }> {\n const tty = opts.stdout.isTTY\n const write = (s: string): void => {\n if (s) opts.stdout.write(s)\n }\n // toolCallId → toolName, so the approval prompt (whose chunk carries only ids) can name the tool.\n const toolNames = new Map<string, string>()\n let sawError = false\n\n for await (const chunk of chunks) {\n if (chunk.type === 'tool-approval-request') {\n const toolName = toolNames.get(chunk.toolCallId)\n await opts.onApproval({ approvalId: chunk.approvalId, toolName: toolName ?? 'tool' })\n continue\n }\n if (chunk.type === 'tool-input-available' && chunk.toolCallId) {\n toolNames.set(chunk.toolCallId, chunk.toolName)\n }\n if (chunk.type === 'error') sawError = true\n write(renderChunkLine(chunk, tty))\n }\n return { sawError }\n}\n\n/** Injectable I/O for the approval prompt (defaults to the process streams at the CLI entry). */\ninterface PromptIO {\n input: Readable & { isTTY?: boolean }\n output: Writable & { isTTY?: boolean }\n}\n\n/**\n * Prompt `Approve <tool>? (y/N)` and resolve to the decision. A non-interactive environment (piped /\n * CI — either stream is not a TTY) AUTO-DENIES without prompting (ADR-M5b fail-safe: a decision that\n * cannot be obtained is a deny, never an auto-approve; mirrors HITL `onTimeout: 'abort'`).\n *\n * `timeoutMs` (the gated tool's `@HumanInTheLoop` timeout) auto-denies + closes the prompt when the\n * human walks away — WITHOUT it the readline question would outlive the SDK run (the registry settles\n * the run at its own timeout but readline has no timer), hanging the CLI on a now-irrelevant prompt.\n */\nexport function promptTerminalApproval(\n req: { toolName: string },\n io: PromptIO,\n timeoutMs?: number,\n): Promise<boolean> {\n if (!io.input.isTTY || !io.output.isTTY) {\n io.output.write(`\\n⚠ Non-interactive terminal — auto-denying approval for '${req.toolName}'.\\n`)\n return Promise.resolve(false)\n }\n return new Promise<boolean>((resolve) => {\n const rl = createInterface({ input: io.input, output: io.output })\n let timer: ReturnType<typeof setTimeout> | undefined\n const settle = (approved: boolean): void => {\n if (timer) clearTimeout(timer)\n rl.close()\n resolve(approved)\n }\n if (timeoutMs !== undefined && timeoutMs > 0) {\n timer = setTimeout(() => {\n io.output.write(`\\n⚠ Approval timed out — denying '${req.toolName}'.\\n`)\n settle(false)\n }, timeoutMs)\n }\n rl.question(`\\n❔ Approve ${req.toolName}? (y/N) `, (answer) => {\n const a = answer.trim().toLowerCase()\n settle(a === 'y' || a === 'yes')\n })\n })\n}\n","/**\n * M5 (theokit-ai-first) — `theokit agent <name> [message]`: run a scanned agent in the terminal.\n *\n * Scans the top-level `agents/` directory (M2), loads the matched `agents/<name>.ts` through the\n * framework's OWN Vite pipeline (the SAME transpile `theokit dev` uses — legacy `@Agent`/`@Tool`\n * decorators need it; Node's native loader can't transpile TS), resolves the provider apiKey, and\n * runs it in the terminal via the M4 harness (`runAgentInTerminal`). A dev-time surface (ADR 0039 D1),\n * not a CLI product. `deps` is injectable so the fail-fast branches are tested without Vite/an LLM.\n */\nimport { loadEnv } from '../../config/load-env.js'\nimport { resolveProvider } from '../../server/agent/provider-resolver.js'\nimport { runAgentInTerminal } from '../../server/agent/run-terminal-agent.js'\nimport { scanAgents } from '../../server/scan/agent-scan.js'\n\ninterface AgentCommandDeps {\n projectRoot?: string\n /** Load a TS agent module; defaults to a fresh minimal Vite SSR server (framework transpile). */\n loadModule?: (filePath: string) => Promise<Record<string, unknown>>\n runAgent?: typeof runAgentInTerminal\n resolveApiKey?: () => string\n /** Agents dir name (config `agentsDir`); defaults to the loaded config's value (\"agents\"). Tests inject it. */\n agentsDir?: string\n}\n\nexport async function agentCommand(\n name: string,\n message: string | undefined,\n deps: AgentCommandDeps = {},\n): Promise<{ sawError: boolean }> {\n if (!message || message.trim().length === 0) {\n throw new Error('theokit agent: a message is required. Usage: theokit agent <name> \"<message>\"')\n }\n const projectRoot = deps.projectRoot ?? process.cwd()\n // #95 follow-up — resolve the agents dir from config (default \"agents\") so `agentsDir: \"core/agents\"`\n // is honored in the terminal, same as dev. Tests inject `agentsDir` to skip the config load.\n const agentsDir =\n deps.agentsDir ??\n (await (await import('../../config/load-config.js')).loadConfig(projectRoot)).agentsDir\n const agents = scanAgents(projectRoot, agentsDir)\n const agent = agents.find((a) => a.name === name)\n if (!agent) {\n const names = agents.map((a) => a.name).join(', ') || `(none found in ${agentsDir}/)`\n throw new Error(`theokit agent: '${name}' not found. Available agents: ${names}`)\n }\n\n const runAgent = deps.runAgent ?? runAgentInTerminal\n // Load .env into process.env BEFORE resolving the provider key — parity with `theokit dev`\n // (dev.ts loadEnv), so an app with OPENROUTER_API_KEY in .env works here too (review H1). Skipped\n // when the key is injected (tests) — a temp project has no .env anyway.\n const apiKey =\n deps.resolveApiKey?.() ??\n (() => {\n loadEnv({ cwd: projectRoot, mode: 'development' })\n return resolveProvider().apiKey\n })()\n\n let loadModule = deps.loadModule\n let dispose: (() => Promise<void>) | undefined\n if (!loadModule) {\n const loader = await createAgentSsrLoader(projectRoot)\n loadModule = loader.load\n dispose = loader.dispose\n }\n\n try {\n const mod = await loadModule(agent.filePath)\n return await runAgent(mod, apiKey, { message, source: agent.filePath })\n } finally {\n if (dispose) await dispose()\n }\n}\n\n/**\n * A minimal middleware-mode Vite server built from the SAME `theoPluginAsync` plugin set `theokit dev`\n * uses (`cli/commands/dev.ts`) — so a class-decorated `agents/<name>.ts` transpiles identically. No\n * HTTP listen, no service orchestration, silent — just `ssrLoadModule`. Caller MUST `dispose()`.\n */\nexport async function createAgentSsrLoader(projectRoot: string): Promise<{\n load: (p: string) => Promise<Record<string, unknown>>\n dispose: () => Promise<void>\n}> {\n const { createServer } = await import('vite')\n const react = (await import('@vitejs/plugin-react')).default\n const { theoPluginAsync } = await import('../../vite-plugin/index.js')\n const { loadConfig } = await import('../../config/load-config.js')\n // Pass the project's ssr/services/optimizeDeps config through, exactly as dev.ts does — so an app\n // that customizes ssr.noExternal transpiles here identically to `theokit dev` (review L1).\n const config = await loadConfig(projectRoot)\n const theoPlugins = await theoPluginAsync({\n root: projectRoot,\n ssr: config.ssr,\n services: config.services,\n viteOptimizeDeps: config.viteOptimizeDeps,\n // #95 — honor config `serverDir` so the terminal SSR loader transpiles under the same root as dev.\n serverDir: config.serverDir,\n // #95 follow-up — honor config `agentsDir`.\n agentsDir: config.agentsDir,\n })\n const server = await createServer({\n root: projectRoot,\n plugins: [react(), ...theoPlugins],\n server: { middlewareMode: true },\n appType: 'custom',\n logLevel: 'silent',\n })\n return {\n load: (p) => server.ssrLoadModule(p) as Promise<Record<string, unknown>>,\n dispose: async () => {\n // Surface a cleanup failure instead of swallowing it (error-handling.md) — non-fatal, the run\n // already finished, so warn rather than throw past the CLI boundary.\n await server.close().catch((err: unknown) => {\n console.warn('[theokit agent] Vite server cleanup error:', err)\n })\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
makeThreadStartRun,
|
|
16
16
|
mintRunId,
|
|
17
17
|
parseAgentRequestBody
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-6VDYQ4I6.js";
|
|
19
19
|
import {
|
|
20
20
|
getApprovalRegistry
|
|
21
21
|
} from "./chunk-TDNNBLGW.js";
|
|
@@ -567,4 +567,4 @@ export {
|
|
|
567
567
|
serveAgentAuxRoute,
|
|
568
568
|
writeWebResponseToServerResponse
|
|
569
569
|
};
|
|
570
|
-
//# sourceMappingURL=chunk-
|
|
570
|
+
//# sourceMappingURL=chunk-VFU7DTUL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/transformer.ts","../src/server/agent/agent-card-handler.ts","../src/server/agent/approve-agent.ts","../src/server/agent/list-approvals-handler.ts","../src/server/agent/handle-agent-run-reconnect.ts","../src/server/agent/thread-dispatcher.ts","../src/server/agent/thread-run-registry.ts","../src/server/agent/handle-thread-routes.ts","../src/server/agent/serve-aux-routes.ts","../src/server/http/node-web-adapter.ts"],"sourcesContent":["import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n","/**\n * M15 (theokit-ai-first) — serve the A2A agent card at `/.well-known/<name>/agent-card.json`.\n *\n * `buildAgentCard` (@theokit/agents) is the pure generator; this handler compiles a loaded agent\n * module to its tools + streaming capability, builds the card, and returns it as a Web-Standard\n * JSON `Response` (G8). The dev middleware + prod handler branch to this before the agent POST route.\n */\nimport {\n type AgentManifestEntry,\n buildAgentCard,\n compileAgentModule,\n} from '@theokit/agents'\n\nconst WELL_KNOWN = /^\\/\\.well-known\\/([^/]+)\\/agent-card\\.json$/\n\n/** Return the agent name when `urlPath` is a well-known card path, else `null`. */\nexport function isAgentCardPath(urlPath: string): string | null {\n const match = WELL_KNOWN.exec(urlPath)\n return match ? decodeURIComponent(match[1]) : null\n}\n\n/** Build a minimal manifest entry (the subset `buildAgentCard` reads) from a compiled agent. */\nfunction toManifestEntry(name: string, route: string, mod: unknown): AgentManifestEntry {\n const compiled = compileAgentModule(mod, `agent card for \"${name}\"`)\n return {\n name,\n route,\n stream: compiled.stream,\n mainLoop: { method: '', strategy: '' },\n guards: [],\n interceptors: [],\n tools: compiled.tools.map((t) => ({\n name: t.name,\n description: t.description,\n approval: false,\n trace: false,\n audit: false,\n })),\n subAgents: [],\n }\n}\n\n/**\n * Serve the A2A card for a loaded agent module. Returns 200 with the card JSON, or 500 with an\n * error body if the module is not a valid agent (fail-clear, not a silent empty card).\n */\nexport function handleAgentCard(mod: unknown, name: string, route: string, baseUrl: string): Response {\n try {\n const card = buildAgentCard(toManifestEntry(name, route, mod), { baseUrl })\n return new Response(JSON.stringify(card), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n } catch (err) {\n return new Response(\n JSON.stringify({ error: { code: 'AGENT_CARD_FAILED', message: err instanceof Error ? err.message : 'card build failed' } }),\n { status: 500, headers: { 'content-type': 'application/json; charset=utf-8' } },\n )\n }\n}\n","/**\n * M4 (theokit-ai-first) — the HITL approve endpoint: `POST /api/agents/<name>/approve/<approvalId>`.\n *\n * The counterpart to `mountAgent`'s HITL pause. While a gated tool holds the SDK run paused (the\n * awaited `pre_tool_call` hook), the client POSTs here with `{ approved }`; this resolves the\n * pending approval in the shared registry, which un-pauses the run (allow) or vetoes the tool (deny).\n *\n * Web-Standard `Request` → `Response`, one wiring point shared by dev (vite middleware) and prod\n * (built server) so the two never drift (EC-4 parity with `mountAgent`). The registry is INJECTED —\n * dev/prod pass the process singleton (`getApprovalRegistry`), tests pass a fresh instance.\n */\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport type { ApprovalDecision, ApprovalRegistry } from './approval-registry.js'\n\n/** The path segment separating the agent name from the approval id. */\nconst APPROVE_SEGMENT = '/approve/'\n\n/**\n * Extract the `<approvalId>` from a `/api/agents/<name>/approve/<approvalId>` path.\n * Returns `null` when the path has no `/approve/` segment or an empty / nested id.\n */\nexport function parseApprovalId(urlPath: string): string | null {\n const at = urlPath.indexOf(APPROVE_SEGMENT)\n if (at === -1) return null\n const id = urlPath.slice(at + APPROVE_SEGMENT.length)\n return id.length > 0 && !id.includes('/') ? id : null\n}\n\n/** True when `urlPath` targets a HITL approve endpoint (used by dev/prod routing to branch early). */\nexport function isApprovalPath(urlPath: string): boolean {\n return urlPath.includes(APPROVE_SEGMENT)\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * M20 — cap on the serialized custom payload (16 KiB). A payload is a small structured note\n * (edited args, a reviewer comment), not a data channel — an oversized one is rejected fail-fast\n * rather than silently truncated (Rule 8).\n */\nconst MAX_PAYLOAD_BYTES = 16 * 1024\n\n/**\n * Extract an {@link ApprovalDecision} from an untrusted body; `null` when the shape is wrong.\n *\n * M20 — accepts an optional `reason` (string) and `payload` (object, capped at\n * {@link MAX_PAYLOAD_BYTES}). Backward-compatible: `{ approved }` and `{ approved, reason }` parse\n * unchanged. A non-object or oversized `payload` is rejected (returns `null` → the route 400s).\n *\n * @public\n */\nexport function parseApprovalBody(body: unknown): ApprovalDecision | null {\n if (typeof body !== 'object' || body === null) return null\n const b = body as Record<string, unknown>\n if (typeof b.approved !== 'boolean') return null\n const decision: ApprovalDecision = { approved: b.approved }\n if (b.reason !== undefined) {\n if (typeof b.reason !== 'string') return null\n decision.reason = b.reason\n }\n if (b.payload !== undefined) {\n if (typeof b.payload !== 'object' || b.payload === null || Array.isArray(b.payload)) return null\n if (JSON.stringify(b.payload).length > MAX_PAYLOAD_BYTES) return null\n decision.payload = b.payload\n }\n return decision\n}\n\n/**\n * Resolve a pending HITL approval. CSRF-guarded like `mountAgent` (the custom `X-Theo-Action`\n * header + Origin match) — a cross-origin POST must not approve a paused tool. Returns:\n * 403 CSRF_FAILED — strict CSRF check failed\n * 400 BAD_REQUEST — no `/approve/<id>` in the path, or body lacks a boolean `approved`\n * 404 NOT_PENDING — the id is unknown or already settled (idempotent double-submit)\n * 200 { resolved:true } — the approval was settled by this call\n */\nexport async function handleAgentApproval(\n request: Request,\n urlPath: string,\n registry: ApprovalRegistry,\n csrfMode: CsrfMode = 'strict',\n): Promise<Response> {\n if (csrfMode !== 'off') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid && csrfMode === 'strict') {\n return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n }\n\n const approvalId = parseApprovalId(urlPath)\n if (approvalId === null) {\n return jsonError(400, 'BAD_REQUEST', 'Approval path must be /api/agents/<name>/approve/<id>.')\n }\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → handled below as a 400 */\n }\n const parsed = parseApprovalBody(body)\n if (parsed === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request body must contain a boolean `approved`.')\n }\n\n const resolved = registry.resolve(approvalId, parsed)\n if (!resolved) {\n return jsonError(404, 'NOT_PENDING', `No pending approval for id '${approvalId}'.`)\n }\n return new Response(JSON.stringify({ resolved: true }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n","/**\n * M14 (theokit-ai-first) — GET /api/agents/<name>/approvals: list pending HITL approvals.\n *\n * Serves `ApprovalRegistry.list()` as JSON. Single-process contract (ADR 0038) — the list is\n * process-wide; the `<name>` segment is accepted for a future per-agent/durable store but the\n * in-process registry lists all pending approvals. Web Standards Response (G8).\n */\nimport type { ApprovalRegistry } from './approval-registry.js'\n\nconst LIST_PATH = /^\\/api\\/agents\\/([^/]+)\\/approvals$/\n\n/** Return the agent name when `urlPath` is the approvals-listing path, else `null`. */\nexport function isListApprovalsPath(urlPath: string): string | null {\n const match = LIST_PATH.exec(urlPath)\n return match ? decodeURIComponent(match[1]) : null\n}\n\n/** Serve the currently-pending approvals as `{ approvals: [...] }` JSON. */\nexport function handleListApprovals(registry: ApprovalRegistry): Response {\n return new Response(JSON.stringify({ approvals: registry.list() }), {\n status: 200,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n","import {\n encodeSse,\n formatSseFrame,\n RUN_ID_HEADER,\n SSE_BASE_HEADERS,\n SSE_DONE_FRAME,\n} from './durable-ui-message-stream-response.js'\nimport type { RunEventCache } from './run-event-cache.js'\n\n/**\n * M37 (ADR-0046 D5) — the reconnect / observe endpoint handler for\n * `GET /api/agents/<name>/runs/<runId>/stream`.\n *\n * Replays the frames the client missed (`seq > Last-Event-ID`, SSE-native), then\n * — if the run is still live — follows the live tail; a SECOND client can\n * observe a run a first started. Run already ended ⇒ replay + `[DONE]`. Unknown\n * `runId` ⇒ 404. The request `AbortSignal` unsubscribes on disconnect.\n */\n\nconst RUN_STREAM_PATH = /^\\/api\\/agents\\/([^/]+)\\/runs\\/([^/]+)\\/stream$/\n\n/**\n * Match `GET /api/agents/<name>/runs/<runId>/stream`; returns the decoded\n * `{ name, runId }` or `null` to fall through (mirrors `isMcpPath` etc.).\n */\nexport function isAgentRunStreamPath(urlPath: string): { name: string; runId: string } | null {\n const m = RUN_STREAM_PATH.exec(urlPath)\n return m ? { name: decodeURIComponent(m[1]), runId: decodeURIComponent(m[2]) } : null\n}\n\n/** Parse `Last-Event-ID` (a frame `seq`) to a number; absent/invalid ⇒ -1 (replay from the start). */\nfunction parseLastEventId(raw: string | null): number {\n if (raw === null) return -1\n const n = Number.parseInt(raw, 10)\n return Number.isInteger(n) && n >= 0 ? n : -1\n}\n\nexport function handleAgentRunReconnect(\n runId: string,\n request: Request,\n cache: RunEventCache,\n): Response {\n // Unknown run ⇒ 404 (a run never started here, or already evicted).\n if (!cache.has(runId)) {\n return new Response(\n JSON.stringify({ error: { code: 'RUN_NOT_FOUND', message: `Unknown run '${runId}'.` } }),\n {\n status: 404,\n headers: { 'content-type': 'application/json' },\n },\n )\n }\n\n const afterSeq = parseLastEventId(request.headers.get('last-event-id'))\n\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n let closed = false\n const safeEnqueue = (text: string): void => {\n if (closed) return\n try {\n controller.enqueue(encodeSse(text))\n } catch {\n /* controller already closed by an abort race — ignore */\n }\n }\n const close = (): void => {\n if (closed) return\n safeEnqueue(SSE_DONE_FRAME)\n closed = true\n try {\n controller.close()\n } catch {\n /* already closed */\n }\n }\n\n // Atomic: snapshot replay frames AND subscribe to the live tail in one tick.\n const res = cache.attach(\n runId,\n afterSeq,\n (frame) => {\n safeEnqueue(formatSseFrame(frame.seq, frame.data))\n },\n () => {\n close()\n },\n )\n // Evicted between has() and attach() (rare TOCTOU) ⇒ just end the stream.\n if (!res.known) {\n close()\n return\n }\n for (const frame of res.replay) {\n safeEnqueue(formatSseFrame(frame.seq, frame.data))\n }\n if (res.ended) {\n close()\n return\n }\n // Client disconnects ⇒ detach the live listener + close.\n request.signal.addEventListener('abort', () => {\n res.unsubscribe()\n close()\n })\n },\n })\n\n return new Response(stream, { headers: { ...SSE_BASE_HEADERS, [RUN_ID_HEADER]: runId } })\n}\n","/**\n * M39 (ADR-0048) — the thread follow-up dispatcher.\n *\n * Drives a run over the M37 durable cache HEADLESS — it iterates the SDK chunk\n * generator and `cache.append`s directly, NOT via a `Response` `ReadableStream`\n * (whose backpressure would stall a run with no HTTP reader). Subscribers read\n * from the cache via the thread / reconnect stream.\n *\n * - Post to an IDLE thread ⇒ start a run + pump.\n * - Post to an ACTIVE thread ⇒ FIFO-queue the follow-up.\n * - On terminal ⇒ `registry.endRun` hands back the next queued follow-up, which\n * is dispatched as a continuation on the SAME sessionId (⇒ the SDK continues\n * the conversation via its `ConversationStorageAdapter`).\n *\n * This adds NO agent loop — it reuses the SDK run (`startRun`) + the M37 cache.\n * Single-process (ADR-0048 D2).\n */\n\nimport type { WireChunk as UIMessageChunk } from '@theokit/presenter/wire'\n\nimport { type RunEventCache, mintRunId } from './run-event-cache.js'\nimport type { ThreadRunRegistry } from './thread-run-registry.js'\n\ninterface ThreadDispatchDeps {\n readonly registry: ThreadRunRegistry\n readonly cache: RunEventCache\n /** Start a run for a follow-up on `sessionId`; returns the SDK chunk stream. */\n readonly startRun: (sessionId: string, message: string) => AsyncIterable<UIMessageChunk>\n}\n\n/** Result of posting a follow-up: the started `runId` (idle thread) or `queued` (active thread). */\ntype PostFollowUpResult = { runId: string } | { queued: true }\n\n/**\n * Post a follow-up on a thread. IDLE ⇒ start a run (returns its `runId`); ACTIVE\n * ⇒ FIFO-queue it (returns `{ queued: true }`) — dispatched when the active run ends.\n */\nexport function postThreadFollowUp(\n deps: ThreadDispatchDeps,\n sessionId: string,\n message: string,\n): PostFollowUpResult {\n if (deps.registry.getActive(sessionId) !== null) {\n deps.registry.queue(sessionId, { message })\n return { queued: true }\n }\n return { runId: startAndPump(deps, sessionId, message) }\n}\n\n/** Mint a runId, mark the thread active, and pump the run into the cache headless. */\nfunction startAndPump(deps: ThreadDispatchDeps, sessionId: string, message: string): string {\n const runId = mintRunId()\n // Register the run in the cache SYNCHRONOUSLY (before the async pump appends its\n // first frame) so a subscriber resolving the active runId can attach immediately.\n deps.cache.begin(runId)\n deps.registry.startRun(sessionId, runId)\n pumpIntoCache(deps, sessionId, runId, deps.startRun(sessionId, message))\n return runId\n}\n\n/**\n * Drive `chunks` into the cache under `runId`, then end the run and dispatch the\n * next queued follow-up (if any) as a continuation. Fire-and-forget — the caller\n * does not await the run; subscribers observe it via the cache.\n */\nfunction pumpIntoCache(\n deps: ThreadDispatchDeps,\n sessionId: string,\n runId: string,\n chunks: AsyncIterable<UIMessageChunk>,\n): void {\n void (async () => {\n try {\n for await (const chunk of chunks) {\n deps.cache.append(runId, JSON.stringify(chunk))\n }\n } catch {\n // The SDK translator owns error semantics (surfaces failures as chunks);\n // the transport guarantees only a terminated, cache-ended run.\n } finally {\n deps.cache.end(runId)\n const next = deps.registry.endRun(sessionId, runId)\n if (next !== undefined) startAndPump(deps, sessionId, next.message)\n }\n })()\n}\n","/**\n * M39 (ADR-0048) — the in-process thread→run registry.\n *\n * A thread is the existing `sessionId` (the SDK conversation key). This registry\n * tracks, per thread, the single ACTIVE run, a FIFO queue of follow-ups to\n * dispatch as continuations, and one-shot \"next run\" waiters (so a client can\n * subscribe to a thread before posting a message and attach when the run starts).\n *\n * Pure state only — it never starts a run or touches the transport. The route\n * layer owns dispatch: on `endRun` it receives the next queued follow-up (if any)\n * and starts the continuation over the M37 durable transport.\n *\n * Single-process contract (ADR-0048 D2): a multi-instance deploy needs a shared\n * registry + leasing — that is infra (TheoCloud), explicitly out of M39. The\n * interface is injectable so a durable impl slots in later without touching the\n * routes. We do NOT build a distributed store now (YAGNI).\n */\n\n/** A follow-up message queued on an active thread, dispatched as a continuation. */\nexport interface FollowUp {\n readonly message: string\n}\n\nexport interface ThreadRunRegistry {\n /** The active runId for a thread, or `null` when the thread is idle. */\n getActive(sessionId: string): string | null\n /** Mark a run started for a thread (sets it active; fires + clears one-shot waiters). */\n startRun(sessionId: string, runId: string): void\n /**\n * Mark `runId` ended for a thread. A no-op unless `runId` is the current active\n * run (a stale terminal never clears a newer run). Clears the active run and\n * returns the next FIFO follow-up to dispatch as a continuation, or `undefined`.\n */\n endRun(sessionId: string, runId: string): FollowUp | undefined\n /** FIFO-enqueue a follow-up on a thread (dispatched when the active run ends). */\n queue(sessionId: string, followUp: FollowUp): void\n /**\n * Register a ONE-SHOT waiter fired with the runId of the NEXT run started on\n * this thread. Returns an unsubscribe fn. Used by subscribe-by-thread on an\n * idle thread (attach when the next run starts).\n */\n onNextRun(sessionId: string, cb: (runId: string) => void): () => void\n}\n\ninterface ThreadState {\n activeRunId: string | null\n readonly queue: FollowUp[]\n readonly waiters: Set<(runId: string) => void>\n}\n\nexport function createInProcessThreadRunRegistry(): ThreadRunRegistry {\n const threads = new Map<string, ThreadState>()\n const ensure = (sessionId: string): ThreadState => {\n let s = threads.get(sessionId)\n if (s === undefined) {\n s = { activeRunId: null, queue: [], waiters: new Set() }\n threads.set(sessionId, s)\n }\n return s\n }\n\n return {\n getActive(sessionId) {\n return threads.get(sessionId)?.activeRunId ?? null\n },\n startRun(sessionId, runId) {\n const s = ensure(sessionId)\n s.activeRunId = runId\n // Fire one-shot waiters, then clear them (single-threaded → no re-entrancy race).\n const waiters = [...s.waiters]\n s.waiters.clear()\n for (const cb of waiters) cb(runId)\n },\n endRun(sessionId, runId) {\n const s = threads.get(sessionId)\n if (s?.activeRunId !== runId) return undefined\n s.activeRunId = null\n return s.queue.shift()\n },\n queue(sessionId, followUp) {\n ensure(sessionId).queue.push(followUp)\n },\n onNextRun(sessionId, cb) {\n const s = ensure(sessionId)\n s.waiters.add(cb)\n return () => {\n s.waiters.delete(cb)\n }\n },\n }\n}\n\nlet serverRegistry: ThreadRunRegistry | undefined\n\n/** Process-wide singleton (mirrors `getRunEventCache` / `getApprovalRegistry`). */\nexport function getThreadRunRegistry(): ThreadRunRegistry {\n serverRegistry ??= createInProcessThreadRunRegistry()\n return serverRegistry\n}\n","/**\n * M39 (ADR-0048) — the thread signal routes over the M37 durable transport:\n *\n * - `POST /api/agents/<name>/threads/<sessionId>/message` — a follow-up. ACTIVE\n * run ⇒ FIFO-queue (dispatched as a continuation when it ends); IDLE ⇒ start a\n * run. Returns `202` (the run streams headless into the cache; observe it via\n * the thread stream). Spends LLM tokens ⇒ CSRF-gated (parity with mount-agent).\n * - `GET /api/agents/<name>/threads/<sessionId>/stream` — subscribe. ACTIVE ⇒\n * attach to the durable stream (reuse the M37 reconnect handler); IDLE ⇒ wait\n * (bounded) for the next run on the thread, then attach (subscribe-then-post).\n *\n * These DRIVE the SDK run (via `makeThreadStartRun`) + the M37 cache — no new loop.\n */\n\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport { makeThreadStartRun } from './build-agent-streamer.js'\nimport {\n encodeSse,\n formatSseFrame,\n RUN_ID_HEADER,\n SSE_BASE_HEADERS,\n SSE_DONE_FRAME,\n} from './durable-ui-message-stream-response.js'\nimport { handleAgentRunReconnect } from './handle-agent-run-reconnect.js'\nimport { parseAgentRequestBody } from './mount-agent.js'\nimport { getRunEventCache, type RunEventCache } from './run-event-cache.js'\nimport { postThreadFollowUp } from './thread-dispatcher.js'\nimport { getThreadRunRegistry, type ThreadRunRegistry } from './thread-run-registry.js'\n\nconst THREAD_MESSAGE_PATH = /^\\/api\\/agents\\/([^/]+)\\/threads\\/([^/]+)\\/message$/\nconst THREAD_STREAM_PATH = /^\\/api\\/agents\\/([^/]+)\\/threads\\/([^/]+)\\/stream$/\n\n/** How long a subscribe-to-idle-thread waits for the next run before closing. */\nconst DEFAULT_IDLE_WAIT_MS = 30_000\n\nfunction matchThread(re: RegExp, urlPath: string): { name: string; sessionId: string } | null {\n const m = re.exec(urlPath)\n return m ? { name: decodeURIComponent(m[1]), sessionId: decodeURIComponent(m[2]) } : null\n}\n\nexport const isThreadMessagePath = (urlPath: string) => matchThread(THREAD_MESSAGE_PATH, urlPath)\nexport const isThreadStreamPath = (urlPath: string) => matchThread(THREAD_STREAM_PATH, urlPath)\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n\n/** Inputs for {@link handleThreadMessage} (bundled to stay within the arity budget). */\ninterface ThreadMessageArgs {\n readonly mod: unknown\n readonly apiKey: string\n readonly sessionId: string\n readonly request: Request\n readonly source: string\n readonly csrfMode?: CsrfMode\n}\n\n/** POST a follow-up on a thread. Returns `202` with `{ runId }` (started) or `{ queued: true }`. */\nexport async function handleThreadMessage(args: ThreadMessageArgs): Promise<Response> {\n const { mod, apiKey, sessionId, request, source, csrfMode = 'strict' } = args\n // A follow-up drives the agent (spends LLM tokens) — reject a cross-origin POST.\n if (csrfMode === 'strict') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid) return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → 400 below */\n }\n const input = parseAgentRequestBody(body)\n if (input === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request must contain a non-empty message.')\n }\n const result = postThreadFollowUp(\n {\n registry: getThreadRunRegistry(),\n cache: getRunEventCache(),\n startRun: makeThreadStartRun(mod, apiKey, source),\n },\n sessionId,\n input.message,\n )\n const headers: Record<string, string> = { 'content-type': 'application/json; charset=utf-8' }\n if ('runId' in result) headers[RUN_ID_HEADER] = result.runId\n return new Response(JSON.stringify(result), { status: 202, headers })\n}\n\n/** GET the thread's durable stream. Active ⇒ attach now; idle ⇒ wait (bounded) for the next run. */\nexport function handleThreadStream(\n sessionId: string,\n request: Request,\n registry: ThreadRunRegistry = getThreadRunRegistry(),\n cache: RunEventCache = getRunEventCache(),\n idleWaitMs = DEFAULT_IDLE_WAIT_MS,\n): Response {\n const active = registry.getActive(sessionId)\n if (active !== null && cache.has(active)) {\n // Reuse the M37 reconnect handler — replay + live tail on the active run.\n return handleAgentRunReconnect(active, request, cache)\n }\n return waitThenAttachStream(sessionId, request, registry, cache, idleWaitMs)\n}\n\n/** Subscribe to an idle thread: wait (bounded) for the NEXT run to start, then attach + tail. */\nfunction waitThenAttachStream(\n sessionId: string,\n request: Request,\n registry: ThreadRunRegistry,\n cache: RunEventCache,\n idleWaitMs: number,\n): Response {\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n let closed = false\n const send = (text: string): void => {\n if (closed) return\n try {\n controller.enqueue(encodeSse(text))\n } catch {\n /* closed by an abort race */\n }\n }\n let detachAttach: (() => void) | undefined\n const close = (): void => {\n if (closed) return\n send(SSE_DONE_FRAME)\n closed = true\n try {\n controller.close()\n } catch {\n /* already closed */\n }\n }\n const offNext = registry.onNextRun(sessionId, (runId) => {\n const res = cache.attach(\n runId,\n -1,\n (frame) => {\n send(formatSseFrame(frame.seq, frame.data))\n },\n () => {\n close()\n },\n )\n if (!res.known) {\n close()\n return\n }\n for (const frame of res.replay) send(formatSseFrame(frame.seq, frame.data))\n if (res.ended) {\n close()\n return\n }\n detachAttach = res.unsubscribe\n })\n // HIGH-1 — the waiter teardown MUST run on BOTH the idle-wait timeout AND the\n // client abort, else the (one-shot) `onNextRun` waiter leaks in the registry\n // when a timed-out subscriber's session never receives another run.\n const teardownWaiter = (): void => {\n offNext()\n detachAttach?.()\n }\n const timer = setTimeout(() => {\n teardownWaiter()\n close()\n }, idleWaitMs)\n timer.unref()\n request.signal.addEventListener('abort', () => {\n teardownWaiter()\n clearTimeout(timer)\n close()\n })\n },\n })\n return new Response(stream, { headers: { ...SSE_BASE_HEADERS } })\n}\n","/**\n * M15/M16 follow-up — shared dispatcher for the agent AUXILIARY routes that BOTH dev (vite\n * middleware) and prod (`theokit start` handler) must serve identically. Before this, these routes\n * were wired only into the dev middleware, so a built/deployed app served none of them (agent cards,\n * MCP, pending-approvals listing all 404'd in production).\n *\n * Single source of truth (DRY): one Web-Request→Response dispatcher, two callers. It handles the\n * routes that derive purely from the agent module + shared registry:\n * - **M15** `GET /.well-known/<name>/agent-card.json` → {@link handleAgentCard}\n * - **M14** `GET /api/agents/<name>/approvals` → {@link handleListApprovals}\n * - **M16** `POST /api/agents/<name>/mcp` → {@link handleMcpJsonRpc}\n *\n * Channels (M27) are NOT here: a channel webhook needs app-supplied `validators` + `onMessage`, so\n * the app wires `handleChannelWebhook` in its own route (it cannot be auto-derived from the module).\n * The HITL approve route stays in each caller (it carries caller-specific rate-limiting/CSRF plumbing).\n */\nimport type { AgentNode } from '../scan/agent-scan.js'\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport { isAgentCardPath, handleAgentCard } from './agent-card-handler.js'\nimport { getApprovalRegistry } from './approval-registry.js'\nimport { handleAgentRunReconnect, isAgentRunStreamPath } from './handle-agent-run-reconnect.js'\nimport {\n handleThreadMessage,\n handleThreadStream,\n isThreadMessagePath,\n isThreadStreamPath,\n} from './handle-thread-routes.js'\nimport { isListApprovalsPath, handleListApprovals } from './list-approvals-handler.js'\nimport { extractAppResources } from './mcp-app-resources.js'\nimport { isMcpPath, handleMcpJsonRpc } from './mcp-handler.js'\nimport { getRunEventCache } from './run-event-cache.js'\n\n/** JSON error envelope (mirrors mount-agent.ts:37 — the parity source for the MCP CSRF gate). */\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json; charset=utf-8' },\n })\n}\n\n/** Dependencies the aux dispatcher needs from its caller (dev or prod). */\ninterface AuxRouteDeps {\n /** Discovered agents (from `scanAgents`). */\n agents: readonly AgentNode[]\n /** Load an agent module from its file path (dev: vite loader; prod: dynamic import). */\n loadModule: (filePath: string) => Promise<unknown>\n /** Absolute base URL (`http(s)://host`) for the agent-card endpoint URLs. */\n baseUrl: string\n /**\n * M34 (#97) — CSRF enforcement mode for the MCP route. `POST /api/agents/<name>/mcp` drives the\n * agent (spends LLM tokens), so a cross-origin POST MUST be rejected in `'strict'` — parity with\n * the agent-run route (`mount-agent.ts:83-91`). Defaults to `'strict'` (safe-by-default); a caller\n * that already gated upstream may pass `'off'`.\n */\n csrfMode?: CsrfMode\n /**\n * M39 — lazily resolve the provider apiKey. Required only for the thread\n * follow-up route (which drives the agent); resolved on demand so non-agent\n * aux routes (card, approvals, stream) never need a provider key.\n */\n resolveApiKey?: () => string\n}\n\n/**\n * Serve an agent auxiliary route. Returns a `Response` when `urlPath` matches an aux route (card /\n * list-approvals / mcp) and the method + agent resolve; returns `null` to fall through (not an aux\n * route, wrong method, or unknown agent — the caller then owns the 404/next).\n */\nexport async function serveAgentAuxRoute(\n request: Request,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n const method = request.method.toUpperCase()\n\n // M15 — A2A agent card at `/.well-known/<name>/agent-card.json` (GET).\n const cardName = isAgentCardPath(urlPath)\n if (cardName !== null) {\n if (method !== 'GET') return null\n const agent = deps.agents.find((a) => a.name === cardName)\n if (!agent) return null\n const mod = await deps.loadModule(agent.filePath)\n return handleAgentCard(mod, agent.name, agent.agentPath, deps.baseUrl)\n }\n\n // M14 — GET /api/agents/<name>/approvals (pending HITL approvals).\n if (isListApprovalsPath(urlPath)) {\n if (method !== 'GET') return null\n return handleListApprovals(getApprovalRegistry())\n }\n\n // M37 — GET /api/agents/<name>/runs/<runId>/stream (durable reconnect / observe).\n // INTENTIONALLY open (no CSRF, no auth gate): a GET is not CSRF-vulnerable, the\n // run-start POST is already gated, and the `runId` is a 122-bit UUID (unguessable).\n // Observe-by-runId is a FEATURE (ADR-0046 D5) — a second client resumes a run a\n // first started. Do NOT add a custom-header CSRF check here: browsers send NO\n // custom headers with `EventSource`, so it would break native SSE reconnect.\n const runStream = isAgentRunStreamPath(urlPath)\n if (runStream !== null) {\n if (method !== 'GET') return null\n if (!deps.agents.some((a) => a.name === runStream.name)) return null\n return handleAgentRunReconnect(runStream.runId, request, getRunEventCache())\n }\n\n // M39 — thread signal routes (follow-up message + subscribe-by-thread). Extracted\n // to keep this dispatcher within the cognitive-complexity budget (G6).\n const threadResponse = await serveThreadRoute(request, method, urlPath, deps)\n if (threadResponse !== null) return threadResponse\n\n // M16 — POST /api/agents/<name>/mcp (JSON-RPC MCP server). Extracted to keep this dispatcher\n // within the cognitive-complexity budget (G6).\n const mcpName = isMcpPath(urlPath)\n if (mcpName !== null) {\n return serveMcpRoute(request, method, mcpName, deps)\n }\n\n return null\n}\n\n/**\n * M39 — serve the thread routes:\n * - `POST .../threads/<sessionId>/message` (follow-up) — loads the module, drives\n * the run headless via the thread dispatcher. Needs `resolveApiKey` (drives the\n * agent) → 501 when absent (rather than a silent 404).\n * - `GET .../threads/<sessionId>/stream` (subscribe) — attach to the active/next\n * run's durable stream. INTENTIONALLY open (GET, no custom headers — like the\n * M37 reconnect route).\n *\n * SECURITY (thread stream): unlike the M37 reconnect route — keyed on an\n * `mintRunId()` UUID (122-bit unguessable) — the thread stream is keyed on the\n * caller-supplied `sessionId`. The open GET is safe ONLY if the `sessionId` is\n * unguessable (e.g. a client-minted UUID). An app that uses a PREDICTABLE\n * sessionId (user id, email, sequential id) MUST add its own auth gate before\n * this endpoint, or any party who can guess the sessionId can read the thread's\n * live conversation stream. Documented in `docs/architecture/multi-surface-architecture.md`.\n *\n * Returns `null` to fall through (not a thread route, wrong method, unknown agent).\n */\nasync function serveThreadRoute(\n request: Request,\n method: string,\n urlPath: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n const stream = isThreadStreamPath(urlPath)\n if (stream !== null) {\n if (method !== 'GET') return null\n if (!deps.agents.some((a) => a.name === stream.name)) return null\n return handleThreadStream(stream.sessionId, request)\n }\n\n const msg = isThreadMessagePath(urlPath)\n if (msg !== null) {\n if (method !== 'POST') return null\n const agent = deps.agents.find((a) => a.name === msg.name)\n if (!agent) return null\n if (deps.resolveApiKey === undefined) {\n // MEDIUM-1 — the path matched but the caller wired no provider-key resolver.\n // Fail loudly (501) instead of a silent 404 that reads as \"route not found\".\n return jsonError(\n 501,\n 'NOT_CONFIGURED',\n 'Thread follow-up requires a provider API key (resolveApiKey was not provided to serveAgentAuxRoute).',\n )\n }\n const mod = await deps.loadModule(agent.filePath)\n return handleThreadMessage({\n mod,\n apiKey: deps.resolveApiKey(),\n sessionId: msg.sessionId,\n request,\n source: `agent \"${msg.name}\"`,\n csrfMode: deps.csrfMode ?? 'strict',\n })\n }\n return null\n}\n\n/**\n * Serve the MCP route with the M34 gates: default-DENY opt-in → CSRF → dispatch. Returns `null` to\n * fall through (wrong method / unknown or non-opted-in agent), a 403 on CSRF failure, else the\n * JSON-RPC response.\n */\nasync function serveMcpRoute(\n request: Request,\n method: string,\n mcpName: string,\n deps: AuxRouteDeps,\n): Promise<Response | null> {\n if (method !== 'POST') return null\n const agent = deps.agents.find((a) => a.name === mcpName)\n if (!agent) return null\n\n const mod = await deps.loadModule(agent.filePath)\n\n // M34 — DEFAULT-DENY: an agent is NOT exposed on MCP unless it explicitly opts in with a named\n // `export const mcp = true` (blueprint D5 — default-EXPOSE is the footgun magnified by the\n // multi-surface thesis). Absent the opt-in, fall through to 404 (the agent is web-only). This is a\n // breaking change from the M16 auto-mount (documented in the CHANGELOG § Security).\n if (!isMcpExposed(mod)) return null\n\n // M34 (#97) — enforce CSRF BEFORE any work. The MCP route drives the agent (real LLM tokens), so a\n // cross-origin POST must be rejected — parity with the agent-run route (`mount-agent.ts:83-91`).\n const csrfMode = deps.csrfMode ?? 'strict'\n if (csrfMode === 'strict') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid) return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* malformed/empty JSON → handleMcpJsonRpc returns a -32600 envelope */\n }\n // M30 — pass the agent's declared `ui://` App resources (named `appResources` export) so the MCP\n // server advertises + serves them via resources/list + resources/read.\n return handleMcpJsonRpc(mod, agent.name, body, extractAppResources(mod))\n}\n\n/**\n * M34 — DEFAULT-DENY opt-in check: is this agent module exposed on the MCP surface? An agent opts in\n * with a named `export const mcp = true` (mirroring the `appResources` named-export convention).\n * Anything else (absent / falsy) → NOT exposed. Read at the emit layer (blueprint D5).\n */\nfunction isMcpExposed(mod: unknown): boolean {\n return (mod as { mcp?: unknown } | null | undefined)?.mcp === true\n}\n","/**\n * T5a.2 Phase G slice 5/N — Node adapter shim for the Web request handler.\n *\n * Bridges Node's `IncomingMessage` + `ServerResponse` shape to the\n * Web-Standards `executeWebRequest` (Phase A → G slices 1-4). Per\n * ADR-0028 R3a, the Node adapter is the ONLY place IncomingMessage ↔\n * Request conversion happens — every other layer of `server/` flows\n * through Web `Request`/`Response`.\n *\n * **Two conversions:**\n *\n * 1. `incomingMessageToWebRequest(req)` — Node → Web. Reads\n * `req.method`, `req.url`, `req.headers`, and (for POST/PUT/etc.)\n * drains the Node Readable body into a Web `ReadableStream`. The\n * request URL is resolved to absolute form using `req.headers.host`\n * (Web Request guarantees absolute URL).\n *\n * 2. `writeWebResponseToServerResponse(response, res)` — Web → Node.\n * Sets status code + status text, copies headers (including all\n * `Set-Cookie` values via `getSetCookie()`), then drains the Web\n * `ReadableStream` body into the Node ServerResponse.\n *\n * Plus the convenience composer `executeWebRequestFromNode(req, res,\n * routeModule, opts?)` that wires both ends — exactly what api-middleware\n * (and the prod CLI start path) need to migrate from the legacy\n * `executeRoute` to `executeWebRequest` without touching call sites.\n *\n * Per `docs/plans/t5a2-incoming-message-to-request-shape-refactor-plan.md`\n * v1.0 § Phase G slice 5/N (closes the executor bridge surface).\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { executeWebRequest, type ExecuteWebRequestOptions } from '../web-handler.js'\n\nimport { incomingMessageToWebRequest } from './node-request.js'\n\n// The primitive `IncomingMessage` → Web `Request` converters live in\n// `node-request.js` (dependency-free, shared with the executor). Re-exported\n// here so existing importers of the adapter keep resolving them.\nexport { incomingMessageToWebRequest } from './node-request.js'\n\n/**\n * Write a Web `Response` into a Node `ServerResponse`. Mirrors the Node\n * `res.writeHead` + `res.end` pattern.\n *\n * Set-Cookie is the only multi-value header the Web spec exposes via\n * `getSetCookie()`. We append each entry individually so Node emits\n * separate `Set-Cookie:` lines per the HTTP spec.\n *\n * If the Response body is a `ReadableStream`, it's piped chunk-by-chunk\n * to `res`. Empty body (null) → just close.\n */\nexport async function writeWebResponseToServerResponse(\n response: Response,\n res: ServerResponse,\n): Promise<void> {\n // Status + headers FIRST (writeHead locks them).\n // EC-3: Set-Cookie needs special handling (writeHead's plain object\n // shape conflicts with multi-value headers; we set them via setHeader\n // BEFORE writeHead so the array form is preserved).\n const setCookies = response.headers.getSetCookie()\n if (setCookies.length > 0) {\n res.setHeader('Set-Cookie', setCookies)\n }\n const otherHeaders: Record<string, string> = {}\n for (const [key, value] of response.headers.entries()) {\n if (key.toLowerCase() === 'set-cookie') continue\n otherHeaders[key] = value\n }\n res.writeHead(response.status, response.statusText, otherHeaders)\n\n // Body — drain ReadableStream OR just end() for null body.\n if (response.body === null) {\n res.end()\n return\n }\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n // Node's res.write accepts Uint8Array natively (no conversion needed).\n res.write(value)\n }\n res.end()\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Convenience composer — full request lifecycle Node → Web → Node.\n *\n * Use case: existing api-middleware (and the prod CLI start path)\n * receive Node `(req, res)` from `http.createServer`. To migrate to the\n * Web-Standards executor without rewriting every call site, wrap with\n * this composer:\n *\n * import * as users from './app/users/route.js'\n * await executeWebRequestFromNode(req, res, users, { csrfMode: 'strict' })\n *\n * The Web request is built, dispatched through executeWebRequest, and\n * the Response is drained back into `res`. Caller does NOT need to call\n * `res.end()` afterwards — this composer handles it.\n */\nexport async function executeWebRequestFromNode(\n req: IncomingMessage,\n res: ServerResponse,\n routeModule: Parameters<typeof executeWebRequest>[1],\n opts?: ExecuteWebRequestOptions,\n): Promise<void> {\n const webRequest = incomingMessageToWebRequest(req)\n const webResponse = await executeWebRequest(webRequest, routeModule, opts)\n await writeWebResponseToServerResponse(webResponse, res)\n}\n"],"mappings":"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","names":["jsonError","jsonError"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import "tsx/esm";
|
|
|
5
5
|
import cac from "cac";
|
|
6
6
|
var cli = cac("theokit");
|
|
7
7
|
cli.command("dev", "Start development server").option("--port <port>", "Port number").action(async (options) => {
|
|
8
|
-
const { devCommand } = await import("../dev-
|
|
8
|
+
const { devCommand } = await import("../dev-YNO26JS3.js");
|
|
9
9
|
await devCommand({ port: options.port ? Number(options.port) : void 0 });
|
|
10
10
|
});
|
|
11
11
|
cli.command("build", "Build for production").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
|
|
12
12
|
try {
|
|
13
|
-
const { buildCommand } = await import("../build-
|
|
13
|
+
const { buildCommand } = await import("../build-YQ6MXOV6.js");
|
|
14
14
|
await buildCommand({ target: options.target });
|
|
15
15
|
} catch (err) {
|
|
16
16
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -22,7 +22,7 @@ cli.command("build", "Build for production").option("--target <target>", "Deploy
|
|
|
22
22
|
});
|
|
23
23
|
cli.command("start", "Start production server").option("--port <port>", "Port number").action(async (options) => {
|
|
24
24
|
try {
|
|
25
|
-
const { startCommand } = await import("../start-
|
|
25
|
+
const { startCommand } = await import("../start-TVT7BZ4V.js");
|
|
26
26
|
await startCommand({ port: options.port ? Number(options.port) : void 0 });
|
|
27
27
|
} catch (err) {
|
|
28
28
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -52,7 +52,7 @@ cli.command(
|
|
|
52
52
|
"Run an agent in the terminal (stream + tool calls + approval)"
|
|
53
53
|
).action(async (name, message) => {
|
|
54
54
|
try {
|
|
55
|
-
const { agentCommand } = await import("../agent-
|
|
55
|
+
const { agentCommand } = await import("../agent-WFULT3OB.js");
|
|
56
56
|
const { sawError } = await agentCommand(name, message);
|
|
57
57
|
if (sawError) process.exit(1);
|
|
58
58
|
} catch (err) {
|
|
@@ -65,7 +65,7 @@ cli.command(
|
|
|
65
65
|
});
|
|
66
66
|
cli.command("mcp <agent>", "Serve an agent as an MCP server over stdio (for desktop MCP clients)").action(async (agent) => {
|
|
67
67
|
try {
|
|
68
|
-
const { mcpCommand } = await import("../mcp-
|
|
68
|
+
const { mcpCommand } = await import("../mcp-3S6A4A4Q.js");
|
|
69
69
|
await mcpCommand(agent);
|
|
70
70
|
} catch (err) {
|
|
71
71
|
const msg = err instanceof Error ? err.message : String(err);
|
package/dist/client/core.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApprovalDecision, AgentClientState, RequestContext, AgentTransport } from '@theokit/agents/client';
|
|
2
2
|
export { AgentClient, AgentClientState, AgentTransport, ApprovalDecision, ChannelPushSource, ChannelTransport, ChannelTransportOptions, ChannelTurnHandlers, HeadersResolver, HttpTransport, HttpTransportOptions, InProcessApprovalRequestLike, InProcessAwaitApproval, InProcessRunInput, InProcessRunner, InProcessTransport, InProcessTransportOptions, RequestContext, UseAgentStatus, consumeChunkStream, consumeUIMessageStream, responseToChunkStream } from '@theokit/agents/client';
|
|
3
|
-
import {
|
|
3
|
+
import { WireMessage } from '@theokit/presenter/wire';
|
|
4
4
|
|
|
5
5
|
interface CreateAgentClientOptions {
|
|
6
6
|
/**
|
|
@@ -40,7 +40,7 @@ interface AgentClientHandle<TInput = unknown> {
|
|
|
40
40
|
* ends when the turn settles. A `for await` script takes the last yielded value as the final result;
|
|
41
41
|
* a failed turn rejects the iterator with the run error.
|
|
42
42
|
*/
|
|
43
|
-
stream(input: TInput): AsyncIterable<
|
|
43
|
+
stream(input: TInput): AsyncIterable<WireMessage>;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Create a standalone (no-React) agent client over a transport. `useAgent` is the React binding over the
|
package/dist/client/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { UseAgentOptions, UseAgentReturn, UseAgentStatus, useAgent } from '@theo
|
|
|
6
6
|
export { AgentClientHandle, CreateAgentClientOptions, createAgentClient } from './core.js';
|
|
7
7
|
import * as react from 'react';
|
|
8
8
|
import { LinkProps as LinkProps$1 } from 'react-router';
|
|
9
|
-
|
|
9
|
+
export { WireMessage as UIMessage, WireChunk as UIMessageChunk, WireMessagePart as UIMessagePart } from '@theokit/presenter/wire';
|
|
10
10
|
|
|
11
11
|
/** Infer the response type from a route's handler return */
|
|
12
12
|
type InferResponse<T> = T extends {
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
} from "./chunk-GEQJAFSV.js";
|
|
9
9
|
import {
|
|
10
10
|
theoPluginAsync
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-5AMXBU2R.js";
|
|
12
|
+
import "./chunk-VFU7DTUL.js";
|
|
13
13
|
import "./chunk-2CVV6CNN.js";
|
|
14
14
|
import "./chunk-IEES3CHD.js";
|
|
15
15
|
import {
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "./chunk-QPVXGC7O.js";
|
|
25
25
|
import "./chunk-5ODOE6EF.js";
|
|
26
26
|
import "./chunk-HRCNHWLP.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-6VDYQ4I6.js";
|
|
28
28
|
import "./chunk-TDNNBLGW.js";
|
|
29
29
|
import "./chunk-FXZZS7YW.js";
|
|
30
30
|
import "./chunk-OXCALO3W.js";
|
|
@@ -112,4 +112,4 @@ export {
|
|
|
112
112
|
devCommand,
|
|
113
113
|
startDevServer
|
|
114
114
|
};
|
|
115
|
-
//# sourceMappingURL=dev-
|
|
115
|
+
//# sourceMappingURL=dev-YNO26JS3.js.map
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
findSuggestion,
|
|
22
22
|
generateNonce,
|
|
23
23
|
mountAgent
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-6VDYQ4I6.js";
|
|
25
25
|
import {
|
|
26
26
|
resolveProvider
|
|
27
27
|
} from "./chunk-TDNNBLGW.js";
|
|
@@ -78,4 +78,4 @@ export {
|
|
|
78
78
|
scanWebSocketRoutes,
|
|
79
79
|
sendError
|
|
80
80
|
};
|
|
81
|
-
//# sourceMappingURL=internal-api-
|
|
81
|
+
//# sourceMappingURL=internal-api-NO24QAXC.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
createAgentSsrLoader
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UYMCKHML.js";
|
|
6
6
|
import {
|
|
7
7
|
extractAppResources,
|
|
8
8
|
handleMcpJsonRpc
|
|
@@ -75,4 +75,4 @@ async function mcpCommand(name, deps = {}) {
|
|
|
75
75
|
export {
|
|
76
76
|
mcpCommand
|
|
77
77
|
};
|
|
78
|
-
//# sourceMappingURL=mcp-
|
|
78
|
+
//# sourceMappingURL=mcp-3S6A4A4Q.js.map
|
|
@@ -4,7 +4,7 @@ export { a as WebSocketHandlerWeb } from '../../define-websocket-CPQcQK9h.js';
|
|
|
4
4
|
import { b as ToolTransform, C as CustomTool } from '../../define-agent-tool-cRuSpidb.js';
|
|
5
5
|
export { D as DefineAgentToolSpec, T as ToolContextMessage, a as ToolHandlerContext, c as applyTransform } from '../../define-agent-tool-cRuSpidb.js';
|
|
6
6
|
import { f as OnRequestHook, i as PreHandlerHook, g as OnResponseHook, O as OnErrorHook, T as TheoPlugin } from '../../plugin-types-D69-lJZ9.js';
|
|
7
|
-
import {
|
|
7
|
+
import { WireChunk } from '@theokit/presenter/wire';
|
|
8
8
|
import { IncomingMessage } from 'node:http';
|
|
9
9
|
export { H as HEALTH_PATH, a as HealthRouteConfig, b as READY_PATH, c as ReadyRouteConfig, d as ReservedResponse, R as ReservedRoutes, e as defineHealthRoute, f as defineReadyRoute, s as serveReservedRoute } from '../../health-route-C0hk64_U.js';
|
|
10
10
|
|
|
@@ -362,7 +362,7 @@ interface PluginBuilder {
|
|
|
362
362
|
/** Start a fluent plugin definition. `name` is required; chain hooks/decorations, then `.build()`. */
|
|
363
363
|
declare function plugin(name: string): PluginBuilder;
|
|
364
364
|
|
|
365
|
-
declare function uiMessageStreamResponse(chunks: AsyncIterable<
|
|
365
|
+
declare function uiMessageStreamResponse(chunks: AsyncIterable<WireChunk>): Response;
|
|
366
366
|
|
|
367
367
|
interface ChannelHandler<TMessage = unknown> {
|
|
368
368
|
onSubscribe?: (ws: WebSocketLike, room: string, req: IncomingMessage) => void;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ import { IncomingMessage } from 'node:http';
|
|
|
35
35
|
import { W as WebOnRequestHook, b as WebPreHandlerHook, c as WebOnResponseHook, d as WebOnErrorHook } from '../plugin-types-D69-lJZ9.js';
|
|
36
36
|
export { e as HookName, H as HookResult, O as OnErrorHook, f as OnRequestHook, g as OnResponseHook, P as PluginContext, h as PluginErrorContext, i as PreHandlerHook, R as RunHookOptions, a as TheoApp, T as TheoPlugin } from '../plugin-types-D69-lJZ9.js';
|
|
37
37
|
export { a as ServiceDefinition, S as ServicesConfig, b as ServicesConfigInput, c as ServicesConfigOutput } from '../schema-BpH6ivDY.js';
|
|
38
|
-
import '
|
|
38
|
+
import '@theokit/presenter/wire';
|
|
39
39
|
import 'vite';
|
|
40
40
|
import '@theokit/sdk';
|
|
41
41
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
resolveTransformer,
|
|
13
13
|
serveAgentAuxRoute,
|
|
14
14
|
writeWebResponseToServerResponse
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VFU7DTUL.js";
|
|
16
16
|
import "./chunk-2CVV6CNN.js";
|
|
17
17
|
import {
|
|
18
18
|
loadConfig
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
findSuggestion,
|
|
33
33
|
generateNonce,
|
|
34
34
|
mountAgent
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6VDYQ4I6.js";
|
|
36
36
|
import {
|
|
37
37
|
getApprovalRegistry,
|
|
38
38
|
resolveProvider
|
|
@@ -928,4 +928,4 @@ export {
|
|
|
928
928
|
resolveSsrEntry,
|
|
929
929
|
startCommand
|
|
930
930
|
};
|
|
931
|
-
//# sourceMappingURL=start-
|
|
931
|
+
//# sourceMappingURL=start-TVT7BZ4V.js.map
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
defineTheoIntegration,
|
|
8
8
|
theoPlugin,
|
|
9
9
|
theoPluginAsync
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-5AMXBU2R.js";
|
|
11
|
+
import "./chunk-VFU7DTUL.js";
|
|
12
12
|
import "./chunk-2CVV6CNN.js";
|
|
13
13
|
import "./chunk-IEES3CHD.js";
|
|
14
14
|
import "./chunk-B7ONQ4QJ.js";
|
|
@@ -17,7 +17,7 @@ import "./chunk-HEK4SET7.js";
|
|
|
17
17
|
import "./chunk-QPVXGC7O.js";
|
|
18
18
|
import "./chunk-5ODOE6EF.js";
|
|
19
19
|
import "./chunk-HRCNHWLP.js";
|
|
20
|
-
import "./chunk-
|
|
20
|
+
import "./chunk-6VDYQ4I6.js";
|
|
21
21
|
import "./chunk-TDNNBLGW.js";
|
|
22
22
|
import "./chunk-FXZZS7YW.js";
|
|
23
23
|
import "./chunk-OXCALO3W.js";
|
|
@@ -33,4 +33,4 @@ export {
|
|
|
33
33
|
theoPlugin,
|
|
34
34
|
theoPluginAsync
|
|
35
35
|
};
|
|
36
|
-
//# sourceMappingURL=vite-plugin-
|
|
36
|
+
//# sourceMappingURL=vite-plugin-K64KQZSU.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theokit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -123,13 +123,13 @@
|
|
|
123
123
|
"tsx": "^4.22.4",
|
|
124
124
|
"typescript": "^5.9.3",
|
|
125
125
|
"vite": "^6.4.3",
|
|
126
|
+
"@theokit/agents": "^7.1.0",
|
|
126
127
|
"@theokit/http": "^1.0.0",
|
|
127
|
-
"@theokit/
|
|
128
|
+
"@theokit/presenter": "^0.4.0"
|
|
128
129
|
},
|
|
129
130
|
"peerDependencies": {
|
|
130
131
|
"@theokit/sdk": "^4.0.1",
|
|
131
132
|
"@theokit/ui": "^1.1.0",
|
|
132
|
-
"ai": ">=7.0.0",
|
|
133
133
|
"db0": "^0.3.0",
|
|
134
134
|
"react": "^19.0.0",
|
|
135
135
|
"react-dom": "^19.0.0",
|
|
@@ -146,9 +146,6 @@
|
|
|
146
146
|
"@theokit/ui": {
|
|
147
147
|
"optional": true
|
|
148
148
|
},
|
|
149
|
-
"ai": {
|
|
150
|
-
"optional": true
|
|
151
|
-
},
|
|
152
149
|
"db0": {
|
|
153
150
|
"optional": true
|
|
154
151
|
},
|
|
@@ -166,7 +163,7 @@
|
|
|
166
163
|
"@theokit/sdk": "^4.39.0",
|
|
167
164
|
"@types/busboy": "^1.5.4",
|
|
168
165
|
"@types/picomatch": "^4.0.3",
|
|
169
|
-
"ai": "
|
|
166
|
+
"ai": "7.0.14",
|
|
170
167
|
"better-sqlite3": "^12.11.1",
|
|
171
168
|
"db0": "^0.3.4",
|
|
172
169
|
"unstorage": "^1.17.5"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/agent/run-terminal-agent.ts","../src/server/agent/render-terminal.ts","../src/cli/commands/agent.ts"],"sourcesContent":["/**\n * M5 (theokit-ai-first) — run a compiled agent in the terminal, reusing the M4 harness.\n *\n * The web `mountAgent` renders `streamAgentUIMessages` to an HTTP `Response` and resolves HITL\n * approvals via the approve route. This is its terminal sibling: SAME `compileAgentModule` + SAME\n * `streamAgentUIMessages` + SAME in-process approval registry — only the render surface (stdout) and\n * the approval input (a readline prompt) differ (ADR 0039 D2). In a single-process CLI the stream's\n * `awaitApproval` (→ `registry.register`) and the terminal prompt (→ `registry.resolve`) share the one\n * in-process registry — the M4 singleton's exact fit. No new runtime, no LLM call, no tool dispatch.\n */\nimport {\n compileAgentModule,\n streamAgentUIMessages,\n type HumanInTheLoopOptions,\n} from '@theokit/agents'\n\nimport { getApprovalRegistry, type ApprovalRegistry } from './approval-registry.js'\nimport {\n promptTerminalApproval,\n renderAgentStreamToTerminal,\n type TerminalOut,\n} from './render-terminal.js'\n\ninterface RunTerminalAgentInput {\n message: string\n /** Resume key; a fresh id per run when omitted. */\n sessionId?: string\n /** Injectable sink (defaults to `process.stdout` at the CLI entry). */\n stdout?: TerminalOut\n /** Injectable shared registry (defaults to the process singleton). */\n registry?: ApprovalRegistry\n /** Injectable approval prompt (defaults to the readline prompt over the process streams). */\n promptApproval?: (req: { toolName: string; timeoutMs?: number }) => Promise<boolean>\n /** Labels a fail-fast `AgentDefinitionError` (the file path). */\n source?: string\n}\n\n/**\n * Compile the module (M4, gathers `@Mixin` toolboxes), stream it, and render to the terminal. When the\n * agent has `@HumanInTheLoop`-gated tools, the terminal prompts and resolves the shared registry —\n * approve runs the tool, deny/timeout/non-TTY denies and the run continues.\n */\nexport async function runAgentInTerminal(\n mod: unknown,\n apiKey: string,\n input: RunTerminalAgentInput,\n): Promise<{ sawError: boolean }> {\n const compiled = compileAgentModule(mod, input.source)\n const stdout = input.stdout ?? process.stdout\n const registry = input.registry ?? getApprovalRegistry()\n const sessionId = input.sessionId ?? crypto.randomUUID()\n const promptApproval =\n input.promptApproval ??\n ((req: { toolName: string; timeoutMs?: number }) =>\n promptTerminalApproval(req, { input: process.stdin, output: process.stdout }, req.timeoutMs))\n\n // Mirror mount-agent's HITL wiring; absent ⇒ the non-HITL stream path (M2), unchanged.\n const gated = compiled.hitl\n const hitl =\n gated && gated.size > 0\n ? {\n gated,\n awaitApproval: (approvalId: string, opts: HumanInTheLoopOptions) =>\n registry.register(approvalId, {\n timeoutMs: opts.timeout ?? 300_000,\n onTimeout: opts.onTimeout ?? 'abort',\n }),\n }\n : undefined\n\n const chunks = streamAgentUIMessages(compiled, apiKey, {\n message: input.message,\n sessionId,\n hitl,\n })\n return renderAgentStreamToTerminal(chunks, {\n stdout,\n onApproval: async ({ approvalId, toolName }) => {\n // The renderer paused here (the SDK run is paused in its awaited pre_tool_call hook); prompt,\n // then resolve the SAME registry the stream registered into — the run resumes with the decision.\n // The prompt shares the gated tool's timeout so it can never outlive the registry-settled run.\n const timeoutMs = gated?.get(toolName)?.timeout ?? 300_000\n const approved = await promptApproval({ toolName, timeoutMs })\n registry.resolve(approvalId, approved)\n },\n })\n}\n","/**\n * M5 (theokit-ai-first) — the terminal render surface for the M4 agent harness.\n *\n * Consumes the M4 `UIMessageChunk` stream (`streamAgentUIMessages`) and writes it to the terminal:\n * streaming text, a dim reasoning line, `▸ tool(input)` / result cards, a checkpoint notice, errors.\n * On a HITL `tool-approval-request` it delegates to the injected `onApproval` (the entry wires that to\n * a readline prompt + the approval registry). This is the ONLY new code M5 adds — no runtime, no LLM\n * call, no tool dispatch (ADR 0039 D2). I/O is INJECTED (`stdout`, prompt streams) so the whole\n * surface is deterministically testable without a TTY (ADR 0039 D4 / ADR-M5a).\n */\nimport { createInterface } from 'node:readline'\nimport type { Readable, Writable } from 'node:stream'\n\nimport type { UIMessageChunk } from 'ai'\n\n/** ANSI helpers — applied only on a TTY so a captured (non-TTY) sink stays plain-text for tests. */\nconst ANSI = {\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n green: '\\x1b[32m',\n reset: '\\x1b[0m',\n}\nfunction paint(text: string, color: keyof typeof ANSI, tty: boolean | undefined): string {\n return tty ? `${ANSI[color]}${text}${ANSI.reset}` : text\n}\n\n/** A stdout-like sink; `isTTY` gates color (Node's `process.stdout` carries it, a test sink does not). */\nexport type TerminalOut = Writable & { isTTY?: boolean }\n\ninterface TerminalRenderOptions {\n stdout: TerminalOut\n /** Resolve a HITL approval (the entry prompts + resolves the registry). */\n onApproval: (req: { approvalId: string; toolName: string }) => Promise<void>\n}\n\n/** Render an `unknown` tool output/input for display (string as-is; other JSON-ish values stringified). */\nfunction display(value: unknown): string {\n return typeof value === 'string' ? value : JSON.stringify(value ?? {})\n}\n\n/**\n * Map a single (non-approval) `UIMessageChunk` to its terminal line(s), or `''` for chunks that render\n * nothing (`start`/`text-start`/`reasoning-start`/…). Pure — the async loop owns approval + I/O.\n * Narrows the `ai` discriminated union directly on `chunk.type` (no cast; G3).\n */\nfunction renderChunkLine(chunk: UIMessageChunk, tty: boolean | undefined): string {\n switch (chunk.type) {\n case 'text-delta':\n return chunk.delta\n case 'text-end':\n case 'finish':\n return '\\n'\n case 'reasoning-delta':\n return paint(chunk.delta, 'dim', tty)\n case 'reasoning-end':\n return '\\n'\n case 'tool-input-available':\n return paint(`\\n▸ ${chunk.toolName}(${display(chunk.input)})\\n`, 'cyan', tty)\n case 'tool-output-available':\n return paint(` ✓ ${display(chunk.output)}\\n`, 'green', tty)\n case 'tool-output-error':\n return paint(` ✗ ${chunk.errorText}\\n`, 'red', tty)\n case 'error':\n return paint(`\\n✗ ${chunk.errorText}\\n`, 'red', tty)\n default:\n // ai-sdk `data-*` transient parts (e.g. the M4 `data-checkpoint` resume handle).\n return chunk.type === 'data-checkpoint'\n ? paint('\\n⎇ checkpoint saved — resume with the same session id\\n', 'dim', tty)\n : ''\n }\n}\n\n/**\n * Render the `UIMessageChunk` stream to `stdout`. Sequential: one chunk → its line(s). The SDK run is\n * already paused inside the awaited `pre_tool_call` hook when a `tool-approval-request` arrives, so a\n * blocking `await onApproval(...)` here is correct — no frame buffer, no concurrency. Returns whether\n * an `error` chunk was seen so the caller can signal a non-zero exit (fail-loud, not silent exit 0).\n */\nexport async function renderAgentStreamToTerminal(\n chunks: AsyncIterable<UIMessageChunk>,\n opts: TerminalRenderOptions,\n): Promise<{ sawError: boolean }> {\n const tty = opts.stdout.isTTY\n const write = (s: string): void => {\n if (s) opts.stdout.write(s)\n }\n // toolCallId → toolName, so the approval prompt (whose chunk carries only ids) can name the tool.\n const toolNames = new Map<string, string>()\n let sawError = false\n\n for await (const chunk of chunks) {\n if (chunk.type === 'tool-approval-request') {\n const toolName = toolNames.get(chunk.toolCallId)\n await opts.onApproval({ approvalId: chunk.approvalId, toolName: toolName ?? 'tool' })\n continue\n }\n if (chunk.type === 'tool-input-available' && chunk.toolCallId) {\n toolNames.set(chunk.toolCallId, chunk.toolName)\n }\n if (chunk.type === 'error') sawError = true\n write(renderChunkLine(chunk, tty))\n }\n return { sawError }\n}\n\n/** Injectable I/O for the approval prompt (defaults to the process streams at the CLI entry). */\ninterface PromptIO {\n input: Readable & { isTTY?: boolean }\n output: Writable & { isTTY?: boolean }\n}\n\n/**\n * Prompt `Approve <tool>? (y/N)` and resolve to the decision. A non-interactive environment (piped /\n * CI — either stream is not a TTY) AUTO-DENIES without prompting (ADR-M5b fail-safe: a decision that\n * cannot be obtained is a deny, never an auto-approve; mirrors HITL `onTimeout: 'abort'`).\n *\n * `timeoutMs` (the gated tool's `@HumanInTheLoop` timeout) auto-denies + closes the prompt when the\n * human walks away — WITHOUT it the readline question would outlive the SDK run (the registry settles\n * the run at its own timeout but readline has no timer), hanging the CLI on a now-irrelevant prompt.\n */\nexport function promptTerminalApproval(\n req: { toolName: string },\n io: PromptIO,\n timeoutMs?: number,\n): Promise<boolean> {\n if (!io.input.isTTY || !io.output.isTTY) {\n io.output.write(`\\n⚠ Non-interactive terminal — auto-denying approval for '${req.toolName}'.\\n`)\n return Promise.resolve(false)\n }\n return new Promise<boolean>((resolve) => {\n const rl = createInterface({ input: io.input, output: io.output })\n let timer: ReturnType<typeof setTimeout> | undefined\n const settle = (approved: boolean): void => {\n if (timer) clearTimeout(timer)\n rl.close()\n resolve(approved)\n }\n if (timeoutMs !== undefined && timeoutMs > 0) {\n timer = setTimeout(() => {\n io.output.write(`\\n⚠ Approval timed out — denying '${req.toolName}'.\\n`)\n settle(false)\n }, timeoutMs)\n }\n rl.question(`\\n❔ Approve ${req.toolName}? (y/N) `, (answer) => {\n const a = answer.trim().toLowerCase()\n settle(a === 'y' || a === 'yes')\n })\n })\n}\n","/**\n * M5 (theokit-ai-first) — `theokit agent <name> [message]`: run a scanned agent in the terminal.\n *\n * Scans the top-level `agents/` directory (M2), loads the matched `agents/<name>.ts` through the\n * framework's OWN Vite pipeline (the SAME transpile `theokit dev` uses — legacy `@Agent`/`@Tool`\n * decorators need it; Node's native loader can't transpile TS), resolves the provider apiKey, and\n * runs it in the terminal via the M4 harness (`runAgentInTerminal`). A dev-time surface (ADR 0039 D1),\n * not a CLI product. `deps` is injectable so the fail-fast branches are tested without Vite/an LLM.\n */\nimport { loadEnv } from '../../config/load-env.js'\nimport { resolveProvider } from '../../server/agent/provider-resolver.js'\nimport { runAgentInTerminal } from '../../server/agent/run-terminal-agent.js'\nimport { scanAgents } from '../../server/scan/agent-scan.js'\n\ninterface AgentCommandDeps {\n projectRoot?: string\n /** Load a TS agent module; defaults to a fresh minimal Vite SSR server (framework transpile). */\n loadModule?: (filePath: string) => Promise<Record<string, unknown>>\n runAgent?: typeof runAgentInTerminal\n resolveApiKey?: () => string\n /** Agents dir name (config `agentsDir`); defaults to the loaded config's value (\"agents\"). Tests inject it. */\n agentsDir?: string\n}\n\nexport async function agentCommand(\n name: string,\n message: string | undefined,\n deps: AgentCommandDeps = {},\n): Promise<{ sawError: boolean }> {\n if (!message || message.trim().length === 0) {\n throw new Error('theokit agent: a message is required. Usage: theokit agent <name> \"<message>\"')\n }\n const projectRoot = deps.projectRoot ?? process.cwd()\n // #95 follow-up — resolve the agents dir from config (default \"agents\") so `agentsDir: \"core/agents\"`\n // is honored in the terminal, same as dev. Tests inject `agentsDir` to skip the config load.\n const agentsDir =\n deps.agentsDir ??\n (await (await import('../../config/load-config.js')).loadConfig(projectRoot)).agentsDir\n const agents = scanAgents(projectRoot, agentsDir)\n const agent = agents.find((a) => a.name === name)\n if (!agent) {\n const names = agents.map((a) => a.name).join(', ') || `(none found in ${agentsDir}/)`\n throw new Error(`theokit agent: '${name}' not found. Available agents: ${names}`)\n }\n\n const runAgent = deps.runAgent ?? runAgentInTerminal\n // Load .env into process.env BEFORE resolving the provider key — parity with `theokit dev`\n // (dev.ts loadEnv), so an app with OPENROUTER_API_KEY in .env works here too (review H1). Skipped\n // when the key is injected (tests) — a temp project has no .env anyway.\n const apiKey =\n deps.resolveApiKey?.() ??\n (() => {\n loadEnv({ cwd: projectRoot, mode: 'development' })\n return resolveProvider().apiKey\n })()\n\n let loadModule = deps.loadModule\n let dispose: (() => Promise<void>) | undefined\n if (!loadModule) {\n const loader = await createAgentSsrLoader(projectRoot)\n loadModule = loader.load\n dispose = loader.dispose\n }\n\n try {\n const mod = await loadModule(agent.filePath)\n return await runAgent(mod, apiKey, { message, source: agent.filePath })\n } finally {\n if (dispose) await dispose()\n }\n}\n\n/**\n * A minimal middleware-mode Vite server built from the SAME `theoPluginAsync` plugin set `theokit dev`\n * uses (`cli/commands/dev.ts`) — so a class-decorated `agents/<name>.ts` transpiles identically. No\n * HTTP listen, no service orchestration, silent — just `ssrLoadModule`. Caller MUST `dispose()`.\n */\nexport async function createAgentSsrLoader(projectRoot: string): Promise<{\n load: (p: string) => Promise<Record<string, unknown>>\n dispose: () => Promise<void>\n}> {\n const { createServer } = await import('vite')\n const react = (await import('@vitejs/plugin-react')).default\n const { theoPluginAsync } = await import('../../vite-plugin/index.js')\n const { loadConfig } = await import('../../config/load-config.js')\n // Pass the project's ssr/services/optimizeDeps config through, exactly as dev.ts does — so an app\n // that customizes ssr.noExternal transpiles here identically to `theokit dev` (review L1).\n const config = await loadConfig(projectRoot)\n const theoPlugins = await theoPluginAsync({\n root: projectRoot,\n ssr: config.ssr,\n services: config.services,\n viteOptimizeDeps: config.viteOptimizeDeps,\n // #95 — honor config `serverDir` so the terminal SSR loader transpiles under the same root as dev.\n serverDir: config.serverDir,\n // #95 follow-up — honor config `agentsDir`.\n agentsDir: config.agentsDir,\n })\n const server = await createServer({\n root: projectRoot,\n plugins: [react(), ...theoPlugins],\n server: { middlewareMode: true },\n appType: 'custom',\n logLevel: 'silent',\n })\n return {\n load: (p) => server.ssrLoadModule(p) as Promise<Record<string, unknown>>,\n dispose: async () => {\n // Surface a cleanup failure instead of swallowing it (error-handling.md) — non-fatal, the run\n // already finished, so warn rather than throw past the CLI boundary.\n await server.close().catch((err: unknown) => {\n console.warn('[theokit agent] Vite server cleanup error:', err)\n })\n },\n }\n}\n"],"mappings":"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","names":[]}
|