@tangle-network/agent-app 0.43.76 → 0.44.1

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 (153) hide show
  1. package/README.md +1 -1
  2. package/dist/DesignCanvas-Z362I3R3.js +10 -0
  3. package/dist/{DesignCanvasEditor-FJIVYOA2.js → DesignCanvasEditor-ZFE2W4OJ.js} +4 -4
  4. package/dist/assets/index.d.ts +4 -4
  5. package/dist/assets/index.js +223 -11
  6. package/dist/assets/index.js.map +1 -1
  7. package/dist/assistant/index.d.ts +7 -7
  8. package/dist/assistant/index.js +3 -3
  9. package/dist/assistant/index.js.map +1 -1
  10. package/dist/{attachment-validation-Dv1A_Puy.d.ts → attachment-validation-DRuLEjAE.d.ts} +1 -1
  11. package/dist/{auth-_FU8w01b.d.ts → auth-anc7mv2W.d.ts} +2 -2
  12. package/dist/chat-routes/index.d.ts +14 -11
  13. package/dist/chat-routes/index.js +16 -17
  14. package/dist/chat-routes/index.js.map +1 -1
  15. package/dist/chat-store/index.d.ts +26 -7
  16. package/dist/chat-store/index.js +14 -5
  17. package/dist/chat-store/index.js.map +1 -1
  18. package/dist/{chunk-ROLFDOMJ.js → chunk-4TI7QWLW.js} +3 -3
  19. package/dist/{chunk-3JUTOVUH.js → chunk-6KMAM7BL.js} +1 -1
  20. package/dist/chunk-6KMAM7BL.js.map +1 -0
  21. package/dist/{chunk-WL7XHLDK.js → chunk-ICOHEZK6.js} +77 -5
  22. package/dist/chunk-ICOHEZK6.js.map +1 -0
  23. package/dist/{chunk-22GLESKE.js → chunk-O63WWYET.js} +2 -4
  24. package/dist/chunk-O63WWYET.js.map +1 -0
  25. package/dist/{chunk-E7N67FYI.js → chunk-Q3FONVAK.js} +5 -5
  26. package/dist/{chunk-E7N67FYI.js.map → chunk-Q3FONVAK.js.map} +1 -1
  27. package/dist/{chunk-RXOTWZ4G.js → chunk-TKRI463Z.js} +4 -4
  28. package/dist/chunk-TKRI463Z.js.map +1 -0
  29. package/dist/config/index.d.ts +1 -1
  30. package/dist/config/index.js +123 -4
  31. package/dist/config/index.js.map +1 -1
  32. package/dist/{contract-B3h7peV3.d.ts → contract-CEewO6DI.d.ts} +1 -1
  33. package/dist/design-canvas/index.d.ts +4 -4
  34. package/dist/design-canvas-react/index.js +7 -7
  35. package/dist/design-canvas-react/lazy.js +1 -1
  36. package/dist/eval/index.d.ts +1 -1
  37. package/dist/eval/index.js +22 -8
  38. package/dist/eval/index.js.map +1 -1
  39. package/dist/{flow-types-CqomVAUN.d.ts → flow-types-CJxEmaRy.d.ts} +1 -1
  40. package/dist/integrations/index.js +67 -5
  41. package/dist/integrations/index.js.map +1 -1
  42. package/dist/interactions/index.d.ts +6 -4
  43. package/dist/interactions/index.js +1 -2
  44. package/dist/knowledge/index.js +63 -4
  45. package/dist/knowledge/index.js.map +1 -1
  46. package/dist/knowledge-loop/index.d.ts +1 -1
  47. package/dist/knowledge-loop/index.js +94 -4
  48. package/dist/knowledge-loop/index.js.map +1 -1
  49. package/dist/{mcp-BjPqQ-3C.d.ts → mcp-D3qVzbE1.d.ts} +2 -2
  50. package/dist/{mcp-rpc-BfUOsalw.d.ts → mcp-rpc-CzU5LWWT.d.ts} +1 -1
  51. package/dist/missions/index.js +1143 -23
  52. package/dist/missions/index.js.map +1 -1
  53. package/dist/{model-DmdkIteM.d.ts → model-CdCDfBA9.d.ts} +1 -1
  54. package/dist/object-store/index.js +113 -8
  55. package/dist/object-store/index.js.map +1 -1
  56. package/dist/{parts-2ymE5cs-.d.ts → parts-Co_lFgLI.d.ts} +3 -3
  57. package/dist/platform/index.d.ts +1 -1
  58. package/dist/preflight/cli.d.ts +13 -0
  59. package/dist/preflight/cli.js +50 -0
  60. package/dist/preflight/cli.js.map +1 -0
  61. package/dist/preset-cloudflare/index.d.ts +1 -1
  62. package/dist/preset-cloudflare/index.js +284 -11
  63. package/dist/preset-cloudflare/index.js.map +1 -1
  64. package/dist/{queue-C24V13h9.d.ts → queue-CD5-_VTX.d.ts} +1 -1
  65. package/dist/redact/index.js +116 -8
  66. package/dist/redact/index.js.map +1 -1
  67. package/dist/runtime/index.d.ts +19 -111
  68. package/dist/runtime/index.js +234 -14
  69. package/dist/runtime/index.js.map +1 -1
  70. package/dist/sandbox/index.d.ts +3 -3
  71. package/dist/sandbox/index.js +2 -3
  72. package/dist/sequences/index.d.ts +4 -4
  73. package/dist/stream/index.d.ts +4 -4
  74. package/dist/{stream-normalizer-QYUl4vnl.d.ts → stream-normalizer-DnuqkZvw.d.ts} +2 -2
  75. package/dist/tangle/index.js +42 -4
  76. package/dist/tangle/index.js.map +1 -1
  77. package/dist/teams/index.js +5 -5
  78. package/dist/teams/invitations-api.js +4 -4
  79. package/dist/teams-react/index.js +3 -3
  80. package/dist/theme/index.js +0 -1
  81. package/dist/tools/index.d.ts +6 -6
  82. package/dist/tools/index.js +5 -7
  83. package/dist/trace/index.d.ts +1 -1
  84. package/dist/trace/index.js +179 -10
  85. package/dist/trace/index.js.map +1 -1
  86. package/dist/{turn-buffer-BaSgxVmK.d.ts → turn-buffer-BcPfhr1_.d.ts} +1 -1
  87. package/dist/turn-stream/index.d.ts +1 -1
  88. package/dist/{types-CBRyqijY.d.ts → types-BCxK0wyS.d.ts} +1 -1
  89. package/dist/{types-CEchbvgz.d.ts → types-XIZqIdX1.d.ts} +1 -1
  90. package/dist/web-react/index.d.ts +10 -10
  91. package/dist/web-react/index.js +5 -5
  92. package/dist/work-product/index.d.ts +4 -4
  93. package/dist/work-product/index.js +735 -15
  94. package/dist/work-product/index.js.map +1 -1
  95. package/dist/work-product-react/index.d.ts +1 -1
  96. package/dist/work-product-react/index.js.map +1 -1
  97. package/package.json +4 -19
  98. package/dist/DesignCanvas-UNL34LS2.js +0 -12
  99. package/dist/chunk-22GLESKE.js.map +0 -1
  100. package/dist/chunk-3JUTOVUH.js.map +0 -1
  101. package/dist/chunk-5EQCITY3.js +0 -20
  102. package/dist/chunk-5EQCITY3.js.map +0 -1
  103. package/dist/chunk-5SD6HT2V.js +0 -236
  104. package/dist/chunk-5SD6HT2V.js.map +0 -1
  105. package/dist/chunk-5TESA4SN.js +0 -74
  106. package/dist/chunk-5TESA4SN.js.map +0 -1
  107. package/dist/chunk-6GQ5DKFK.js +0 -920
  108. package/dist/chunk-6GQ5DKFK.js.map +0 -1
  109. package/dist/chunk-7ESQUSAC.js +0 -343
  110. package/dist/chunk-7ESQUSAC.js.map +0 -1
  111. package/dist/chunk-7LNGJDNA.js +0 -1
  112. package/dist/chunk-7LNGJDNA.js.map +0 -1
  113. package/dist/chunk-AP2UXKUK.js +0 -69
  114. package/dist/chunk-AP2UXKUK.js.map +0 -1
  115. package/dist/chunk-FA4XR66Y.js +0 -32
  116. package/dist/chunk-FA4XR66Y.js.map +0 -1
  117. package/dist/chunk-FKCB5FTD.js +0 -126
  118. package/dist/chunk-FKCB5FTD.js.map +0 -1
  119. package/dist/chunk-GPBRL2XR.js +0 -102
  120. package/dist/chunk-GPBRL2XR.js.map +0 -1
  121. package/dist/chunk-HRH7ASAG.js +0 -759
  122. package/dist/chunk-HRH7ASAG.js.map +0 -1
  123. package/dist/chunk-HYTIHYNV.js +0 -297
  124. package/dist/chunk-HYTIHYNV.js.map +0 -1
  125. package/dist/chunk-LGBO24FL.js +0 -190
  126. package/dist/chunk-LGBO24FL.js.map +0 -1
  127. package/dist/chunk-NLSSFLLS.js +0 -1168
  128. package/dist/chunk-NLSSFLLS.js.map +0 -1
  129. package/dist/chunk-OSMNYPOG.js +0 -129
  130. package/dist/chunk-OSMNYPOG.js.map +0 -1
  131. package/dist/chunk-QYOD3K56.js +0 -124
  132. package/dist/chunk-QYOD3K56.js.map +0 -1
  133. package/dist/chunk-RXOTWZ4G.js.map +0 -1
  134. package/dist/chunk-UAKFCMWK.js +0 -1
  135. package/dist/chunk-UAKFCMWK.js.map +0 -1
  136. package/dist/chunk-UKTYKG57.js +0 -48
  137. package/dist/chunk-UKTYKG57.js.map +0 -1
  138. package/dist/chunk-UOAYS72M.js +0 -80
  139. package/dist/chunk-UOAYS72M.js.map +0 -1
  140. package/dist/chunk-WL7XHLDK.js.map +0 -1
  141. package/dist/composer/index.d.ts +0 -2
  142. package/dist/composer/index.js +0 -22
  143. package/dist/composer/index.js.map +0 -1
  144. package/dist/core-7qIM7svy.d.ts +0 -21
  145. package/dist/durable-chat/index.d.ts +0 -464
  146. package/dist/durable-chat/index.js +0 -59
  147. package/dist/durable-chat/index.js.map +0 -1
  148. package/dist/index.d.ts +0 -51
  149. package/dist/index.js +0 -827
  150. package/dist/index.js.map +0 -1
  151. /package/dist/{DesignCanvas-UNL34LS2.js.map → DesignCanvas-Z362I3R3.js.map} +0 -0
  152. /package/dist/{DesignCanvasEditor-FJIVYOA2.js.map → DesignCanvasEditor-ZFE2W4OJ.js.map} +0 -0
  153. /package/dist/{chunk-ROLFDOMJ.js.map → chunk-4TI7QWLW.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../../src/missions/service.ts","../../src/missions/events.ts","../../src/missions/engine.ts","../../src/missions/plan-parse.ts","../../src/missions/agent-activity.ts"],"sourcesContent":["/**\n * Durable mission state — the guarded status machine for a multi-step agent run.\n *\n * A mission is a persisted plan (ordered steps), a cursor (count of completed\n * steps), a cost ledger + budget, and a status machine. This module owns the\n * legal transitions and the guarded mutation surface; it does NOT execute steps\n * (the engine in `./engine` does) and it does NOT own persistence — products\n * implement {@link MissionStorePort} over their own tables. Every state change\n * appends a {@link MissionAuditEvent} so the run timeline is a single durable\n * audit trail.\n *\n * Concurrency contract: a mission MUST be driven by a single serialized owner\n * (a Durable Object, a Cloudflare Workflow, a queue consumer — one per\n * mission). The service is the typed guard layer, not a serializer: every\n * mutation re-reads the record and asks the store for a compare-and-set write\n * guarded on the values it read. When the guard misses, the row changed under\n * us and the caller gets `{ succeeded: false, conflict: true }` — never a\n * silent clobber, never a stale overwrite.\n */\n\nexport type MissionStatus =\n | 'scheduled'\n | 'running'\n | 'paused'\n | 'waiting_approval'\n | 'blocked'\n | 'succeeded'\n | 'failed'\n | 'aborted'\n | 'cancelled'\n\n/** Define possible statuses for a mission step during its execution lifecycle */\nexport type MissionStepStatus = 'pending' | 'running' | 'waiting_approval' | 'done' | 'failed'\n\n/** Define the structure and state details of a mission step within a workflow system */\nexport interface MissionStep {\n id: string\n /** What the step should accomplish — an intent, never an implementation. */\n intent: string\n /** Product-defined kind label. Labels intent for gating/UX; it never selects\n * a different execution path. */\n kind: string\n status: MissionStepStatus\n /** Count of genuine `* -> running` edges (retries inflate this; idempotent\n * re-asserts do not). */\n attempts: number\n /** One-line live status surfaced on the step row (\"7/15 refs\"). */\n sublabel?: string\n /** Small pointer at the produced artifact (vault path, asset id) — never the\n * full payload. */\n resultRef?: string\n}\n\n/** Define the structure for tracking mission token usage, cost, duration, and LLM call counts */\nexport interface MissionCostLedger {\n tokensIn: number\n tokensOut: number\n costUsd: number\n wallMs: number\n llmCalls: number\n}\n\n/** The durable mission row, shape-normalized. Timestamps are epoch ms. */\nexport interface MissionRecord {\n id: string\n workspaceId: string\n status: MissionStatus\n /** Product-defined origin label ('chat', 'manual', 'cron', …). */\n trigger: string\n summary: string | null\n plan: MissionStep[]\n /** Count of durably-completed steps; the next step to run is `plan[cursor]`. */\n cursor: number\n cost: MissionCostLedger | null\n budgetUsd: number | null\n spentUsd: number\n pauseReason: string | null\n /** The single owning engine's instance id, write-once (see `setEngineRef`). */\n engineRef: string | null\n scheduledAt: number | null\n startedAt: number\n completedAt: number | null\n metadata: Record<string, unknown> | null\n}\n\n/**\n * Discriminated outcome for guarded operations. Callers MUST inspect\n * `succeeded` before reading `value` — illegal transitions and missing rows\n * surface here, never as a throw-and-swallow or a silent no-op. `conflict`\n * distinguishes a lost guarded race (retryable — re-read and re-apply) from a\n * logic rejection (illegal edge, missing step — deterministic, never retried).\n */\nexport type MissionOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; error: string; conflict: boolean }\n\n/** Fields a guarded write compares against the values the caller read. A SQL\n * implementation compares JSON columns as serialized text\n * (`coalesce(col, 'null') = JSON.stringify(expected)`), matching how the\n * in-memory store compares. An absent field is unguarded. */\nexport interface MissionUpdateGuard {\n status?: MissionStatus\n cursor?: number\n plan?: MissionStep[]\n cost?: MissionCostLedger | null\n metadata?: Record<string, unknown> | null\n /** Guard that no engine has bound yet (the write-once bind). */\n engineRefIsNull?: true\n}\n\n/** Fields a guarded write sets when the guard holds. `null` values are real\n * writes (clear the column), not skips. */\nexport interface MissionUpdatePatch {\n status?: MissionStatus\n pauseReason?: string | null\n summary?: string\n completedAt?: number | null\n plan?: MissionStep[]\n cursor?: number\n cost?: MissionCostLedger\n spentUsd?: number\n metadata?: Record<string, unknown>\n engineRef?: string\n}\n\n/** One audit-trail row. Appended after every committed state change, so an\n * event always denotes a real transition (no phantom rows on rejected or\n * no-op calls). */\nexport interface MissionAuditEvent {\n missionId: string\n workspaceId: string\n level: 'info' | 'warn' | 'error'\n /** Machine-readable transition name ('mission.created', 'mission.step.done',\n * 'mission.cursor', 'mission.cost', 'mission.paused', …). */\n step: string\n message: string\n metadata: Record<string, unknown>\n at: number\n}\n\n/**\n * Persistence seam — the product implements this over its own tables. The\n * invariant the implementation MUST keep: `update` applies `patch` ONLY when\n * every guard field still equals the stored value, and returns `null` when the\n * guard misses. That null is how a concurrent write surfaces as a typed\n * failure instead of a clobber.\n */\nexport interface MissionStorePort {\n load(id: string): Promise<MissionRecord | null>\n /** `extras` are the opaque product-column values from\n * `CreateMissionInput.extras` — write them in the SAME statement as the\n * record (single-write creation) or ignore them if the table has no extra\n * columns. */\n insert(record: MissionRecord, extras?: Record<string, unknown>): Promise<MissionRecord>\n update(id: string, guard: MissionUpdateGuard, patch: MissionUpdatePatch): Promise<MissionRecord | null>\n appendEvent(event: MissionAuditEvent): Promise<void>\n}\n\nconst TERMINAL_STATUSES: ReadonlySet<MissionStatus> = new Set<MissionStatus>([\n 'succeeded',\n 'failed',\n 'aborted',\n 'cancelled',\n])\n\n/** Statuses a mission can never leave — the run is done. */\nexport function isMissionTerminal(status: MissionStatus): boolean {\n return TERMINAL_STATUSES.has(status)\n}\n\n/** The cooperative kill switch: a stop request rides metadata so it survives\n * any status and is honored by the engine before the next side effect. */\nexport function isMissionStopRequested(mission: MissionRecord): boolean {\n return (mission.metadata ?? {}).stopRequested === true\n}\n\n// Legal mission status transitions. A target absent from a source's set is\n// rejected by the guarded helpers. Terminal statuses have no outgoing edges.\nconst MISSION_TRANSITIONS: Record<MissionStatus, ReadonlySet<MissionStatus>> = {\n scheduled: new Set<MissionStatus>(['running', 'cancelled', 'aborted']),\n running: new Set<MissionStatus>([\n 'paused',\n 'waiting_approval',\n 'blocked',\n 'succeeded',\n 'failed',\n 'aborted',\n ]),\n paused: new Set<MissionStatus>(['running', 'aborted', 'cancelled']),\n waiting_approval: new Set<MissionStatus>(['running', 'aborted', 'cancelled']),\n blocked: new Set<MissionStatus>(['running', 'aborted', 'cancelled']),\n succeeded: new Set<MissionStatus>(),\n failed: new Set<MissionStatus>(),\n aborted: new Set<MissionStatus>(),\n cancelled: new Set<MissionStatus>(),\n}\n\n// Legal per-step transitions inside a plan.\nconst STEP_TRANSITIONS: Record<MissionStepStatus, ReadonlySet<MissionStepStatus>> = {\n pending: new Set<MissionStepStatus>(['running', 'waiting_approval', 'failed']),\n running: new Set<MissionStepStatus>(['done', 'failed', 'waiting_approval']),\n waiting_approval: new Set<MissionStepStatus>(['running', 'done', 'failed']),\n done: new Set<MissionStepStatus>([]),\n failed: new Set<MissionStepStatus>(['pending', 'running']),\n}\n\nconst ZERO_LEDGER: MissionCostLedger = {\n tokensIn: 0,\n tokensOut: 0,\n costUsd: 0,\n wallMs: 0,\n llmCalls: 0,\n}\n\n/** Define input parameters for creating a mission including optional deterministic id and unique plan steps */\nexport interface CreateMissionInput {\n /** Explicit row id. Omit to use the service's id generator. Pass a\n * DETERMINISTIC id (derived from the originating turn) when the caller may\n * re-create the same mission under at-least-once delivery — the duplicate\n * insert then trips the store's uniqueness instead of spawning a second run. */\n id?: string\n workspaceId: string\n /** Becomes the mission summary. */\n title: string\n /** Plan step ids MUST be unique: the owning workflow keys its durable step\n * cache by step id, so a collision would silently replay the wrong result.\n * A duplicate is rejected here (fail loud). */\n plan: MissionStep[]\n budgetUsd?: number | null\n /** Epoch ms. Present → the mission starts `scheduled` instead of `running`. */\n scheduledAt?: number | null\n trigger: string\n /** Caller-defined context stamped onto the record (thread ids, source turn,\n * model). Read back via `mission.metadata`; the engine only reads\n * `stopRequested` from it. */\n metadata?: Record<string, unknown> | null\n /**\n * Opaque product-column values handed VERBATIM to `MissionStorePort.insert`\n * (e.g. a `workflowId` FK on the product's mission table), so creation is a\n * single write — no post-insert stamp. The service never reads, validates,\n * or persists them itself; they exist only on the insert call.\n */\n extras?: Record<string, unknown>\n}\n\n/** Define a patch to update the status and optional metadata of a step in a process */\nexport interface SetStepStatusPatch {\n sublabel?: string\n resultRef?: string\n error?: string\n /**\n * Spend committed ATOMICALLY with the step transition. Folding the cost into\n * the SAME guarded write makes step completion the per-step idempotency key:\n * a same-status replay (the step is already `done`) short-circuits as a no-op\n * BEFORE the write, so the charge lands exactly once even when an engine\n * RESUME re-dispatches a step whose cost previously committed. `deltaUsd` is\n * the marginal spend; `ledgerDelta` carries the token/wall/llm breakdown.\n */\n cost?: {\n deltaUsd: number\n ledgerDelta?: Partial<MissionCostLedger>\n }\n}\n\n/** Define input parameters to complete a mission with status and optional summary */\nexport interface CompleteMissionInput {\n ok: boolean\n summary?: string\n}\n\n/** Define methods to create, retrieve, and update missions with controlled engine binding and metadata merging */\nexport interface MissionService {\n createMission(input: CreateMissionInput): Promise<MissionRecord>\n getMission(id: string): Promise<MissionRecord | null>\n /** Bind the executing engine's instance id, write-once from the single\n * owner: re-asserting the same ref is a no-op; binding a DIFFERENT ref over\n * an existing one is rejected so a second owner can never steal the run. */\n setEngineRef(id: string, engineRef: string): Promise<MissionOutcome<MissionRecord>>\n /** Shallow-merge keys into metadata. Guarded on the metadata read, so racing\n * merges surface as conflicts instead of silently dropping keys. */\n mergeMetadata(id: string, patch: Record<string, unknown>): Promise<MissionOutcome<MissionRecord>>\n /** Mutate one plan step's status (+ optional sublabel/resultRef) and append a\n * transition event. Rejects unknown steps and illegal step edges. Does NOT\n * move the cursor — call `advanceCursor` for that. */\n setStepStatus(\n id: string,\n stepId: string,\n status: MissionStepStatus,\n patch?: SetStepStatusPatch,\n ): Promise<MissionOutcome<MissionRecord>>\n /** Move the done-count cursor forward by one. Rejects advancing past the end\n * of the plan so the caller learns the mission has no further work. */\n advanceCursor(id: string): Promise<MissionOutcome<MissionRecord>>\n /** Increment spentUsd and merge a partial ledger into the cumulative ledger.\n * `deltaUsd` is the marginal spend; `ledgerDelta` carries the token/wall/\n * llm-call breakdown for the same unit of work. */\n addCost(\n id: string,\n deltaUsd: number,\n ledgerDelta?: Partial<MissionCostLedger>,\n ): Promise<MissionOutcome<MissionRecord>>\n pause(id: string, reason: string): Promise<MissionOutcome<MissionRecord>>\n resume(id: string): Promise<MissionOutcome<MissionRecord>>\n abort(id: string): Promise<MissionOutcome<MissionRecord>>\n /** Flip one step and the whole mission to waiting_approval together. The\n * mission transition is validated FIRST so an illegal source is rejected\n * without mutating the step — no half-applied state. */\n markWaitingApproval(id: string, stepId: string): Promise<MissionOutcome<MissionRecord>>\n complete(id: string, input: CompleteMissionInput): Promise<MissionOutcome<MissionRecord>>\n}\n\n/** Define options for configuring mission service behavior including storage, time, and ID generation */\nexport interface MissionServiceOptions {\n store: MissionStorePort\n /** Injectable clock (epoch ms). Default `Date.now`. */\n now?: () => number\n /**\n * Row-id generator when `CreateMissionInput.id` is omitted. Default\n * `crypto.randomUUID()` — a 36-char dashed UUID. Inject your own when your\n * mission table already has an id shape (e.g. 32-hex matching D1 row\n * defaults): the service stamps the generated id verbatim onto the inserted\n * record, so a mismatch with the rest of your schema surfaces only at the\n * product layer. Keep id shape parity here, not in the store.\n */\n generateId?: () => string\n}\n\nfunction rejected<T>(error: string): MissionOutcome<T> {\n return { succeeded: false, error, conflict: false }\n}\n\nfunction lostRace<T>(id: string): MissionOutcome<T> {\n return { succeeded: false, error: `Mission ${id} changed concurrently`, conflict: true }\n}\n\n/** Create a mission service that manages mission records and audit events with customizable options */\nexport function createMissionService(options: MissionServiceOptions): MissionService {\n const { store } = options\n const now = options.now ?? (() => Date.now())\n const generateId = options.generateId ?? (() => crypto.randomUUID())\n\n async function appendEvent(\n mission: MissionRecord,\n level: MissionAuditEvent['level'],\n step: string,\n message: string,\n metadata: Record<string, unknown> = {},\n ): Promise<void> {\n await store.appendEvent({\n missionId: mission.id,\n workspaceId: mission.workspaceId,\n level,\n step,\n message,\n metadata,\n at: now(),\n })\n }\n\n // Guarded status transition: load → validate against the transition table →\n // CAS-write guarded on the status we read → event. The loser of a racing\n // transition gets a conflict instead of silently violating the machine.\n async function transition(\n id: string,\n to: MissionStatus,\n patch: Omit<MissionUpdatePatch, 'status'> = {},\n eventMeta: Record<string, unknown> = {},\n ): Promise<MissionOutcome<MissionRecord>> {\n const mission = await store.load(id)\n if (!mission) return rejected(`Mission ${id} not found`)\n\n const from = mission.status\n if (TERMINAL_STATUSES.has(from)) {\n return rejected(`Mission ${id} is terminal (${from}); cannot transition to ${to}`)\n }\n // Same-status calls are a true no-op (resume-while-running, idempotent\n // re-assert under at-least-once delivery): no patch re-apply, no phantom\n // event, so an event row always denotes a real state change.\n if (from === to) return { succeeded: true, value: mission }\n if (!MISSION_TRANSITIONS[from].has(to)) {\n return rejected(`Illegal mission transition ${from} -> ${to} for mission ${id}`)\n }\n\n const updated = await store.update(id, { status: from }, { status: to, ...patch })\n if (!updated) return lostRace(id)\n await appendEvent(updated, to === 'failed' ? 'error' : 'info', `mission.${to}`, `Mission ${from} -> ${to}`, {\n from,\n to,\n ...eventMeta,\n })\n return { succeeded: true, value: updated }\n }\n\n const createMission: MissionService['createMission'] = async (input) => {\n const seen = new Set<string>()\n for (const step of input.plan) {\n if (seen.has(step.id)) {\n throw new Error(`Duplicate plan step id \"${step.id}\" — mission plan step ids must be unique`)\n }\n seen.add(step.id)\n }\n\n const scheduledAt = input.scheduledAt ?? null\n const status: MissionStatus = scheduledAt !== null ? 'scheduled' : 'running'\n const plan: MissionStep[] = input.plan.map((step) => ({\n id: step.id,\n intent: step.intent,\n kind: step.kind,\n status: step.status,\n attempts: step.attempts,\n ...(step.sublabel === undefined ? {} : { sublabel: step.sublabel }),\n ...(step.resultRef === undefined ? {} : { resultRef: step.resultRef }),\n }))\n\n const record = await store.insert(\n {\n id: input.id ?? generateId(),\n workspaceId: input.workspaceId,\n status,\n trigger: input.trigger,\n summary: input.title,\n plan,\n cursor: 0,\n cost: { ...ZERO_LEDGER },\n budgetUsd: input.budgetUsd ?? null,\n spentUsd: 0,\n pauseReason: null,\n engineRef: null,\n scheduledAt,\n startedAt: now(),\n completedAt: null,\n metadata: input.metadata ?? null,\n },\n input.extras,\n )\n\n await appendEvent(record, 'info', 'mission.created', `Mission \"${input.title}\" ${status}`, {\n status,\n stepCount: plan.length,\n budgetUsd: input.budgetUsd ?? null,\n scheduledAt,\n })\n return record\n }\n\n const getMission: MissionService['getMission'] = (id) => store.load(id)\n\n const setEngineRef: MissionService['setEngineRef'] = async (id, engineRef) => {\n const mission = await store.load(id)\n if (!mission) return rejected(`Mission ${id} not found`)\n if (TERMINAL_STATUSES.has(mission.status) || isMissionStopRequested(mission)) {\n return rejected(`Mission ${id} is not writable in status ${mission.status}`)\n }\n if (mission.engineRef === engineRef) return { succeeded: true, value: mission }\n if (mission.engineRef !== null) {\n return rejected(`Mission ${id} is already bound to engine ${mission.engineRef}`)\n }\n\n // Guard on the null engineRef we read so two racing owners cannot both\n // bind: the second write misses the guard and reports the lost race —\n // distinct from the deterministic already-bound rejection above.\n const updated = await store.update(id, { engineRefIsNull: true }, { engineRef })\n if (!updated) return lostRace(id)\n await appendEvent(updated, 'info', 'mission.engine', `Engine bound: ${engineRef}`, { engineRef })\n return { succeeded: true, value: updated }\n }\n\n const mergeMetadata: MissionService['mergeMetadata'] = async (id, patch) => {\n const mission = await store.load(id)\n if (!mission) return rejected(`Mission ${id} not found`)\n if (TERMINAL_STATUSES.has(mission.status) || isMissionStopRequested(mission)) {\n return rejected(`Mission ${id} is not writable in status ${mission.status}`)\n }\n\n const updated = await store.update(\n id,\n { metadata: mission.metadata },\n { metadata: { ...(mission.metadata ?? {}), ...patch } },\n )\n if (!updated) return lostRace(id)\n return { succeeded: true, value: updated }\n }\n\n const setStepStatus: MissionService['setStepStatus'] = async (id, stepId, status, patch = {}) => {\n const mission = await store.load(id)\n if (!mission) return rejected(`Mission ${id} not found`)\n\n const plan = mission.plan\n const index = plan.findIndex((step) => step.id === stepId)\n const current = index < 0 ? undefined : plan[index]\n if (!current) return rejected(`Step ${stepId} not found in mission ${id}`)\n\n const sameStatus = current.status === status\n if (!sameStatus && !STEP_TRANSITIONS[current.status].has(status)) {\n return rejected(`Illegal step transition ${current.status} -> ${status} for step ${stepId}`)\n }\n\n // True no-op: re-asserting the same status with no patch field change must\n // not bump attempts or write a duplicate event (at-least-once delivery,\n // retries, and reconnects re-assert state). A patch on the same status\n // still persists, but does not count a new attempt.\n const sublabelChanges = patch.sublabel !== undefined && patch.sublabel !== current.sublabel\n const resultRefChanges = patch.resultRef !== undefined && patch.resultRef !== current.resultRef\n if (sameStatus && !sublabelChanges && !resultRefChanges) {\n return { succeeded: true, value: mission }\n }\n\n // Only a genuine `* -> running` edge counts an attempt; a running->running\n // re-assert (handled as a no-op above) never inflates the counter.\n const nextStep: MissionStep = {\n ...current,\n status,\n attempts: status === 'running' && !sameStatus ? current.attempts + 1 : current.attempts,\n ...(patch.sublabel === undefined ? {} : { sublabel: patch.sublabel }),\n ...(patch.resultRef === undefined ? {} : { resultRef: patch.resultRef }),\n }\n const nextPlan = plan.slice()\n nextPlan[index] = nextStep\n\n // Spend folded into the SAME guarded write as the transition. Because the\n // same-status no-op above returns BEFORE this point, a step that is already\n // `done` never reaches here — so an engine RESUME re-dispatching a committed\n // step cannot charge a second time. The ledger CAS guard preserves the\n // over-spend protection `addCost` carried: an undercounted concurrent merge\n // surfaces as a lost race, never a clobber.\n const cost = patch.cost\n const ledgerDelta = cost?.ledgerDelta\n const base = mission.cost ?? { ...ZERO_LEDGER }\n const nextCost: MissionCostLedger | undefined =\n cost === undefined\n ? undefined\n : {\n tokensIn: base.tokensIn + (ledgerDelta?.tokensIn ?? 0),\n tokensOut: base.tokensOut + (ledgerDelta?.tokensOut ?? 0),\n costUsd: base.costUsd + (ledgerDelta?.costUsd ?? cost.deltaUsd),\n wallMs: base.wallMs + (ledgerDelta?.wallMs ?? 0),\n llmCalls: base.llmCalls + (ledgerDelta?.llmCalls ?? 0),\n }\n\n // The plan write is guarded on the status, plan, metadata, AND (when a cost\n // rides along) the ledger we read: a concurrent stop request (a metadata\n // write), any plan mutation, or a racing spend flips a guard and the loser\n // reports the race instead of clobbering it.\n const updated = await store.update(\n id,\n {\n status: mission.status,\n plan: mission.plan,\n metadata: mission.metadata,\n ...(cost === undefined ? {} : { cost: mission.cost }),\n },\n {\n plan: nextPlan,\n ...(cost === undefined ? {} : { cost: nextCost!, spentUsd: mission.spentUsd + cost.deltaUsd }),\n },\n )\n if (!updated) return lostRace(id)\n await appendEvent(\n updated,\n status === 'failed' ? 'error' : 'info',\n `mission.step.${status}`,\n patch.error ?? `Step ${stepId} (${current.intent}) -> ${status}`,\n {\n stepId,\n from: current.status,\n to: status,\n attempts: nextStep.attempts,\n ...(patch.resultRef ? { resultRef: patch.resultRef } : {}),\n },\n )\n if (cost !== undefined) {\n await appendEvent(updated, 'info', 'mission.cost', `Spent +$${cost.deltaUsd.toFixed(4)}`, {\n deltaUsd: cost.deltaUsd,\n spentUsd: updated.spentUsd,\n budgetUsd: updated.budgetUsd,\n })\n }\n return { succeeded: true, value: updated }\n }\n\n const advanceCursor: MissionService['advanceCursor'] = async (id) => {\n const mission = await store.load(id)\n if (!mission) return rejected(`Mission ${id} not found`)\n\n const next = mission.cursor + 1\n if (next > mission.plan.length) {\n return rejected(`Cursor ${mission.cursor} is already at the end of mission ${id}`)\n }\n\n // Guarded on the cursor (and status) we read: a concurrent advance shifts\n // the cursor, the guard misses, and we report the lost race rather than\n // writing a stale value that drops the other increment.\n const updated = await store.update(\n id,\n { status: mission.status, cursor: mission.cursor },\n { cursor: next },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, 'info', 'mission.cursor', `Cursor ${mission.cursor} -> ${updated.cursor}`, {\n from: mission.cursor,\n to: updated.cursor,\n })\n return { succeeded: true, value: updated }\n }\n\n const addCost: MissionService['addCost'] = async (id, deltaUsd, ledgerDelta) => {\n const mission = await store.load(id)\n if (!mission) return rejected(`Mission ${id} not found`)\n\n const base = mission.cost ?? { ...ZERO_LEDGER }\n const nextCost: MissionCostLedger = {\n tokensIn: base.tokensIn + (ledgerDelta?.tokensIn ?? 0),\n tokensOut: base.tokensOut + (ledgerDelta?.tokensOut ?? 0),\n costUsd: base.costUsd + (ledgerDelta?.costUsd ?? deltaUsd),\n wallMs: base.wallMs + (ledgerDelta?.wallMs ?? 0),\n llmCalls: base.llmCalls + (ledgerDelta?.llmCalls ?? 0),\n }\n\n // Guarded on the ledger we read: an undercounted spend is an over-spend\n // against the budget gate, so a concurrent merge must surface as a lost\n // race (retry re-reads and re-merges), never be clobbered.\n const updated = await store.update(\n id,\n { cost: mission.cost },\n { cost: nextCost, spentUsd: mission.spentUsd + deltaUsd },\n )\n if (!updated) return lostRace(id)\n await appendEvent(updated, 'info', 'mission.cost', `Spent +$${deltaUsd.toFixed(4)}`, {\n deltaUsd,\n spentUsd: updated.spentUsd,\n budgetUsd: updated.budgetUsd,\n })\n return { succeeded: true, value: updated }\n }\n\n const markWaitingApproval: MissionService['markWaitingApproval'] = async (id, stepId) => {\n const mission = await store.load(id)\n if (!mission) return rejected(`Mission ${id} not found`)\n if (!MISSION_TRANSITIONS[mission.status].has('waiting_approval')) {\n return rejected(`Illegal mission transition ${mission.status} -> waiting_approval for mission ${id}`)\n }\n\n const stepResult = await setStepStatus(id, stepId, 'waiting_approval')\n if (!stepResult.succeeded) return stepResult\n return transition(id, 'waiting_approval', {}, { stepId })\n }\n\n return {\n createMission,\n getMission,\n setEngineRef,\n mergeMetadata,\n setStepStatus,\n advanceCursor,\n addCost,\n markWaitingApproval,\n pause: (id, reason) => transition(id, 'paused', { pauseReason: reason }),\n resume: (id) => transition(id, 'running', { pauseReason: null }),\n abort: (id) => transition(id, 'aborted', { completedAt: now() }),\n complete: (id, input) =>\n transition(id, input.ok ? 'succeeded' : 'failed', {\n completedAt: now(),\n ...(input.summary === undefined ? {} : { summary: input.summary }),\n }),\n }\n}\n\n// ── in-memory store ──────────────────────────────────────────────────────────\n\n/** Define an in-memory mission store that tracks events and allows direct record writes */\nexport interface InMemoryMissionStore extends MissionStorePort {\n /** The full audit trail, append order. */\n events(): MissionAuditEvent[]\n /** Unguarded direct write — simulates a concurrent owner or a crash-shaped\n * state in tests. Production writers go through the guarded `update`. */\n put(record: MissionRecord): void\n}\n\n/**\n * In-memory {@link MissionStorePort} — the portable backend for tests and\n * sandbox/eval shells. Guard comparison uses JSON serialization of the read\n * value, the same contract a SQL implementation honors by comparing stored\n * JSON text. Records are deep-copied on every boundary so callers can never\n * mutate stored state around the guards.\n */\nexport function createInMemoryMissionStore(): InMemoryMissionStore {\n const rows = new Map<string, MissionRecord>()\n const events: MissionAuditEvent[] = []\n\n return {\n async load(id) {\n const record = rows.get(id)\n return record ? structuredClone(record) : null\n },\n async insert(record) {\n if (rows.has(record.id)) throw new Error(`Mission ${record.id} already exists`)\n rows.set(record.id, structuredClone(record))\n return structuredClone(record)\n },\n async update(id, guard, patch) {\n const current = rows.get(id)\n if (!current) return null\n if (guard.status !== undefined && current.status !== guard.status) return null\n if (guard.cursor !== undefined && current.cursor !== guard.cursor) return null\n if (guard.plan !== undefined && JSON.stringify(current.plan) !== JSON.stringify(guard.plan)) return null\n if (guard.cost !== undefined && JSON.stringify(current.cost) !== JSON.stringify(guard.cost)) return null\n if (\n guard.metadata !== undefined &&\n JSON.stringify(current.metadata) !== JSON.stringify(guard.metadata)\n ) {\n return null\n }\n if (guard.engineRefIsNull && current.engineRef !== null) return null\n\n const next: MissionRecord = { ...current }\n if (patch.status !== undefined) next.status = patch.status\n if (patch.pauseReason !== undefined) next.pauseReason = patch.pauseReason\n if (patch.summary !== undefined) next.summary = patch.summary\n if (patch.completedAt !== undefined) next.completedAt = patch.completedAt\n if (patch.plan !== undefined) next.plan = patch.plan\n if (patch.cursor !== undefined) next.cursor = patch.cursor\n if (patch.cost !== undefined) next.cost = patch.cost\n if (patch.spentUsd !== undefined) next.spentUsd = patch.spentUsd\n if (patch.metadata !== undefined) next.metadata = patch.metadata\n if (patch.engineRef !== undefined) next.engineRef = patch.engineRef\n rows.set(id, structuredClone(next))\n return structuredClone(next)\n },\n async appendEvent(event) {\n events.push(structuredClone(event))\n },\n events() {\n return events.map((event) => structuredClone(event))\n },\n put(record) {\n rows.set(record.id, structuredClone(record))\n },\n }\n}\n","/**\n * Shared mission realtime contract — the single source of truth for the typed\n * events the engine BROADCASTS over a live channel and the client REDUCES into\n * live mission state. Server emit and client reduce import the same module so\n * the wire shape can never drift between the two ends.\n *\n * This module is CLIENT-SAFE: no server imports, no platform globals, no DB\n * types. It is pure data + a pure reducer. Keep it that way — a server-only\n * import here would leak into the browser bundle.\n *\n * Sink contract — best-effort UI notification, never load-bearing:\n * - fire-and-forget: the engine never awaits `emit` and a sink failure can\n * never fail a step (the engine wraps every emit). The durable audit-event\n * row is the authoritative timeline; the socket is a convenience.\n * - replay-safe: the engine re-emits on a resume/replay. The reducer below is\n * idempotent + order-tolerant, so a re-sent or duplicated event converges.\n * The sink itself does no dedupe.\n */\n\nimport type { StepAgentActivity } from './agent-activity'\n\n/** Handle mission stream events by processing emitted MissionStreamEvent objects */\nexport interface MissionEventSink {\n emit(event: MissionStreamEvent): void\n}\n\n/** A sink that drops every event — the engine default when no live channel is\n * wired (and the unit-test default). */\nexport const noopEventSink: MissionEventSink = { emit() {} }\n\n/** Workspace-wide channel id missions broadcast on (alongside any per-thread\n * channel the product keys). */\nexport const MISSION_CONTROL_CHANNEL_ID = 'missions'\n\n/** One plan step as it appears on the wire — only what a live UI needs\n * (`sublabel` updates travel separately via `step.updated` so the snapshot\n * stays small). */\nexport interface MissionStreamStep {\n id: string\n intent: string\n kind: string\n status: MissionStreamStepStatus\n}\n\n/** Define possible status values for a mission stream step */\nexport type MissionStreamStepStatus =\n | 'pending'\n | 'running'\n | 'done'\n | 'failed'\n | 'waiting_approval'\n\n/** Define possible statuses representing the current state of a mission stream */\nexport type MissionStreamStatus =\n | 'scheduled'\n | 'running'\n | 'paused'\n | 'waiting_approval'\n | 'succeeded'\n | 'aborted'\n | 'cancelled'\n | 'failed'\n\n/**\n * Discriminated union of every live mission event. Every member carries\n * `missionId` (one channel may multiplex several missions) and a `type` the\n * client switches on. `at` is the emitter's wall-clock ms — used only for\n * display ordering; the reducer never trusts it for causality.\n */\nexport type MissionStreamEvent =\n | {\n type: 'mission.created'\n missionId: string\n at: number\n title: string\n status?: MissionStreamStatus\n steps: MissionStreamStep[]\n budgetUsd?: number | null\n }\n | { type: 'mission.started'; missionId: string; at: number }\n | { type: 'step.started'; missionId: string; at: number; stepId: string }\n | {\n type: 'step.updated'\n missionId: string\n at: number\n stepId: string\n sublabel?: string\n /**\n * Full CURRENT snapshot of the step's delegated runs — never a delta.\n * The reducer replaces the whole lane (latest snapshot wins by `at`), so\n * emitters re-send everything they know each time and at-least-once /\n * out-of-order delivery converges.\n */\n agentActivity?: StepAgentActivity[]\n }\n | {\n type: 'step.completed'\n missionId: string\n at: number\n stepId: string\n ok: boolean\n reason?: string\n durationMs?: number\n }\n | {\n type: 'cost.updated'\n missionId: string\n at: number\n spentUsd: number\n capUsd?: number | null\n }\n | { type: 'mission.paused'; missionId: string; at: number; reason?: string }\n | { type: 'mission.waiting_approval'; missionId: string; at: number; reason?: string }\n | { type: 'mission.resumed'; missionId: string; at: number }\n | {\n type: 'mission.plan.updated'\n missionId: string\n at: number\n title: string\n steps: MissionStreamStep[]\n budgetUsd?: number | null\n }\n | {\n type: 'mission.completed'\n missionId: string\n at: number\n ok: boolean\n status?: Extract<MissionStreamStatus, 'succeeded' | 'failed' | 'aborted' | 'cancelled'>\n summary?: string\n }\n\n// All mission event `type` discriminants. Used to cheaply reject the non-mission\n// events that share a channel without a full switch.\nconst MISSION_EVENT_TYPES: ReadonlySet<string> = new Set<MissionStreamEvent['type']>([\n 'mission.created',\n 'mission.started',\n 'step.started',\n 'step.updated',\n 'step.completed',\n 'cost.updated',\n 'mission.paused',\n 'mission.waiting_approval',\n 'mission.resumed',\n 'mission.plan.updated',\n 'mission.completed',\n])\n\n/**\n * Reconstruct the flat MissionStreamEvent from a broadcast envelope of shape\n * `{ type, data: { ...missionFields } }` (transports may also stamp routing\n * fields like workspaceId/threadId into `data`). The envelope `type` is the\n * AUTHORITATIVE discriminant set by the server, so it is spread LAST — a\n * payload that happens to carry a top-level `type` inside `data` cannot shadow\n * it and mis-render as a mission event. Non-mission envelopes and malformed\n * payloads return null and are simply skipped, so one channel can carry both\n * streams.\n */\nexport function parseSessionStreamEnvelope(raw: unknown): MissionStreamEvent | null {\n if (!raw || typeof raw !== 'object') return null\n const envelope = raw as { type?: unknown; data?: unknown }\n if (typeof envelope.type !== 'string') return null\n const data = envelope.data && typeof envelope.data === 'object' ? envelope.data : {}\n return asMissionStreamEvent({ ...(data as Record<string, unknown>), type: envelope.type })\n}\n\n/** Narrow an arbitrary channel payload to a MissionStreamEvent. Returns null\n * for non-mission events and anything malformed — the reducer skips those. */\nexport function asMissionStreamEvent(value: unknown): MissionStreamEvent | null {\n if (!value || typeof value !== 'object') return null\n const record = value as Record<string, unknown>\n const type = record.type\n if (typeof type !== 'string' || !MISSION_EVENT_TYPES.has(type)) return null\n if (typeof record.missionId !== 'string' || !record.missionId) return null\n // `at` is best-effort: this narrows the type but does not fill a default —\n // the reducer tolerates a missing `at` and treats it as 0.\n return value as MissionStreamEvent\n}\n\n/** Live per-step view the reducer maintains. `status` only ever moves FORWARD\n * (see STEP_RANK) so a duplicate/out-of-order event can never regress a step\n * from done back to running. */\nexport interface MissionStepState {\n id: string\n intent: string\n kind: string\n status: MissionStreamStepStatus\n sublabel?: string\n reason?: string\n durationMs?: number\n /** Latest delegated-run snapshot for the step (see `step.updated`). */\n agentActivity?: StepAgentActivity[]\n /** The `at` of the snapshot currently held — an older snapshot arriving\n * late never replaces a newer one. */\n agentActivityAt?: number\n}\n\n/** Live per-mission view the reducer folds events into. */\nexport interface MissionState {\n missionId: string\n title?: string\n status: MissionStreamStatus\n steps: MissionStepState[]\n spentUsd: number\n capUsd?: number | null\n pauseReason?: string\n summary?: string\n /** The largest `at` folded so far — purely for display; never gates folding. */\n lastEventAt: number\n /** The largest pause/resume control `at` folded — lets a newer resume beat an\n * older pause that arrives late. */\n lastControlAt?: number\n}\n\n// Monotonic rank for step status. A fold NEVER moves a step to a lower rank, so\n// a late/duplicate `step.started` arriving after `step.completed` is a no-op —\n// the reducer tolerates out-of-order and at-least-once delivery.\nconst STEP_RANK: Record<MissionStreamStepStatus, number> = {\n pending: 0,\n running: 1,\n waiting_approval: 2,\n // done and failed are both TERMINAL for a step; rank them equal-and-highest\n // so neither can be overwritten by the other or regressed to running.\n done: 3,\n failed: 3,\n}\n\n// Monotonic rank for mission status. Same forward-only guarantee: a stray\n// `mission.started` after `mission.completed` cannot un-finish a mission.\nconst MISSION_RANK: Record<MissionStreamStatus, number> = {\n scheduled: 0,\n running: 1,\n paused: 2,\n waiting_approval: 2,\n succeeded: 3,\n aborted: 3,\n cancelled: 3,\n failed: 3,\n}\n\nfunction maxStepStatus(\n current: MissionStreamStepStatus,\n next: MissionStreamStepStatus,\n): MissionStreamStepStatus {\n // Equal rank but different terminal kind (done vs failed): the FIRST terminal\n // wins — a step.completed is authoritative and a later contradicting one is a\n // duplicate-class artifact we ignore.\n if (STEP_RANK[next] <= STEP_RANK[current]) return current\n return next\n}\n\nfunction maxMissionStatus(\n current: MissionStreamStatus,\n next: MissionStreamStatus,\n): MissionStreamStatus {\n if (MISSION_RANK[next] <= MISSION_RANK[current]) return current\n return next\n}\n\nfunction emptyMission(missionId: string): MissionState {\n // Seed at the LOWEST status rank so a fold-in is purely monotonic. A\n // step.started that arrives before mission.created must not pin the mission\n // at `running`: were the create to carry `scheduled`, the reducer would then\n // diverge by arrival order (created-first → scheduled, started-first →\n // running). Seeding `scheduled` makes every status edge climb upward only, so\n // both orders converge to the same state.\n return {\n missionId,\n status: 'scheduled',\n steps: [],\n spentUsd: 0,\n lastEventAt: 0,\n lastControlAt: 0,\n }\n}\n\nfunction stepStateFrom(step: MissionStreamStep): MissionStepState {\n return { id: step.id, intent: step.intent, kind: step.kind, status: step.status }\n}\n\n/**\n * Fold one event into one mission's state. PURE: returns a new state, mutates\n * nothing. Idempotent + order-tolerant — every status move is clamped through\n * the monotonic ranks above, so duplicates and out-of-order delivery converge\n * to the same terminal state regardless of arrival order.\n */\nexport function applyMissionEvent(\n prev: MissionState | undefined,\n event: MissionStreamEvent,\n): MissionState {\n const at = typeof event.at === 'number' ? event.at : 0\n const base = prev ?? emptyMission(event.missionId)\n const lastEventAt = Math.max(base.lastEventAt, at)\n const lastControlAt = base.lastControlAt ?? 0\n\n switch (event.type) {\n case 'mission.created': {\n // The authoritative plan snapshot. Merge rather than overwrite so a\n // late-arriving create (e.g. after the client already saw a step.started\n // from a reconnect race) does not clobber forward step progress.\n const merged = event.steps.map((incoming) => {\n const existing = base.steps.find((s) => s.id === incoming.id)\n if (!existing) return stepStateFrom(incoming)\n return {\n ...existing,\n intent: incoming.intent,\n kind: incoming.kind,\n status: maxStepStatus(existing.status, incoming.status),\n }\n })\n // Keep any steps the client already knew that the snapshot omits (it\n // never should, but never drop known progress).\n for (const known of base.steps) {\n if (!merged.some((s) => s.id === known.id)) merged.push(known)\n }\n return {\n ...base,\n title: event.title || base.title,\n status: maxMissionStatus(base.status, event.status ?? base.status),\n capUsd: event.budgetUsd ?? base.capUsd,\n steps: merged,\n lastEventAt,\n }\n }\n\n case 'mission.started':\n return { ...base, status: maxMissionStatus(base.status, 'running'), lastEventAt }\n\n case 'step.started':\n return {\n ...base,\n steps: upsertStep(base.steps, event.stepId, (step) => ({\n ...step,\n status: maxStepStatus(step.status, 'running'),\n })),\n lastEventAt,\n }\n\n case 'step.updated':\n return {\n ...base,\n steps: upsertStep(base.steps, event.stepId, (step) => ({\n ...step,\n // A sublabel is a live counter (\"7/15\") — always take the latest; it\n // does not move status.\n ...(event.sublabel !== undefined ? { sublabel: event.sublabel } : {}),\n // agentActivity is a full snapshot, replaced wholesale. Guarded by\n // `at` so a stale snapshot delivered late never erases newer rows;\n // an equal-`at` replay rewrites identical content (idempotent).\n ...(event.agentActivity !== undefined && at >= (step.agentActivityAt ?? 0)\n ? { agentActivity: event.agentActivity, agentActivityAt: at }\n : {}),\n })),\n lastEventAt,\n }\n\n case 'step.completed':\n return {\n ...base,\n steps: upsertStep(base.steps, event.stepId, (step) => ({\n ...step,\n status: maxStepStatus(step.status, event.ok ? 'done' : 'failed'),\n ...(event.reason !== undefined ? { reason: event.reason } : {}),\n ...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),\n })),\n lastEventAt,\n }\n\n case 'cost.updated':\n return {\n ...base,\n // spentUsd is cumulative and monotonically non-decreasing at the\n // source; clamp so an out-of-order older value never lowers the\n // displayed spend.\n spentUsd: Math.max(base.spentUsd, event.spentUsd),\n capUsd: event.capUsd ?? base.capUsd,\n lastEventAt,\n }\n\n case 'mission.paused':\n if (at <= lastControlAt) return { ...base, lastEventAt }\n return {\n ...base,\n status: maxMissionStatus(base.status, 'paused'),\n ...(event.reason !== undefined ? { pauseReason: event.reason } : {}),\n lastEventAt,\n lastControlAt: Math.max(lastControlAt, at),\n }\n\n case 'mission.waiting_approval':\n if (at <= lastControlAt) return { ...base, lastEventAt }\n return {\n ...base,\n status: maxMissionStatus(base.status, 'waiting_approval'),\n ...(event.reason !== undefined ? { pauseReason: event.reason } : {}),\n lastEventAt,\n lastControlAt: Math.max(lastControlAt, at),\n }\n\n case 'mission.resumed':\n if (at <= lastControlAt) return { ...base, lastEventAt }\n return {\n ...base,\n status: isTerminalStreamStatus(base.status) ? base.status : 'running',\n pauseReason: undefined,\n lastEventAt,\n lastControlAt: Math.max(lastControlAt, at),\n }\n\n case 'mission.plan.updated':\n return {\n ...base,\n title: event.title || base.title,\n capUsd: event.budgetUsd ?? base.capUsd,\n steps: event.steps.map((incoming) => {\n const existing = base.steps.find((s) => s.id === incoming.id)\n if (!existing) return stepStateFrom(incoming)\n return {\n ...stepStateFrom(incoming),\n status: maxStepStatus(existing.status, incoming.status),\n ...(existing.sublabel !== undefined ? { sublabel: existing.sublabel } : {}),\n ...(existing.reason !== undefined ? { reason: existing.reason } : {}),\n ...(existing.durationMs !== undefined ? { durationMs: existing.durationMs } : {}),\n ...(existing.agentActivity !== undefined ? { agentActivity: existing.agentActivity } : {}),\n ...(existing.agentActivityAt !== undefined ? { agentActivityAt: existing.agentActivityAt } : {}),\n }\n }),\n lastEventAt,\n }\n\n case 'mission.completed':\n return {\n ...base,\n status: maxMissionStatus(base.status, event.status ?? (event.ok ? 'succeeded' : 'failed')),\n ...(event.summary !== undefined ? { summary: event.summary } : {}),\n lastEventAt,\n }\n }\n}\n\nfunction isTerminalStreamStatus(status: MissionStreamStatus): boolean {\n return status === 'succeeded' || status === 'failed' || status === 'aborted' || status === 'cancelled'\n}\n\n// Upsert one step by id, applying `patch`. A step unknown to the client (e.g. a\n// step.started arriving before the create snapshot on a reconnect) is created\n// as a minimal `pending` row so progress is never dropped; the create snapshot\n// fills in intent/kind when it arrives.\nfunction upsertStep(\n steps: MissionStepState[],\n stepId: string,\n patch: (step: MissionStepState) => MissionStepState,\n): MissionStepState[] {\n const index = steps.findIndex((s) => s.id === stepId)\n const existing = index < 0 ? undefined : steps[index]\n if (!existing) {\n const placeholder: MissionStepState = {\n id: stepId,\n intent: '',\n kind: '',\n status: 'pending',\n }\n return [...steps, patch(placeholder)]\n }\n const next = steps.slice()\n next[index] = patch(existing)\n return next\n}\n\n/**\n * Merge a loader SEED into the live state for one mission, advancing through\n * the SAME monotonic clamps the event reducer uses. The durable mission row is\n * the authoritative converged state: while the live channel is down the row\n * advances but the frozen live state does not, and nothing re-fires the gap to\n * a reconnecting client. Folding the seed THROUGH the clamps backfills that gap\n * on reconnect while never regressing a more-advanced live value:\n * - a stale seed for a more-advanced live mission is a no-op,\n * - an advanced seed after an outage fills the gap (status/steps/spend move\n * forward to the row's converged state).\n * `live === undefined` (mission unknown to the client) just adopts the seed.\n */\nexport function mergeMissionState(live: MissionState | undefined, seed: MissionState): MissionState {\n if (!live) return seed\n\n // The loader seed carries the authoritative current plan. Rebuild from it,\n // while preserving live progress fields for matching ids and retaining\n // live-only steps that have real progress evidence.\n const steps: MissionStepState[] = []\n for (const seededStep of seed.steps) {\n const current = live.steps.find((s) => s.id === seededStep.id)\n if (!current) {\n steps.push(seededStep)\n continue\n }\n steps.push({\n ...seededStep,\n intent: current.intent || seededStep.intent,\n kind: current.kind || seededStep.kind,\n status: maxStepStatus(current.status, seededStep.status),\n ...(current.sublabel !== undefined ? { sublabel: current.sublabel } : seededStep.sublabel !== undefined ? { sublabel: seededStep.sublabel } : {}),\n ...(current.reason !== undefined ? { reason: current.reason } : seededStep.reason !== undefined ? { reason: seededStep.reason } : {}),\n ...(current.durationMs !== undefined ? { durationMs: current.durationMs } : seededStep.durationMs !== undefined ? { durationMs: seededStep.durationMs } : {}),\n // The newer snapshot wins by its stamped `at`; an unstamped lane (loader\n // seed copied from the settled artifact) only fills an empty live lane.\n ...(mergeActivity(current, seededStep)),\n })\n }\n for (const current of live.steps) {\n if (seed.steps.some((step) => step.id === current.id)) continue\n if (hasStepProgressEvidence(current)) steps.push(current)\n }\n\n const status = mergeSeedMissionStatus(live.status, seed.status, live.lastControlAt ?? 0, seed.lastControlAt ?? 0)\n return {\n ...live,\n title: live.title ?? seed.title,\n status,\n steps,\n spentUsd: Math.max(live.spentUsd, seed.spentUsd),\n capUsd: live.capUsd ?? seed.capUsd,\n pauseReason: status === 'running' || status === 'scheduled' ? undefined : seed.pauseReason ?? live.pauseReason,\n summary: live.summary ?? seed.summary,\n lastEventAt: Math.max(live.lastEventAt, seed.lastEventAt),\n lastControlAt: Math.max(live.lastControlAt ?? 0, seed.lastControlAt ?? 0),\n }\n}\n\nfunction hasStepProgressEvidence(step: MissionStepState): boolean {\n return step.status !== 'pending' ||\n step.sublabel !== undefined ||\n step.reason !== undefined ||\n step.durationMs !== undefined ||\n step.agentActivity !== undefined\n}\n\n// Pick the activity lane to keep when merging a loader seed into live state:\n// the snapshot with the larger `at` wins; ties keep the live side.\nfunction mergeActivity(\n live: MissionStepState,\n seeded: MissionStepState,\n): Pick<MissionStepState, 'agentActivity' | 'agentActivityAt'> {\n const winner = (seeded.agentActivityAt ?? 0) > (live.agentActivityAt ?? 0) ? seeded : live\n if (winner.agentActivity === undefined) {\n const fallback = winner === live ? seeded : live\n if (fallback.agentActivity === undefined) return {}\n return {\n agentActivity: fallback.agentActivity,\n ...(fallback.agentActivityAt !== undefined ? { agentActivityAt: fallback.agentActivityAt } : {}),\n }\n }\n return {\n agentActivity: winner.agentActivity,\n ...(winner.agentActivityAt !== undefined ? { agentActivityAt: winner.agentActivityAt } : {}),\n }\n}\n\nfunction mergeSeedMissionStatus(\n liveStatus: MissionStreamStatus,\n seedStatus: MissionStreamStatus,\n liveControlAt: number,\n seedControlAt: number,\n): MissionStreamStatus {\n if (isTerminalStreamStatus(liveStatus)) return liveStatus\n if (isTerminalStreamStatus(seedStatus)) return seedStatus\n if ((seedStatus === 'paused' || seedStatus === 'waiting_approval') && liveControlAt > seedControlAt) {\n return liveStatus\n }\n if (seedStatus === 'running') return 'running'\n if (seedStatus === 'scheduled' && liveStatus !== 'scheduled') return liveStatus\n return maxMissionStatus(liveStatus, seedStatus)\n}\n\n/**\n * Fold a whole event sequence into a Map<missionId, MissionState>. PURE and\n * order-tolerant: feeding the same events in any order (with duplicates)\n * converges to the same map. `seed` lets a reload start from loader-rehydrated\n * state before live events arrive.\n */\nexport function reduceMissionEvents(\n events: MissionStreamEvent[],\n seed?: Map<string, MissionState>,\n): Map<string, MissionState> {\n const next = new Map<string, MissionState>(seed ?? [])\n for (const event of events) {\n next.set(event.missionId, applyMissionEvent(next.get(event.missionId), event))\n }\n return next\n}\n","/**\n * Mission execution engine — drives one mission's plan to completion under a\n * SINGLE serialized owner (a Cloudflare Workflow, a Durable Object alarm, a\n * queue consumer — one per mission). The owner wraps each `runStep` call in its\n * durable-step primitive (e.g. Workflows `step.do(step.id, …)`) so a completed\n * step's result is persisted and replayed instead of re-run after a mid-run\n * restart. This module holds the logic that must be correct independent of any\n * runtime, so it is injectable and unit-testable with the dispatch mocked.\n *\n * Idempotency is layered, belt-and-suspenders:\n * 1. The owner's durable-step cache replays a completed step's result.\n * 2. `runStep` re-reads the mission first; a step already `done` (with a\n * resultRef) returns the cached pointer WITHOUT re-dispatching — this\n * closes the at-least-once window where a callback re-runs after the\n * side effect committed but before the owner durably recorded it.\n * 3. The cursor advances only after a step is `done`, so a fresh run resumes\n * from `mission.cursor` and never re-touches earlier steps.\n *\n * Seams (the product supplies domain; the engine owns mechanism):\n * - {@link SandboxDispatch} — how a step actually executes.\n * - {@link MissionEngineOptions.estimateStepCostUsd} — per-step USD estimate.\n * - {@link MissionGateOptions.classifyStep} — which steps need approval.\n * - {@link MissionApprovalsPort} — where gate proposals live and how they\n * resolve.\n */\n\nimport type {\n MissionCostLedger,\n MissionOutcome,\n MissionRecord,\n MissionService,\n MissionStatus,\n MissionStep,\n} from './service'\nimport { isMissionStopRequested, isMissionTerminal } from './service'\nimport { noopEventSink, type MissionEventSink, type MissionStreamEvent } from './events'\n\n/**\n * A side-effecting unit of per-step work. The owner supplies the real\n * implementation (e.g. a detached sandbox-session dispatcher); tests supply a\n * mock. MUST return a SMALL pointer — large output is written to the product's\n * storage and only the resultRef is returned.\n */\nexport type SandboxDispatch = (input: SandboxDispatchInput) => Promise<SandboxDispatchResult>\n\n/** Define input parameters for dispatching a mission step in the sandbox environment */\nexport interface SandboxDispatchInput {\n mission: MissionRecord\n step: MissionStep\n stepIndex: number\n}\n\n/** Define the result of a completed sandbox dispatch including artifact reference and optional cost details */\nexport interface SandboxDispatchDoneResult {\n kind?: 'done'\n /** Small pointer at the produced artifact/output (vault path, asset id, exec\n * digest). Stored on the step as `resultRef`; never the full payload. */\n resultRef: string\n /** Optional one-line status surfaced on the step row. */\n sublabel?: string\n /** Optional marginal spend for this step. `ledgerDelta` carries platform-\n * reported truth (real token counts, wall time); `deltaUsd` is set ONLY when\n * a provider-authored price is known. Omit fields rather than synthesizing\n * zeros — the engine substitutes its injected per-step estimate for a\n * missing deltaUsd and records that estimate in the ledger. */\n cost?: {\n deltaUsd?: number\n ledgerDelta?: Partial<MissionCostLedger>\n }\n}\n\n/** The dispatched step's detached session is still executing on the platform.\n * The owner sleeps `pollAfterMs` and re-invokes the step; the dispatch is\n * idempotent on the session ref, so the re-invocation settles the same session\n * rather than starting a second run. */\nexport interface SandboxDispatchInProgressResult {\n kind: 'in_progress'\n sessionRef: string\n pollAfterMs: number\n sublabel?: string\n}\n\n/** Resolve the result of a sandbox dispatch as done or in progress */\nexport type SandboxDispatchResult = SandboxDispatchDoneResult | SandboxDispatchInProgressResult\n\n/** Outcome of running a single step. `cached` distinguishes a replay/skip\n * (step was already done) from a fresh execution so the engine and its tests\n * can assert the dispatch was NOT re-invoked. */\nexport type StepOutcome =\n | { kind: 'done'; resultRef: string; cached: boolean }\n | { kind: 'in_progress'; sessionRef: string; pollAfterMs: number; sublabel?: string }\n | { kind: 'skipped-cursor'; reason: string }\n | { kind: 'failed'; error: string; fatal: boolean }\n\n/** Outcome of running the whole plan from the cursor to the end. */\nexport type PlanOutcome =\n | { kind: 'completed'; summary: string }\n | { kind: 'in_progress'; stepId: string; sessionRef: string; pollAfterMs: number; sublabel?: string }\n | { kind: 'failed'; failedStepId: string; error: string }\n | { kind: 'halted'; status: MissionStatus; reason?: string | null }\n | { kind: 'terminal'; status: MissionStatus }\n | { kind: 'not-found' }\n\ntype StepGateOutcome =\n | { kind: 'continue' }\n | { kind: 'halted'; status: MissionStatus; reason: string }\n\n/** Define options to control mission plan execution with optional pre-step veto logic */\nexport interface MissionPlanRunOptions {\n /** Pre-step veto (kill switch, schedule window). A non-null return pauses the\n * mission with that reason before the step's side effect starts. */\n beforeStep?: (mission: MissionRecord, step: MissionStep) => Promise<string | null>\n}\n\n/** Thrown to make the owner's durable-step wrapper retry. The single-owner\n * invariant makes a genuine concurrent change rare (it means another writer\n * touched the row), so retrying — rather than corrupting state by forcing a\n * stale write — is the correct response. Distinct from a task failure, which\n * is recorded on the step. */\nexport class MissionConcurrencyError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'MissionConcurrencyError'\n }\n}\n\n/** Thrown by a {@link SandboxDispatch} for a TRANSIENT failure (platform blip,\n * exec-time network fault) that should be re-attempted. `runStep` RE-THROWS it\n * so the owner engages its bounded retry+backoff; the step is left `running`\n * and the re-dispatch is made idempotent by the cached-done guard. A\n * deterministic failure must be a plain Error instead — that is recorded as a\n * fatal `failed` step and is never retried (no money-burning loop on a\n * deterministic error). */\nexport class RetryableStepError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'RetryableStepError'\n }\n}\n\n/** Resolution states a gate proposal can be in. `approved`/`executed` unblock\n * the gated step; everything else keeps the mission parked. */\nexport type MissionProposalResolution = 'pending' | 'approved' | 'rejected' | 'executed' | 'ignored'\n\n/** Define mission gate categories as step, budget, or volume */\nexport type MissionGateKind = 'step' | 'budget' | 'volume'\n\n/** Product classification of one step. Returned by\n * {@link MissionGateOptions.classifyStep}; the matching rules (regexes, intent\n * vocabularies, path allowlists) are product domain and never live here. */\nexport interface StepGateClassification {\n /** Product approval-type label persisted on the proposal ('generate',\n * 'integration_invoke', …). */\n type: string\n /** Counted against the per-mission external-action volume cap. */\n externalAction?: boolean\n estCostUsd?: number | null\n}\n\n/** A gate proposal the engine asks the product to persist. The id is\n * deterministic per (gate, mission, step) — see the `*ProposalId` helpers —\n * so a replay re-finds the same proposal instead of duplicating it. The\n * product composes its own title/description from the structured fields. */\nexport interface MissionGateProposal {\n id: string\n missionId: string\n stepId: string\n gate: MissionGateKind\n mission: MissionRecord\n step: MissionStep\n /** Present for `gate: 'step'` — the classification that triggered the gate. */\n classification?: StepGateClassification\n /** Present for `gate: 'budget'`. */\n budget?: { spentUsd: number; budgetUsd: number; estimatedCostUsd: number }\n /** Present for `gate: 'volume'`. */\n volume?: { externalActionCount: number; cap: number }\n}\n\n/** Approval persistence seam — the product implements this over its own\n * proposal table and resolution flow. */\nexport interface MissionApprovalsPort {\n /** Resolution of the proposal with this id, or null when none exists. */\n findResolution(proposalId: string): Promise<MissionProposalResolution | null>\n /** Persist a new gate proposal (id is deterministic; called at most once per\n * (gate, mission, step) absent a resolution). */\n createProposal(proposal: MissionGateProposal): Promise<void>\n /** Count of this mission's `gate: 'step'` proposals whose classification was\n * `externalAction: true` — the denominator of the volume cap. */\n countExternalActionProposals(missionId: string): Promise<number>\n}\n\n/** Define configuration options for mission gating including approvals, step classification, and action limits */\nexport interface MissionGateOptions {\n approvals: MissionApprovalsPort\n /** Which steps need human approval, and as what. Return null for an ungated\n * step. The rules are product domain (intent regexes, kind tables). */\n classifyStep: (step: MissionStep) => StepGateClassification | null\n /** Max external-action approvals per mission before an approved override is\n * required to request another. Default 5. */\n externalActionCap?: number\n}\n\n/** Define configuration options for initializing and controlling the mission engine behavior */\nexport interface MissionEngineOptions {\n service: MissionService\n /** Per-step USD estimate. Load-bearing twice: the budget gate parks on it\n * BEFORE a step runs, and the engine records it as the step's spend when the\n * dispatch reports no provider-authored price — using one estimator keeps\n * spend and gate consistent. */\n estimateStepCostUsd: (step: MissionStep) => number\n /** Best-effort live notifier. Fired AFTER each guarded write commits, so a\n * broadcast always reflects persisted state; re-fired on idempotent replays\n * so a reconnecting client converges. Never awaited; a throwing sink can\n * never fail a step. Default: drop everything. */\n sink?: MissionEventSink\n /** Approval gating. Omitted → no classification/volume gates, and a budget\n * overrun pauses the mission (fail closed) instead of parking it\n * waiting_approval behind an override proposal. */\n gates?: MissionGateOptions\n /** Step kinds whose failure does NOT abort the whole mission — enrichment\n * steps the plan can complete without. Every other kind is fatal-on-failure.\n * Default `['optional', 'best-effort']`. */\n nonFatalStepKinds?: readonly string[]\n}\n\n/** Resolve mission plan steps with concurrency control and durable state management */\nexport interface MissionEngine {\n /** Run exactly one plan step. Idempotent: re-invoking for a step already\n * `done` returns the cached pointer without re-dispatching. A lost guarded\n * race throws {@link MissionConcurrencyError} so the owner's durable-step\n * wrapper retries instead of writing a stale value. */\n runStep(missionId: string, stepId: string, dispatch: SandboxDispatch): Promise<StepOutcome>\n /** Walk the plan from the durable cursor to the end, re-reading the mission\n * between steps so a pause/stop control that lands while a step is running\n * is honored before the next side effect. `runStep` is the owner's boundary:\n * in production `(step) => durableStep.do(step.id, () => engine.runStep(…))`;\n * in tests `engine.runStep` directly. */\n runPlan(\n missionId: string,\n runStep: (step: MissionStep, stepIndex: number) => Promise<StepOutcome>,\n options?: MissionPlanRunOptions,\n ): Promise<PlanOutcome>\n /** Record spend durable-first, live second: the guarded ledger write commits,\n * then the sink sees the new total. A guarded failure returns unchanged. */\n recordCost(\n missionId: string,\n deltaUsd: number,\n ledgerDelta?: Partial<MissionCostLedger>,\n ): Promise<MissionOutcome<MissionRecord>>\n /** Pause durable-first, live second (the paused event fires only on a real\n * edge, not an idempotent re-pause). */\n pauseMission(missionId: string, reason: string): Promise<MissionOutcome<MissionRecord>>\n}\n\nconst DEFAULT_EXTERNAL_ACTION_CAP = 5\nconst DEFAULT_NON_FATAL_STEP_KINDS: readonly string[] = ['optional', 'best-effort']\n\n/** Deterministic proposal id for a step-classification gate. */\nexport function stepGateProposalId(missionId: string, stepId: string): string {\n return `mission-step-gate:${missionId}:${stepId}`\n}\n\n/** Deterministic proposal id for a budget-overrun override. */\nexport function budgetGateProposalId(missionId: string, stepId: string): string {\n return `mission-budget-gate:${missionId}:${stepId}`\n}\n\n/** Deterministic proposal id for an external-action volume-cap override. */\nexport function volumeGateProposalId(missionId: string, stepId: string): string {\n return `mission-volume-gate:${missionId}:${stepId}`\n}\n\n// Emit a live event without letting the sink's failure touch the step path.\n// The engine owns the fire-and-forget guarantee at this boundary: a sink that\n// throws synchronously can NEVER fail a step. The durable audit row is the\n// authoritative timeline; the live channel is a convenience.\nfunction safeEmit(sink: MissionEventSink, event: MissionStreamEvent): void {\n try {\n sink.emit(event)\n } catch {\n // Best-effort UI notification — a broadcast fault never fails the step.\n }\n}\n\nfunction unblocked(resolution: MissionProposalResolution | null): boolean {\n return resolution === 'approved' || resolution === 'executed'\n}\n\n// Map a durable terminal status onto the live `mission.completed` event. Only\n// `succeeded` is ok=true; aborted/cancelled/failed fold to ok=false with the\n// terminal status preserved so the reducer keeps user stops distinct from\n// failures.\nfunction terminalMissionEvent(\n missionId: string,\n status: MissionStatus,\n): Extract<MissionStreamEvent, { type: 'mission.completed' }> {\n const terminal = status === 'succeeded' || status === 'failed' || status === 'aborted' || status === 'cancelled'\n ? status\n : 'failed'\n return {\n type: 'mission.completed',\n missionId,\n at: Date.now(),\n ok: terminal === 'succeeded',\n status: terminal,\n ...(terminal === 'succeeded' ? {} : { summary: `Mission ${status}` }),\n }\n}\n\n/** Create a mission engine configured with options to manage mission execution and error handling */\nexport function createMissionEngine(options: MissionEngineOptions): MissionEngine {\n const { service, estimateStepCostUsd, gates } = options\n const sink = options.sink ?? noopEventSink\n const nonFatalStepKinds = new Set(options.nonFatalStepKinds ?? DEFAULT_NON_FATAL_STEP_KINDS)\n const externalActionCap = gates?.externalActionCap ?? DEFAULT_EXTERNAL_ACTION_CAP\n\n function isFatalStepKind(kind: string): boolean {\n return !nonFatalStepKinds.has(kind)\n }\n\n // A guarded service rejection is one of two things:\n // - a lost race (conflict: true) — THROW so the owner's durable-step\n // wrapper retries; the single owner makes this rare and a retry re-reads\n // fresh state rather than forcing a stale write.\n // - a logic rejection (illegal transition, missing step) — that step is\n // structurally broken; return a fatal `failed` outcome instead of\n // looping a retry forever (no money-burning retry on a deterministic\n // error).\n function rejectStep(failure: { error: string; conflict: boolean }): StepOutcome {\n if (failure.conflict) throw new MissionConcurrencyError(failure.error)\n return { kind: 'failed', error: failure.error, fatal: true }\n }\n\n function isStepCurrentOrFuture(mission: MissionRecord, stepId: string): boolean {\n const index = mission.plan.findIndex((candidate) => candidate.id === stepId)\n return index >= mission.cursor\n }\n\n const recordCost: MissionEngine['recordCost'] = async (missionId, deltaUsd, ledgerDelta) => {\n const recorded = await service.addCost(missionId, deltaUsd, ledgerDelta)\n if (!recorded.succeeded) return recorded\n safeEmit(sink, {\n type: 'cost.updated',\n missionId,\n at: Date.now(),\n spentUsd: recorded.value.spentUsd,\n capUsd: recorded.value.budgetUsd,\n })\n return recorded\n }\n\n const pauseMission: MissionEngine['pauseMission'] = async (missionId, reason) => {\n const before = await service.getMission(missionId)\n const paused = await service.pause(missionId, reason)\n if (!paused.succeeded) return paused\n if (before?.status !== 'paused') {\n safeEmit(sink, {\n type: 'mission.paused',\n missionId,\n at: Date.now(),\n reason: paused.value.pauseReason ?? reason,\n })\n }\n return paused\n }\n\n const runStep: MissionEngine['runStep'] = async (missionId, stepId, dispatch) => {\n const mission = await service.getMission(missionId)\n if (!mission) throw new MissionConcurrencyError(`Mission ${missionId} not found mid-run`)\n\n const stepIndex = mission.plan.findIndex((candidate) => candidate.id === stepId)\n const step = stepIndex < 0 ? undefined : mission.plan[stepIndex]\n if (!step) {\n return { kind: 'skipped-cursor', reason: `Step ${stepId} is no longer in mission plan` }\n }\n if (isMissionStopRequested(mission)) {\n return { kind: 'failed', error: mission.pauseReason ?? 'Mission stop requested', fatal: true }\n }\n if (mission.status !== 'running') {\n return { kind: 'skipped-cursor', reason: mission.pauseReason ?? `Mission is ${mission.status}` }\n }\n\n // (2) Idempotent short-circuit: a step that already reached `done` keeps\n // its resultRef. Return it WITHOUT re-running the side effect. Reconcile\n // the cursor first: a crash in the window between setStepStatus('done')\n // and advanceCursor leaves the cursor one slot behind this done step; a\n // fresh resume would otherwise end with cursor = plan.length - 1. Advance\n // it here (guarded — a lost race throws for the owner to retry) so the\n // cursor stays a true done-count.\n if (step.status === 'done' && step.resultRef) {\n if (stepIndex === mission.cursor) {\n const reconciled = await service.advanceCursor(missionId)\n if (!reconciled.succeeded) return rejectStep(reconciled)\n }\n // Re-emit the terminal event on replay so a client that connected after\n // the step finished still folds it (idempotent at the reducer).\n safeEmit(sink, { type: 'step.completed', missionId, at: Date.now(), stepId, ok: true })\n return { kind: 'done', resultRef: step.resultRef, cached: true }\n }\n\n // (3) The cursor has already moved past this step but it is not `done` —\n // the owner is replaying a step the engine no longer considers active.\n // Skip it rather than re-run; the cursor is the source of truth.\n if (stepIndex < mission.cursor) {\n return { kind: 'skipped-cursor', reason: `Step ${stepId} is behind cursor ${mission.cursor}` }\n }\n\n const startedAt = Date.now()\n if (step.status !== 'running') {\n const running = await service.setStepStatus(missionId, stepId, 'running')\n if (!running.succeeded) return rejectStep(running)\n }\n safeEmit(sink, { type: 'step.started', missionId, at: startedAt, stepId })\n\n let dispatched: SandboxDispatchResult\n try {\n dispatched = await dispatch({ mission, step, stepIndex })\n } catch (error) {\n // A TRANSIENT failure re-throws so the owner engages its bounded\n // retry+backoff: leave the step `running` (the re-dispatch is idempotent\n // via the cached-done guard) and let the owner re-attempt up to its limit.\n if (error instanceof RetryableStepError) throw error\n const latest = await service.getMission(missionId)\n if (\n latest &&\n !isMissionTerminal(latest.status) &&\n !isMissionStopRequested(latest) &&\n !isStepCurrentOrFuture(latest, stepId)\n ) {\n return { kind: 'skipped-cursor', reason: `Step ${stepId} is no longer active` }\n }\n // A deterministic failure is recorded as a fatal `failed` step and is\n // NOT retried — looping a retry on a deterministic error burns money.\n const message = error instanceof Error ? error.message : 'Sandbox dispatch failed'\n const failed = await service.setStepStatus(missionId, stepId, 'failed', { error: message })\n if (!failed.succeeded) return rejectStep(failed)\n safeEmit(sink, {\n type: 'step.completed',\n missionId,\n at: Date.now(),\n stepId,\n ok: false,\n reason: message,\n durationMs: Date.now() - startedAt,\n })\n return { kind: 'failed', error: message, fatal: isFatalStepKind(step.kind) }\n }\n\n const afterDispatch = await service.getMission(missionId)\n if (!afterDispatch) throw new MissionConcurrencyError(`Mission ${missionId} not found after dispatch`)\n if (isMissionTerminal(afterDispatch.status) || isMissionStopRequested(afterDispatch)) {\n return { kind: 'failed', error: afterDispatch.pauseReason ?? 'Mission stop requested', fatal: true }\n }\n if (afterDispatch.status !== 'running') {\n return { kind: 'skipped-cursor', reason: afterDispatch.pauseReason ?? `Mission is ${afterDispatch.status}` }\n }\n if (!isStepCurrentOrFuture(afterDispatch, stepId)) {\n return { kind: 'skipped-cursor', reason: `Step ${stepId} is no longer active` }\n }\n\n // The detached session is still running: surface the elapsed sublabel and\n // hand the poll cadence to the owner. The step row stays `running`.\n if (dispatched.kind === 'in_progress') {\n if (dispatched.sublabel !== undefined) {\n safeEmit(sink, { type: 'step.updated', missionId, at: Date.now(), stepId, sublabel: dispatched.sublabel })\n }\n return {\n kind: 'in_progress',\n sessionRef: dispatched.sessionRef,\n pollAfterMs: dispatched.pollAfterMs,\n ...(dispatched.sublabel === undefined ? {} : { sublabel: dispatched.sublabel }),\n }\n }\n\n // A live counter (\"7/15\") the dispatch surfaced — push it before the\n // terminal event so a long step shows progress, then settles to done.\n if (dispatched.sublabel !== undefined) {\n safeEmit(sink, { type: 'step.updated', missionId, at: Date.now(), stepId, sublabel: dispatched.sublabel })\n }\n\n // USD: provider-authored price when the dispatch reports one, the injected\n // estimate otherwise (the budget gate runs on the same estimate, so spend\n // and gate stay consistent). Token counts/wall time come from the\n // dispatch's ledgerDelta when the platform reported them. The spend is\n // folded into the SAME guarded pending->done write below so step completion\n // is the per-step idempotency key: a RESUME re-dispatching an already-done\n // step short-circuits as a no-op and never charges twice.\n const deltaUsd = dispatched.cost?.deltaUsd ?? estimateStepCostUsd(step)\n const chargeable = deltaUsd > 0 || Boolean(dispatched.cost?.ledgerDelta)\n const spentBefore = afterDispatch.spentUsd\n\n const done = await service.setStepStatus(missionId, stepId, 'done', {\n resultRef: dispatched.resultRef,\n ...(dispatched.sublabel === undefined ? {} : { sublabel: dispatched.sublabel }),\n ...(chargeable\n ? {\n cost: {\n deltaUsd,\n ledgerDelta: {\n costUsd: deltaUsd,\n llmCalls: 1,\n ...(dispatched.cost?.ledgerDelta ?? {}),\n },\n },\n }\n : {}),\n })\n if (!done.succeeded) return rejectStep(done)\n // Emit cost.updated only when the spend actually committed (a real\n // transition, not a replayed no-op that re-asserted an already-done step).\n if (done.value.spentUsd !== spentBefore) {\n safeEmit(sink, {\n type: 'cost.updated',\n missionId,\n at: Date.now(),\n spentUsd: done.value.spentUsd,\n capUsd: done.value.budgetUsd,\n })\n }\n safeEmit(sink, {\n type: 'step.completed',\n missionId,\n at: Date.now(),\n stepId,\n ok: true,\n durationMs: Date.now() - startedAt,\n })\n\n // Advance the cursor only after the step is durably `done`. A racing\n // advance (single owner ⇒ should not happen) surfaces as a retry.\n const advanced = await service.advanceCursor(missionId)\n if (!advanced.succeeded) return rejectStep(advanced)\n\n return { kind: 'done', resultRef: dispatched.resultRef, cached: false }\n }\n\n async function parkForApproval(\n mission: MissionRecord,\n step: MissionStep,\n reason: string,\n ): Promise<StepGateOutcome> {\n const waiting = await service.markWaitingApproval(mission.id, step.id)\n if (!waiting.succeeded) {\n if (waiting.conflict) throw new MissionConcurrencyError(waiting.error)\n return { kind: 'halted', status: mission.status, reason: waiting.error }\n }\n safeEmit(sink, {\n type: 'mission.waiting_approval',\n missionId: mission.id,\n at: Date.now(),\n reason,\n })\n safeEmit(sink, {\n type: 'mission.plan.updated',\n missionId: mission.id,\n at: Date.now(),\n title: waiting.value.summary ?? 'Mission',\n steps: waiting.value.plan.map((candidate) => ({\n id: candidate.id,\n intent: candidate.intent,\n kind: candidate.kind,\n status: candidate.status,\n })),\n budgetUsd: waiting.value.budgetUsd,\n })\n return { kind: 'halted', status: 'waiting_approval', reason }\n }\n\n // A budgeted mission never starts a step whose estimate would push spend\n // past the cap. With an approvals port the overrun parks behind a\n // deterministic override proposal; without one it pauses (fail closed) for a\n // manual budget raise + resume.\n async function enforceBudget(mission: MissionRecord, step: MissionStep): Promise<StepGateOutcome> {\n const capUsd = mission.budgetUsd\n if (capUsd === null) return { kind: 'continue' }\n const estimatedCostUsd = estimateStepCostUsd(step)\n if (estimatedCostUsd <= 0) return { kind: 'continue' }\n // Compare in integer cents: USD amounts accumulate binary-float error (e.g.\n // 0.1 + 0.2 > 0.3), so a raw float compare can spuriously trip the gate at\n // the exact cap. Cent-rounding makes the boundary deterministic.\n if (Math.round((mission.spentUsd + estimatedCostUsd) * 100) <= Math.round(capUsd * 100)) {\n return { kind: 'continue' }\n }\n\n if (!gates) {\n const reason = `Budget cap reached before step ${step.id}: $${mission.spentUsd.toFixed(2)} spent of $${capUsd.toFixed(2)}, next step estimated $${estimatedCostUsd.toFixed(2)}`\n const paused = await pauseMission(mission.id, reason)\n if (!paused.succeeded) {\n if (paused.conflict) throw new MissionConcurrencyError(paused.error)\n return { kind: 'halted', status: mission.status, reason: paused.error }\n }\n return { kind: 'halted', status: paused.value.status, reason }\n }\n\n const proposalId = budgetGateProposalId(mission.id, step.id)\n const resolution = await gates.approvals.findResolution(proposalId)\n if (unblocked(resolution)) return { kind: 'continue' }\n if (resolution === null) {\n await gates.approvals.createProposal({\n id: proposalId,\n missionId: mission.id,\n stepId: step.id,\n gate: 'budget',\n mission,\n step,\n budget: { spentUsd: mission.spentUsd, budgetUsd: capUsd, estimatedCostUsd },\n })\n }\n return parkForApproval(mission, step, `Budget approval required for step ${step.id}`)\n }\n\n async function enforceVolumeCap(mission: MissionRecord, step: MissionStep): Promise<StepGateOutcome> {\n if (!gates) return { kind: 'continue' }\n const overrideId = volumeGateProposalId(mission.id, step.id)\n const override = await gates.approvals.findResolution(overrideId)\n if (unblocked(override)) return { kind: 'continue' }\n\n const externalCount = await gates.approvals.countExternalActionProposals(mission.id)\n if (externalCount < externalActionCap) return { kind: 'continue' }\n\n if (override === null) {\n await gates.approvals.createProposal({\n id: overrideId,\n missionId: mission.id,\n stepId: step.id,\n gate: 'volume',\n mission,\n step,\n volume: { externalActionCount: externalCount, cap: externalActionCap },\n })\n }\n return parkForApproval(mission, step, `External action cap approval required for step ${step.id}`)\n }\n\n async function enforceStepGate(mission: MissionRecord, step: MissionStep): Promise<StepGateOutcome> {\n if (!gates) return { kind: 'continue' }\n const classification = gates.classifyStep(step)\n if (!classification) return { kind: 'continue' }\n\n if (classification.externalAction) {\n const volume = await enforceVolumeCap(mission, step)\n if (volume.kind === 'halted') return volume\n }\n\n const proposalId = stepGateProposalId(mission.id, step.id)\n const resolution = await gates.approvals.findResolution(proposalId)\n if (unblocked(resolution)) return { kind: 'continue' }\n\n if (resolution === null) {\n await gates.approvals.createProposal({\n id: proposalId,\n missionId: mission.id,\n stepId: step.id,\n gate: 'step',\n mission,\n step,\n classification,\n })\n }\n return parkForApproval(mission, step, `Approval required for step ${step.id}`)\n }\n\n const runPlan: MissionEngine['runPlan'] = async (missionId, runStepFn, planOptions = {}) => {\n const mission = await service.getMission(missionId)\n if (!mission) return { kind: 'not-found' }\n if (isMissionTerminal(mission.status)) {\n // Already terminal on entry (a replay after the row finished, or a\n // resume of an aborted/cancelled run). Re-emit the terminal event so a\n // client that connected during an outage still converges off this run.\n // Idempotent at the reducer.\n safeEmit(sink, terminalMissionEvent(missionId, mission.status))\n return { kind: 'terminal', status: mission.status }\n }\n\n // Resume from the durable cursor. Re-read the mission between steps so a\n // pause/stop control that lands while a step is running is honored before\n // the next side effect starts.\n while (true) {\n const currentMission = await service.getMission(missionId)\n if (!currentMission) return { kind: 'not-found' }\n if (isMissionTerminal(currentMission.status)) {\n safeEmit(sink, terminalMissionEvent(missionId, currentMission.status))\n return { kind: 'terminal', status: currentMission.status }\n }\n if (isMissionStopRequested(currentMission)) {\n return {\n kind: 'halted',\n status: currentMission.status,\n reason: currentMission.pauseReason ?? 'Mission stop requested',\n }\n }\n if (currentMission.status !== 'running') {\n return {\n kind: 'halted',\n status: currentMission.status,\n reason: currentMission.pauseReason,\n }\n }\n\n const index = currentMission.cursor\n if (index >= currentMission.plan.length) break\n const step = currentMission.plan[index]\n if (!step) break\n\n const haltReason = await planOptions.beforeStep?.(currentMission, step)\n if (haltReason) {\n const paused = await pauseMission(missionId, haltReason)\n if (!paused.succeeded) throw new MissionConcurrencyError(paused.error)\n return { kind: 'halted', status: paused.value.status, reason: paused.value.pauseReason ?? haltReason }\n }\n\n if (step.status !== 'done') {\n const budget = await enforceBudget(currentMission, step)\n if (budget.kind === 'halted') {\n return { kind: 'halted', status: budget.status, reason: budget.reason }\n }\n const gate = await enforceStepGate(currentMission, step)\n if (gate.kind === 'halted') {\n return { kind: 'halted', status: gate.status, reason: gate.reason }\n }\n }\n\n const outcome = await runStepFn(step, index)\n if (outcome.kind === 'failed' && outcome.fatal) {\n // The owner flips the mission row to `failed` on this outcome; emit the\n // live terminal event here so a watcher sees the failure without\n // waiting for the next loader poll. Idempotent at the reducer.\n safeEmit(sink, {\n type: 'mission.completed',\n missionId,\n at: Date.now(),\n ok: false,\n summary: `Step ${step.id} failed: ${outcome.error}`,\n })\n return { kind: 'failed', failedStepId: step.id, error: outcome.error }\n }\n if (outcome.kind === 'failed' && !outcome.fatal) {\n const advanced = await service.advanceCursor(missionId)\n if (!advanced.succeeded) throw new MissionConcurrencyError(advanced.error)\n }\n if (outcome.kind === 'in_progress') {\n return {\n kind: 'in_progress',\n stepId: step.id,\n sessionRef: outcome.sessionRef,\n pollAfterMs: outcome.pollAfterMs,\n ...(outcome.sublabel === undefined ? {} : { sublabel: outcome.sublabel }),\n }\n }\n // Cursor-skips fall through: another pass already moved past it. The next\n // loop re-reads the authoritative cursor and plan.\n }\n\n const finalMission = await service.getMission(missionId)\n const planLength = finalMission?.plan.length ?? 0\n const summary = `Completed ${planLength} step${planLength === 1 ? '' : 's'}`\n const completed = await service.complete(missionId, { ok: true, summary })\n if (!completed.succeeded) {\n // complete() is guarded; a concurrent terminal transition (abort/cancel)\n // wins the race. Re-read to report the real terminal status rather than\n // claim a completion that did not land.\n const after = await service.getMission(missionId)\n if (after && isMissionTerminal(after.status)) {\n safeEmit(sink, terminalMissionEvent(missionId, after.status))\n return { kind: 'terminal', status: after.status }\n }\n throw new MissionConcurrencyError(completed.error)\n }\n safeEmit(sink, { type: 'mission.completed', missionId, at: Date.now(), ok: true, summary })\n return { kind: 'completed', summary }\n }\n\n return { runStep, runPlan, recordCost, pauseMission }\n}\n","/**\n * Parsing for the agent-authored `:::mission` block — the bridge from a chat\n * prompt contract to the engine's MissionStep[] shape. The block format:\n *\n * :::mission\n * title: <mission title>\n * <id>: <kind> | <intent>\n * :::\n *\n * The allowed kind vocabulary is a PARAMETER — products pass their own list to\n * match their prompt directive; {@link DEFAULT_MISSION_STEP_KINDS} is the\n * default. Kinds label intent for gating and UX; they never select a different\n * execution path.\n */\n\nimport type { MissionStep } from './service'\n\n/** Default step-kind vocabulary. `best-effort` matches the engine's default\n * non-fatal kind (a failure does not abort the mission); the rest are\n * fatal-on-failure agent sub-tasks. */\nexport const DEFAULT_MISSION_STEP_KINDS: readonly string[] = [\n 'research',\n 'generate',\n 'analyze',\n 'write',\n 'best-effort',\n]\n\n/** Define the structure representing a parsed mission step with id, kind, and intent fields */\nexport interface ParsedMissionStep {\n id: string\n kind: string\n intent: string\n}\n\n/** Describe a mission with a title and an ordered list of parsed steps */\nexport interface ParsedMission {\n title: string\n steps: ParsedMissionStep[]\n}\n\n/** Define options to specify allowed lowercase step kinds for parsing mission blocks */\nexport interface ParseMissionBlocksOptions {\n /** Allowed step kinds (lowercase). Default {@link DEFAULT_MISSION_STEP_KINDS}. */\n kinds?: readonly string[]\n}\n\n/**\n * Parse every well-formed `:::mission` block. A block without a title or\n * without at least one valid step yields nothing (it is malformed — never\n * guess a plan from loose prose). Unknown kinds and malformed step lines are\n * dropped; an empty result lets the caller skip the block rather than start an\n * empty mission.\n */\nexport function parseMissionBlocks(\n fullContent: string,\n options: ParseMissionBlocksOptions = {},\n): ParsedMission[] {\n const kinds = new Set(options.kinds ?? DEFAULT_MISSION_STEP_KINDS)\n const missionRegex = /:::mission\\s*\\n([\\s\\S]*?)\\n\\s*:::/g\n const missions: ParsedMission[] = []\n let match\n while ((match = missionRegex.exec(fullContent)) !== null) {\n const body = match[1]\n if (body === undefined) continue\n const parsed = parseMissionBody(body, kinds)\n if (parsed) missions.push(parsed)\n }\n return missions\n}\n\nfunction parseMissionBody(body: string, kinds: ReadonlySet<string>): ParsedMission | null {\n let title: string | null = null\n const steps: ParsedMissionStep[] = []\n for (const rawLine of body.split('\\n')) {\n const line = rawLine.trim()\n if (!line) continue\n const titleMatch = /^title\\s*:\\s*(.+)$/i.exec(line)\n if (titleMatch?.[1] !== undefined) {\n if (title === null) title = titleMatch[1].trim()\n continue\n }\n // `<id>: <kind> | <intent>`\n const stepMatch = /^([A-Za-z0-9][A-Za-z0-9_-]*)\\s*:\\s*([A-Za-z-]+)\\s*\\|\\s*(.+)$/.exec(line)\n if (!stepMatch) continue\n const id = stepMatch[1]?.trim()\n const kind = stepMatch[2]?.trim().toLowerCase()\n const intent = stepMatch[3]?.trim()\n if (!id || !kind || !intent) continue\n if (!kinds.has(kind)) continue\n steps.push({ id, kind, intent })\n }\n if (!title || steps.length === 0) return null\n return { title, steps }\n}\n\n/**\n * Materialize parsed steps into the engine's MissionStep[] shape. Rejects a\n * duplicate step id (fail loud — the owner keys its durable step cache by\n * step id and `createMission` rejects duplicates anyway; catching it here\n * gives a clearer diagnostic). Every step starts `pending` with zero attempts.\n */\nexport function buildAgentMissionPlan(steps: ParsedMissionStep[]): MissionStep[] {\n if (steps.length === 0) throw new Error('mission plan must have at least one step')\n const seen = new Set<string>()\n for (const step of steps) {\n if (seen.has(step.id)) {\n throw new Error(`duplicate mission step id \"${step.id}\" — step ids must be unique`)\n }\n seen.add(step.id)\n }\n return steps.map((step) => ({\n id: step.id,\n intent: step.intent,\n kind: step.kind,\n status: 'pending' as const,\n attempts: 0,\n }))\n}\n","/**\n * Canonical shape for the per-step agent-activity lane — the delegated runs\n * (delegation MCP registry entries) a mission step spawned, journaled onto the\n * step so a live UI can render \"what the agent is actually doing\" under the\n * step row.\n *\n * CLIENT-SAFE, like everything in `./events`: pure data + a validator. The\n * server attaches `StepAgentActivity[]` when a step settles (and live via\n * `step.updated`); the client re-validates with {@link stepAgentActivity}\n * because the value rides loader/JSON boundaries as untyped metadata.\n */\n\nexport interface StepAgentActivity {\n taskId: string\n /** Delegation profile: coder | researcher | ui-auditor | product-defined. */\n tool: string\n status: string\n /** Title-ish excerpt of the delegation's args (goal / question / audit root). */\n detail: string\n costUsd?: number\n durationMs?: number\n /** ISO timestamp the delegation started. */\n startedAt: string\n /** Live loop progress (DelegationProgress.iteration) while the run is in flight. */\n iteration?: number\n /** Live loop progress (DelegationProgress.phase) while the run is in flight. */\n phase?: string\n /** 32-hex trace id when the delegation joined a mission trace (see `/trace`). */\n traceId?: string\n /** 16-hex span id of the delegation's span inside that trace. */\n spanId?: string\n}\n\n/** Step state extended with the activity lane a loader/seed route attaches. */\nexport type WithAgentActivity<Step> = Step & { agentActivity?: StepAgentActivity[] }\n\n/**\n * Re-validate an `agentActivity` lane that crossed a JSON boundary. Entries\n * missing any required field are dropped (a torn row must not crash the lane);\n * optional fields are kept only when well-typed.\n */\nexport function stepAgentActivity(step: object): StepAgentActivity[] {\n const value = (step as { agentActivity?: unknown }).agentActivity\n if (!Array.isArray(value)) return []\n const items: StepAgentActivity[] = []\n for (const entry of value) {\n if (!entry || typeof entry !== 'object') continue\n const record = entry as Record<string, unknown>\n if (\n typeof record.taskId !== 'string' ||\n typeof record.tool !== 'string' ||\n typeof record.status !== 'string' ||\n typeof record.detail !== 'string' ||\n typeof record.startedAt !== 'string'\n ) {\n continue\n }\n items.push({\n taskId: record.taskId,\n tool: record.tool,\n status: record.status,\n detail: record.detail,\n startedAt: record.startedAt,\n ...(typeof record.costUsd === 'number' && Number.isFinite(record.costUsd) ? { costUsd: record.costUsd } : {}),\n ...(typeof record.durationMs === 'number' && Number.isFinite(record.durationMs) ? { durationMs: record.durationMs } : {}),\n ...(typeof record.iteration === 'number' && Number.isFinite(record.iteration) ? { iteration: record.iteration } : {}),\n ...(typeof record.phase === 'string' ? { phase: record.phase } : {}),\n ...(typeof record.traceId === 'string' ? { traceId: record.traceId } : {}),\n ...(typeof record.spanId === 'string' ? { spanId: record.spanId } : {}),\n })\n }\n return items\n}\n"],"mappings":"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","names":[]}
