@agent-native/core 0.53.0 → 0.54.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.
Files changed (99) hide show
  1. package/dist/action.d.ts +40 -1
  2. package/dist/action.d.ts.map +1 -1
  3. package/dist/action.js +69 -2
  4. package/dist/action.js.map +1 -1
  5. package/dist/agent/index.d.ts +1 -0
  6. package/dist/agent/index.d.ts.map +1 -1
  7. package/dist/agent/index.js +1 -0
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/agent/observational-memory/index.d.ts +6 -6
  10. package/dist/agent/observational-memory/index.js +6 -6
  11. package/dist/agent/observational-memory/index.js.map +1 -1
  12. package/dist/agent/observational-memory/read.d.ts +7 -9
  13. package/dist/agent/observational-memory/read.d.ts.map +1 -1
  14. package/dist/agent/observational-memory/read.js +7 -9
  15. package/dist/agent/observational-memory/read.js.map +1 -1
  16. package/dist/agent/processors.d.ts +146 -0
  17. package/dist/agent/processors.d.ts.map +1 -0
  18. package/dist/agent/processors.js +122 -0
  19. package/dist/agent/processors.js.map +1 -0
  20. package/dist/agent/production-agent.d.ts +10 -0
  21. package/dist/agent/production-agent.d.ts.map +1 -1
  22. package/dist/agent/production-agent.js +101 -0
  23. package/dist/agent/production-agent.js.map +1 -1
  24. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  25. package/dist/agent/run-loop-with-resume.js +4 -5
  26. package/dist/agent/run-loop-with-resume.js.map +1 -1
  27. package/dist/agent/tool-call-journal.d.ts +6 -8
  28. package/dist/agent/tool-call-journal.d.ts.map +1 -1
  29. package/dist/agent/tool-call-journal.js +6 -8
  30. package/dist/agent/tool-call-journal.js.map +1 -1
  31. package/dist/agent/types.d.ts +11 -0
  32. package/dist/agent/types.d.ts.map +1 -1
  33. package/dist/agent/types.js.map +1 -1
  34. package/dist/cli/plan-local.d.ts.map +1 -1
  35. package/dist/cli/plan-local.js +129 -4
  36. package/dist/cli/plan-local.js.map +1 -1
  37. package/dist/cli/skills.d.ts.map +1 -1
  38. package/dist/cli/skills.js +38 -3
  39. package/dist/cli/skills.js.map +1 -1
  40. package/dist/coding-tools/run-code.d.ts.map +1 -1
  41. package/dist/coding-tools/run-code.js +18 -2
  42. package/dist/coding-tools/run-code.js.map +1 -1
  43. package/dist/extensions/fetch-tool.d.ts.map +1 -1
  44. package/dist/extensions/fetch-tool.js +80 -15
  45. package/dist/extensions/fetch-tool.js.map +1 -1
  46. package/dist/extensions/web-content.d.ts +61 -0
  47. package/dist/extensions/web-content.d.ts.map +1 -0
  48. package/dist/extensions/web-content.js +468 -0
  49. package/dist/extensions/web-content.js.map +1 -0
  50. package/dist/extensions/web-search-tool.js +3 -3
  51. package/dist/extensions/web-search-tool.js.map +1 -1
  52. package/dist/mcp/build-server.d.ts.map +1 -1
  53. package/dist/mcp/build-server.js +4 -1
  54. package/dist/mcp/build-server.js.map +1 -1
  55. package/dist/provider-api/corpus-jobs.d.ts +80 -0
  56. package/dist/provider-api/corpus-jobs.d.ts.map +1 -1
  57. package/dist/provider-api/corpus-jobs.js +219 -22
  58. package/dist/provider-api/corpus-jobs.js.map +1 -1
  59. package/dist/provider-api/index.d.ts +24 -32
  60. package/dist/provider-api/index.d.ts.map +1 -1
  61. package/dist/provider-api/index.js +28 -1
  62. package/dist/provider-api/index.js.map +1 -1
  63. package/dist/server/agent-chat-plugin.js +1 -1
  64. package/dist/server/agent-chat-plugin.js.map +1 -1
  65. package/dist/server/better-auth-instance.d.ts +7 -0
  66. package/dist/server/better-auth-instance.d.ts.map +1 -1
  67. package/dist/server/better-auth-instance.js +90 -0
  68. package/dist/server/better-auth-instance.js.map +1 -1
  69. package/dist/server/deep-link.d.ts +7 -0
  70. package/dist/server/deep-link.d.ts.map +1 -1
  71. package/dist/server/deep-link.js +13 -2
  72. package/dist/server/deep-link.js.map +1 -1
  73. package/dist/server/index.d.ts +1 -1
  74. package/dist/server/index.d.ts.map +1 -1
  75. package/dist/server/index.js +1 -1
  76. package/dist/server/index.js.map +1 -1
  77. package/dist/templates/default/.agents/skills/actions/SKILL.md +52 -1
  78. package/dist/templates/default/.agents/skills/security/SKILL.md +22 -0
  79. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +52 -1
  80. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -4
  81. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +11 -0
  82. package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +22 -0
  83. package/docs/content/actions.md +50 -0
  84. package/docs/content/durable-resume.md +49 -0
  85. package/docs/content/external-agents.md +2 -2
  86. package/docs/content/human-approval.md +101 -0
  87. package/docs/content/observability.md +21 -0
  88. package/docs/content/observational-memory.md +63 -0
  89. package/docs/content/plan-plugin.md +5 -0
  90. package/docs/content/pr-visual-recap.md +4 -3
  91. package/docs/content/processors.md +99 -0
  92. package/docs/content/template-plan.md +78 -14
  93. package/package.json +6 -1
  94. package/src/templates/default/.agents/skills/actions/SKILL.md +52 -1
  95. package/src/templates/default/.agents/skills/security/SKILL.md +22 -0
  96. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +52 -1
  97. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -4
  98. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +11 -0
  99. package/src/templates/workspace-core/.agents/skills/security/SKILL.md +22 -0
@@ -1 +1 @@
1
- {"version":3,"file":"run-loop-with-resume.d.ts","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,YAAY,EAIb,MAAM,uBAAuB,CAAC;AAqD/B;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;;;;;GAQG;AACH,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACxC,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CA4FnD"}
1
+ {"version":3,"file":"run-loop-with-resume.d.ts","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,YAAY,EAIb,MAAM,uBAAuB,CAAC;AAoD/B;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;;;;;GAQG;AACH,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACxC,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CA4FnD"}
@@ -40,11 +40,10 @@ import { classifyToolCallJournal, buildResumeJournalNote, } from "./tool-call-jo
40
40
  * what it was before. Best-effort: any ledger read/parse failure is swallowed so
41
41
  * a journal hiccup can never block a recovery that would otherwise succeed.
42
42
  *
43
- * TODO(charlie-merge): optional hard-block once CHARLIE's loop edits land,
44
- * pair this prompt-level journal with tool-layer enforcement in
45
- * production-agent.ts/runToolCall that refuses to re-execute a journaled-
46
- * complete tool call (return the journaled result instead). See
47
- * `tool-call-journal.ts` for the keying to use.
43
+ * This prompt-level journal is paired with tool-layer enforcement in
44
+ * production-agent.ts/runToolCall, which refuses to re-execute a journaled-
45
+ * complete write tool (returning the journaled result instead). See
46
+ * `tool-call-journal.ts` (`findCompletedJournalEntry`) for the keying used.
48
47
  */