@@ -142,4 +142,4 @@ declare function createTangleRouterModelConfig(opts: CreateTangleRouterModelConf
142
142
  */
143
143
  declare function resolveTangleModelConfig(opts?: ResolveModelOptions): TangleModelConfig;
144
144
 
145
- export { type CreateTangleRouterModelConfigOptions as C, DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR as D, type ResolveModelOptions as R, type TangleBillingEnforcementOptions as T, DEFAULT_TANGLE_ROUTER_BASE_URL as a, type ResolveTangleDevOrUserKeyOptions as b, type ResolveUserTangleExecutionKeyForUserOptions as c, type ResolveUserTangleExecutionKeyOptions as d, type ResolvedTangleExecutionKey as e, type TangleExecutionEnvironment as f, TangleExecutionKeyError as g, type TangleExecutionKeyErrorCode as h, type TangleExecutionKeyHttpError as i, type TangleExecutionKeySource as j, type TangleModelConfig as k, createTangleRouterModelConfig as l, isTangleBillingEnforcementDisabled as m, isTangleExecutionKeyError as n, resolveTangleExecutionEnvironment as o, resolveTangleModelConfig as p, resolveUserTangleExecutionKey as q, resolveTangleDevOrUserKey as r, resolveUserTangleExecutionKeyForUser as s, tangleExecutionKeyHttpError as t, trimOrNull as u };
145
+ export { type CreateTangleRouterModelConfigOptions as C, DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR as D, type ResolveModelOptions as R, type TangleModelConfig as T, type TangleExecutionEnvironment as a, type TangleExecutionKeySource as b, DEFAULT_TANGLE_ROUTER_BASE_URL as c, type ResolveTangleDevOrUserKeyOptions as d, type ResolveUserTangleExecutionKeyForUserOptions as e, type ResolveUserTangleExecutionKeyOptions as f, type ResolvedTangleExecutionKey as g, type TangleBillingEnforcementOptions as h, TangleExecutionKeyError as i, type TangleExecutionKeyErrorCode as j, type TangleExecutionKeyHttpError as k, createTangleRouterModelConfig as l, isTangleBillingEnforcementDisabled as m, isTangleExecutionKeyError as n, resolveTangleExecutionEnvironment as o, resolveTangleModelConfig as p, resolveUserTangleExecutionKey as q, resolveTangleDevOrUserKey as r, resolveUserTangleExecutionKeyForUser as s, tangleExecutionKeyHttpError as t, trimOrNull as u };
@@ -1,12 +1,117 @@
1
1
  import {
2
- assertSafeKeySegment,
3
- createProxiedArtifactRoute,
4
- createR2ObjectStore,
5
- objectKey,
6
- signObjectUrl,
7
- verifyObjectUrl
8
- } from "../chunk-QYOD3K56.js";
9
- import "../chunk-S5SRJJQG.js";
2
+ constantTimeEqual,
3
+ hmacSha256Base64Url
4
+ } from "../chunk-S5SRJJQG.js";
5
+
6
+ // src/object-store/index.ts
7
+ function createR2ObjectStore({ bucket }) {
8
+ return {
9
+ async put(key, body, opts) {
10
+ const options = opts?.contentType ? { httpMetadata: { contentType: opts.contentType } } : void 0;
11
+ await bucket.put(key, body, options);
12
+ },
13
+ async get(key) {
14
+ const obj = await bucket.get(key);
15
+ if (!obj) return null;
16
+ return {
17
+ stream: () => obj.body,
18
+ size: obj.size,
19
+ contentType: obj.httpMetadata?.contentType
20
+ };
21
+ },
22
+ async head(key) {
23
+ const obj = await bucket.head(key);
24
+ if (!obj) return null;
25
+ return { size: obj.size, contentType: obj.httpMetadata?.contentType };
26
+ },
27
+ async delete(key) {
28
+ await bucket.delete(key);
29
+ }
30
+ };
31
+ }
32
+ function assertSafeKeySegment(s) {
33
+ if (s.length === 0) throw new Error("object-store: empty key segment");
34
+ if (s.includes("..")) throw new Error(`object-store: unsafe key segment (contains "..") \u2014 ${s}`);
35
+ if (s.includes("/")) throw new Error(`object-store: unsafe key segment (contains "/") \u2014 ${s}`);
36
+ if (s.includes("\\")) throw new Error(`object-store: unsafe key segment (backslash) \u2014 ${s}`);
37
+ return s;
38
+ }
39
+ function sanitizeFilename(filename) {
40
+ const leaf = filename.split(/[/\\]/).pop() ?? "";
41
+ const cleaned = leaf.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+/, "");
42
+ return cleaned.length > 0 ? cleaned : "file";
43
+ }
44
+ function objectKey({ operatorId, customerId, uploadId, filename }) {
45
+ const operator = assertSafeKeySegment(operatorId);
46
+ const customer = customerId == null ? "_unattributed" : assertSafeKeySegment(customerId);
47
+ const upload = assertSafeKeySegment(uploadId);
48
+ return `${operator}/${customer}/${upload}-${sanitizeFilename(filename)}`;
49
+ }
50
+ function canonicalizeObjectKey(raw) {
51
+ let decoded;
52
+ try {
53
+ decoded = decodeURIComponent(raw);
54
+ } catch {
55
+ throw new Error("object-store: malformed key encoding");
56
+ }
57
+ for (const segment of decoded.split("/")) assertSafeKeySegment(segment);
58
+ return decoded;
59
+ }
60
+ function signingMessage(canonicalKey, exp) {
61
+ return JSON.stringify({ v: 1, exp, key: canonicalKey });
62
+ }
63
+ async function signObjectUrl({ key, exp, secret }) {
64
+ if (!secret) throw new Error("object-store: signObjectUrl requires a non-empty secret (fail-closed)");
65
+ const canonical = canonicalizeObjectKey(key);
66
+ const sig = await hmacSha256Base64Url(signingMessage(canonical, exp), secret);
67
+ const params = new URLSearchParams({ key: canonical, exp: String(exp), sig });
68
+ return `?${params.toString()}`;
69
+ }
70
+ async function verifyObjectUrl(request, { secret }) {
71
+ if (!secret) return { ok: false };
72
+ const url = new URL(request.url);
73
+ const rawKey = url.searchParams.get("key");
74
+ const expRaw = url.searchParams.get("exp");
75
+ const sig = url.searchParams.get("sig");
76
+ if (!rawKey || !expRaw || !sig) return { ok: false };
77
+ let key;
78
+ try {
79
+ key = canonicalizeObjectKey(rawKey);
80
+ } catch {
81
+ return { ok: false };
82
+ }
83
+ const exp = Number(expRaw);
84
+ if (!Number.isFinite(exp)) return { ok: false };
85
+ const expected = await hmacSha256Base64Url(signingMessage(key, exp), secret);
86
+ if (!constantTimeEqual(expected, sig)) return { ok: false };
87
+ if (Date.now() > exp) return { ok: false };
88
+ return { ok: true, key };
89
+ }
90
+ function createProxiedArtifactRoute({
91
+ store,
92
+ secret
93
+ }) {
94
+ return async (request) => {
95
+ const rawKey = new URL(request.url).searchParams.get("key");
96
+ if (!rawKey) return new Response("Missing key", { status: 400 });
97
+ try {
98
+ canonicalizeObjectKey(rawKey);
99
+ } catch {
100
+ return new Response("Malformed key", { status: 400 });
101
+ }
102
+ const verified = await verifyObjectUrl(request, { secret });
103
+ if (!verified.ok) return new Response("Forbidden", { status: 403 });
104
+ const obj = await store.get(verified.key);
105
+ if (!obj) return new Response("Not found", { status: 404 });
106
+ const headers = {
107
+ "Content-Type": "application/octet-stream",
108
+ "Content-Disposition": "attachment",
109
+ "X-Content-Type-Options": "nosniff"
110
+ };
111
+ if (Number.isFinite(obj.size) && obj.size >= 0) headers["Content-Length"] = String(obj.size);
112
+ return new Response(obj.stream(), { status: 200, headers });
113
+ };
114
+ }
10
115
  export {
11
116
  assertSafeKeySegment,
12
117
  createProxiedArtifactRoute,
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../../src/object-store/index.ts"],"sourcesContent":["/**\n * `/object-store` — a portable, secure object store for durable large\n * attachments (uploaded PDFs, images, exports) that are too big to ride a chat\n * turn body or live in KV.\n *\n * SECURITY POSTURE (this module guards a legal privilege wall). Every object key\n * carries an operator segment and a customer segment, and access is by a\n * short-lived HMAC-signed URL whose signature covers the EXACT key (operator +\n * customer + upload id + filename) AND the expiry. A tampered key, a swapped\n * customer segment, or a replayed-after-expiry URL all fail the signature or the\n * expiry check and are refused. The signing secret is a PARAMETER on every call\n * — this module reads NOTHING global (no `process.env`, no ambient config); if a\n * product forgets to bind the secret, {@link verifyObjectUrl} fails closed and\n * {@link signObjectUrl} throws rather than minting an unsigned URL.\n *\n * PURE MECHANISM behind two seams: an {@link ObjectStore} port (the R2 impl maps\n * 1:1 to a bucket held behind the structural {@link R2LikeBucket} — so\n * `@cloudflare/workers-types` never leaks into this package's public `.d.ts`)\n * and the signing `secret`. Reads and writes STREAM: {@link createR2ObjectStore}\n * returns the R2 object's `.body` stream directly and never calls\n * `.text()`/`.arrayBuffer()`, so a 15 MB PDF flows through the worker without\n * buffering the whole file into the isolate's heap.\n *\n * RECONCILIATION with the sandbox `storage` seam (`../sandbox` →\n * `SandboxRuntimeConfig.storage`): that is BYOS3/R2 *snapshot* storage — it\n * checkpoints and restores a sandbox box's filesystem for durable session\n * execution. This is a DIFFERENT concern: a content-addressed store for\n * user-facing attachments gated by a signed-URL privilege wall. They are not\n * interchangeable and must not be merged: one persists agent runtime state, the\n * other serves per-operator/per-customer documents to browsers. Do not route\n * artifact downloads through the snapshot bucket, or vice versa.\n */\n\nimport { constantTimeEqual, hmacSha256Base64Url } from '../crypto/web-token'\n\n// ── The store port + R2 impl ────────────────────────────────────────────────\n\n/** Options for a single {@link ObjectStore.put}. Both fields are optional; a\n * store that needs a fixed content length (e.g. R2 with a `ReadableStream`\n * body) uses `contentLength` when present. */\nexport interface PutObjectOptions {\n /** MIME type recorded with the object (returned by `get`/`head`). Advisory:\n * the proxied download route always serves `application/octet-stream`. */\n contentType?: string\n /** Byte length of `body`, when known. Some backends require it for a\n * streamed body; the R2 impl passes a known length through when supplied. */\n contentLength?: number\n}\n\n/** A retrieved object. `stream()` is the ONLY way to read the bytes — there is\n * deliberately no `text()`/`arrayBuffer()`, so a large object never buffers\n * into the isolate heap. */\nexport interface ObjectBody {\n /** The object's bytes as a `ReadableStream` (backed by R2's `.body`). */\n stream(): ReadableStream\n /** Size in bytes. */\n size: number\n /** Recorded MIME type, if any. */\n contentType?: string\n}\n\n/**\n * Portable object-store port. `get`/`head` return `null` on a miss and NEVER\n * throw for a missing key (a miss is a normal control-flow outcome, not an\n * error); `delete` is idempotent.\n */\nexport interface ObjectStore {\n put(key: string, body: ReadableStream | Uint8Array, opts?: PutObjectOptions): Promise<void>\n /** `null` on a miss — never throws for a missing key. */\n get(key: string): Promise<ObjectBody | null>\n /** `null` on a miss — never throws for a missing key. */\n head(key: string): Promise<{ size: number; contentType?: string } | null>\n delete(key: string): Promise<void>\n}\n\n/** Head/metadata shape of a stored object (structural match of R2's `R2Object`). */\nexport interface R2LikeObjectHead {\n size: number\n httpMetadata?: { contentType?: string }\n}\n\n/** Body shape of a retrieved object (structural match of R2's `R2ObjectBody`).\n * `body` is the streamed content — the impl reads this, never `.text()`. */\nexport interface R2LikeObjectBody extends R2LikeObjectHead {\n body: ReadableStream\n}\n\n/**\n * The minimal slice of Cloudflare's `R2Bucket` this module calls. A real\n * `R2Bucket` satisfies it structurally, so the consumer passes its binding\n * without this package ever importing `@cloudflare/workers-types` (which would\n * otherwise leak into the public `.d.ts`).\n */\nexport interface R2LikeBucket {\n put(\n key: string,\n value: ReadableStream | Uint8Array | ArrayBuffer,\n options?: { httpMetadata?: { contentType?: string } },\n ): Promise<unknown>\n get(key: string): Promise<R2LikeObjectBody | null>\n head(key: string): Promise<R2LikeObjectHead | null>\n delete(key: string): Promise<void>\n}\n\n/**\n * Map the {@link ObjectStore} port onto an R2 bucket 1:1. `get` STREAMS: it\n * returns the R2 object's `.body` behind `ObjectBody.stream()` and never calls\n * `.text()`/`.arrayBuffer()`, so a large file flows through the worker rather\n * than buffering into the isolate.\n */\nexport function createR2ObjectStore({ bucket }: { bucket: R2LikeBucket }): ObjectStore {\n return {\n async put(key, body, opts) {\n const options = opts?.contentType ? { httpMetadata: { contentType: opts.contentType } } : undefined\n await bucket.put(key, body, options)\n },\n async get(key) {\n const obj = await bucket.get(key)\n if (!obj) return null\n return {\n stream: () => obj.body,\n size: obj.size,\n contentType: obj.httpMetadata?.contentType,\n }\n },\n async head(key) {\n const obj = await bucket.head(key)\n if (!obj) return null\n return { size: obj.size, contentType: obj.httpMetadata?.contentType }\n },\n async delete(key) {\n await bucket.delete(key)\n },\n }\n}\n\n// ── Key construction + safety ───────────────────────────────────────────────\n\n/**\n * Assert a single object-key path SEGMENT (an operator id, customer id, or\n * upload id) is safe to interpolate into a key, and return it. Throws on the\n * traversal / injection shapes: `..` anywhere, ANY `/` (a segment must be a\n * single path component — a leading, interior, or trailing slash all widen the\n * key by injecting extra levels), a backslash, or an empty segment. Consumers\n * should call this on caller-supplied identifiers BEFORE any `get`/`put` so an\n * attacker-controlled id can never widen the key beyond its own operator+\n * customer prefix.\n */\nexport function assertSafeKeySegment(s: string): string {\n if (s.length === 0) throw new Error('object-store: empty key segment')\n if (s.includes('..')) throw new Error(`object-store: unsafe key segment (contains \"..\") — ${s}`)\n // Reject ANY '/' — leading, interior, or trailing. A key segment is a single\n // path component; an interior slash (`a/b`) would silently add a key level and\n // widen the operator/customer prefix, so it is refused just like a leading one.\n if (s.includes('/')) throw new Error(`object-store: unsafe key segment (contains \"/\") — ${s}`)\n if (s.includes('\\\\')) throw new Error(`object-store: unsafe key segment (backslash) — ${s}`)\n return s\n}\n\n/** Strip a filename down to a safe leaf: drop any path, keep only\n * `[A-Za-z0-9._-]`, and remove leading dots so a `..`/`.hidden`/dot-only name\n * can neither traverse nor vanish. Never returns an empty string. */\nfunction sanitizeFilename(filename: string): string {\n const leaf = filename.split(/[/\\\\]/).pop() ?? ''\n const cleaned = leaf.replace(/[^A-Za-z0-9._-]/g, '_').replace(/^\\.+/, '')\n return cleaned.length > 0 ? cleaned : 'file'\n}\n\n/** Inputs to {@link objectKey}. `customerId` is optional — an unattributed\n * upload lands under the reserved `_unattributed` customer segment. */\nexport interface ObjectKeyParts {\n operatorId: string\n /** Optional — omitted/undefined groups the upload under `_unattributed`. */\n customerId?: string\n uploadId: string\n filename: string\n}\n\n/**\n * Build the canonical object key: `operator/customer/upload-filename`. The\n * operator, customer, and upload segments are asserted safe (throwing on\n * traversal); the filename is sanitized to a safe leaf. The customer segment\n * falls back to `_unattributed` when no customer is attributed. Because a signed\n * URL binds the EXACT key, the operator and customer segments are part of the\n * privilege wall — a caller cannot later swap them without breaking the\n * signature.\n */\nexport function objectKey({ operatorId, customerId, uploadId, filename }: ObjectKeyParts): string {\n const operator = assertSafeKeySegment(operatorId)\n const customer = customerId == null ? '_unattributed' : assertSafeKeySegment(customerId)\n const upload = assertSafeKeySegment(uploadId)\n return `${operator}/${customer}/${upload}-${sanitizeFilename(filename)}`\n}\n\n/**\n * Decode a key ONCE and assert every `/`-delimited segment is safe. Slash- and\n * percent-encoded forms of the same key canonicalize identically (`a/b` and\n * `a%2Fb` both → `a/b`), so a signature binds the key's MEANING, not its\n * on-the-wire spelling. Throws on malformed percent-encoding or an unsafe\n * segment. Our own keys never contain `%`, so the single decode is idempotent.\n */\nfunction canonicalizeObjectKey(raw: string): string {\n let decoded: string\n try {\n decoded = decodeURIComponent(raw)\n } catch {\n throw new Error('object-store: malformed key encoding')\n }\n for (const segment of decoded.split('/')) assertSafeKeySegment(segment)\n return decoded\n}\n\n// ── Signed URLs ─────────────────────────────────────────────────────────────\n\n/** The exact string the signature covers: a versioned JSON of the canonical key\n * and the expiry. JSON escaping makes it delimiter-injection-proof (a key\n * cannot forge the `exp` field). Both sign and verify build it identically. */\nfunction signingMessage(canonicalKey: string, exp: number): string {\n return JSON.stringify({ v: 1, exp, key: canonicalKey })\n}\n\n/** Arguments to {@link signObjectUrl}. */\nexport interface SignObjectUrlArgs {\n /** The exact object key to authorize (from {@link objectKey}). */\n key: string\n /** Absolute expiry in epoch MILLISECONDS (e.g. `Date.now() + 5 * 60_000` for a\n * ~5-minute TTL — the recommended default; keep it short). */\n exp: number\n /** HMAC signing secret. Must be non-empty — signing fails closed otherwise. */\n secret: string\n}\n\n/**\n * Mint a signed query string (`?key=…&exp=…&sig=…`) authorizing a download of\n * `key` until `exp`. The product appends it to its artifact route path\n * (`` `/artifacts${await signObjectUrl(...)}` ``); {@link createProxiedArtifactRoute}\n * / {@link verifyObjectUrl} read it straight off the request URL, so the route's\n * own mount path never matters.\n *\n * FAIL-CLOSED: throws if `secret` is empty (never mints an unsigned URL) and if\n * `key` is unsafe (traversal). Async because the HMAC primitive is WebCrypto.\n *\n * TTL: `exp` is caller-supplied on purpose (the caller knows the sensitivity);\n * keep it SHORT — ~5 minutes is the recommended default for a privilege-walled\n * document.\n */\nexport async function signObjectUrl({ key, exp, secret }: SignObjectUrlArgs): Promise<string> {\n if (!secret) throw new Error('object-store: signObjectUrl requires a non-empty secret (fail-closed)')\n const canonical = canonicalizeObjectKey(key)\n const sig = await hmacSha256Base64Url(signingMessage(canonical, exp), secret)\n const params = new URLSearchParams({ key: canonical, exp: String(exp), sig })\n return `?${params.toString()}`\n}\n\n/** Result of {@link verifyObjectUrl}: the canonical key on success, nothing\n * distinguishing on failure (so a rejection leaks no detail). */\nexport type VerifyObjectUrlResult = { ok: true; key: string } | { ok: false }\n\n/**\n * Verify a signed download request. Reads `key`/`exp`/`sig` off the request URL,\n * canonicalizes the key identically to the signer, recomputes the HMAC and\n * constant-time-compares it, and checks the expiry. Returns the canonical key on\n * success.\n *\n * FAIL-CLOSED everywhere: an empty `secret`, a missing/ malformed parameter, a\n * non-finite expiry, a signature mismatch, or an expired URL all return\n * `{ ok: false }` — and the mismatch path uses a constant-time compare so a\n * near-correct signature is not distinguishable by timing. Async because the\n * HMAC primitive is WebCrypto.\n */\nexport async function verifyObjectUrl(\n request: Request,\n { secret }: { secret: string },\n): Promise<VerifyObjectUrlResult> {\n if (!secret) return { ok: false }\n const url = new URL(request.url)\n const rawKey = url.searchParams.get('key')\n const expRaw = url.searchParams.get('exp')\n const sig = url.searchParams.get('sig')\n if (!rawKey || !expRaw || !sig) return { ok: false }\n\n let key: string\n try {\n key = canonicalizeObjectKey(rawKey)\n } catch {\n return { ok: false }\n }\n\n const exp = Number(expRaw)\n if (!Number.isFinite(exp)) return { ok: false }\n\n const expected = await hmacSha256Base64Url(signingMessage(key, exp), secret)\n if (!constantTimeEqual(expected, sig)) return { ok: false }\n // Signature is valid; enforce expiry last so the constant-time compare always\n // runs (an expired-but-otherwise-valid URL is refused just the same).\n if (Date.now() > exp) return { ok: false }\n return { ok: true, key }\n}\n\n// ── Proxied download route ──────────────────────────────────────────────────\n\n/**\n * Build a download handler that verifies a signed request and STREAMS the object\n * back with a conservative, non-executable content type. Status contract:\n *\n * - `400` — the `key` is missing or malformed (bad encoding / traversal). This\n * is an unauthenticated client error and leaks NOTHING about object existence.\n * - `403` — the signature is missing, wrong, or expired.\n * - `404` — the signature was valid but no object exists at that key (existence\n * is only ever revealed to a validly-signed request).\n * - `200` — streams the bytes with `Content-Disposition: attachment`,\n * `Content-Type: application/octet-stream`, and `X-Content-Type-Options:\n * nosniff`, so the worker never serves active/inline content.\n */\nexport function createProxiedArtifactRoute({\n store,\n secret,\n}: {\n store: ObjectStore\n secret: string\n}): (request: Request) => Promise<Response> {\n return async (request) => {\n // 1. Malformed/missing key → 400, decided BEFORE auth so it leaks no\n // existence signal (a malformed key can never carry a valid signature).\n const rawKey = new URL(request.url).searchParams.get('key')\n if (!rawKey) return new Response('Missing key', { status: 400 })\n try {\n canonicalizeObjectKey(rawKey)\n } catch {\n return new Response('Malformed key', { status: 400 })\n }\n\n // 2. Bad / expired signature → 403.\n const verified = await verifyObjectUrl(request, { secret })\n if (!verified.ok) return new Response('Forbidden', { status: 403 })\n\n // 3. Fetch by the VERIFIED canonical key. Miss → 404 (only reachable behind\n // a valid signature). Hit → stream with a conservative content type.\n const obj = await store.get(verified.key)\n if (!obj) return new Response('Not found', { status: 404 })\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'Content-Disposition': 'attachment',\n 'X-Content-Type-Options': 'nosniff',\n }\n if (Number.isFinite(obj.size) && obj.size >= 0) headers['Content-Length'] = String(obj.size)\n return new Response(obj.stream(), { status: 200, headers })\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import { Part } from '@tangle-network/agent-interface';
2
- import { b as ChatInteractionField, c as ChatInteractionStatus, i as InteractionAnswers, N as NoticeKind } from './contract-B3h7peV3.js';
3
- import { g as WorkProductPersistedPart } from './types-CEchbvgz.js';
2
+ import { a as ChatInteractionField, C as ChatInteractionStatus, I as InteractionAnswers, N as NoticeKind } from './contract-CEewO6DI.js';
3
+ import { c as WorkProductPersistedPart } from './types-XIZqIdX1.js';
4
4
  import { ChatPlanPersistedPart } from './plans/index.js';
5
5
 
6
6
  /**
@@ -607,4 +607,4 @@ declare function historyContentWithAttachments(message: {
607
607
  parts?: ReadonlyArray<Record<string, unknown>> | null;
608
608
  }, header?: string): string;
609
609
 
610
- export { type ProducerErrorEvent as $, isChatInteractionPart as A, isChatMentionPart as B, type ChatAttachmentKind as C, DEFAULT_ATTACHMENT_PROMPT_HEADER as D, isChatPlanPart as E, isChatStepFinishPart as F, isChatTextPart as G, isChatToolPart as H, isChatWorkProductPart as I, mentionInputToPart as J, mentionPartsFromMessageParts as K, toChatMessageParts as L, type FileMention as M, type ChatTurnRequestPayload as N, type ChatTurnPartInput as O, type ChatTurnFilePartInput as P, type ChatAttachmentInput as Q, ChatTurnInputError as R, type StorableHarnessPartKind as S, type ChatTurnTextPartInput as T, DISPATCH_MAX_MEDIA_PARTS as U, DISPATCH_MAX_PARTS as V, DISPATCH_REQUEST_MAX_BYTES as W, DISPATCH_STRUCTURAL_RESERVE_BYTES as X, type FileMentionsToPartsOptions as Y, INLINE_PARTS_MAX_BYTES as Z, MENTION_MAX_COUNT as _, type ChatAttachmentPart as a, type ProducerNoticeEvent as a0, type ProducerPassthroughEvent as a1, type ProducerPassthroughEventType as a2, type ProducerReasoningEvent as a3, type ProducerTextEvent as a4, type ProducerToolCallEvent as a5, type ProducerToolResultEvent as a6, type ProducerUsageEvent as a7, type ProducerWireEvent as a8, type SandboxMentionPathCheck as a9, assertPromptPartsWithinCap as aa, base64WireLen as ab, buildMentionPromptBlock as ac, chatTurnRequestInit as ad, fileMentionsToParts as ae, formatBytes as af, mediaTypeForMentionPath as ag, mentionKindForPath as ah, parseChatTurnParts as ai, parseFileMentions as aj, promptPartsByteSize as ak, validateSandboxMentionPath as al, type ChatFilePart as b, type ChatImagePart as c, type ChatInteractionPart as d, type ChatMentionKind as e, type ChatMentionPart as f, type ChatMessagePart as g, type ChatNoticePart as h, type ChatPartTime as i, type ChatPlanPart as j, type ChatReasoningPart as k, type ChatStepFinishPart as l, type ChatStepStartPart as m, type ChatSubtaskPart as n, type ChatTextPart as o, type ChatToolPart as p, type ChatToolState as q, type ChatToolStatus as r, type ChatUsageTokens as s, type ChatWorkProductPart as t, attachmentInputToPart as u, attachmentKindForMime as v, attachmentPartsFromMessageParts as w, buildAttachmentPromptBlock as x, historyContentWithAttachments as y, isChatAttachmentPart as z };
610
+ export { type ProducerErrorEvent as $, isChatInteractionPart as A, isChatMentionPart as B, type ChatMessagePart as C, DEFAULT_ATTACHMENT_PROMPT_HEADER as D, isChatPlanPart as E, isChatStepFinishPart as F, isChatTextPart as G, isChatToolPart as H, isChatWorkProductPart as I, mentionInputToPart as J, mentionPartsFromMessageParts as K, toChatMessageParts as L, type FileMention as M, type ChatTurnRequestPayload as N, type ChatTurnPartInput as O, type ChatTurnFilePartInput as P, type ChatAttachmentInput as Q, ChatTurnInputError as R, type StorableHarnessPartKind as S, type ChatTurnTextPartInput as T, DISPATCH_MAX_MEDIA_PARTS as U, DISPATCH_MAX_PARTS as V, DISPATCH_REQUEST_MAX_BYTES as W, DISPATCH_STRUCTURAL_RESERVE_BYTES as X, type FileMentionsToPartsOptions as Y, INLINE_PARTS_MAX_BYTES as Z, MENTION_MAX_COUNT as _, type ChatAttachmentKind as a, type ProducerNoticeEvent as a0, type ProducerPassthroughEvent as a1, type ProducerPassthroughEventType as a2, type ProducerReasoningEvent as a3, type ProducerTextEvent as a4, type ProducerToolCallEvent as a5, type ProducerToolResultEvent as a6, type ProducerUsageEvent as a7, type ProducerWireEvent as a8, type SandboxMentionPathCheck as a9, assertPromptPartsWithinCap as aa, base64WireLen as ab, buildMentionPromptBlock as ac, chatTurnRequestInit as ad, fileMentionsToParts as ae, formatBytes as af, mediaTypeForMentionPath as ag, mentionKindForPath as ah, parseChatTurnParts as ai, parseFileMentions as aj, promptPartsByteSize as ak, validateSandboxMentionPath as al, type ChatAttachmentPart as b, type ChatFilePart as c, type ChatImagePart as d, type ChatInteractionPart as e, type ChatMentionKind as f, type ChatMentionPart as g, type ChatNoticePart as h, type ChatPartTime as i, type ChatPlanPart as j, type ChatReasoningPart as k, type ChatStepFinishPart as l, type ChatStepStartPart as m, type ChatSubtaskPart as n, type ChatTextPart as o, type ChatToolPart as p, type ChatToolState as q, type ChatToolStatus as r, type ChatUsageTokens as s, type ChatWorkProductPart as t, attachmentInputToPart as u, attachmentKindForMime as v, attachmentPartsFromMessageParts as w, buildAttachmentPromptBlock as x, historyContentWithAttachments as y, isChatAttachmentPart as z };
@@ -1,5 +1,5 @@
1
1
  export { c as AdminGuardOptions, d as AssertBillableBalanceOptions, A as AuthGuard, e as AuthGuardOptions, B as BetterAuthSessionCookieMinterOptions, f as BetterAuthSessionCookieSource, g as BillableBalanceState, G as GuardResolution, S as SsoStateConfig, b as TangleSsoAccountStore, a as TangleSsoAuthClient, h as TangleSsoExchangeResult, i as TangleSsoHandlerOptions, T as TangleSsoHandlers, j as TangleSsoSessionCookieArgs, k as TangleSsoUserCreateError, l as assertBillableBalance, m as createAdminGuard, n as createAuthGuard, o as createBetterAuthSessionCookieMinter, p as createSignedSsoState, q as createTangleSsoHandlers, r as guardResolution, s as parseAdminEmails, t as signSessionCookieValue, v as verifySignedSsoState } from '../sso-Cm00S61u.js';
2
- import { f as TangleExecutionEnvironment, j as TangleExecutionKeySource } from '../model-DmdkIteM.js';
2
+ import { a as TangleExecutionEnvironment, b as TangleExecutionKeySource } from '../model-CdCDfBA9.js';
3
3
  import { PlatformIdentity, PlatformBillingClient } from '../billing/index.js';
4
4
 
5
5
  /**
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { PreflightProbe } from './index.js';
3
+
4
+ /**
5
+ * Load the probe list from a config module. Fail loud on every miss — a
6
+ * preflight that silently probes nothing and exits 0 is worse than no
7
+ * preflight, because it reports the deploy as verified.
8
+ */
9
+ declare function loadProbes(configPath: string): Promise<PreflightProbe[]>;
10
+ /** Run the probes named by `argv` and resolve with the process exit code. */
11
+ declare function runPreflightCli(argv: readonly string[]): Promise<number>;
12
+
13
+ export { loadProbes, runPreflightCli };
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ formatPreflightReport,
4
+ runPreflight
5
+ } from "../chunk-JTLYXUEV.js";
6
+
7
+ // src/preflight/cli.ts
8
+ import { pathToFileURL } from "url";
9
+ import { resolve } from "path";
10
+ import { existsSync } from "fs";
11
+ var DEFAULT_CONFIG = "preflight.config.mjs";
12
+ async function loadProbes(configPath) {
13
+ const abs = resolve(process.cwd(), configPath);
14
+ if (!existsSync(abs)) {
15
+ throw new Error(
16
+ `agent-app-preflight: no config at "${abs}". Create one that default-exports a PreflightProbe[], or pass a path.`
17
+ );
18
+ }
19
+ const mod = await import(pathToFileURL(abs).href);
20
+ const probes = mod.default;
21
+ if (!Array.isArray(probes)) {
22
+ throw new Error(
23
+ `agent-app-preflight: "${abs}" must default-export an array of probes; got ${typeof probes}.`
24
+ );
25
+ }
26
+ if (probes.length === 0) {
27
+ throw new Error(`agent-app-preflight: "${abs}" exported an empty probe list \u2014 nothing would be verified.`);
28
+ }
29
+ return probes;
30
+ }
31
+ async function runPreflightCli(argv) {
32
+ const probes = await loadProbes(argv[0] ?? DEFAULT_CONFIG);
33
+ const report = await runPreflight(probes);
34
+ console.log(formatPreflightReport(report));
35
+ return report.ok ? 0 : 1;
36
+ }
37
+ var invokedDirectly = process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href;
38
+ if (invokedDirectly) {
39
+ runPreflightCli(process.argv.slice(2)).then((code) => {
40
+ process.exit(code);
41
+ }).catch((err) => {
42
+ console.error(err instanceof Error ? err.message : String(err));
43
+ process.exit(1);
44
+ });
45
+ }
46
+ export {
47
+ loadProbes,
48
+ runPreflightCli
49
+ };
50
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/preflight/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-preflight` — the deploy-time probe runner.\n *\n * Loads a product's `preflight.config.mjs` (default export: `PreflightProbe[]`),\n * runs every probe, prints the operator table, and exits non-zero when a\n * critical probe fails so a deploy step can gate on it.\n *\n * Run it where the REAL secrets are — after `wrangler secret put …`, not in CI.\n * CI has no secrets, so a probe there proves nothing.\n *\n * agent-app-preflight # ./preflight.config.mjs\n * agent-app-preflight ops/probes.mjs # explicit path\n *\n * This bin existed only as a promise in the module docs until 0.44.0; at least\n * one product had hand-rolled a byte-identical `scripts/preflight.mjs` around\n * `runPreflight` + `formatPreflightReport` to fill the gap.\n */\nimport { pathToFileURL } from 'node:url'\nimport { resolve } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { formatPreflightReport, runPreflight, type PreflightProbe } from './index'\n\nconst DEFAULT_CONFIG = 'preflight.config.mjs'\n\n/**\n * Load the probe list from a config module. Fail loud on every miss — a\n * preflight that silently probes nothing and exits 0 is worse than no\n * preflight, because it reports the deploy as verified.\n */\nexport async function loadProbes(configPath: string): Promise<PreflightProbe[]> {\n const abs = resolve(process.cwd(), configPath)\n if (!existsSync(abs)) {\n throw new Error(\n `agent-app-preflight: no config at \"${abs}\". Create one that default-exports a PreflightProbe[], or pass a path.`,\n )\n }\n const mod: unknown = await import(pathToFileURL(abs).href)\n const probes = (mod as { default?: unknown }).default\n if (!Array.isArray(probes)) {\n throw new Error(\n `agent-app-preflight: \"${abs}\" must default-export an array of probes; got ${typeof probes}.`,\n )\n }\n if (probes.length === 0) {\n throw new Error(`agent-app-preflight: \"${abs}\" exported an empty probe list — nothing would be verified.`)\n }\n return probes as PreflightProbe[]\n}\n\n/** Run the probes named by `argv` and resolve with the process exit code. */\nexport async function runPreflightCli(argv: readonly string[]): Promise<number> {\n const probes = await loadProbes(argv[0] ?? DEFAULT_CONFIG)\n const report = await runPreflight(probes)\n console.log(formatPreflightReport(report))\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nconst invokedDirectly =\n process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href\n\nif (invokedDirectly) {\n runPreflightCli(process.argv.slice(2))\n .then((code) => {\n process.exit(code)\n })\n .catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n })\n}\n/* c8 ignore stop */\n"],"mappings":";;;;;;;AAkBA,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAG3B,IAAM,iBAAiB;AAOvB,eAAsB,WAAW,YAA+C;AAC9E,QAAM,MAAM,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAC7C,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,UAAM,IAAI;AAAA,MACR,sCAAsC,GAAG;AAAA,IAC3C;AAAA,EACF;AACA,QAAM,MAAe,MAAM,OAAO,cAAc,GAAG,EAAE;AACrD,QAAM,SAAU,IAA8B;AAC9C,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,yBAAyB,GAAG,iDAAiD,OAAO,MAAM;AAAA,IAC5F;AAAA,EACF;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,yBAAyB,GAAG,kEAA6D;AAAA,EAC3G;AACA,SAAO;AACT;AAGA,eAAsB,gBAAgB,MAA0C;AAC9E,QAAM,SAAS,MAAM,WAAW,KAAK,CAAC,KAAK,cAAc;AACzD,QAAM,SAAS,MAAM,aAAa,MAAM;AACxC,UAAQ,IAAI,sBAAsB,MAAM,CAAC;AACzC,SAAO,OAAO,KAAK,IAAI;AACzB;AAGA,IAAM,kBACJ,QAAQ,KAAK,CAAC,MAAM,UAAa,YAAY,QAAQ,cAAc,QAAQ,KAAK,CAAC,CAAC,EAAE;AAEtF,IAAI,iBAAiB;AACnB,kBAAgB,QAAQ,KAAK,MAAM,CAAC,CAAC,EAClC,KAAK,CAAC,SAAS;AACd,YAAQ,KAAK,IAAI;AAAA,EACnB,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACL;","names":[]}