49
48
  async function appendToolCallJournalNote(messages, threadId) {
50
49
  if (!threadId)
@@ -1 +1 @@
1
- {"version":3,"file":"run-loop-with-resume.js","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,YAAY,EACZ,2BAA2B,EAC3B,sBAAsB,EACtB,mCAAmC,GACpC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,KAAK,UAAU,yBAAyB,CACtC,QAAyB,EACzB,QAA4B;IAE5B,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,6BAA6B,CAAC,QAAQ,CAAC,CAAC;QAC7D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,OAAO,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SACxC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,0EAA0E;QAC1E,uEAAuE;IACzE,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACrD,IAAwC,EACxC,aAAsB;IAEtB,MAAM,SAAS,GAAG,uBAAuB,CAAC,aAAa,CAAC,CAAC;IACzD,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAE9C,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;IACnC,MAAM,KAAK,GAA6C;QACtD,WAAW,EAAE,CAAC;QACd,YAAY,EAAE,CAAC;QACf,eAAe,EAAE,CAAC;QAClB,gBAAgB,EAAE,CAAC;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,IAA8C,EAAE,EAAE;QAClE,KAAK,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC;QACtC,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC;QACxC,KAAK,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe,CAAC;QAC9C,KAAK,CAAC,gBAAgB,IAAI,IAAI,CAAC,gBAAgB,CAAC;QAChD,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAC3B,CAAC,CAAC;IAEF,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,OAAO,CAAC,cAAc,CAAC,OAAO,IAAI,QAAQ,GAAG,0BAA0B,EAAE,CAAC;QACxE,QAAQ,EAAE,CAAC;QACX,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,iBAAiB,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACnD,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3B,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,EAAE;gBAC1D,IAAI,EAAE,IAAI;aACX,CAAC,CAAC;QACL,CAAC;QAED,IAAI,YAAY,GAAG,KAAK,CAAC;QACzB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO;YACtC,YAAY,GAAG,IAAI,CAAC;YACpB,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC;gBACnC,GAAG,IAAI;gBACP,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,QAAQ,CAAC,SAAS,CAAC,CAAC;YACpB,IAAI,YAAY,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;gBAC5C,2BAA2B,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;gBAC1D,MAAM,yBAAyB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC9D,SAAS;YACX,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,YAAY,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;gBAC5C,iEAAiE;gBACjE,kEAAkE;gBAClE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC7B,2BAA2B,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;gBAC1D,MAAM,yBAAyB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC9D,SAAS;YACX,CAAC;YACD,wEAAwE;YACxE,kEAAkE;YAClE,wEAAwE;YACxE,wEAAwE;YACxE,uEAAuE;YACvE,oEAAoE;YACpE,2CAA2C;YAC3C,EAAE;YACF,uEAAuE;YACvE,yEAAyE;YACzE,oEAAoE;YACpE,qEAAqE;YACrE,kEAAkE;YAClE,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3D,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC7B,2BAA2B,CACzB,IAAI,CAAC,QAAQ,EACb,mCAAmC,CAAC,GAAG,CAAC,CACzC,CAAC;gBACF,MAAM,yBAAyB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC9D,SAAS;YACX,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,cAAc,CAAC,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["/**\n * Wraps `runAgentLoop` with two layered recovery mechanisms so a single hosted\n * invocation can survive interruptions without showing the user a dead chat:\n *\n * 1. **Soft timeout** — an inner timer that aborts the LLM call before the\n * hosting function's hard limit (Lambda 75s, Vercel 60s, etc.) so we have a\n * chance to gracefully wind down and append a continuation nudge. Without\n * this the function gets killed mid-stream and the user sees a frozen\n * spinner.\n *\n * 2. **Resumable-error continuation** — when the LLM call errors with a\n * transport- or gateway-level interruption (Builder gateway 45s timeout,\n * socket hang up, ECONNRESET, upstream 5xx that survived engine retries),\n * we save the conversation prefix, append a \"continue from where you left\n * off\" message, and run another LLM call. Anthropic's prompt cache makes\n * the resume call dramatically faster than the cold first attempt, and the\n * agent gets explicit context that it was cut off so it doesn't re-do\n * completed work.\n *\n * Both paths route through `appendAgentLoopContinuation` so the agent sees a\n * uniform \"continue\" instruction regardless of which recovery fired.\n */\n\nimport {\n runAgentLoop,\n appendAgentLoopContinuation,\n isResumableEngineError,\n continuationReasonForResumableError,\n} from \"./production-agent.js\";\nimport { resolveRunSoftTimeoutMs } from \"./run-manager.js\";\nimport { getCurrentTurnEventsForThread } from \"./run-store.js\";\nimport {\n classifyToolCallJournal,\n buildResumeJournalNote,\n} from \"./tool-call-journal.js\";\nimport type { EngineMessage } from \"./engine/types.js\";\n\n/**\n * Derive the per-turn tool-call journal from the durable run-event ledger and,\n * when there is anything to report, append a STRUCTURED note to the message\n * prefix so the resumed model:\n * - does NOT re-execute tool calls that already completed (avoiding duplicate\n * side effects like re-sending an email or re-creating a ticket), and\n * - is explicitly told about any tool call that started but whose outcome was\n * never recorded (\"interrupted, unknown outcome\") so it can decide.\n *\n * This is additive to the existing \"continue from where you left off\" nudge —\n * it is appended right after it. When the journal is empty (no completed or\n * interrupted tool calls — e.g. a turn with no tool activity, or a clean\n * continuation), nothing extra is appended and resume behavior is byte-for-byte\n * what it was before. Best-effort: any ledger read/parse failure is swallowed so\n * a journal hiccup can never block a recovery that would otherwise succeed.\n *\n * TODO(charlie-merge): optional hard-block — once CHARLIE's loop edits land,\n * pair this prompt-level journal with tool-layer enforcement in\n * production-agent.ts/runToolCall that refuses to re-execute a journaled-\n * complete tool call (return the journaled result instead). See\n * `tool-call-journal.ts` for the keying to use.\n */\nasync function appendToolCallJournalNote(\n messages: EngineMessage[],\n threadId: string | undefined,\n): Promise<void> {\n if (!threadId) return;\n try {\n const events = await getCurrentTurnEventsForThread(threadId);\n if (events.length === 0) return;\n const journal = classifyToolCallJournal(events);\n const note = buildResumeJournalNote(journal);\n if (!note) return;\n messages.push({\n role: \"user\",\n content: [{ type: \"text\", text: note }],\n });\n } catch {\n // The journal is a hardening layer, never a gate. A failed ledger read or\n // parse must not break the resume that the continuation nudge already set\n // up — the model still continues, just without the structured journal.\n }\n}\n\n/**\n * Cap on continuation iterations inside a single\n * `runAgentLoopDirectWithSoftTimeout` invocation. The host's hard function\n * timeout usually bounds this naturally — but a defensive cap prevents an\n * instant-error spiral from looping forever inside hosting environments with a\n * generous budget.\n *\n * 6 leaves room for: 1 normal completion + a few resume rounds for design\n * generation (prompt + 3 variants ≈ 4 LLM calls), with a small safety margin.\n */\nexport const MAX_RUN_LOOP_CONTINUATIONS = 6;\n\n/**\n * Internal entry point used by the agent-chat plugin's run handler. Wraps\n * `runAgentLoop` with soft-timeout + resumable-error continuation recovery.\n *\n * The `softTimeoutMs` argument falls back to `resolveRunSoftTimeoutMs(...)` so\n * different hosting environments (Lambda, Vercel, Cloudflare, local dev) get\n * an appropriate inner budget. Setting it to <= 0 disables both layers — the\n * call goes straight to `runAgentLoop` with no wrapping.\n */\nexport async function runAgentLoopDirectWithSoftTimeout(\n opts: Parameters<typeof runAgentLoop>[0],\n softTimeoutMs?: number,\n): Promise<Awaited<ReturnType<typeof runAgentLoop>>> {\n const timeoutMs = resolveRunSoftTimeoutMs(softTimeoutMs);\n if (timeoutMs <= 0) return runAgentLoop(opts);\n\n const upstreamSignal = opts.signal;\n const usage: Awaited<ReturnType<typeof runAgentLoop>> = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n model: opts.model,\n };\n\n const addUsage = (next: Awaited<ReturnType<typeof runAgentLoop>>) => {\n usage.inputTokens += next.inputTokens;\n usage.outputTokens += next.outputTokens;\n usage.cacheReadTokens += next.cacheReadTokens;\n usage.cacheWriteTokens += next.cacheWriteTokens;\n usage.model = next.model;\n };\n\n let attempts = 0;\n while (!upstreamSignal.aborted && attempts < MAX_RUN_LOOP_CONTINUATIONS) {\n attempts++;\n const controller = new AbortController();\n const abortFromUpstream = () => controller.abort();\n if (upstreamSignal.aborted) {\n controller.abort();\n } else {\n upstreamSignal.addEventListener(\"abort\", abortFromUpstream, {\n once: true,\n });\n }\n\n let softTimedOut = false;\n const timer = setTimeout(() => {\n if (controller.signal.aborted) return;\n softTimedOut = true;\n controller.abort();\n }, timeoutMs);\n\n try {\n const nextUsage = await runAgentLoop({\n ...opts,\n signal: controller.signal,\n });\n addUsage(nextUsage);\n if (softTimedOut && !upstreamSignal.aborted) {\n appendAgentLoopContinuation(opts.messages, \"run_timeout\");\n await appendToolCallJournalNote(opts.messages, opts.threadId);\n continue;\n }\n return usage;\n } catch (err) {\n if (softTimedOut && !upstreamSignal.aborted) {\n // Clear partial text the client received before the abort so the\n // resumed model doesn't re-emit it and produce duplicated output.\n opts.send({ type: \"clear\" });\n appendAgentLoopContinuation(opts.messages, \"run_timeout\");\n await appendToolCallJournalNote(opts.messages, opts.threadId);\n continue;\n }\n // Resumable transport / gateway interruptions: the LLM call was cut off\n // mid-stream (gateway 45s timeout, socket hang up, function-level\n // timeout that didn't trip our soft timer first). Treat it the same way\n // as a soft timeout — append a \"continue from where you left off\" nudge\n // and let the loop run another LLM call. The conversation prefix up to\n // the cut-off is preserved in opts.messages, and Anthropic's prompt\n // cache makes the resume call much faster.\n //\n // Emit 'clear' so any partial streamed text is discarded on the client\n // before the model resumes. Without this the model restarts its sentence\n // from scratch and the fold produces duplicated text in one message\n // (the partial text was already sent to the client but never entered\n // the in-memory messages array, so the next attempt re-emits it).\n if (!upstreamSignal.aborted && isResumableEngineError(err)) {\n opts.send({ type: \"clear\" });\n appendAgentLoopContinuation(\n opts.messages,\n continuationReasonForResumableError(err),\n );\n await appendToolCallJournalNote(opts.messages, opts.threadId);\n continue;\n }\n throw err;\n } finally {\n clearTimeout(timer);\n upstreamSignal.removeEventListener(\"abort\", abortFromUpstream);\n }\n }\n\n return usage;\n}\n"]}
1
+ {"version":3,"file":"run-loop-with-resume.js","sourceRoot":"","sources":["../../src/agent/run-loop-with-resume.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps `runAgentLoop` with two layered recovery mechanisms so a single hosted\n * invocation can survive interruptions without showing the user a dead chat:\n *\n * 1. **Soft timeout** — an inner timer that aborts the LLM call before the\n * hosting function's hard limit (Lambda 75s, Vercel 60s, etc.) so we have a\n * chance to gracefully wind down and append a continuation nudge. Without\n * this the function gets killed mid-stream and the user sees a frozen\n * spinner.\n *\n * 2. **Resumable-error continuation** — when the LLM call errors with a\n * transport- or gateway-level interruption (Builder gateway 45s timeout,\n * socket hang up, ECONNRESET, upstream 5xx that survived engine retries),\n * we save the conversation prefix, append a \"continue from where you left\n * off\" message, and run another LLM call. Anthropic's prompt cache makes\n * the resume call dramatically faster than the cold first attempt, and the\n * agent gets explicit context that it was cut off so it doesn't re-do\n * completed work.\n *\n * Both paths route through `appendAgentLoopContinuation` so the agent sees a\n * uniform \"continue\" instruction regardless of which recovery fired.\n */\n\nimport {\n runAgentLoop,\n appendAgentLoopContinuation,\n isResumableEngineError,\n continuationReasonForResumableError,\n} from \"./production-agent.js\";\nimport { resolveRunSoftTimeoutMs } from \"./run-manager.js\";\nimport { getCurrentTurnEventsForThread } from \"./run-store.js\";\nimport {\n classifyToolCallJournal,\n buildResumeJournalNote,\n} from \"./tool-call-journal.js\";\nimport type { EngineMessage } from \"./engine/types.js\";\n\n/**\n * Derive the per-turn tool-call journal from the durable run-event ledger and,\n * when there is anything to report, append a STRUCTURED note to the message\n * prefix so the resumed model:\n * - does NOT re-execute tool calls that already completed (avoiding duplicate\n * side effects like re-sending an email or re-creating a ticket), and\n * - is explicitly told about any tool call that started but whose outcome was\n * never recorded (\"interrupted, unknown outcome\") so it can decide.\n *\n * This is additive to the existing \"continue from where you left off\" nudge —\n * it is appended right after it. When the journal is empty (no completed or\n * interrupted tool calls — e.g. a turn with no tool activity, or a clean\n * continuation), nothing extra is appended and resume behavior is byte-for-byte\n * what it was before. Best-effort: any ledger read/parse failure is swallowed so\n * a journal hiccup can never block a recovery that would otherwise succeed.\n *\n * This prompt-level journal is paired with tool-layer enforcement in\n * production-agent.ts/runToolCall, which refuses to re-execute a journaled-\n * complete write tool (returning the journaled result instead). See\n * `tool-call-journal.ts` (`findCompletedJournalEntry`) for the keying used.\n */\nasync function appendToolCallJournalNote(\n messages: EngineMessage[],\n threadId: string | undefined,\n): Promise<void> {\n if (!threadId) return;\n try {\n const events = await getCurrentTurnEventsForThread(threadId);\n if (events.length === 0) return;\n const journal = classifyToolCallJournal(events);\n const note = buildResumeJournalNote(journal);\n if (!note) return;\n messages.push({\n role: \"user\",\n content: [{ type: \"text\", text: note }],\n });\n } catch {\n // The journal is a hardening layer, never a gate. A failed ledger read or\n // parse must not break the resume that the continuation nudge already set\n // up — the model still continues, just without the structured journal.\n }\n}\n\n/**\n * Cap on continuation iterations inside a single\n * `runAgentLoopDirectWithSoftTimeout` invocation. The host's hard function\n * timeout usually bounds this naturally — but a defensive cap prevents an\n * instant-error spiral from looping forever inside hosting environments with a\n * generous budget.\n *\n * 6 leaves room for: 1 normal completion + a few resume rounds for design\n * generation (prompt + 3 variants ≈ 4 LLM calls), with a small safety margin.\n */\nexport const MAX_RUN_LOOP_CONTINUATIONS = 6;\n\n/**\n * Internal entry point used by the agent-chat plugin's run handler. Wraps\n * `runAgentLoop` with soft-timeout + resumable-error continuation recovery.\n *\n * The `softTimeoutMs` argument falls back to `resolveRunSoftTimeoutMs(...)` so\n * different hosting environments (Lambda, Vercel, Cloudflare, local dev) get\n * an appropriate inner budget. Setting it to <= 0 disables both layers — the\n * call goes straight to `runAgentLoop` with no wrapping.\n */\nexport async function runAgentLoopDirectWithSoftTimeout(\n opts: Parameters<typeof runAgentLoop>[0],\n softTimeoutMs?: number,\n): Promise<Awaited<ReturnType<typeof runAgentLoop>>> {\n const timeoutMs = resolveRunSoftTimeoutMs(softTimeoutMs);\n if (timeoutMs <= 0) return runAgentLoop(opts);\n\n const upstreamSignal = opts.signal;\n const usage: Awaited<ReturnType<typeof runAgentLoop>> = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n model: opts.model,\n };\n\n const addUsage = (next: Awaited<ReturnType<typeof runAgentLoop>>) => {\n usage.inputTokens += next.inputTokens;\n usage.outputTokens += next.outputTokens;\n usage.cacheReadTokens += next.cacheReadTokens;\n usage.cacheWriteTokens += next.cacheWriteTokens;\n usage.model = next.model;\n };\n\n let attempts = 0;\n while (!upstreamSignal.aborted && attempts < MAX_RUN_LOOP_CONTINUATIONS) {\n attempts++;\n const controller = new AbortController();\n const abortFromUpstream = () => controller.abort();\n if (upstreamSignal.aborted) {\n controller.abort();\n } else {\n upstreamSignal.addEventListener(\"abort\", abortFromUpstream, {\n once: true,\n });\n }\n\n let softTimedOut = false;\n const timer = setTimeout(() => {\n if (controller.signal.aborted) return;\n softTimedOut = true;\n controller.abort();\n }, timeoutMs);\n\n try {\n const nextUsage = await runAgentLoop({\n ...opts,\n signal: controller.signal,\n });\n addUsage(nextUsage);\n if (softTimedOut && !upstreamSignal.aborted) {\n appendAgentLoopContinuation(opts.messages, \"run_timeout\");\n await appendToolCallJournalNote(opts.messages, opts.threadId);\n continue;\n }\n return usage;\n } catch (err) {\n if (softTimedOut && !upstreamSignal.aborted) {\n // Clear partial text the client received before the abort so the\n // resumed model doesn't re-emit it and produce duplicated output.\n opts.send({ type: \"clear\" });\n appendAgentLoopContinuation(opts.messages, \"run_timeout\");\n await appendToolCallJournalNote(opts.messages, opts.threadId);\n continue;\n }\n // Resumable transport / gateway interruptions: the LLM call was cut off\n // mid-stream (gateway 45s timeout, socket hang up, function-level\n // timeout that didn't trip our soft timer first). Treat it the same way\n // as a soft timeout — append a \"continue from where you left off\" nudge\n // and let the loop run another LLM call. The conversation prefix up to\n // the cut-off is preserved in opts.messages, and Anthropic's prompt\n // cache makes the resume call much faster.\n //\n // Emit 'clear' so any partial streamed text is discarded on the client\n // before the model resumes. Without this the model restarts its sentence\n // from scratch and the fold produces duplicated text in one message\n // (the partial text was already sent to the client but never entered\n // the in-memory messages array, so the next attempt re-emits it).\n if (!upstreamSignal.aborted && isResumableEngineError(err)) {\n opts.send({ type: \"clear\" });\n appendAgentLoopContinuation(\n opts.messages,\n continuationReasonForResumableError(err),\n );\n await appendToolCallJournalNote(opts.messages, opts.threadId);\n continue;\n }\n throw err;\n } finally {\n clearTimeout(timer);\n upstreamSignal.removeEventListener(\"abort\", abortFromUpstream);\n }\n }\n\n return usage;\n}\n"]}
@@ -24,14 +24,12 @@
24
24
  * the result. We surface those as "interrupted / unknown outcome" so the model
25
25
  * decides rather than blindly re-running.
26
26
  *
27
- * TODO(charlie-merge): optional hard-block refuse re-execution of
28
- * journaled-complete tool calls at the tool layer in
29
- * production-agent.ts/runToolCall (CHARLIE owns that file). The prompt-level
30
- * journal here is the shippable first cut; the tool-layer enforcement is the
31
- * stronger guarantee and must wait until CHARLIE's loop edits land. When wiring
32
- * it, key off `classifyToolCallJournal(...).completed` and short-circuit a
33
- * re-dispatched write tool whose (tool name + input + order) matches a
34
- * completed entry, returning the journaled result instead of executing.
27
+ * Two layers of protection are built on this: (1) a prompt-level note on resume
28
+ * (see `run-loop-with-resume.ts`) telling the model what already completed, and
29
+ * (2) tool-layer enforcement in `production-agent.ts`/`runToolCall`, which uses
30
+ * `findCompletedJournalEntry(...)` to refuse re-executing a journaled-complete
31
+ * write tool returning the journaled result instead of running the side
32
+ * effect again. Layer 2 is the stronger guarantee.
35
33
  */
36
34
  import type { AgentChatEvent } from "./types.js";
37
35
  /** A single recorded tool-call ledger entry, classified by outcome. */
@@ -1 +1 @@
1
- {"version":3,"file":"tool-call-journal.d.ts","sourceRoot":"","sources":["../../src/agent/tool-call-journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,uEAAuE;AACvE,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sDAAsD;AACtD,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC;;;OAGG;IACH,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC;AA6BD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC,eAAe,CAyDjB;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,oBAAoB,GAAG,SAAS,CAUlC;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,GACvB,MAAM,GAAG,IAAI,CAiCf"}
1
+ {"version":3,"file":"tool-call-journal.d.ts","sourceRoot":"","sources":["../../src/agent/tool-call-journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,uEAAuE;AACvE,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sDAAsD;AACtD,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC;;;OAGG;IACH,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC;AA6BD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC,eAAe,CAyDjB;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,oBAAoB,GAAG,SAAS,CAUlC;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,GACvB,MAAM,GAAG,IAAI,CAiCf"}
@@ -24,14 +24,12 @@
24
24
  * the result. We surface those as "interrupted / unknown outcome" so the model
25
25
  * decides rather than blindly re-running.
26
26
  *
27
- * TODO(charlie-merge): optional hard-block refuse re-execution of
28
- * journaled-complete tool calls at the tool layer in
29
- * production-agent.ts/runToolCall (CHARLIE owns that file). The prompt-level
30
- * journal here is the shippable first cut; the tool-layer enforcement is the
31
- * stronger guarantee and must wait until CHARLIE's loop edits land. When wiring
32
- * it, key off `classifyToolCallJournal(...).completed` and short-circuit a
33
- * re-dispatched write tool whose (tool name + input + order) matches a
34
- * completed entry, returning the journaled result instead of executing.
27
+ * Two layers of protection are built on this: (1) a prompt-level note on resume
28
+ * (see `run-loop-with-resume.ts`) telling the model what already completed, and
29
+ * (2) tool-layer enforcement in `production-agent.ts`/`runToolCall`, which uses
30
+ * `findCompletedJournalEntry(...)` to refuse re-executing a journaled-complete
31
+ * write tool returning the journaled result instead of running the side
32
+ * effect again. Layer 2 is the stronger guarantee.
35
33
  */
36
34
  /** Max length of an input signature included in a journal key (debug-readable). */
37
35
  const INPUT_SIGNATURE_MAX_CHARS = 120;
@@ -1 +1 @@
1
- {"version":3,"file":"tool-call-journal.js","sourceRoot":"","sources":["../../src/agent/tool-call-journal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-turn tool-call journal — derived (not separately recorded) from the\n * existing run-event ledger. Borrowed from Flue's durable-execution journal:\n * when an agent run resumes after an interruption (gateway/transport drop, cold\n * start, soft-timeout auto-continue), we classify the tool calls already in the\n * ledger so the resumed model does NOT re-execute side effects it already\n * completed (re-sending an email, re-creating a ticket) and so it is explicitly\n * told about any tool call that started but whose outcome was never recorded.\n *\n * IMPORTANT — this is a pure read-over-the-ledger view. There is no new\n * recording hook anywhere in the hot path. The classification reuses the exact\n * positional `tool_start` → `tool_done` matching that `thread-data-builder.ts`\n * already relies on to rebuild durable turns:\n *\n * - `tool_start` events carry `{ tool, input }` (no tool-call id at this\n * layer; the ledger event stream omits it).\n * - `tool_done` events carry `{ tool, result }`.\n * - A `tool_done` is matched to the OLDEST still-open `tool_start` for the\n * same tool name (FIFO per tool), mirroring how the dispatch loop emits a\n * start immediately before its matching done.\n *\n * A `tool_start` with no matching `tool_done` is the dangerous case: the call\n * began, its side effect may or may not have landed, and the interruption ate\n * the result. We surface those as \"interrupted / unknown outcome\" so the model\n * decides rather than blindly re-running.\n *\n * TODO(charlie-merge): optional hard-block — refuse re-execution of\n * journaled-complete tool calls at the tool layer in\n * production-agent.ts/runToolCall (CHARLIE owns that file). The prompt-level\n * journal here is the shippable first cut; the tool-layer enforcement is the\n * stronger guarantee and must wait until CHARLIE's loop edits land. When wiring\n * it, key off `classifyToolCallJournal(...).completed` and short-circuit a\n * re-dispatched write tool whose (tool name + input + order) matches a\n * completed entry, returning the journaled result instead of executing.\n */\n\nimport type { AgentChatEvent } from \"./types.js\";\n\n/** A single recorded tool-call ledger entry, classified by outcome. */\nexport interface ToolCallJournalEntry {\n /**\n * Stable-ish identity for this tool call within the turn. The ledger event\n * stream has no tool-call id, so we key by tool name + a short input\n * signature + positional order, exactly enough to disambiguate repeats of the\n * same tool within one turn for human/model-readable reporting.\n */\n key: string;\n /** Tool / action name (from the `tool_start` event). */\n tool: string;\n /** Tool input captured at `tool_start`, if any. */\n input?: Record<string, string>;\n /** 0-based position of the `tool_start` among all tool calls in the turn. */\n order: number;\n /** Result text from the matched `tool_done`, when the call completed. */\n result?: string;\n}\n\n/** Result of classifying one turn's ledger events. */\nexport interface ToolCallJournal {\n /** Tool calls with a matching `tool_done` — already ran, do NOT re-run. */\n completed: ToolCallJournalEntry[];\n /**\n * Tool calls with a `tool_start` but no matching `tool_done` — interrupted,\n * outcome unknown. The model must decide whether re-running is safe.\n */\n interrupted: ToolCallJournalEntry[];\n}\n\n/** Max length of an input signature included in a journal key (debug-readable). */\nconst INPUT_SIGNATURE_MAX_CHARS = 120;\n\n/** Max length of a per-tool-call result summary surfaced in the resume note. */\nconst RESULT_SUMMARY_MAX_CHARS = 400;\n\nfunction inputSignature(input: Record<string, string> | undefined): string {\n if (!input) return \"\";\n let sig: string;\n try {\n // Stable key order so the same logical input produces the same signature\n // regardless of how the engine serialized the object.\n const sorted = Object.keys(input)\n .sort()\n .reduce<Record<string, string>>((acc, k) => {\n acc[k] = input[k];\n return acc;\n }, {});\n sig = JSON.stringify(sorted);\n } catch {\n sig = String(input);\n }\n return sig.length > INPUT_SIGNATURE_MAX_CHARS\n ? sig.slice(0, INPUT_SIGNATURE_MAX_CHARS)\n : sig;\n}\n\n/**\n * Classify a single turn's recorded events into completed vs interrupted tool\n * calls. Pure and side-effect free — given the same events it always returns\n * the same journal. A `clear` event resets the per-turn tally exactly as the\n * thread rebuild does, so partial streamed output that was discarded on resume\n * doesn't leave phantom open tool calls.\n */\nexport function classifyToolCallJournal(\n events: readonly AgentChatEvent[],\n): ToolCallJournal {\n // Open tool_start entries awaiting a matching tool_done, in FIFO order. We\n // index by tool name so a tool_done matches the OLDEST open start for that\n // same tool — the dispatch loop always emits start-then-done per call, so the\n // first unmatched start of a given name is the one this done belongs to.\n const openByTool = new Map<string, ToolCallJournalEntry[]>();\n const completed: ToolCallJournalEntry[] = [];\n let order = 0;\n\n for (const event of events) {\n if (event.type === \"clear\") {\n // Discarded partial output: drop any not-yet-completed starts so they\n // aren't reported as interrupted. Already-completed entries stay.\n openByTool.clear();\n continue;\n }\n\n if (event.type === \"tool_start\") {\n const tool = event.tool ?? \"unknown\";\n const input = (event.input ?? undefined) as\n | Record<string, string>\n | undefined;\n const entry: ToolCallJournalEntry = {\n key: `${tool}#${order}:${inputSignature(input)}`,\n tool,\n ...(input ? { input } : {}),\n order,\n };\n order += 1;\n const queue = openByTool.get(tool);\n if (queue) queue.push(entry);\n else openByTool.set(tool, [entry]);\n continue;\n }\n\n if (event.type === \"tool_done\") {\n const tool = event.tool ?? \"unknown\";\n const queue = openByTool.get(tool);\n const entry = queue?.shift();\n if (entry) {\n entry.result = event.result ?? \"\";\n completed.push(entry);\n }\n // A tool_done with no open start is ignored — it can't be re-associated\n // and re-reporting a duplicate done would be noise.\n continue;\n }\n }\n\n // Anything still open never received a tool_done → interrupted / unknown.\n const interrupted: ToolCallJournalEntry[] = [];\n for (const queue of openByTool.values()) {\n for (const entry of queue) interrupted.push(entry);\n }\n interrupted.sort((a, b) => a.order - b.order);\n\n return { completed, interrupted };\n}\n\n/** True when the journal has nothing worth telling a resuming model about. */\nexport function isJournalEmpty(journal: ToolCallJournal): boolean {\n return journal.completed.length === 0 && journal.interrupted.length === 0;\n}\n\n/**\n * Find a COMPLETED journal entry that matches a tool call about to be\n * dispatched, by tool name + input signature (position-independent — a resumed\n * call may sit at a different order than the original). Used by the tool-layer\n * hard-block in production-agent.ts/runToolCall to skip re-executing a side\n * effect that already completed in a prior interrupted chunk: when this returns\n * an entry, the loop returns `entry.result` instead of running the action.\n *\n * Returns the FIRST unmatched completed entry for that (name + input); the\n * caller is expected to claim it (mark it consumed) so two identical fresh\n * calls in the same turn don't both short-circuit on one journaled completion.\n * Returns undefined when there is no completed entry for this exact call —\n * including every fresh call, which must execute normally.\n */\nexport function findCompletedJournalEntry(\n journal: ToolCallJournal,\n toolName: string,\n input: unknown,\n consumedKeys?: Set<string>,\n): ToolCallJournalEntry | undefined {\n const wantSig = inputSignature(normalizeInputForSignature(input));\n for (const entry of journal.completed) {\n if (entry.tool !== toolName) continue;\n if (inputSignature(entry.input) !== wantSig) continue;\n if (consumedKeys?.has(entry.key)) continue;\n consumedKeys?.add(entry.key);\n return entry;\n }\n return undefined;\n}\n\n/**\n * Coerce an arbitrary tool input into the `Record<string, string>` shape the\n * journal recorded at `tool_start` so signatures compare apples-to-apples. The\n * ledger stores `tool_start.input` as a string map; the live call's `input` is\n * the parsed object — both pass through `inputSignature`, which sorts keys and\n * JSON-stringifies, so a plain object compares correctly.\n */\nfunction normalizeInputForSignature(\n input: unknown,\n): Record<string, string> | undefined {\n if (input == null || typeof input !== \"object\") return undefined;\n return input as Record<string, string>;\n}\n\nfunction summarizeResult(result: string | undefined): string {\n if (!result) return \"(no result recorded)\";\n const oneLine = result.replace(/\\s+/g, \" \").trim();\n if (oneLine.length === 0) return \"(empty result)\";\n return oneLine.length > RESULT_SUMMARY_MAX_CHARS\n ? oneLine.slice(0, RESULT_SUMMARY_MAX_CHARS) + \"…\"\n : oneLine;\n}\n\nfunction describeInput(input: Record<string, string> | undefined): string {\n if (!input) return \"\";\n const sig = inputSignature(input);\n return sig && sig !== \"{}\" ? ` input: ${sig}` : \"\";\n}\n\n/**\n * Build the structured resume note injected on auto-continue. Returns `null`\n * when there are no completed or interrupted tool calls to report, so the\n * caller can preserve the exact pre-existing \"continue from where you left off\"\n * behavior on normal resumes (no regression for turns with no tool activity).\n *\n * The note is intentionally a flat, model-readable block: a list of already-run\n * tool calls (with short results) the model must NOT re-run, and a separate list\n * of interrupted calls whose outcome is unknown for the model to decide on.\n */\nexport function buildResumeJournalNote(\n journal: ToolCallJournal,\n): string | null {\n if (isJournalEmpty(journal)) return null;\n\n const lines: string[] = [];\n lines.push(\n \"Tool-call journal from the interrupted attempt (derived from the durable run ledger):\",\n );\n\n if (journal.completed.length > 0) {\n lines.push(\"\");\n lines.push(\n \"Already completed (do NOT re-run these — their side effects already happened; reuse the results below):\",\n );\n for (const entry of journal.completed) {\n lines.push(\n `- ${entry.tool}${describeInput(entry.input)} → ${summarizeResult(entry.result)}`,\n );\n }\n }\n\n if (journal.interrupted.length > 0) {\n lines.push(\"\");\n lines.push(\n \"Interrupted / unknown outcome (these started but no result was recorded before the cut-off — do not assume they succeeded OR failed; if re-running could duplicate a side effect, verify state first):\",\n );\n for (const entry of journal.interrupted) {\n lines.push(\n `- ${entry.tool}${describeInput(entry.input)} → (no result recorded)`,\n );\n }\n }\n\n return lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"tool-call-journal.js","sourceRoot":"","sources":["../../src/agent/tool-call-journal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-turn tool-call journal — derived (not separately recorded) from the\n * existing run-event ledger. Borrowed from Flue's durable-execution journal:\n * when an agent run resumes after an interruption (gateway/transport drop, cold\n * start, soft-timeout auto-continue), we classify the tool calls already in the\n * ledger so the resumed model does NOT re-execute side effects it already\n * completed (re-sending an email, re-creating a ticket) and so it is explicitly\n * told about any tool call that started but whose outcome was never recorded.\n *\n * IMPORTANT — this is a pure read-over-the-ledger view. There is no new\n * recording hook anywhere in the hot path. The classification reuses the exact\n * positional `tool_start` → `tool_done` matching that `thread-data-builder.ts`\n * already relies on to rebuild durable turns:\n *\n * - `tool_start` events carry `{ tool, input }` (no tool-call id at this\n * layer; the ledger event stream omits it).\n * - `tool_done` events carry `{ tool, result }`.\n * - A `tool_done` is matched to the OLDEST still-open `tool_start` for the\n * same tool name (FIFO per tool), mirroring how the dispatch loop emits a\n * start immediately before its matching done.\n *\n * A `tool_start` with no matching `tool_done` is the dangerous case: the call\n * began, its side effect may or may not have landed, and the interruption ate\n * the result. We surface those as \"interrupted / unknown outcome\" so the model\n * decides rather than blindly re-running.\n *\n * Two layers of protection are built on this: (1) a prompt-level note on resume\n * (see `run-loop-with-resume.ts`) telling the model what already completed, and\n * (2) tool-layer enforcement in `production-agent.ts`/`runToolCall`, which uses\n * `findCompletedJournalEntry(...)` to refuse re-executing a journaled-complete\n * write tool — returning the journaled result instead of running the side\n * effect again. Layer 2 is the stronger guarantee.\n */\n\nimport type { AgentChatEvent } from \"./types.js\";\n\n/** A single recorded tool-call ledger entry, classified by outcome. */\nexport interface ToolCallJournalEntry {\n /**\n * Stable-ish identity for this tool call within the turn. The ledger event\n * stream has no tool-call id, so we key by tool name + a short input\n * signature + positional order, exactly enough to disambiguate repeats of the\n * same tool within one turn for human/model-readable reporting.\n */\n key: string;\n /** Tool / action name (from the `tool_start` event). */\n tool: string;\n /** Tool input captured at `tool_start`, if any. */\n input?: Record<string, string>;\n /** 0-based position of the `tool_start` among all tool calls in the turn. */\n order: number;\n /** Result text from the matched `tool_done`, when the call completed. */\n result?: string;\n}\n\n/** Result of classifying one turn's ledger events. */\nexport interface ToolCallJournal {\n /** Tool calls with a matching `tool_done` — already ran, do NOT re-run. */\n completed: ToolCallJournalEntry[];\n /**\n * Tool calls with a `tool_start` but no matching `tool_done` — interrupted,\n * outcome unknown. The model must decide whether re-running is safe.\n */\n interrupted: ToolCallJournalEntry[];\n}\n\n/** Max length of an input signature included in a journal key (debug-readable). */\nconst INPUT_SIGNATURE_MAX_CHARS = 120;\n\n/** Max length of a per-tool-call result summary surfaced in the resume note. */\nconst RESULT_SUMMARY_MAX_CHARS = 400;\n\nfunction inputSignature(input: Record<string, string> | undefined): string {\n if (!input) return \"\";\n let sig: string;\n try {\n // Stable key order so the same logical input produces the same signature\n // regardless of how the engine serialized the object.\n const sorted = Object.keys(input)\n .sort()\n .reduce<Record<string, string>>((acc, k) => {\n acc[k] = input[k];\n return acc;\n }, {});\n sig = JSON.stringify(sorted);\n } catch {\n sig = String(input);\n }\n return sig.length > INPUT_SIGNATURE_MAX_CHARS\n ? sig.slice(0, INPUT_SIGNATURE_MAX_CHARS)\n : sig;\n}\n\n/**\n * Classify a single turn's recorded events into completed vs interrupted tool\n * calls. Pure and side-effect free — given the same events it always returns\n * the same journal. A `clear` event resets the per-turn tally exactly as the\n * thread rebuild does, so partial streamed output that was discarded on resume\n * doesn't leave phantom open tool calls.\n */\nexport function classifyToolCallJournal(\n events: readonly AgentChatEvent[],\n): ToolCallJournal {\n // Open tool_start entries awaiting a matching tool_done, in FIFO order. We\n // index by tool name so a tool_done matches the OLDEST open start for that\n // same tool — the dispatch loop always emits start-then-done per call, so the\n // first unmatched start of a given name is the one this done belongs to.\n const openByTool = new Map<string, ToolCallJournalEntry[]>();\n const completed: ToolCallJournalEntry[] = [];\n let order = 0;\n\n for (const event of events) {\n if (event.type === \"clear\") {\n // Discarded partial output: drop any not-yet-completed starts so they\n // aren't reported as interrupted. Already-completed entries stay.\n openByTool.clear();\n continue;\n }\n\n if (event.type === \"tool_start\") {\n const tool = event.tool ?? \"unknown\";\n const input = (event.input ?? undefined) as\n | Record<string, string>\n | undefined;\n const entry: ToolCallJournalEntry = {\n key: `${tool}#${order}:${inputSignature(input)}`,\n tool,\n ...(input ? { input } : {}),\n order,\n };\n order += 1;\n const queue = openByTool.get(tool);\n if (queue) queue.push(entry);\n else openByTool.set(tool, [entry]);\n continue;\n }\n\n if (event.type === \"tool_done\") {\n const tool = event.tool ?? \"unknown\";\n const queue = openByTool.get(tool);\n const entry = queue?.shift();\n if (entry) {\n entry.result = event.result ?? \"\";\n completed.push(entry);\n }\n // A tool_done with no open start is ignored — it can't be re-associated\n // and re-reporting a duplicate done would be noise.\n continue;\n }\n }\n\n // Anything still open never received a tool_done → interrupted / unknown.\n const interrupted: ToolCallJournalEntry[] = [];\n for (const queue of openByTool.values()) {\n for (const entry of queue) interrupted.push(entry);\n }\n interrupted.sort((a, b) => a.order - b.order);\n\n return { completed, interrupted };\n}\n\n/** True when the journal has nothing worth telling a resuming model about. */\nexport function isJournalEmpty(journal: ToolCallJournal): boolean {\n return journal.completed.length === 0 && journal.interrupted.length === 0;\n}\n\n/**\n * Find a COMPLETED journal entry that matches a tool call about to be\n * dispatched, by tool name + input signature (position-independent — a resumed\n * call may sit at a different order than the original). Used by the tool-layer\n * hard-block in production-agent.ts/runToolCall to skip re-executing a side\n * effect that already completed in a prior interrupted chunk: when this returns\n * an entry, the loop returns `entry.result` instead of running the action.\n *\n * Returns the FIRST unmatched completed entry for that (name + input); the\n * caller is expected to claim it (mark it consumed) so two identical fresh\n * calls in the same turn don't both short-circuit on one journaled completion.\n * Returns undefined when there is no completed entry for this exact call —\n * including every fresh call, which must execute normally.\n */\nexport function findCompletedJournalEntry(\n journal: ToolCallJournal,\n toolName: string,\n input: unknown,\n consumedKeys?: Set<string>,\n): ToolCallJournalEntry | undefined {\n const wantSig = inputSignature(normalizeInputForSignature(input));\n for (const entry of journal.completed) {\n if (entry.tool !== toolName) continue;\n if (inputSignature(entry.input) !== wantSig) continue;\n if (consumedKeys?.has(entry.key)) continue;\n consumedKeys?.add(entry.key);\n return entry;\n }\n return undefined;\n}\n\n/**\n * Coerce an arbitrary tool input into the `Record<string, string>` shape the\n * journal recorded at `tool_start` so signatures compare apples-to-apples. The\n * ledger stores `tool_start.input` as a string map; the live call's `input` is\n * the parsed object — both pass through `inputSignature`, which sorts keys and\n * JSON-stringifies, so a plain object compares correctly.\n */\nfunction normalizeInputForSignature(\n input: unknown,\n): Record<string, string> | undefined {\n if (input == null || typeof input !== \"object\") return undefined;\n return input as Record<string, string>;\n}\n\nfunction summarizeResult(result: string | undefined): string {\n if (!result) return \"(no result recorded)\";\n const oneLine = result.replace(/\\s+/g, \" \").trim();\n if (oneLine.length === 0) return \"(empty result)\";\n return oneLine.length > RESULT_SUMMARY_MAX_CHARS\n ? oneLine.slice(0, RESULT_SUMMARY_MAX_CHARS) + \"…\"\n : oneLine;\n}\n\nfunction describeInput(input: Record<string, string> | undefined): string {\n if (!input) return \"\";\n const sig = inputSignature(input);\n return sig && sig !== \"{}\" ? ` input: ${sig}` : \"\";\n}\n\n/**\n * Build the structured resume note injected on auto-continue. Returns `null`\n * when there are no completed or interrupted tool calls to report, so the\n * caller can preserve the exact pre-existing \"continue from where you left off\"\n * behavior on normal resumes (no regression for turns with no tool activity).\n *\n * The note is intentionally a flat, model-readable block: a list of already-run\n * tool calls (with short results) the model must NOT re-run, and a separate list\n * of interrupted calls whose outcome is unknown for the model to decide on.\n */\nexport function buildResumeJournalNote(\n journal: ToolCallJournal,\n): string | null {\n if (isJournalEmpty(journal)) return null;\n\n const lines: string[] = [];\n lines.push(\n \"Tool-call journal from the interrupted attempt (derived from the durable run ledger):\",\n );\n\n if (journal.completed.length > 0) {\n lines.push(\"\");\n lines.push(\n \"Already completed (do NOT re-run these — their side effects already happened; reuse the results below):\",\n );\n for (const entry of journal.completed) {\n lines.push(\n `- ${entry.tool}${describeInput(entry.input)} → ${summarizeResult(entry.result)}`,\n );\n }\n }\n\n if (journal.interrupted.length > 0) {\n lines.push(\"\");\n lines.push(\n \"Interrupted / unknown outcome (these started but no result was recorded before the cut-off — do not assume they succeeded OR failed; if re-running could duplicate a side effect, verify state first):\",\n );\n for (const entry of journal.interrupted) {\n lines.push(\n `- ${entry.tool}${describeInput(entry.input)} → (no result recorded)`,\n );\n }\n }\n\n return lines.join(\"\\n\");\n}\n"]}
@@ -215,6 +215,17 @@ export type AgentChatEvent = {
215
215
  } | {
216
216
  type: "loop_limit";
217
217
  maxIterations?: number;
218
+ } | {
219
+ /**
220
+ * An in-loop `Processor` aborted the run via `abort()` (which throws a
221
+ * `TripWire`). The loop catches it, emits this event, stops cleanly, and
222
+ * surfaces the reason as a final assistant message. Structural hook for
223
+ * real-time guardrails and a proof-of-done / coverage gate.
224
+ */
225
+ type: "tripwire";
226
+ reason: string;
227
+ /** Name of the processor that aborted, when it declared one. */
228
+ processor?: string;
218
229
  } | {
219
230
  type: "auto_continue";
220
231
  reason: "run_timeout" | "loop_limit" | "no_progress" | "stream_ended" | "gateway_timeout" | "network_interrupted";
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReasoningEffort } from \"../shared/reasoning-effort.js\";\nimport type { AgentMcpAppPayload } from \"../mcp-client/app-result.js\";\n\nexport interface ActionTool {\n description: string;\n parameters?: {\n type: \"object\";\n properties: Record<\n string,\n {\n type: string;\n description?: string;\n enum?: string[];\n }\n >;\n required?: string[];\n };\n}\n\n/** @deprecated Use `ActionTool` instead */\nexport type ScriptTool = ActionTool;\n\nexport interface AgentMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\nexport type AgentChatStructuredContentPart =\n | { type: \"text\"; text: string }\n | {\n type: \"tool-call\";\n id?: string;\n toolCallId?: string;\n name?: string;\n toolName?: string;\n input?: unknown;\n args?: unknown;\n }\n | {\n type: \"tool-result\";\n toolCallId: string;\n /** Persisted for replay; omitted in older rows is backfilled server-side. */\n toolName?: string;\n toolInput?: string;\n content: string;\n isError?: boolean;\n };\n\nexport interface AgentChatStructuredMessage {\n role: \"user\" | \"assistant\";\n content: AgentChatStructuredContentPart[];\n}\n\nexport interface AgentChatReference {\n type: \"file\" | \"skill\" | \"mention\" | \"agent\" | \"custom-agent\";\n path: string;\n name: string;\n source: string;\n refType?: string;\n refId?: string;\n}\n\nexport interface MentionProviderItem {\n id: string;\n label: string;\n description?: string;\n icon?: string;\n refType: string;\n refId?: string;\n refPath?: string;\n}\n\nexport interface MentionProvider {\n label: string;\n icon?: string;\n search: (\n query: string,\n /** The H3 event for the current request — use to make internal API calls */\n event?: any,\n ) => MentionProviderItem[] | Promise<MentionProviderItem[]>;\n}\n\nexport interface AgentChatAttachment {\n type: string;\n name: string;\n data?: string;\n contentType?: string;\n text?: string;\n}\n\nexport interface AgentChatScope {\n type: string;\n id: string;\n label?: string;\n}\n\nexport interface AgentChatRequest {\n message: string;\n /**\n * User-visible text to persist in chat history. `message` may be normalized\n * for the model (for example mention markup or internal continuation text).\n */\n displayMessage?: string;\n history?: AgentMessage[];\n /**\n * Provider-neutral transcript used for run recovery. Unlike `history`,\n * this preserves assistant tool calls and matching tool results so\n * continuation turns do not re-run completed read-only tools.\n */\n structuredHistory?: AgentChatStructuredMessage[];\n references?: AgentChatReference[];\n threadId?: string;\n attachments?: AgentChatAttachment[];\n /** Internal retry/continuation requests should not create visible user turns. */\n internalContinuation?: boolean;\n /**\n * Stable identity for the logical assistant turn this request belongs to.\n * The client sends the SAME turnId for the initial POST and every\n * auto-continuation re-POST of one turn, so the server can fold each\n * continuation run's output onto a single durable assistant message instead\n * of dropping the earlier chunks. Defaults to the run id when absent.\n */\n turnId?: string;\n /** Execution mode for this turn. Plan mode is read-only and proposes before acting. */\n mode?: \"act\" | \"plan\";\n /** Per-request model override (ephemeral, from the composer model picker). */\n model?: string;\n /** Per-request engine override (sent alongside model for cross-provider switches). */\n engine?: string;\n /** Per-request reasoning effort override (ephemeral, from the composer picker). */\n effort?: ReasoningEffort;\n /** Usage-tracking label for this call (e.g. \"chat\", \"summarize\"). Default: \"chat\". */\n usageLabel?: string;\n /** Stable browser tab id so screen/url context and navigation commands are tab-scoped. */\n browserTabId?: string;\n /** Resource scope for this chat thread, e.g. the deck currently bound to the tab. */\n scope?: AgentChatScope | null;\n /** When true, expose this chat turn as a user-visible run in RunsTray. */\n trackInRunsTray?: boolean;\n /**\n * Approval grants for human-in-the-loop actions. Each entry is a stable\n * approval key (see the `approval_required` event's `approvalKey`). When the\n * agent calls an action declared `needsApproval`, the loop pauses and emits\n * `approval_required`; the client re-issues the turn (typically an empty\n * continuation) with the approved call's key here so the gate lets it run.\n * Keys not present here keep the action paused. The model never sees or sets\n * this — it is supplied by the human's approve affordance.\n */\n approvedToolCalls?: string[];\n}\n\nexport type AgentChatEvent =\n | { type: \"text\"; text: string }\n | { type: \"thinking\"; text: string }\n | { type: \"activity\"; label: string; tool?: string }\n | { type: \"tool_start\"; tool: string; input: Record<string, string> }\n | {\n type: \"tool_done\";\n tool: string;\n result: string;\n mcpApp?: AgentMcpAppPayload;\n }\n | {\n /**\n * The agent tried to call an action declared `needsApproval` and the loop\n * paused instead of executing it. The client should surface an\n * approve/deny affordance; on approve, re-issue the turn with\n * `approvedToolCalls: [approvalKey]` so the gate lets this call run.\n */\n type: \"approval_required\";\n tool: string;\n input: Record<string, string>;\n /** Stable key the client echoes back in `approvedToolCalls` to approve. */\n approvalKey: string;\n /** The model-side tool-call id for this paused call, when available. */\n toolCallId?: string;\n }\n | {\n type: \"agent_call\";\n agent: string;\n status: \"start\" | \"done\" | \"error\";\n }\n | { type: \"agent_call_text\"; agent: string; text: string }\n | {\n type: \"agent_task\";\n taskId: string;\n threadId: string;\n description: string;\n status: \"running\" | \"completed\" | \"errored\";\n }\n | {\n type: \"agent_task_update\";\n taskId: string;\n preview: string;\n currentStep?: string;\n }\n | {\n type: \"agent_task_complete\";\n taskId: string;\n summary: string;\n }\n | { type: \"done\" }\n | {\n type: \"error\";\n error: string;\n /**\n * Optional machine-readable error code. Builder gateway uses codes\n * like \"credits-limit-monthly\" / \"unauthorized\" / \"gateway_not_enabled\"\n * so the chat UI can render a structured CTA (e.g. upgrade button).\n */\n errorCode?: string;\n /** Optional link paired with errorCode — e.g. Builder billing page. */\n upgradeUrl?: string;\n /** Optional details for expandable UI/debugging. */\n details?: string;\n /** True when the user can reasonably continue/retry from partial work. */\n recoverable?: boolean;\n }\n /**\n * Legacy SSE terminal event. New streams emit\n * `{ type: \"error\", errorCode: \"missing_credentials\" }` instead.\n */\n | { type: \"missing_api_key\" }\n | { type: \"loop_limit\"; maxIterations?: number }\n | {\n type: \"auto_continue\";\n reason:\n | \"run_timeout\"\n | \"loop_limit\"\n | \"no_progress\"\n | \"stream_ended\"\n | \"gateway_timeout\"\n | \"network_interrupted\";\n maxIterations?: number;\n }\n | { type: \"clear\" };\n\nexport interface RunEvent {\n seq: number;\n event: AgentChatEvent;\n}\n\nexport type RunStatus = \"running\" | \"completed\" | \"errored\" | \"aborted\";\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReasoningEffort } from \"../shared/reasoning-effort.js\";\nimport type { AgentMcpAppPayload } from \"../mcp-client/app-result.js\";\n\nexport interface ActionTool {\n description: string;\n parameters?: {\n type: \"object\";\n properties: Record<\n string,\n {\n type: string;\n description?: string;\n enum?: string[];\n }\n >;\n required?: string[];\n };\n}\n\n/** @deprecated Use `ActionTool` instead */\nexport type ScriptTool = ActionTool;\n\nexport interface AgentMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\nexport type AgentChatStructuredContentPart =\n | { type: \"text\"; text: string }\n | {\n type: \"tool-call\";\n id?: string;\n toolCallId?: string;\n name?: string;\n toolName?: string;\n input?: unknown;\n args?: unknown;\n }\n | {\n type: \"tool-result\";\n toolCallId: string;\n /** Persisted for replay; omitted in older rows is backfilled server-side. */\n toolName?: string;\n toolInput?: string;\n content: string;\n isError?: boolean;\n };\n\nexport interface AgentChatStructuredMessage {\n role: \"user\" | \"assistant\";\n content: AgentChatStructuredContentPart[];\n}\n\nexport interface AgentChatReference {\n type: \"file\" | \"skill\" | \"mention\" | \"agent\" | \"custom-agent\";\n path: string;\n name: string;\n source: string;\n refType?: string;\n refId?: string;\n}\n\nexport interface MentionProviderItem {\n id: string;\n label: string;\n description?: string;\n icon?: string;\n refType: string;\n refId?: string;\n refPath?: string;\n}\n\nexport interface MentionProvider {\n label: string;\n icon?: string;\n search: (\n query: string,\n /** The H3 event for the current request — use to make internal API calls */\n event?: any,\n ) => MentionProviderItem[] | Promise<MentionProviderItem[]>;\n}\n\nexport interface AgentChatAttachment {\n type: string;\n name: string;\n data?: string;\n contentType?: string;\n text?: string;\n}\n\nexport interface AgentChatScope {\n type: string;\n id: string;\n label?: string;\n}\n\nexport interface AgentChatRequest {\n message: string;\n /**\n * User-visible text to persist in chat history. `message` may be normalized\n * for the model (for example mention markup or internal continuation text).\n */\n displayMessage?: string;\n history?: AgentMessage[];\n /**\n * Provider-neutral transcript used for run recovery. Unlike `history`,\n * this preserves assistant tool calls and matching tool results so\n * continuation turns do not re-run completed read-only tools.\n */\n structuredHistory?: AgentChatStructuredMessage[];\n references?: AgentChatReference[];\n threadId?: string;\n attachments?: AgentChatAttachment[];\n /** Internal retry/continuation requests should not create visible user turns. */\n internalContinuation?: boolean;\n /**\n * Stable identity for the logical assistant turn this request belongs to.\n * The client sends the SAME turnId for the initial POST and every\n * auto-continuation re-POST of one turn, so the server can fold each\n * continuation run's output onto a single durable assistant message instead\n * of dropping the earlier chunks. Defaults to the run id when absent.\n */\n turnId?: string;\n /** Execution mode for this turn. Plan mode is read-only and proposes before acting. */\n mode?: \"act\" | \"plan\";\n /** Per-request model override (ephemeral, from the composer model picker). */\n model?: string;\n /** Per-request engine override (sent alongside model for cross-provider switches). */\n engine?: string;\n /** Per-request reasoning effort override (ephemeral, from the composer picker). */\n effort?: ReasoningEffort;\n /** Usage-tracking label for this call (e.g. \"chat\", \"summarize\"). Default: \"chat\". */\n usageLabel?: string;\n /** Stable browser tab id so screen/url context and navigation commands are tab-scoped. */\n browserTabId?: string;\n /** Resource scope for this chat thread, e.g. the deck currently bound to the tab. */\n scope?: AgentChatScope | null;\n /** When true, expose this chat turn as a user-visible run in RunsTray. */\n trackInRunsTray?: boolean;\n /**\n * Approval grants for human-in-the-loop actions. Each entry is a stable\n * approval key (see the `approval_required` event's `approvalKey`). When the\n * agent calls an action declared `needsApproval`, the loop pauses and emits\n * `approval_required`; the client re-issues the turn (typically an empty\n * continuation) with the approved call's key here so the gate lets it run.\n * Keys not present here keep the action paused. The model never sees or sets\n * this — it is supplied by the human's approve affordance.\n */\n approvedToolCalls?: string[];\n}\n\nexport type AgentChatEvent =\n | { type: \"text\"; text: string }\n | { type: \"thinking\"; text: string }\n | { type: \"activity\"; label: string; tool?: string }\n | { type: \"tool_start\"; tool: string; input: Record<string, string> }\n | {\n type: \"tool_done\";\n tool: string;\n result: string;\n mcpApp?: AgentMcpAppPayload;\n }\n | {\n /**\n * The agent tried to call an action declared `needsApproval` and the loop\n * paused instead of executing it. The client should surface an\n * approve/deny affordance; on approve, re-issue the turn with\n * `approvedToolCalls: [approvalKey]` so the gate lets this call run.\n */\n type: \"approval_required\";\n tool: string;\n input: Record<string, string>;\n /** Stable key the client echoes back in `approvedToolCalls` to approve. */\n approvalKey: string;\n /** The model-side tool-call id for this paused call, when available. */\n toolCallId?: string;\n }\n | {\n type: \"agent_call\";\n agent: string;\n status: \"start\" | \"done\" | \"error\";\n }\n | { type: \"agent_call_text\"; agent: string; text: string }\n | {\n type: \"agent_task\";\n taskId: string;\n threadId: string;\n description: string;\n status: \"running\" | \"completed\" | \"errored\";\n }\n | {\n type: \"agent_task_update\";\n taskId: string;\n preview: string;\n currentStep?: string;\n }\n | {\n type: \"agent_task_complete\";\n taskId: string;\n summary: string;\n }\n | { type: \"done\" }\n | {\n type: \"error\";\n error: string;\n /**\n * Optional machine-readable error code. Builder gateway uses codes\n * like \"credits-limit-monthly\" / \"unauthorized\" / \"gateway_not_enabled\"\n * so the chat UI can render a structured CTA (e.g. upgrade button).\n */\n errorCode?: string;\n /** Optional link paired with errorCode — e.g. Builder billing page. */\n upgradeUrl?: string;\n /** Optional details for expandable UI/debugging. */\n details?: string;\n /** True when the user can reasonably continue/retry from partial work. */\n recoverable?: boolean;\n }\n /**\n * Legacy SSE terminal event. New streams emit\n * `{ type: \"error\", errorCode: \"missing_credentials\" }` instead.\n */\n | { type: \"missing_api_key\" }\n | { type: \"loop_limit\"; maxIterations?: number }\n | {\n /**\n * An in-loop `Processor` aborted the run via `abort()` (which throws a\n * `TripWire`). The loop catches it, emits this event, stops cleanly, and\n * surfaces the reason as a final assistant message. Structural hook for\n * real-time guardrails and a proof-of-done / coverage gate.\n */\n type: \"tripwire\";\n reason: string;\n /** Name of the processor that aborted, when it declared one. */\n processor?: string;\n }\n | {\n type: \"auto_continue\";\n reason:\n | \"run_timeout\"\n | \"loop_limit\"\n | \"no_progress\"\n | \"stream_ended\"\n | \"gateway_timeout\"\n | \"network_interrupted\";\n maxIterations?: number;\n }\n | { type: \"clear\" };\n\nexport interface RunEvent {\n seq: number;\n event: AgentChatEvent;\n}\n\nexport type RunStatus = \"running\" | \"completed\" | \"errored\" | \"aborted\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"plan-local.d.ts","sourceRoot":"","sources":["../../src/cli/plan-local.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan-local.d.ts","sourceRoot":"","sources":["../../src/cli/plan-local.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@ import { pathToFileURL } from "node:url";
17
17
  import { DEFAULT_PLAN_APP_URL, defaultPlanBlocksOut, fetchPlanBlockCatalog, normalizePlanBlockFormat, } from "./plan-blocks.js";
18
18
  const LOCAL_PLAN_ASSET_MAX_SINGLE_BYTES = 2 * 1024 * 1024;
19
19
  const LOCAL_PLAN_ASSET_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
20
+ const AGENT_NATIVE_MANIFEST_FILE = "agent-native.json";
20
21
  const LOCAL_PLAN_ASSET_EXTENSIONS = new Set([
21
22
  "png",
22
23
  "jpg",
@@ -74,8 +75,92 @@ function normalizeKind(value) {
74
75
  return value;
75
76
  throw new Error(`Invalid --kind "${value}" (expected plan or recap)`);
76
77
  }
78
+ function normalizeSlash(filePath) {
79
+ return filePath.replace(/\\/g, "/");
80
+ }
81
+ function normalizeRelativePath(filePath) {
82
+ if (!filePath.trim() || path.isAbsolute(filePath))
83
+ return null;
84
+ const normalized = path.posix
85
+ .normalize(normalizeSlash(filePath.trim()))
86
+ .replace(/\/+$/, "");
87
+ if (!normalized ||
88
+ normalized === "." ||
89
+ normalized === ".." ||
90
+ normalized.startsWith("../") ||
91
+ normalized.split("/").some((part) => !part || part === "." || part === "..")) {
92
+ return null;
93
+ }
94
+ return normalized;
95
+ }
96
+ function findUpward(startDir, filename) {
97
+ let current = path.resolve(startDir);
98
+ for (;;) {
99
+ const candidate = path.join(current, filename);
100
+ if (fs.existsSync(candidate))
101
+ return candidate;
102
+ const parent = path.dirname(current);
103
+ if (parent === current)
104
+ return null;
105
+ current = parent;
106
+ }
107
+ }
108
+ function isRecord(value) {
109
+ return !!value && typeof value === "object" && !Array.isArray(value);
110
+ }
111
+ function manifestRootPath(value) {
112
+ if (typeof value === "string")
113
+ return normalizeRelativePath(value);
114
+ if (isRecord(value) && typeof value.path === "string") {
115
+ return normalizeRelativePath(value.path);
116
+ }
117
+ return null;
118
+ }
119
+ function planManifestConfig() {
120
+ const configuredManifest = process.env.AGENT_NATIVE_MANIFEST?.trim() ||
121
+ process.env.AGENT_NATIVE_MANIFEST_PATH?.trim();
122
+ const manifestPath = configuredManifest
123
+ ? path.resolve(configuredManifest)
124
+ : findUpward(process.cwd(), AGENT_NATIVE_MANIFEST_FILE);
125
+ if (!manifestPath)
126
+ return null;
127
+ try {
128
+ const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
129
+ const apps = isRecord(parsed) && isRecord(parsed.apps) ? parsed.apps : null;
130
+ const planApp = apps && isRecord(apps.plan) ? apps.plan : null;
131
+ const roots = planApp && Array.isArray(planApp.roots) ? planApp.roots : [];
132
+ const plansPath = roots
133
+ .map(manifestRootPath)
134
+ .find((item) => Boolean(item));
135
+ return plansPath
136
+ ? { rootDir: path.dirname(manifestPath), plansPath }
137
+ : null;
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ function localPlanWorkspaceRoot(startDir = process.cwd()) {
144
+ const manifestPath = findUpward(startDir, AGENT_NATIVE_MANIFEST_FILE);
145
+ if (manifestPath)
146
+ return path.dirname(manifestPath);
147
+ const gitDir = findUpward(startDir, ".git");
148
+ if (gitDir)
149
+ return path.dirname(gitDir);
150
+ const configuredManifest = process.env.AGENT_NATIVE_MANIFEST?.trim() ||
151
+ process.env.AGENT_NATIVE_MANIFEST_PATH?.trim();
152
+ if (configuredManifest)
153
+ return path.dirname(path.resolve(configuredManifest));
154
+ return process.cwd();
155
+ }
77
156
  function defaultPlansDir() {
78
- return path.resolve(process.env.PLAN_LOCAL_DIR || "plans");
157
+ const configured = process.env.PLAN_LOCAL_DIR;
158
+ if (configured?.trim())
159
+ return path.resolve(configured.trim());
160
+ const manifest = planManifestConfig();
161
+ if (manifest)
162
+ return path.resolve(manifest.rootDir, manifest.plansPath);
163
+ return path.resolve("plans");
79
164
  }
80
165
  function defaultLocalPlanAppUrl() {
81
166
  return (process.env.PLAN_LOCAL_APP_URL ||
@@ -94,7 +179,28 @@ function normalizeBridgeAppUrl(value) {
94
179
  return (value || defaultLocalPlanBridgeAppUrl()).replace(/\/+$/, "");
95
180
  }
96
181
  function localPlanPreviewUrl(dir, appUrl) {
97
- return `${normalizeAppUrl(appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(dir)))}`;
182
+ const base = `${normalizeAppUrl(appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(dir)))}`;
183
+ const repoPath = repoRelativePlanPath(dir);
184
+ if (!repoPath)
185
+ return base;
186
+ return `${base}?${new URLSearchParams({ path: repoPath }).toString()}`;
187
+ }
188
+ function realpathIfExists(filePath) {
189
+ try {
190
+ return fs.realpathSync.native(filePath);
191
+ }
192
+ catch {
193
+ return path.resolve(filePath);
194
+ }
195
+ }
196
+ function repoRelativePlanPath(dir) {
197
+ const resolved = realpathIfExists(dir);
198
+ const root = realpathIfExists(localPlanWorkspaceRoot(resolved));
199
+ const relative = path.relative(root, resolved);
200
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
201
+ return null;
202
+ }
203
+ return normalizeSlash(relative);
98
204
  }
99
205
  function localPlanBridgePageUrl(input) {
100
206
  return `${normalizeBridgeAppUrl(input.appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(input.dir)))}?bridge=${encodeURIComponent(input.bridgeUrl)}`;
@@ -523,11 +629,22 @@ function lintColumnsBlocks(file, source, issues) {
523
629
  }
524
630
  }
525
631
  }
632
+ // Blank out fenced code blocks and inline code spans (preserving newlines and
633
+ // length) so block-tag linters don't trip on documentation examples written in
634
+ // prose — e.g. an inline `<WireframeBlock><Screen>...</Screen></WireframeBlock>`
635
+ // example is not a real block to validate. Real blocks (outside code) are left
636
+ // intact, so their offsets/line numbers stay correct. Without this the default
637
+ // `plan local init` scaffold fails its own `plan local check`/`serve` lint.
638
+ function maskCodeRegions(source) {
639
+ const blank = (s) => s.replace(/[^\n]/g, " ");
640
+ return source.replace(/```[\s\S]*?```/g, blank).replace(/`[^`\n]*`/g, blank);
641
+ }
526
642
  export function validateLocalPlanFiles(files) {
527
643
  const issues = [];
528
644
  for (const entry of localPlanSourceEntries(files)) {
529
- lintWireframeBlocks(entry.file, entry.source, issues);
530
- lintColumnsBlocks(entry.file, entry.source, issues);
645
+ const source = maskCodeRegions(entry.source);
646
+ lintWireframeBlocks(entry.file, source, issues);
647
+ lintColumnsBlocks(entry.file, source, issues);
531
648
  }
532
649
  return issues;
533
650
  }
@@ -724,6 +841,8 @@ function sendBridgeJson(res, status, payload) {
724
841
  "access-control-allow-origin": "*",
725
842
  "access-control-allow-methods": "GET, OPTIONS",
726
843
  "access-control-allow-headers": "content-type",
844
+ // Required when the hosted HTTPS Plan UI fetches this localhost bridge.
845
+ "access-control-allow-private-network": "true",
727
846
  "cache-control": "no-store",
728
847
  "content-type": "application/json; charset=utf-8",
729
848
  "x-agent-native-local-bridge": "1",
@@ -1055,6 +1174,12 @@ export async function runPlan(argv) {
1055
1174
  process.exit(1);
1056
1175
  }
1057
1176
  const args = parseArgs(rest);
1177
+ // `plan local <sub> --help` / `-h` shows help instead of running the
1178
+ // subcommand (e.g. `plan local init --help` must not scaffold a folder).
1179
+ if (args.help === true || args.h === true) {
1180
+ process.stdout.write(HELP);
1181
+ return;
1182
+ }
1058
1183
  switch (sub) {
1059
1184
  case "init":
1060
1185
  runInit(args);