@tangle-network/agent-runtime 0.115.1 → 0.117.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/{activation-Bt_XVkjK.js → activation-BZ2FyX0O.js} +3 -3
- package/dist/{activation-Bt_XVkjK.js.map → activation-BZ2FyX0O.js.map} +1 -1
- package/dist/agent.d.ts +3 -64
- package/dist/agent.js +5 -207
- package/dist/agent.js.map +1 -1
- package/dist/{analyst-loop-BoNIG2hA.js → analyst-loop-DvSciOfB.js} +2 -2
- package/dist/{analyst-loop-BoNIG2hA.js.map → analyst-loop-DvSciOfB.js.map} +1 -1
- package/dist/analyst-loop.js +1 -1
- package/dist/candidate-execution/index.d.ts +3 -3
- package/dist/candidate-execution/index.js +5 -5
- package/dist/{candidate-execution-CfpJrd3o.js → candidate-execution-PX61zXd2.js} +4 -4
- package/dist/{candidate-execution-CfpJrd3o.js.map → candidate-execution-PX61zXd2.js.map} +1 -1
- package/dist/{environment-provider-Cygthiy3.d.ts → environment-provider-CTZjdIUg.d.ts} +661 -34
- package/dist/{environment-provider-CCaEhA-l.js → environment-provider-CzcEyRL7.js} +140 -31
- package/dist/environment-provider-CzcEyRL7.js.map +1 -0
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/{improvement-cycle-tEswzEPr.js → improvement-cycle-Bcqz8rZI.js} +160 -19
- package/dist/improvement-cycle-Bcqz8rZI.js.map +1 -0
- package/dist/{index-DZukewLl.d.ts → index-BHSYaWrC.d.ts} +3 -3
- package/dist/{index-K7nucOmw.d.ts → index-BTUG4Xln.d.ts} +8 -7
- package/dist/{index-BUWd8QJq.d.ts → index-DPWJxSRa.d.ts} +884 -497
- package/dist/{index-C-FYUuFG.d.ts → index-X2YwyWkc.d.ts} +2 -2
- package/dist/index.d.ts +9 -10
- package/dist/index.js +12 -13
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +10 -9
- package/dist/intelligence.js +15 -9
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +8 -8
- package/dist/{knowledge-CUXQE8Sq.js → knowledge-CDlrayRc.js} +7 -7
- package/dist/knowledge-CDlrayRc.js.map +1 -0
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{local-harness-BnB1E9nM.d.ts → local-harness-t6cDWDQ2.d.ts} +3 -105
- package/dist/{loop-runner-bin-DzJz48Fb.js → loop-runner-bin-BxhhDtmG.js} +4 -4
- package/dist/{loop-runner-bin-DzJz48Fb.js.map → loop-runner-bin-BxhhDtmG.js.map} +1 -1
- package/dist/{loop-runner-bin-BZl5vp7t.d.ts → loop-runner-bin-oyL7it5O.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +43 -10
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +4 -43
- package/dist/mcp/index.js +5 -205
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-CynwZMZd.js → openai-tools-CO9X36zy.js} +2 -2
- package/dist/{openai-tools-CynwZMZd.js.map → openai-tools-CO9X36zy.js.map} +1 -1
- package/dist/{prepare-BHQBb02e.js → prepare-BbI6qGOW.js} +5 -2
- package/dist/prepare-BbI6qGOW.js.map +1 -0
- package/dist/primeintellect/index.d.ts +1 -1
- package/dist/{protected-model-port-BP6Z4eau.d.ts → protected-model-port-B0yXMpLL.d.ts} +10 -2
- package/dist/{protected-model-port-DqAH1Z2M.js → protected-model-port-COnyZfML.js} +2 -2
- package/dist/{protected-model-port-DqAH1Z2M.js.map → protected-model-port-COnyZfML.js.map} +1 -1
- package/dist/{redact-BRaHUFMf.d.ts → redact-BEtQtvd6.d.ts} +2 -2
- package/dist/{runtime-BatQajPB.js → runtime-Ce39zfqn.js} +145 -53
- package/dist/runtime-Ce39zfqn.js.map +1 -0
- package/dist/{sandbox-events-DeI5xX8P.js → sandbox-events-Yhd1GYWl.js} +4 -2
- package/dist/sandbox-events-Yhd1GYWl.js.map +1 -0
- package/dist/spawn-journal-B_mu_oH2.js +857 -0
- package/dist/spawn-journal-B_mu_oH2.js.map +1 -0
- package/dist/{structural-rollout-DEf37yQy.js → structural-rollout-DLwW69R4.js} +4 -4
- package/dist/structural-rollout-DLwW69R4.js.map +1 -0
- package/dist/{supervise-B7TIJR3D.js → supervise-U7_K5yuV.js} +2645 -829
- package/dist/supervise-U7_K5yuV.js.map +1 -0
- package/dist/{supervisor-DzQu5Ydu.js → supervisor-CAXiCKnX.js} +3802 -967
- package/dist/supervisor-CAXiCKnX.js.map +1 -0
- package/dist/testing.js +98 -76
- package/dist/testing.js.map +1 -1
- package/dist/{workspace-archive-DXzJq7WP.js → workspace-archive-CqPIijFA.js} +2 -2
- package/dist/{workspace-archive-DXzJq7WP.js.map → workspace-archive-CqPIijFA.js.map} +1 -1
- package/package.json +1 -1
- package/dist/environment-provider-CCaEhA-l.js.map +0 -1
- package/dist/improvement-cycle-tEswzEPr.js.map +0 -1
- package/dist/knowledge-CUXQE8Sq.js.map +0 -1
- package/dist/otel-export-CPZTSADj.js +0 -852
- package/dist/otel-export-CPZTSADj.js.map +0 -1
- package/dist/prepare-BHQBb02e.js.map +0 -1
- package/dist/runtime-BatQajPB.js.map +0 -1
- package/dist/sandbox-events-DeI5xX8P.js.map +0 -1
- package/dist/spawn-journal-DCPbicXB.js +0 -457
- package/dist/spawn-journal-DCPbicXB.js.map +0 -1
- package/dist/structural-rollout-DEf37yQy.js.map +0 -1
- package/dist/supervise-B7TIJR3D.js.map +0 -1
- package/dist/supervisor-DzQu5Ydu.js.map +0 -1
- package/dist/trace-propagation-B-pL7xn_.js +0 -57
- package/dist/trace-propagation-B-pL7xn_.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox-events-DeI5xX8P.js","names":[],"sources":["../src/runtime/sandbox-events.ts"],"sourcesContent":["/**\n * Sandbox-event → runtime-event mapping.\n *\n * The sandbox SDK emits a polymorphic `SandboxEvent = { type, data, id? }`\n * whose `type` vocabulary is backend-determined (opencode, etc.) rather than\n * enumerated by the SDK. Two consumers project it:\n * - the loop kernel's cost ledger (`extractLlmCallEvent`) — sums usage off\n * every cost-bearing event, regardless of stream shape;\n * - the `AgentRuntime.act` streaming contract (`mapSandboxEvent`) — projects\n * incremental events to the `RuntimeStreamEvent` chat-UX vocabulary.\n *\n * Both live here so the empirically-observed `type` vocabulary has one home.\n */\n\nimport type { SandboxEvent } from '@tangle-network/sandbox'\nimport type { RuntimeStreamEvent } from '../types'\n\n/**\n * Forward a sandbox event to an optional observer without letting observer\n * behavior affect the run. The observer receives a defensive copy, synchronous\n * throws are swallowed, and returned promises are deliberately not awaited.\n */\nexport function notifySandboxEventObserver<Meta>(\n event: SandboxEvent,\n observer: ((event: SandboxEvent, meta: Meta) => void | PromiseLike<void>) | undefined,\n meta: Meta,\n): void {\n if (!observer) return\n try {\n const result = observer(cloneEventForObserver(event), meta)\n if (result && typeof result.then === 'function') {\n void result.then(undefined, () => {})\n }\n } catch {\n // Live observation is optional and must never interrupt the event stream.\n }\n}\n\nfunction cloneEventForObserver(event: SandboxEvent): SandboxEvent {\n try {\n return structuredClone(event)\n } catch {\n return copyPlainSpine(event, new WeakMap()) as SandboxEvent\n }\n}\n\nfunction copyPlainSpine(value: unknown, seen: WeakMap<object, unknown>): unknown {\n if (value === null || typeof value !== 'object') return value\n const existing = seen.get(value)\n if (existing !== undefined) return existing\n if (Array.isArray(value)) {\n const copy: unknown[] = []\n seen.set(value, copy)\n for (const item of value) copy.push(copyPlainSpine(item, seen))\n return copy\n }\n const proto = Object.getPrototypeOf(value)\n if (proto !== Object.prototype && proto !== null) return {}\n const copy: Record<string, unknown> = {}\n seen.set(value, copy)\n for (const [key, child] of Object.entries(value)) {\n copy[key] = copyPlainSpine(child, seen)\n }\n return copy\n}\n\n/**\n * Extract a `RuntimeStreamEvent`-shaped `llm_call` from a sandbox event when\n * the event carries usage/cost data. Returns `undefined` for non-cost events\n * so the kernel can iterate the full stream without branching.\n *\n * Canonical cost-carrying types observed in the wild:\n * - `llm_call` — `data: { model, tokensIn, tokensOut, costUsd, ... }`\n * - `message.completed` / `result` — `data: { usage: { inputTokens,\n * outputTokens, totalCostUsd? } }`\n * - `cost.usage` / `usage` — same shape under a dedicated type\n *\n * Numeric coercion is strict: `Number.isFinite` gates every accumulator write\n * so a sentinel `NaN` from a misbehaving backend cannot poison the ledger.\n */\nexport function extractLlmCallEvent(\n event: SandboxEvent,\n agentRunName: string,\n): (RuntimeStreamEvent & { type: 'llm_call' }) | undefined {\n if (!event || typeof event !== 'object') return undefined\n const type = String(event.type ?? '')\n const data =\n event.data && typeof event.data === 'object'\n ? (event.data as Record<string, unknown>)\n : ({} as Record<string, unknown>)\n\n if (type === 'llm_call' || type === 'cost.usage' || type === 'usage') {\n return buildLlmCall(data, agentRunName)\n }\n if (type === 'message.completed' || type === 'result' || type === 'final') {\n const usage = data.usage as Record<string, unknown> | undefined\n if (!usage || typeof usage !== 'object') return undefined\n return buildLlmCall({ ...usage, model: data.model ?? usage.model }, agentRunName)\n }\n // sandbox 0.4.0 terminal event: `data = { tokenUsage: { inputTokens, outputTokens,\n // reasoningTokens, cacheReadInputTokens }, totalCostUsd }`. Usage lives under\n // `tokenUsage` (not `usage`) and the cost is top-level — neither matched the\n // branches above, so an in-process loopDispatch run reported {0,0} and the\n // backend-integrity guard misread a real run as a stub. Reasoning tokens are\n // billed output (reasoning models), so they fold into the output count.\n if (type === 'done') {\n const usage = data.tokenUsage as Record<string, unknown> | undefined\n if (!usage || typeof usage !== 'object') return undefined\n const out = pickFiniteNumber(usage, ['outputTokens', 'completion_tokens', 'tokensOut'])\n const reasoning = pickFiniteNumber(usage, ['reasoningTokens'])\n const mergedOut =\n out !== undefined || reasoning !== undefined ? (out ?? 0) + (reasoning ?? 0) : undefined\n return buildLlmCall(\n {\n inputTokens: usage.inputTokens,\n outputTokens: mergedOut,\n totalCostUsd: data.totalCostUsd,\n model: data.model ?? usage.model,\n },\n agentRunName,\n )\n }\n return undefined\n}\n\n/**\n * Sum the token usage + USD cost of a sandbox turn's events — the one honest way to meter an\n * `openSandboxRun` cell. Folds `extractLlmCallEvent` over the stream (which reads usage off EVERY backend\n * event shape), so a `runProfileMatrix` dispatch can report it to `ctx.cost`:\n *\n * receipt: (turn) => {\n * const u = sumSandboxUsage(turn.events)\n * return { model, inputTokens: u.input, outputTokens: u.output,\n * ...(u.costUsd > 0 ? { actualCostUsd: u.costUsd } : {}) }\n * }\n *\n * Without this a cell reads `{tokens:0, cost:0}` and the backend-integrity guard correctly aborts the\n * matrix as a stub. `agentRunName` is the fallback model label for cost-only events (default `'agent'`).\n */\nexport function sumSandboxUsage(\n events: readonly SandboxEvent[],\n agentRunName = 'agent',\n): { input: number; output: number; costUsd: number } {\n let input = 0\n let output = 0\n let costUsd = 0\n for (const ev of events) {\n const call = extractLlmCallEvent(ev, agentRunName)\n if (!call) continue\n input += call.tokensIn ?? 0\n output += call.tokensOut ?? 0\n costUsd += call.costUsd ?? 0\n }\n return { input, output, costUsd }\n}\n\nfunction buildLlmCall(\n data: Record<string, unknown>,\n agentRunName: string,\n): (RuntimeStreamEvent & { type: 'llm_call' }) | undefined {\n const tokensIn = pickFiniteNumber(data, ['tokensIn', 'inputTokens', 'prompt_tokens'])\n const tokensOut = pickFiniteNumber(data, ['tokensOut', 'outputTokens', 'completion_tokens'])\n const costUsd = pickFiniteNumber(data, ['costUsd', 'totalCostUsd', 'cost_usd', 'cost'])\n if (tokensIn === undefined && tokensOut === undefined && costUsd === undefined) {\n return undefined\n }\n const model = typeof data.model === 'string' && data.model.length > 0 ? data.model : agentRunName\n const event: RuntimeStreamEvent & { type: 'llm_call' } = {\n type: 'llm_call',\n model,\n }\n if (tokensIn !== undefined) event.tokensIn = tokensIn\n if (tokensOut !== undefined) event.tokensOut = tokensOut\n if (costUsd !== undefined) event.costUsd = costUsd\n return event\n}\n\nfunction pickFiniteNumber(data: Record<string, unknown>, keys: string[]): number | undefined {\n for (const key of keys) {\n const value = data[key]\n if (typeof value === 'number' && Number.isFinite(value)) return value\n }\n return undefined\n}\n\n/**\n * Cross-event state for {@link mapSandboxToolEvent}. Sandbox backends emit a\n * tool invocation as MANY `message.part.updated` frames on the same call id\n * (pending → running → completed), so faithful projection needs per-call\n * status memory: one `tool_call` on first sighting, at most one `tool_result`\n * on the terminal transition, nothing on intermediate re-frames. Create one\n * state per turn via {@link createSandboxToolPartState}.\n *\n * @experimental\n */\nexport interface SandboxToolPartState {\n /** Last seen status per tool call id. A terminal status is sticky — later\n * frames on a settled call project to nothing. */\n statusByCall: Map<string, string>\n /** Sequence for synthesized call ids when an event carries none. */\n seq: number\n}\n\n/**\n * Fresh per-turn {@link SandboxToolPartState} for {@link mapSandboxToolEvent} — an\n * empty call-status map so each turn projects tool frames independently.\n *\n * @experimental\n */\nexport function createSandboxToolPartState(): SandboxToolPartState {\n return { statusByCall: new Map(), seq: 0 }\n}\n\n/** Terminal tool statuses that are failures (everything here settles the call). */\nconst TERMINAL_TOOL_FAILURE =\n /^(error|errored|failed|failure|cancelled|canceled|timeout|timed_out)$/i\n\n/**\n * Project one `SandboxEvent` onto the `tool_call` / `tool_result` variants of\n * `RuntimeStreamEvent` — the tool-part projection `mapSandboxEvent`\n * deliberately does NOT perform. Opt-in and additive: `mapSandboxEvent`'s\n * default vocabulary (text/reasoning deltas + `llm_call`) is unchanged;\n * consumers that need the tool surface (chat UIs rendering tool activity)\n * compose this projector alongside it — `streamAgentTurn` does exactly that\n * under its `preserveToolParts` option.\n *\n * Handled shapes (observed on the opencode / claude-code sandbox backends):\n * - `message.part.updated` with `part.type === 'tool'` — stateful: a\n * `tool_call` on the call id's first frame (args from `state.input` or\n * `state.metadata.input`), a `tool_result` when the status transitions to\n * `completed` (result from `state.output` / `metadata.output`) or to a\n * terminal failure (result is `{ error, status, output? }` — the error\n * surfaced in-band, never dropped).\n * - bare `tool*` event types (`tool.call`, `tool_result`, …) — stateless:\n * `*result*` types project to `tool_result`, the rest to `tool_call`.\n *\n * Returns `[]` for every non-tool event.\n *\n * @experimental\n */\nexport function mapSandboxToolEvent(\n event: SandboxEvent,\n state: SandboxToolPartState,\n): (RuntimeStreamEvent & { type: 'tool_call' | 'tool_result' })[] {\n if (!event || typeof event !== 'object') return []\n const type = String(event.type ?? '')\n const data =\n event.data && typeof event.data === 'object'\n ? (event.data as Record<string, unknown>)\n : ({} as Record<string, unknown>)\n\n if (type === 'message.part.updated') {\n const part =\n data.part && typeof data.part === 'object' ? (data.part as Record<string, unknown>) : {}\n if (String(part.type ?? '') !== 'tool') return []\n return projectToolPart(part, state, typeof event.id === 'string' ? event.id : undefined)\n }\n\n if (type.includes('tool')) {\n const callId =\n pickString(data, ['toolCallId', 'tool_use_id', 'id']) ??\n (typeof event.id === 'string' ? event.id : undefined) ??\n `sandbox-tool-${++state.seq}`\n const toolName = pickString(data, ['name', 'toolName', 'tool']) ?? 'sandbox_tool'\n if (type.includes('result')) {\n return [\n {\n type: 'tool_result',\n toolName,\n toolCallId: callId,\n result: data.output ?? data.result ?? data.content ?? data,\n },\n ]\n }\n return [\n { type: 'tool_call', toolName, toolCallId: callId, args: data.input ?? data.args ?? {} },\n ]\n }\n\n return []\n}\n\nfunction projectToolPart(\n part: Record<string, unknown>,\n state: SandboxToolPartState,\n eventId: string | undefined,\n): (RuntimeStreamEvent & { type: 'tool_call' | 'tool_result' })[] {\n const callId =\n pickString(part, ['callID', 'callId', 'toolCallId', 'id']) ??\n eventId ??\n `sandbox-tool-${++state.seq}`\n const toolName = pickString(part, ['tool', 'toolName', 'name']) ?? 'sandbox_tool'\n const toolState =\n part.state && typeof part.state === 'object' ? (part.state as Record<string, unknown>) : {}\n const metadata =\n toolState.metadata && typeof toolState.metadata === 'object'\n ? (toolState.metadata as Record<string, unknown>)\n : {}\n const status = pickString(toolState, ['status']) ?? 'updated'\n\n const previous = state.statusByCall.get(callId)\n const settled =\n previous === 'completed' || (previous !== undefined && TERMINAL_TOOL_FAILURE.test(previous))\n if (settled) return []\n\n const out: (RuntimeStreamEvent & { type: 'tool_call' | 'tool_result' })[] = []\n if (previous === undefined) {\n out.push({\n type: 'tool_call',\n toolName,\n toolCallId: callId,\n args: toolState.input ?? metadata.input ?? {},\n })\n }\n state.statusByCall.set(callId, status)\n\n if (status === 'completed') {\n out.push({\n type: 'tool_result',\n toolName,\n toolCallId: callId,\n result: toolState.output ?? metadata.output ?? '',\n })\n } else if (TERMINAL_TOOL_FAILURE.test(status)) {\n const message =\n pickString(toolState, ['error', 'message']) ??\n pickString(metadata, ['error', 'message']) ??\n `sandbox tool ended with status ${status}`\n const output = toolState.output ?? metadata.output\n out.push({\n type: 'tool_result',\n toolName,\n toolCallId: callId,\n result: { error: message, status, ...(output !== undefined ? { output } : {}) },\n })\n }\n return out\n}\n\nfunction pickString(data: Record<string, unknown>, keys: string[]): string | undefined {\n for (const key of keys) {\n const value = data[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\n/**\n * Project one `SandboxEvent` onto the `RuntimeStreamEvent` chat-UX vocabulary,\n * for runtimes that bridge a sandbox `streamPrompt` into the\n * `AgentRuntime.act` streaming contract. Returns `undefined` for events that\n * have no faithful projection — the raw stream is preserved separately for the\n * `OutputAdapter`, so an unmapped event never loses data.\n *\n * Mapped (the task-optional incremental variants — no synthesized task\n * lifecycle, no guessed tool-part shapes):\n * - `message.part.updated` text part → `text_delta`\n * - `message.part.updated` reasoning/thinking part → `reasoning_delta`\n * - cost-bearing events → `llm_call` (shared with the ledger extractor)\n *\n * Tool parts are deliberately NOT mapped here (unchanged default) — compose\n * {@link mapSandboxToolEvent} alongside when a consumer needs them.\n *\n * The opencode backend emits incremental text as\n * `{ type: 'message.part.updated', data: { part: { type, text }, delta } }`;\n * `delta` is the increment, `part.text` the running accumulation.\n */\nexport function mapSandboxEvent(\n event: SandboxEvent,\n opts: { agentRunName?: string } = {},\n): RuntimeStreamEvent | undefined {\n if (!event || typeof event !== 'object') return undefined\n const type = String(event.type ?? '')\n const data =\n event.data && typeof event.data === 'object'\n ? (event.data as Record<string, unknown>)\n : ({} as Record<string, unknown>)\n\n if (type === 'message.part.updated') {\n const part =\n data.part && typeof data.part === 'object' ? (data.part as Record<string, unknown>) : {}\n const partType = String(part.type ?? '')\n const delta = typeof data.delta === 'string' ? data.delta : undefined\n const text = delta ?? (typeof part.text === 'string' ? part.text : undefined)\n if (text === undefined) return undefined\n if (partType === 'text') return { type: 'text_delta', text }\n if (partType === 'reasoning' || partType === 'thinking')\n return { type: 'reasoning_delta', text }\n return undefined\n }\n\n return extractLlmCallEvent(event, opts.agentRunName ?? 'agent')\n}\n"],"mappings":"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"}
|
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
import { p as zeroTokenUsage } from "./util-Cc9g9Y-o.js";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
//#region src/durable/spawn-journal.ts
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
* Event-sourced spawn journal for the recursive execution atom (build steps 3 + 7).
|
|
7
|
-
*
|
|
8
|
-
* The supervision tree is journaled as an append-only event log: every `spawned`,
|
|
9
|
-
* `settled`, and `cancelled` is recorded AFTER it is observed-committed (never
|
|
10
|
-
* speculative), mirroring `ConversationJournal`'s begin/append/load shape. The log
|
|
11
|
-
* holds only the THIN decision record — ids, parentage, budget, the spend a decision
|
|
12
|
-
* consumed, and a content-addressed `outRef`. The payloads the driver branched on
|
|
13
|
-
* (the `out` artifacts) live in a separate `ResultBlobStore`, keyed by `outRef`, so
|
|
14
|
-
* the journal stays small (decisions) and replay rehydrates the exact `Settled` from
|
|
15
|
-
* the blob store (evidence). This is the decision/payload split the replay argument
|
|
16
|
-
* rests on (B1/B2).
|
|
17
|
-
*
|
|
18
|
-
* Replay determinism (B2): `seq` is the monotonic cursor order `scope.next()` yielded
|
|
19
|
-
* each settlement — NOT wall-clock. `replaySpawnTree` sorts strictly by `seq` before
|
|
20
|
-
* touching the blob store, so the order in which rehydration `get`s resolve can never
|
|
21
|
-
* reorder the replayed `Settled[]`; the result is identical regardless of blob latency.
|
|
22
|
-
*
|
|
23
|
-
* @experimental
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* Mint the content-addressed `outRef` for a result artifact: `sha256:<hex>` over a
|
|
27
|
-
* stable JSON encoding. Producers call this to derive the `outRef` they journal and
|
|
28
|
-
* `put`; the FS/in-mem stores re-derive it on `put` to verify the supplied ref
|
|
29
|
-
* matches (fail loud on a mismatch — a forged ref breaks the replay invariant).
|
|
30
|
-
*
|
|
31
|
-
* Stable encoding: object keys are sorted recursively so two structurally-equal
|
|
32
|
-
* artifacts hash identically regardless of key insertion order.
|
|
33
|
-
*/
|
|
34
|
-
function contentAddress(artifact) {
|
|
35
|
-
return `sha256:${createHash("sha256").update(stableStringify(artifact), "utf-8").digest("hex")}`;
|
|
36
|
-
}
|
|
37
|
-
function stableStringify(value) {
|
|
38
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
39
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
40
|
-
return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* In-memory `ResultBlobStore`. Content-addressed: `put` verifies the supplied
|
|
44
|
-
* `outRef` matches the artifact's hash so a stale/forged ref fails loud rather than
|
|
45
|
-
* silently rehydrating the wrong payload. Idempotent on an identical re-put.
|
|
46
|
-
*/
|
|
47
|
-
var InMemoryResultBlobStore = class {
|
|
48
|
-
blobs = /* @__PURE__ */ new Map();
|
|
49
|
-
async put(outRef, artifact) {
|
|
50
|
-
assertContentAddress(outRef, artifact);
|
|
51
|
-
this.blobs.set(outRef, artifact);
|
|
52
|
-
}
|
|
53
|
-
async get(outRef) {
|
|
54
|
-
return this.blobs.has(outRef) ? this.blobs.get(outRef) : void 0;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* FS `ResultBlobStore`. One JSON file per artifact under `dir`, named by a
|
|
59
|
-
* filesystem-safe encoding of the `outRef` (`sha256:<hex>` → `sha256-<hex>.json`).
|
|
60
|
-
* `put` fsyncs so a crash between writes never loses an acknowledged blob.
|
|
61
|
-
*/
|
|
62
|
-
var FileResultBlobStore = class {
|
|
63
|
-
dir;
|
|
64
|
-
constructor(dir) {
|
|
65
|
-
this.dir = dir;
|
|
66
|
-
}
|
|
67
|
-
async put(outRef, artifact) {
|
|
68
|
-
assertContentAddress(outRef, artifact);
|
|
69
|
-
const fs = await import("node:fs/promises");
|
|
70
|
-
await fs.mkdir(this.dir, { recursive: true });
|
|
71
|
-
const fh = await fs.open(this.blobPath(outRef), "w");
|
|
72
|
-
try {
|
|
73
|
-
await fh.write(JSON.stringify(artifact));
|
|
74
|
-
await fh.sync();
|
|
75
|
-
} finally {
|
|
76
|
-
await fh.close();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
async get(outRef) {
|
|
80
|
-
const fs = await import("node:fs/promises");
|
|
81
|
-
let text;
|
|
82
|
-
try {
|
|
83
|
-
text = await fs.readFile(this.blobPath(outRef), "utf8");
|
|
84
|
-
} catch (err) {
|
|
85
|
-
if (isNoEntError(err)) return void 0;
|
|
86
|
-
throw err;
|
|
87
|
-
}
|
|
88
|
-
return JSON.parse(text);
|
|
89
|
-
}
|
|
90
|
-
blobPath(outRef) {
|
|
91
|
-
return `${this.dir}/${outRef.replace(/:/g, "-")}.json`;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
function assertContentAddress(outRef, artifact) {
|
|
95
|
-
const expected = contentAddress(artifact);
|
|
96
|
-
if (outRef !== expected) throw new Error(`blob outRef '${outRef}' does not match the artifact content hash '${expected}'; a content-addressed store refuses a mismatched ref (breaks the replay invariant)`);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* In-memory `SpawnJournal`. Appends are observed-committed only; the impl enforces
|
|
100
|
-
* the corruption guards a durable replay rests on:
|
|
101
|
-
* - an event before `beginTree` is a corrupted tree (fail loud),
|
|
102
|
-
* - a duplicate `seq` within a tree is a corrupted cursor (fail loud) — two
|
|
103
|
-
* settlements cannot share the cursor position replay orders by.
|
|
104
|
-
*/
|
|
105
|
-
var InMemorySpawnJournal = class {
|
|
106
|
-
trees = /* @__PURE__ */ new Map();
|
|
107
|
-
async loadTree(root) {
|
|
108
|
-
const tree = this.trees.get(root);
|
|
109
|
-
if (!tree) return void 0;
|
|
110
|
-
return tree.events.map((ev) => ({ ...ev }));
|
|
111
|
-
}
|
|
112
|
-
async beginTree(root, at) {
|
|
113
|
-
const existing = this.trees.get(root);
|
|
114
|
-
if (existing) {
|
|
115
|
-
if (existing.begunAt !== at) throw new Error(`spawn tree '${root}' already begun at ${existing.begunAt}; refusing to overwrite with ${at}`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
this.trees.set(root, {
|
|
119
|
-
begunAt: at,
|
|
120
|
-
events: []
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
async appendEvent(root, ev) {
|
|
124
|
-
const tree = this.trees.get(root);
|
|
125
|
-
if (!tree) throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`);
|
|
126
|
-
assertSeqUnique(root, tree.events, ev);
|
|
127
|
-
tree.events.push({ ...ev });
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
/**
|
|
131
|
-
* JSONL on disk. One line per record: the first record is `begin`, subsequent records
|
|
132
|
-
* are `event` envelopes wrapping a `SpawnEvent`. `loadTree` replays the whole file,
|
|
133
|
-
* filtering by `root`, and applies the same begin-precedes-events + unique-seq
|
|
134
|
-
* corruption guards as the in-memory impl. Each append fsyncs so a crash between
|
|
135
|
-
* writes never loses an acknowledged event.
|
|
136
|
-
*/
|
|
137
|
-
var FileSpawnJournal = class {
|
|
138
|
-
path;
|
|
139
|
-
constructor(path) {
|
|
140
|
-
this.path = path;
|
|
141
|
-
}
|
|
142
|
-
async loadTree(root) {
|
|
143
|
-
const fs = await import("node:fs/promises");
|
|
144
|
-
let text;
|
|
145
|
-
try {
|
|
146
|
-
text = await fs.readFile(this.path, "utf8");
|
|
147
|
-
} catch (err) {
|
|
148
|
-
if (isNoEntError(err)) return void 0;
|
|
149
|
-
throw err;
|
|
150
|
-
}
|
|
151
|
-
const lines = text.split("\n").filter((line) => line.length > 0);
|
|
152
|
-
let begun = false;
|
|
153
|
-
const events = [];
|
|
154
|
-
for (const line of lines) {
|
|
155
|
-
const record = JSON.parse(line);
|
|
156
|
-
if (record.root !== root) continue;
|
|
157
|
-
if (record.kind === "begin") begun = true;
|
|
158
|
-
else {
|
|
159
|
-
if (!begun) throw new Error(`spawn journal corrupted: event for tree '${root}' precedes its begin record`);
|
|
160
|
-
assertSeqUnique(root, events, record.event);
|
|
161
|
-
events.push(record.event);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return begun ? events : void 0;
|
|
165
|
-
}
|
|
166
|
-
async beginTree(root, at) {
|
|
167
|
-
const existing = await this.loadTreeBegin(root);
|
|
168
|
-
if (existing) {
|
|
169
|
-
if (existing !== at) throw new Error(`spawn tree '${root}' already begun in ${this.path} at ${existing}; refusing to overwrite with ${at}`);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
await this.appendRecord({
|
|
173
|
-
kind: "begin",
|
|
174
|
-
root,
|
|
175
|
-
at
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
async appendEvent(root, ev) {
|
|
179
|
-
const events = await this.loadTree(root);
|
|
180
|
-
if (events === void 0) throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`);
|
|
181
|
-
assertSeqUnique(root, events, ev);
|
|
182
|
-
await this.appendRecord({
|
|
183
|
-
kind: "event",
|
|
184
|
-
root,
|
|
185
|
-
event: ev
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
async loadTreeBegin(root) {
|
|
189
|
-
const fs = await import("node:fs/promises");
|
|
190
|
-
let text;
|
|
191
|
-
try {
|
|
192
|
-
text = await fs.readFile(this.path, "utf8");
|
|
193
|
-
} catch (err) {
|
|
194
|
-
if (isNoEntError(err)) return void 0;
|
|
195
|
-
throw err;
|
|
196
|
-
}
|
|
197
|
-
const lines = text.split("\n").filter((line) => line.length > 0);
|
|
198
|
-
for (const line of lines) {
|
|
199
|
-
const record = JSON.parse(line);
|
|
200
|
-
if (record.root === root && record.kind === "begin") return record.at;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
async appendRecord(record) {
|
|
204
|
-
const fs = await import("node:fs/promises");
|
|
205
|
-
const path = await import("node:path");
|
|
206
|
-
await fs.mkdir(path.dirname(this.path), { recursive: true });
|
|
207
|
-
const fh = await fs.open(this.path, "a");
|
|
208
|
-
try {
|
|
209
|
-
await fh.write(`${JSON.stringify(record)}\n`);
|
|
210
|
-
await fh.sync();
|
|
211
|
-
} finally {
|
|
212
|
-
await fh.close();
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
/**
|
|
217
|
-
* Two `seq` namespaces share the journal: a `spawned` event's `seq` is the spawn ordinal
|
|
218
|
-
* (the order children were created), and a `settled`/`cancelled` event's `seq` is the
|
|
219
|
-
* monotonic CURSOR order `scope.next()` yielded that settlement (B2). The uniqueness
|
|
220
|
-
* replay rests on is the cursor namespace — two settlements cannot share the position
|
|
221
|
-
* replay orders by — so the guard checks only settled/cancelled events. A `spawned`
|
|
222
|
-
* ordinal legitimately equals a later `settled` cursor seq and is not a collision.
|
|
223
|
-
*/
|
|
224
|
-
function assertSeqUnique(root, events, ev) {
|
|
225
|
-
if (outsideCursorNamespace(ev)) return;
|
|
226
|
-
if (events.some((e) => !outsideCursorNamespace(e) && e.seq === ev.seq)) throw new Error(`spawn journal corrupted: duplicate cursor seq ${ev.seq} in tree '${root}'; the cursor order replay relies on is not unique`);
|
|
227
|
-
}
|
|
228
|
-
/** Node-CREATION and informational records — outside the cursor namespace whose uniqueness replay
|
|
229
|
-
* ordering rests on. The single predicate both the guard's sides read, so a new event kind is
|
|
230
|
-
* classified once. */
|
|
231
|
-
function outsideCursorNamespace(ev) {
|
|
232
|
-
return ev.kind === "spawned" || ev.kind === "waiting" || ev.kind === "metered";
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Re-feed a journaled spawn tree in strict `seq` order, rehydrating each settled
|
|
236
|
-
* child's `out` from the blob store by `outRef`, and return the `Settled[]` exactly
|
|
237
|
-
* as `scope.next()` originally delivered them.
|
|
238
|
-
*
|
|
239
|
-
* Determinism (B2): the events are sorted by `seq` BEFORE any blob `get`, so the
|
|
240
|
-
* replay order is the recorded cursor order regardless of how fast each rehydration
|
|
241
|
-
* resolves. `at` (wall-clock) is never a replay input. Fail loud on a tree that was
|
|
242
|
-
* never begun, a settled-done event missing its `outRef`, or a blob the store can't
|
|
243
|
-
* rehydrate — a silent gap would let `act` branch on the wrong evidence.
|
|
244
|
-
*/
|
|
245
|
-
async function replaySpawnTree(journal, blobs, root) {
|
|
246
|
-
const events = await journal.loadTree(root);
|
|
247
|
-
if (events === void 0) throw new Error(`replaySpawnTree: no journaled tree for root '${root}'`);
|
|
248
|
-
const ordered = [...events].sort((a, b) => a.seq - b.seq);
|
|
249
|
-
const labels = /* @__PURE__ */ new Map();
|
|
250
|
-
for (const ev of ordered) if (ev.kind === "spawned" || ev.kind === "waiting") labels.set(ev.id, ev.label);
|
|
251
|
-
const settled = [];
|
|
252
|
-
for (const ev of ordered) {
|
|
253
|
-
if (ev.kind === "spawned") continue;
|
|
254
|
-
if (ev.kind === "waiting") continue;
|
|
255
|
-
if (ev.kind === "metered") continue;
|
|
256
|
-
if (ev.kind === "woken") {
|
|
257
|
-
if (ev.by === "cancelled" || ev.outRef === void 0) {
|
|
258
|
-
settled.push({
|
|
259
|
-
kind: "down",
|
|
260
|
-
handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "cancelled"),
|
|
261
|
-
reason: "wait cancelled",
|
|
262
|
-
infra: false,
|
|
263
|
-
restartCount: 0,
|
|
264
|
-
seq: ev.seq
|
|
265
|
-
});
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
const outcome = await blobs.get(ev.outRef);
|
|
269
|
-
if (outcome === void 0) throw new Error(`replaySpawnTree: blob store has no wait outcome for outRef '${ev.outRef}' (node '${ev.id}', seq ${ev.seq})`);
|
|
270
|
-
settled.push({
|
|
271
|
-
kind: "done",
|
|
272
|
-
handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "done"),
|
|
273
|
-
out: outcome,
|
|
274
|
-
outRef: ev.outRef,
|
|
275
|
-
spent: zeroSpend(),
|
|
276
|
-
seq: ev.seq
|
|
277
|
-
});
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (ev.kind === "cancelled") {
|
|
281
|
-
settled.push({
|
|
282
|
-
kind: "down",
|
|
283
|
-
handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "cancelled"),
|
|
284
|
-
reason: ev.reason,
|
|
285
|
-
infra: false,
|
|
286
|
-
restartCount: 0,
|
|
287
|
-
seq: ev.seq
|
|
288
|
-
});
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
if (ev.status === "down") {
|
|
292
|
-
settled.push({
|
|
293
|
-
kind: "down",
|
|
294
|
-
handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "failed"),
|
|
295
|
-
reason: ev.verdict?.notes ?? "child down",
|
|
296
|
-
infra: ev.infra === true,
|
|
297
|
-
restartCount: 0,
|
|
298
|
-
seq: ev.seq
|
|
299
|
-
});
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
if (ev.outRef === void 0) throw new Error(`replaySpawnTree: settled-done event for '${ev.id}' (seq ${ev.seq}) has no outRef; cannot rehydrate the result the driver branched on`);
|
|
303
|
-
const out = await blobs.get(ev.outRef);
|
|
304
|
-
if (out === void 0) throw new Error(`replaySpawnTree: blob store has no artifact for outRef '${ev.outRef}' (node '${ev.id}', seq ${ev.seq})`);
|
|
305
|
-
settled.push({
|
|
306
|
-
kind: "done",
|
|
307
|
-
handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "done"),
|
|
308
|
-
out,
|
|
309
|
-
outRef: ev.outRef,
|
|
310
|
-
verdict: ev.verdict,
|
|
311
|
-
spent: ev.spent,
|
|
312
|
-
seq: ev.seq
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
return settled;
|
|
316
|
-
}
|
|
317
|
-
function replayHandle(id, label, status) {
|
|
318
|
-
return {
|
|
319
|
-
id,
|
|
320
|
-
label,
|
|
321
|
-
status,
|
|
322
|
-
abort() {
|
|
323
|
-
throw new Error(`cannot abort node '${id}': replayed handles are terminal, not live`);
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Materialize a recorded `TreeView` from a journaled event list for inspection. Folds
|
|
329
|
-
* `spawned`/`settled`/`cancelled` into a per-node snapshot in `seq` order, then adds each
|
|
330
|
-
* `metered` event's driver-inference spend onto its node in a separate additive pass so the view
|
|
331
|
-
* matches the recorded cursor. It does not recover live executors or driver state after restart.
|
|
332
|
-
*/
|
|
333
|
-
function materializeTreeView(events) {
|
|
334
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
335
|
-
let root;
|
|
336
|
-
const spawns = events.filter((ev) => ev.kind === "spawned" || ev.kind === "waiting").sort((a, b) => a.seq - b.seq);
|
|
337
|
-
const settlements = events.filter((ev) => ev.kind !== "spawned" && ev.kind !== "waiting" && ev.kind !== "metered").sort((a, b) => a.seq - b.seq);
|
|
338
|
-
for (const ev of spawns) {
|
|
339
|
-
if (ev.kind === "waiting") {
|
|
340
|
-
nodes.set(ev.id, {
|
|
341
|
-
id: ev.id,
|
|
342
|
-
parent: ev.parent,
|
|
343
|
-
label: ev.label,
|
|
344
|
-
status: "waiting",
|
|
345
|
-
runtime: "wait",
|
|
346
|
-
budget: {
|
|
347
|
-
maxIterations: 0,
|
|
348
|
-
maxTokens: 0
|
|
349
|
-
},
|
|
350
|
-
spent: zeroSpend()
|
|
351
|
-
});
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
if (ev.parent === void 0 && root === void 0) root = ev.id;
|
|
355
|
-
nodes.set(ev.id, {
|
|
356
|
-
id: ev.id,
|
|
357
|
-
parent: ev.parent,
|
|
358
|
-
label: ev.label,
|
|
359
|
-
status: "pending",
|
|
360
|
-
runtime: ev.runtime,
|
|
361
|
-
budget: ev.budget,
|
|
362
|
-
spent: zeroSpend()
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
for (const ev of settlements) if (ev.kind === "settled") {
|
|
366
|
-
const node = requireNode(nodes, ev.id);
|
|
367
|
-
node.status = ev.status === "done" ? "done" : "failed";
|
|
368
|
-
node.spent = ev.spent;
|
|
369
|
-
node.outRef = ev.outRef;
|
|
370
|
-
} else if (ev.kind === "woken") {
|
|
371
|
-
const node = requireNode(nodes, ev.id);
|
|
372
|
-
node.status = ev.by === "cancelled" ? "cancelled" : "done";
|
|
373
|
-
node.outRef = ev.outRef;
|
|
374
|
-
} else {
|
|
375
|
-
const node = requireNode(nodes, ev.id);
|
|
376
|
-
node.status = "cancelled";
|
|
377
|
-
}
|
|
378
|
-
for (const ev of events) {
|
|
379
|
-
if (ev.kind !== "metered") continue;
|
|
380
|
-
const node = requireNode(nodes, ev.id);
|
|
381
|
-
node.spent = addJournalSpend(node.spent, ev.spend);
|
|
382
|
-
}
|
|
383
|
-
const snapshots = [...nodes.values()].map(freezeSnapshot);
|
|
384
|
-
return {
|
|
385
|
-
root: root ?? snapshots[0]?.id ?? "",
|
|
386
|
-
nodes: snapshots,
|
|
387
|
-
inFlight: snapshots.filter((n) => n.status === "running" || n.status === "acquiring").length,
|
|
388
|
-
waiting: snapshots.filter((n) => n.status === "waiting").length
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* The waits a journaled tree shows as ARMED but never woken — what a resumed run re-arms with the
|
|
393
|
-
* ORIGINAL absolute deadline. Reading it from the journal (rather than from any live state) is
|
|
394
|
-
* what makes "SIGKILL a waiting tree, a new process keeps waiting to the same instant" true.
|
|
395
|
-
*/
|
|
396
|
-
function pendingWaits(events) {
|
|
397
|
-
const woken = /* @__PURE__ */ new Set();
|
|
398
|
-
for (const ev of events) if (ev.kind === "woken") woken.add(ev.id);
|
|
399
|
-
const pending = [];
|
|
400
|
-
for (const ev of events) {
|
|
401
|
-
if (ev.kind !== "waiting" || woken.has(ev.id)) continue;
|
|
402
|
-
pending.push({
|
|
403
|
-
id: ev.id,
|
|
404
|
-
label: ev.label,
|
|
405
|
-
spec: ev.spec,
|
|
406
|
-
armedAt: ev.armedAt,
|
|
407
|
-
ordinal: ev.seq
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
return pending.sort((a, b) => a.ordinal - b.ordinal);
|
|
411
|
-
}
|
|
412
|
-
function zeroSpend() {
|
|
413
|
-
return {
|
|
414
|
-
iterations: 0,
|
|
415
|
-
tokens: zeroTokenUsage(),
|
|
416
|
-
usd: 0,
|
|
417
|
-
ms: 0
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
/** Add a `metered` spend record onto a node's accumulated spend (per channel). */
|
|
421
|
-
function addJournalSpend(a, b) {
|
|
422
|
-
return {
|
|
423
|
-
iterations: a.iterations + b.iterations,
|
|
424
|
-
tokens: {
|
|
425
|
-
input: a.tokens.input + b.tokens.input,
|
|
426
|
-
output: a.tokens.output + b.tokens.output
|
|
427
|
-
},
|
|
428
|
-
usd: a.usd + b.usd,
|
|
429
|
-
...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
|
|
430
|
-
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
431
|
-
ms: a.ms + b.ms
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
function requireNode(nodes, id) {
|
|
435
|
-
const node = nodes.get(id);
|
|
436
|
-
if (!node) throw new Error(`spawn journal corrupted: settle/cancel for node '${id}' with no prior spawn`);
|
|
437
|
-
return node;
|
|
438
|
-
}
|
|
439
|
-
function freezeSnapshot(node) {
|
|
440
|
-
return {
|
|
441
|
-
id: node.id,
|
|
442
|
-
parent: node.parent,
|
|
443
|
-
label: node.label,
|
|
444
|
-
status: node.status,
|
|
445
|
-
runtime: node.runtime,
|
|
446
|
-
budget: node.budget,
|
|
447
|
-
spent: node.spent,
|
|
448
|
-
outRef: node.outRef
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
function isNoEntError(err) {
|
|
452
|
-
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
453
|
-
}
|
|
454
|
-
//#endregion
|
|
455
|
-
export { contentAddress as a, replaySpawnTree as c, InMemorySpawnJournal as i, FileSpawnJournal as n, materializeTreeView as o, InMemoryResultBlobStore as r, pendingWaits as s, FileResultBlobStore as t };
|
|
456
|
-
|
|
457
|
-
//# sourceMappingURL=spawn-journal-DCPbicXB.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-journal-DCPbicXB.js","names":[],"sources":["../src/durable/spawn-journal.ts"],"sourcesContent":["/**\n *\n * Event-sourced spawn journal for the recursive execution atom (build steps 3 + 7).\n *\n * The supervision tree is journaled as an append-only event log: every `spawned`,\n * `settled`, and `cancelled` is recorded AFTER it is observed-committed (never\n * speculative), mirroring `ConversationJournal`'s begin/append/load shape. The log\n * holds only the THIN decision record — ids, parentage, budget, the spend a decision\n * consumed, and a content-addressed `outRef`. The payloads the driver branched on\n * (the `out` artifacts) live in a separate `ResultBlobStore`, keyed by `outRef`, so\n * the journal stays small (decisions) and replay rehydrates the exact `Settled` from\n * the blob store (evidence). This is the decision/payload split the replay argument\n * rests on (B1/B2).\n *\n * Replay determinism (B2): `seq` is the monotonic cursor order `scope.next()` yielded\n * each settlement — NOT wall-clock. `replaySpawnTree` sorts strictly by `seq` before\n * touching the blob store, so the order in which rehydration `get`s resolve can never\n * reorder the replayed `Settled[]`; the result is identical regardless of blob latency.\n *\n * @experimental\n */\n\nimport { createHash } from 'node:crypto'\nimport type {\n NodeId,\n NodeSnapshot,\n NodeStatus,\n ResultBlobStore,\n Runtime,\n Settled,\n SpawnEvent,\n SpawnJournal,\n Spend,\n TreeView,\n} from '../runtime/supervise/types'\nimport type { PendingWait } from '../runtime/supervise/wait'\nimport { zeroTokenUsage } from '../runtime/util'\n\n// ── Content addressing ──────────────────────────────────────────────────────\n\n/**\n * Mint the content-addressed `outRef` for a result artifact: `sha256:<hex>` over a\n * stable JSON encoding. Producers call this to derive the `outRef` they journal and\n * `put`; the FS/in-mem stores re-derive it on `put` to verify the supplied ref\n * matches (fail loud on a mismatch — a forged ref breaks the replay invariant).\n *\n * Stable encoding: object keys are sorted recursively so two structurally-equal\n * artifacts hash identically regardless of key insertion order.\n */\nexport function contentAddress(artifact: unknown): string {\n const hex = createHash('sha256').update(stableStringify(artifact), 'utf-8').digest('hex')\n return `sha256:${hex}`\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(',')}}`\n}\n\n// ── Result blob store ─────────────────────────────────────────────────────────\n\n/**\n * In-memory `ResultBlobStore`. Content-addressed: `put` verifies the supplied\n * `outRef` matches the artifact's hash so a stale/forged ref fails loud rather than\n * silently rehydrating the wrong payload. Idempotent on an identical re-put.\n */\nexport class InMemoryResultBlobStore implements ResultBlobStore {\n private readonly blobs = new Map<string, unknown>()\n\n async put(outRef: string, artifact: unknown): Promise<void> {\n assertContentAddress(outRef, artifact)\n this.blobs.set(outRef, artifact)\n }\n\n async get(outRef: string): Promise<unknown | undefined> {\n return this.blobs.has(outRef) ? this.blobs.get(outRef) : undefined\n }\n}\n\n/**\n * FS `ResultBlobStore`. One JSON file per artifact under `dir`, named by a\n * filesystem-safe encoding of the `outRef` (`sha256:<hex>` → `sha256-<hex>.json`).\n * `put` fsyncs so a crash between writes never loses an acknowledged blob.\n */\nexport class FileResultBlobStore implements ResultBlobStore {\n constructor(private readonly dir: string) {}\n\n async put(outRef: string, artifact: unknown): Promise<void> {\n assertContentAddress(outRef, artifact)\n const fs = await import('node:fs/promises')\n await fs.mkdir(this.dir, { recursive: true })\n const fh = await fs.open(this.blobPath(outRef), 'w')\n try {\n await fh.write(JSON.stringify(artifact))\n await fh.sync()\n } finally {\n await fh.close()\n }\n }\n\n async get(outRef: string): Promise<unknown | undefined> {\n const fs = await import('node:fs/promises')\n let text: string\n try {\n text = await fs.readFile(this.blobPath(outRef), 'utf8')\n } catch (err) {\n if (isNoEntError(err)) return undefined\n throw err\n }\n return JSON.parse(text)\n }\n\n private blobPath(outRef: string): string {\n return `${this.dir}/${outRef.replace(/:/g, '-')}.json`\n }\n}\n\nfunction assertContentAddress(outRef: string, artifact: unknown): void {\n const expected = contentAddress(artifact)\n if (outRef !== expected) {\n throw new Error(\n `blob outRef '${outRef}' does not match the artifact content hash '${expected}'; ` +\n 'a content-addressed store refuses a mismatched ref (breaks the replay invariant)',\n )\n }\n}\n\n// ── Spawn journal ──────────────────────────────────────────────────────────────\n\n/**\n * In-memory `SpawnJournal`. Appends are observed-committed only; the impl enforces\n * the corruption guards a durable replay rests on:\n * - an event before `beginTree` is a corrupted tree (fail loud),\n * - a duplicate `seq` within a tree is a corrupted cursor (fail loud) — two\n * settlements cannot share the cursor position replay orders by.\n */\nexport class InMemorySpawnJournal implements SpawnJournal {\n private readonly trees = new Map<NodeId, { begunAt: string; events: SpawnEvent[] }>()\n\n async loadTree(root: NodeId): Promise<SpawnEvent[] | undefined> {\n const tree = this.trees.get(root)\n if (!tree) return undefined\n return tree.events.map((ev) => ({ ...ev }))\n }\n\n async beginTree(root: NodeId, at: string): Promise<void> {\n const existing = this.trees.get(root)\n if (existing) {\n if (existing.begunAt !== at) {\n throw new Error(\n `spawn tree '${root}' already begun at ${existing.begunAt}; refusing to overwrite with ${at}`,\n )\n }\n return\n }\n this.trees.set(root, { begunAt: at, events: [] })\n }\n\n async appendEvent(root: NodeId, ev: SpawnEvent): Promise<void> {\n const tree = this.trees.get(root)\n if (!tree) {\n throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`)\n }\n assertSeqUnique(root, tree.events, ev)\n tree.events.push({ ...ev })\n }\n}\n\n/**\n * JSONL on disk. One line per record: the first record is `begin`, subsequent records\n * are `event` envelopes wrapping a `SpawnEvent`. `loadTree` replays the whole file,\n * filtering by `root`, and applies the same begin-precedes-events + unique-seq\n * corruption guards as the in-memory impl. Each append fsyncs so a crash between\n * writes never loses an acknowledged event.\n */\nexport class FileSpawnJournal implements SpawnJournal {\n constructor(private readonly path: string) {}\n\n async loadTree(root: NodeId): Promise<SpawnEvent[] | undefined> {\n const fs = await import('node:fs/promises')\n let text: string\n try {\n text = await fs.readFile(this.path, 'utf8')\n } catch (err) {\n if (isNoEntError(err)) return undefined\n throw err\n }\n const lines = text.split('\\n').filter((line) => line.length > 0)\n let begun = false\n const events: SpawnEvent[] = []\n for (const line of lines) {\n const record = JSON.parse(line) as SpawnJournalRecord\n if (record.root !== root) continue\n if (record.kind === 'begin') {\n begun = true\n } else {\n if (!begun) {\n throw new Error(\n `spawn journal corrupted: event for tree '${root}' precedes its begin record`,\n )\n }\n assertSeqUnique(root, events, record.event)\n events.push(record.event)\n }\n }\n return begun ? events : undefined\n }\n\n async beginTree(root: NodeId, at: string): Promise<void> {\n const existing = await this.loadTreeBegin(root)\n if (existing) {\n if (existing !== at) {\n throw new Error(\n `spawn tree '${root}' already begun in ${this.path} at ${existing}; refusing to overwrite with ${at}`,\n )\n }\n return\n }\n await this.appendRecord({ kind: 'begin', root, at })\n }\n\n async appendEvent(root: NodeId, ev: SpawnEvent): Promise<void> {\n const events = await this.loadTree(root)\n if (events === undefined) {\n throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`)\n }\n assertSeqUnique(root, events, ev)\n await this.appendRecord({ kind: 'event', root, event: ev })\n }\n\n private async loadTreeBegin(root: NodeId): Promise<string | undefined> {\n const fs = await import('node:fs/promises')\n let text: string\n try {\n text = await fs.readFile(this.path, 'utf8')\n } catch (err) {\n if (isNoEntError(err)) return undefined\n throw err\n }\n const lines = text.split('\\n').filter((line) => line.length > 0)\n for (const line of lines) {\n const record = JSON.parse(line) as SpawnJournalRecord\n if (record.root === root && record.kind === 'begin') return record.at\n }\n return undefined\n }\n\n private async appendRecord(record: SpawnJournalRecord): Promise<void> {\n const fs = await import('node:fs/promises')\n const path = await import('node:path')\n await fs.mkdir(path.dirname(this.path), { recursive: true })\n const fh = await fs.open(this.path, 'a')\n try {\n await fh.write(`${JSON.stringify(record)}\\n`)\n await fh.sync()\n } finally {\n await fh.close()\n }\n }\n}\n\ntype SpawnJournalRecord =\n | { kind: 'begin'; root: NodeId; at: string }\n | { kind: 'event'; root: NodeId; event: SpawnEvent }\n\n/**\n * Two `seq` namespaces share the journal: a `spawned` event's `seq` is the spawn ordinal\n * (the order children were created), and a `settled`/`cancelled` event's `seq` is the\n * monotonic CURSOR order `scope.next()` yielded that settlement (B2). The uniqueness\n * replay rests on is the cursor namespace — two settlements cannot share the position\n * replay orders by — so the guard checks only settled/cancelled events. A `spawned`\n * ordinal legitimately equals a later `settled` cursor seq and is not a collision.\n */\nfunction assertSeqUnique(root: NodeId, events: SpawnEvent[], ev: SpawnEvent): void {\n // `spawned` (ordinal namespace), `waiting` (the wait-ordinal namespace — it CREATES a node, it\n // does not settle one), and `metered` (informational spend, no settlement order) live outside\n // the cursor-uniqueness namespace replay relies on. `woken` IS a settlement and does not.\n if (outsideCursorNamespace(ev)) return\n if (events.some((e) => !outsideCursorNamespace(e) && e.seq === ev.seq)) {\n throw new Error(\n `spawn journal corrupted: duplicate cursor seq ${ev.seq} in tree '${root}'; ` +\n 'the cursor order replay relies on is not unique',\n )\n }\n}\n\n/** Node-CREATION and informational records — outside the cursor namespace whose uniqueness replay\n * ordering rests on. The single predicate both the guard's sides read, so a new event kind is\n * classified once. */\nfunction outsideCursorNamespace(ev: SpawnEvent): boolean {\n return ev.kind === 'spawned' || ev.kind === 'waiting' || ev.kind === 'metered'\n}\n\n// ── Replay executor (build step 7) ───────────────────────────────────────────────\n\n/**\n * Re-feed a journaled spawn tree in strict `seq` order, rehydrating each settled\n * child's `out` from the blob store by `outRef`, and return the `Settled[]` exactly\n * as `scope.next()` originally delivered them.\n *\n * Determinism (B2): the events are sorted by `seq` BEFORE any blob `get`, so the\n * replay order is the recorded cursor order regardless of how fast each rehydration\n * resolves. `at` (wall-clock) is never a replay input. Fail loud on a tree that was\n * never begun, a settled-done event missing its `outRef`, or a blob the store can't\n * rehydrate — a silent gap would let `act` branch on the wrong evidence.\n */\nexport async function replaySpawnTree(\n journal: SpawnJournal,\n blobs: ResultBlobStore,\n root: NodeId,\n): Promise<Settled<unknown>[]> {\n const events = await journal.loadTree(root)\n if (events === undefined) {\n throw new Error(`replaySpawnTree: no journaled tree for root '${root}'`)\n }\n const ordered = [...events].sort((a, b) => a.seq - b.seq)\n const labels = new Map<NodeId, string>()\n for (const ev of ordered) {\n if (ev.kind === 'spawned' || ev.kind === 'waiting') labels.set(ev.id, ev.label)\n }\n const settled: Settled<unknown>[] = []\n for (const ev of ordered) {\n if (ev.kind === 'spawned') continue\n if (ev.kind === 'waiting') continue // arms a wait node; `woken` is its settlement\n if (ev.kind === 'metered') continue // a spend record, not a settlement — irrelevant to replay\n if (ev.kind === 'woken') {\n // A wait that was cancelled carries no outcome blob — it replays as a `down`, exactly as a\n // cancelled worker does. A fired/timed-out wait rehydrates its `WaitOutcome` and costs zero.\n if (ev.by === 'cancelled' || ev.outRef === undefined) {\n settled.push({\n kind: 'down',\n handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, 'cancelled'),\n reason: 'wait cancelled',\n infra: false,\n restartCount: 0,\n seq: ev.seq,\n })\n continue\n }\n const outcome = await blobs.get(ev.outRef)\n if (outcome === undefined) {\n throw new Error(\n `replaySpawnTree: blob store has no wait outcome for outRef '${ev.outRef}' (node '${ev.id}', seq ${ev.seq})`,\n )\n }\n settled.push({\n kind: 'done',\n handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, 'done'),\n out: outcome,\n outRef: ev.outRef,\n spent: zeroSpend(),\n seq: ev.seq,\n })\n continue\n }\n if (ev.kind === 'cancelled') {\n settled.push({\n kind: 'down',\n handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, 'cancelled'),\n reason: ev.reason,\n infra: false,\n restartCount: 0,\n seq: ev.seq,\n })\n continue\n }\n if (ev.status === 'down') {\n settled.push({\n kind: 'down',\n handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, 'failed'),\n reason: ev.verdict?.notes ?? 'child down',\n infra: ev.infra === true,\n restartCount: 0,\n seq: ev.seq,\n })\n continue\n }\n if (ev.outRef === undefined) {\n throw new Error(\n `replaySpawnTree: settled-done event for '${ev.id}' (seq ${ev.seq}) has no outRef; ` +\n 'cannot rehydrate the result the driver branched on',\n )\n }\n const out = await blobs.get(ev.outRef)\n if (out === undefined) {\n throw new Error(\n `replaySpawnTree: blob store has no artifact for outRef '${ev.outRef}' (node '${ev.id}', seq ${ev.seq})`,\n )\n }\n settled.push({\n kind: 'done',\n handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, 'done'),\n out,\n outRef: ev.outRef,\n verdict: ev.verdict,\n spent: ev.spent,\n seq: ev.seq,\n })\n }\n return settled\n}\n\nfunction replayHandle(id: NodeId, label: string, status: NodeStatus) {\n return {\n id,\n label,\n status,\n abort() {\n throw new Error(`cannot abort node '${id}': replayed handles are terminal, not live`)\n },\n }\n}\n\n/**\n * Materialize a recorded `TreeView` from a journaled event list for inspection. Folds\n * `spawned`/`settled`/`cancelled` into a per-node snapshot in `seq` order, then adds each\n * `metered` event's driver-inference spend onto its node in a separate additive pass so the view\n * matches the recorded cursor. It does not recover live executors or driver state after restart.\n */\nexport function materializeTreeView(events: SpawnEvent[]): TreeView {\n const nodes = new Map<NodeId, MutableSnapshot>()\n let root: NodeId | undefined\n // `spawned` (ordinal namespace) and `settled`/`cancelled` (cursor namespace) carry\n // overlapping `seq` values, so create every node before any update — process spawns in\n // ordinal order, then settlements/cancellations in cursor order. A settle/cancel for an\n // un-spawned node is a corrupted log (fail loud via requireNode).\n const spawns = events\n .filter(\n (ev): ev is Extract<SpawnEvent, { kind: 'spawned' | 'waiting' }> =>\n ev.kind === 'spawned' || ev.kind === 'waiting',\n )\n .sort((a, b) => a.seq - b.seq)\n const settlements = events\n .filter((ev) => ev.kind !== 'spawned' && ev.kind !== 'waiting' && ev.kind !== 'metered')\n .sort((a, b) => a.seq - b.seq)\n for (const ev of spawns) {\n if (ev.kind === 'waiting') {\n // An ARMED wait reads `waiting` until a `woken` event lands. That is the whole durability\n // claim: a materialized tree from a journal whose process died mid-wait still shows the wait.\n nodes.set(ev.id, {\n id: ev.id,\n parent: ev.parent,\n label: ev.label,\n status: 'waiting',\n runtime: 'wait',\n budget: { maxIterations: 0, maxTokens: 0 },\n spent: zeroSpend(),\n })\n continue\n }\n if (ev.parent === undefined && root === undefined) root = ev.id\n nodes.set(ev.id, {\n id: ev.id,\n parent: ev.parent,\n label: ev.label,\n status: 'pending',\n runtime: ev.runtime,\n budget: ev.budget,\n spent: zeroSpend(),\n })\n }\n for (const ev of settlements) {\n if (ev.kind === 'settled') {\n const node = requireNode(nodes, ev.id)\n node.status = ev.status === 'done' ? 'done' : 'failed'\n node.spent = ev.spent\n node.outRef = ev.outRef\n } else if (ev.kind === 'woken') {\n const node = requireNode(nodes, ev.id)\n node.status = ev.by === 'cancelled' ? 'cancelled' : 'done'\n node.outRef = ev.outRef\n } else {\n const node = requireNode(nodes, ev.id)\n node.status = 'cancelled'\n }\n }\n // Driver inference: a separate pass so it accumulates ONTO the settled child-work base (no\n // dependence on metered-vs-settled seq order) without touching node status.\n for (const ev of events) {\n if (ev.kind !== 'metered') continue\n const node = requireNode(nodes, ev.id)\n node.spent = addJournalSpend(node.spent, ev.spend)\n }\n const snapshots = [...nodes.values()].map(freezeSnapshot)\n return {\n root: root ?? snapshots[0]?.id ?? '',\n nodes: snapshots,\n inFlight: snapshots.filter((n) => n.status === 'running' || n.status === 'acquiring').length,\n waiting: snapshots.filter((n) => n.status === 'waiting').length,\n }\n}\n\n/**\n * The waits a journaled tree shows as ARMED but never woken — what a resumed run re-arms with the\n * ORIGINAL absolute deadline. Reading it from the journal (rather than from any live state) is\n * what makes \"SIGKILL a waiting tree, a new process keeps waiting to the same instant\" true.\n */\nexport function pendingWaits(events: SpawnEvent[]): PendingWait[] {\n const woken = new Set<NodeId>()\n for (const ev of events) if (ev.kind === 'woken') woken.add(ev.id)\n const pending: PendingWait[] = []\n for (const ev of events) {\n if (ev.kind !== 'waiting' || woken.has(ev.id)) continue\n pending.push({\n id: ev.id,\n label: ev.label,\n spec: ev.spec,\n armedAt: ev.armedAt,\n ordinal: ev.seq,\n })\n }\n return pending.sort((a, b) => a.ordinal - b.ordinal)\n}\n\ninterface MutableSnapshot {\n id: NodeId\n parent?: NodeId\n label: string\n status: NodeStatus\n runtime: Runtime\n budget: NodeSnapshot['budget']\n spent: Spend\n outRef?: string\n}\n\nfunction zeroSpend(): Spend {\n return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 }\n}\n\n/** Add a `metered` spend record onto a node's accumulated spend (per channel). */\nfunction addJournalSpend(a: Spend, b: Spend): Spend {\n return {\n iterations: a.iterations + b.iterations,\n tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },\n usd: a.usd + b.usd,\n ...(a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {}),\n ...(a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {}),\n ms: a.ms + b.ms,\n }\n}\n\nfunction requireNode(nodes: Map<NodeId, MutableSnapshot>, id: NodeId): MutableSnapshot {\n const node = nodes.get(id)\n if (!node) {\n throw new Error(`spawn journal corrupted: settle/cancel for node '${id}' with no prior spawn`)\n }\n return node\n}\n\nfunction freezeSnapshot(node: MutableSnapshot): NodeSnapshot {\n return {\n id: node.id,\n parent: node.parent,\n label: node.label,\n status: node.status,\n runtime: node.runtime,\n budget: node.budget,\n spent: node.spent,\n outRef: node.outRef,\n }\n}\n\nfunction isNoEntError(err: unknown): boolean {\n return (\n typeof err === 'object' &&\n err !== null &&\n 'code' in err &&\n (err as { code: unknown }).code === 'ENOENT'\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,eAAe,UAA2B;CAExD,OAAO,UADK,WAAW,QAAQ,CAAC,CAAC,OAAO,gBAAgB,QAAQ,GAAG,OAAO,CAAC,CAAC,OAAO,KAChE;AACrB;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK,KAAK;CACjF,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAI1E,OAAO,IAHS,OAAO,QAAQ,KAAgC,CAAC,CAC7D,QAAQ,GAAG,OAAO,MAAM,KAAA,CAAS,CAAC,CAClC,MAAM,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAC/B,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE;AAC7F;;;;;;AASA,IAAa,0BAAb,MAAgE;CAC9D,wBAAyB,IAAI,IAAqB;CAElD,MAAM,IAAI,QAAgB,UAAkC;EAC1D,qBAAqB,QAAQ,QAAQ;EACrC,KAAK,MAAM,IAAI,QAAQ,QAAQ;CACjC;CAEA,MAAM,IAAI,QAA8C;EACtD,OAAO,KAAK,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM,IAAI,MAAM,IAAI,KAAA;CAC3D;AACF;;;;;;AAOA,IAAa,sBAAb,MAA4D;CAC7B;CAA7B,YAAY,KAA8B;EAAb,KAAA,MAAA;CAAc;CAE3C,MAAM,IAAI,QAAgB,UAAkC;EAC1D,qBAAqB,QAAQ,QAAQ;EACrC,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,GAAG,MAAM,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC;EAC5C,MAAM,KAAK,MAAM,GAAG,KAAK,KAAK,SAAS,MAAM,GAAG,GAAG;EACnD,IAAI;GACF,MAAM,GAAG,MAAM,KAAK,UAAU,QAAQ,CAAC;GACvC,MAAM,GAAG,KAAK;EAChB,UAAU;GACR,MAAM,GAAG,MAAM;EACjB;CACF;CAEA,MAAM,IAAI,QAA8C;EACtD,MAAM,KAAK,MAAM,OAAO;EACxB,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,GAAG,SAAS,KAAK,SAAS,MAAM,GAAG,MAAM;EACxD,SAAS,KAAK;GACZ,IAAI,aAAa,GAAG,GAAG,OAAO,KAAA;GAC9B,MAAM;EACR;EACA,OAAO,KAAK,MAAM,IAAI;CACxB;CAEA,SAAiB,QAAwB;EACvC,OAAO,GAAG,KAAK,IAAI,GAAG,OAAO,QAAQ,MAAM,GAAG,EAAE;CAClD;AACF;AAEA,SAAS,qBAAqB,QAAgB,UAAyB;CACrE,MAAM,WAAW,eAAe,QAAQ;CACxC,IAAI,WAAW,UACb,MAAM,IAAI,MACR,gBAAgB,OAAO,8CAA8C,SAAS,oFAEhF;AAEJ;;;;;;;;AAWA,IAAa,uBAAb,MAA0D;CACxD,wBAAyB,IAAI,IAAuD;CAEpF,MAAM,SAAS,MAAiD;EAC9D,MAAM,OAAO,KAAK,MAAM,IAAI,IAAI;EAChC,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,OAAO,KAAK,OAAO,KAAK,QAAQ,EAAE,GAAG,GAAG,EAAE;CAC5C;CAEA,MAAM,UAAU,MAAc,IAA2B;EACvD,MAAM,WAAW,KAAK,MAAM,IAAI,IAAI;EACpC,IAAI,UAAU;GACZ,IAAI,SAAS,YAAY,IACvB,MAAM,IAAI,MACR,eAAe,KAAK,qBAAqB,SAAS,QAAQ,+BAA+B,IAC3F;GAEF;EACF;EACA,KAAK,MAAM,IAAI,MAAM;GAAE,SAAS;GAAI,QAAQ,CAAC;EAAE,CAAC;CAClD;CAEA,MAAM,YAAY,MAAc,IAA+B;EAC7D,MAAM,OAAO,KAAK,MAAM,IAAI,IAAI;EAChC,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,8CAA8C,KAAK,wBAAwB;EAE7F,gBAAgB,MAAM,KAAK,QAAQ,EAAE;EACrC,KAAK,OAAO,KAAK,EAAE,GAAG,GAAG,CAAC;CAC5B;AACF;;;;;;;;AASA,IAAa,mBAAb,MAAsD;CACvB;CAA7B,YAAY,MAA+B;EAAd,KAAA,OAAA;CAAe;CAE5C,MAAM,SAAS,MAAiD;EAC9D,MAAM,KAAK,MAAM,OAAO;EACxB,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,GAAG,SAAS,KAAK,MAAM,MAAM;EAC5C,SAAS,KAAK;GACZ,IAAI,aAAa,GAAG,GAAG,OAAO,KAAA;GAC9B,MAAM;EACR;EACA,MAAM,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,SAAS,KAAK,SAAS,CAAC;EAC/D,IAAI,QAAQ;EACZ,MAAM,SAAuB,CAAC;EAC9B,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,SAAS,KAAK,MAAM,IAAI;GAC9B,IAAI,OAAO,SAAS,MAAM;GAC1B,IAAI,OAAO,SAAS,SAClB,QAAQ;QACH;IACL,IAAI,CAAC,OACH,MAAM,IAAI,MACR,4CAA4C,KAAK,4BACnD;IAEF,gBAAgB,MAAM,QAAQ,OAAO,KAAK;IAC1C,OAAO,KAAK,OAAO,KAAK;GAC1B;EACF;EACA,OAAO,QAAQ,SAAS,KAAA;CAC1B;CAEA,MAAM,UAAU,MAAc,IAA2B;EACvD,MAAM,WAAW,MAAM,KAAK,cAAc,IAAI;EAC9C,IAAI,UAAU;GACZ,IAAI,aAAa,IACf,MAAM,IAAI,MACR,eAAe,KAAK,qBAAqB,KAAK,KAAK,MAAM,SAAS,+BAA+B,IACnG;GAEF;EACF;EACA,MAAM,KAAK,aAAa;GAAE,MAAM;GAAS;GAAM;EAAG,CAAC;CACrD;CAEA,MAAM,YAAY,MAAc,IAA+B;EAC7D,MAAM,SAAS,MAAM,KAAK,SAAS,IAAI;EACvC,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,MAAM,8CAA8C,KAAK,wBAAwB;EAE7F,gBAAgB,MAAM,QAAQ,EAAE;EAChC,MAAM,KAAK,aAAa;GAAE,MAAM;GAAS;GAAM,OAAO;EAAG,CAAC;CAC5D;CAEA,MAAc,cAAc,MAA2C;EACrE,MAAM,KAAK,MAAM,OAAO;EACxB,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,GAAG,SAAS,KAAK,MAAM,MAAM;EAC5C,SAAS,KAAK;GACZ,IAAI,aAAa,GAAG,GAAG,OAAO,KAAA;GAC9B,MAAM;EACR;EACA,MAAM,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,SAAS,KAAK,SAAS,CAAC;EAC/D,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,SAAS,KAAK,MAAM,IAAI;GAC9B,IAAI,OAAO,SAAS,QAAQ,OAAO,SAAS,SAAS,OAAO,OAAO;EACrE;CAEF;CAEA,MAAc,aAAa,QAA2C;EACpE,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,OAAO,MAAM,OAAO;EAC1B,MAAM,GAAG,MAAM,KAAK,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC3D,MAAM,KAAK,MAAM,GAAG,KAAK,KAAK,MAAM,GAAG;EACvC,IAAI;GACF,MAAM,GAAG,MAAM,GAAG,KAAK,UAAU,MAAM,EAAE,GAAG;GAC5C,MAAM,GAAG,KAAK;EAChB,UAAU;GACR,MAAM,GAAG,MAAM;EACjB;CACF;AACF;;;;;;;;;AAcA,SAAS,gBAAgB,MAAc,QAAsB,IAAsB;CAIjF,IAAI,uBAAuB,EAAE,GAAG;CAChC,IAAI,OAAO,MAAM,MAAM,CAAC,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,GAAG,GACnE,MAAM,IAAI,MACR,iDAAiD,GAAG,IAAI,YAAY,KAAK,mDAE3E;AAEJ;;;;AAKA,SAAS,uBAAuB,IAAyB;CACvD,OAAO,GAAG,SAAS,aAAa,GAAG,SAAS,aAAa,GAAG,SAAS;AACvE;;;;;;;;;;;;AAeA,eAAsB,gBACpB,SACA,OACA,MAC6B;CAC7B,MAAM,SAAS,MAAM,QAAQ,SAAS,IAAI;CAC1C,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,MAAM,gDAAgD,KAAK,EAAE;CAEzE,MAAM,UAAU,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;CACxD,MAAM,yBAAS,IAAI,IAAoB;CACvC,KAAK,MAAM,MAAM,SACf,IAAI,GAAG,SAAS,aAAa,GAAG,SAAS,WAAW,OAAO,IAAI,GAAG,IAAI,GAAG,KAAK;CAEhF,MAAM,UAA8B,CAAC;CACrC,KAAK,MAAM,MAAM,SAAS;EACxB,IAAI,GAAG,SAAS,WAAW;EAC3B,IAAI,GAAG,SAAS,WAAW;EAC3B,IAAI,GAAG,SAAS,WAAW;EAC3B,IAAI,GAAG,SAAS,SAAS;GAGvB,IAAI,GAAG,OAAO,eAAe,GAAG,WAAW,KAAA,GAAW;IACpD,QAAQ,KAAK;KACX,MAAM;KACN,QAAQ,aAAa,GAAG,IAAI,OAAO,IAAI,GAAG,EAAE,KAAK,GAAG,IAAI,WAAW;KACnE,QAAQ;KACR,OAAO;KACP,cAAc;KACd,KAAK,GAAG;IACV,CAAC;IACD;GACF;GACA,MAAM,UAAU,MAAM,MAAM,IAAI,GAAG,MAAM;GACzC,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,MACR,+DAA+D,GAAG,OAAO,WAAW,GAAG,GAAG,SAAS,GAAG,IAAI,EAC5G;GAEF,QAAQ,KAAK;IACX,MAAM;IACN,QAAQ,aAAa,GAAG,IAAI,OAAO,IAAI,GAAG,EAAE,KAAK,GAAG,IAAI,MAAM;IAC9D,KAAK;IACL,QAAQ,GAAG;IACX,OAAO,UAAU;IACjB,KAAK,GAAG;GACV,CAAC;GACD;EACF;EACA,IAAI,GAAG,SAAS,aAAa;GAC3B,QAAQ,KAAK;IACX,MAAM;IACN,QAAQ,aAAa,GAAG,IAAI,OAAO,IAAI,GAAG,EAAE,KAAK,GAAG,IAAI,WAAW;IACnE,QAAQ,GAAG;IACX,OAAO;IACP,cAAc;IACd,KAAK,GAAG;GACV,CAAC;GACD;EACF;EACA,IAAI,GAAG,WAAW,QAAQ;GACxB,QAAQ,KAAK;IACX,MAAM;IACN,QAAQ,aAAa,GAAG,IAAI,OAAO,IAAI,GAAG,EAAE,KAAK,GAAG,IAAI,QAAQ;IAChE,QAAQ,GAAG,SAAS,SAAS;IAC7B,OAAO,GAAG,UAAU;IACpB,cAAc;IACd,KAAK,GAAG;GACV,CAAC;GACD;EACF;EACA,IAAI,GAAG,WAAW,KAAA,GAChB,MAAM,IAAI,MACR,4CAA4C,GAAG,GAAG,SAAS,GAAG,IAAI,oEAEpE;EAEF,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG,MAAM;EACrC,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,MACR,2DAA2D,GAAG,OAAO,WAAW,GAAG,GAAG,SAAS,GAAG,IAAI,EACxG;EAEF,QAAQ,KAAK;GACX,MAAM;GACN,QAAQ,aAAa,GAAG,IAAI,OAAO,IAAI,GAAG,EAAE,KAAK,GAAG,IAAI,MAAM;GAC9D;GACA,QAAQ,GAAG;GACX,SAAS,GAAG;GACZ,OAAO,GAAG;GACV,KAAK,GAAG;EACV,CAAC;CACH;CACA,OAAO;AACT;AAEA,SAAS,aAAa,IAAY,OAAe,QAAoB;CACnE,OAAO;EACL;EACA;EACA;EACA,QAAQ;GACN,MAAM,IAAI,MAAM,sBAAsB,GAAG,2CAA2C;EACtF;CACF;AACF;;;;;;;AAQA,SAAgB,oBAAoB,QAAgC;CAClE,MAAM,wBAAQ,IAAI,IAA6B;CAC/C,IAAI;CAKJ,MAAM,SAAS,OACZ,QACE,OACC,GAAG,SAAS,aAAa,GAAG,SAAS,SACzC,CAAC,CACA,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;CAC/B,MAAM,cAAc,OACjB,QAAQ,OAAO,GAAG,SAAS,aAAa,GAAG,SAAS,aAAa,GAAG,SAAS,SAAS,CAAC,CACvF,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;CAC/B,KAAK,MAAM,MAAM,QAAQ;EACvB,IAAI,GAAG,SAAS,WAAW;GAGzB,MAAM,IAAI,GAAG,IAAI;IACf,IAAI,GAAG;IACP,QAAQ,GAAG;IACX,OAAO,GAAG;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;KAAE,eAAe;KAAG,WAAW;IAAE;IACzC,OAAO,UAAU;GACnB,CAAC;GACD;EACF;EACA,IAAI,GAAG,WAAW,KAAA,KAAa,SAAS,KAAA,GAAW,OAAO,GAAG;EAC7D,MAAM,IAAI,GAAG,IAAI;GACf,IAAI,GAAG;GACP,QAAQ,GAAG;GACX,OAAO,GAAG;GACV,QAAQ;GACR,SAAS,GAAG;GACZ,QAAQ,GAAG;GACX,OAAO,UAAU;EACnB,CAAC;CACH;CACA,KAAK,MAAM,MAAM,aACf,IAAI,GAAG,SAAS,WAAW;EACzB,MAAM,OAAO,YAAY,OAAO,GAAG,EAAE;EACrC,KAAK,SAAS,GAAG,WAAW,SAAS,SAAS;EAC9C,KAAK,QAAQ,GAAG;EAChB,KAAK,SAAS,GAAG;CACnB,OAAO,IAAI,GAAG,SAAS,SAAS;EAC9B,MAAM,OAAO,YAAY,OAAO,GAAG,EAAE;EACrC,KAAK,SAAS,GAAG,OAAO,cAAc,cAAc;EACpD,KAAK,SAAS,GAAG;CACnB,OAAO;EACL,MAAM,OAAO,YAAY,OAAO,GAAG,EAAE;EACrC,KAAK,SAAS;CAChB;CAIF,KAAK,MAAM,MAAM,QAAQ;EACvB,IAAI,GAAG,SAAS,WAAW;EAC3B,MAAM,OAAO,YAAY,OAAO,GAAG,EAAE;EACrC,KAAK,QAAQ,gBAAgB,KAAK,OAAO,GAAG,KAAK;CACnD;CACA,MAAM,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,IAAI,cAAc;CACxD,OAAO;EACL,MAAM,QAAQ,UAAU,EAAE,EAAE,MAAM;EAClC,OAAO;EACP,UAAU,UAAU,QAAQ,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,WAAW,CAAC,CAAC;EACtF,SAAS,UAAU,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,CAAC;CAC3D;AACF;;;;;;AAOA,SAAgB,aAAa,QAAqC;CAChE,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,MAAM,QAAQ,IAAI,GAAG,SAAS,SAAS,MAAM,IAAI,GAAG,EAAE;CACjE,MAAM,UAAyB,CAAC;CAChC,KAAK,MAAM,MAAM,QAAQ;EACvB,IAAI,GAAG,SAAS,aAAa,MAAM,IAAI,GAAG,EAAE,GAAG;EAC/C,QAAQ,KAAK;GACX,IAAI,GAAG;GACP,OAAO,GAAG;GACV,MAAM,GAAG;GACT,SAAS,GAAG;GACZ,SAAS,GAAG;EACd,CAAC;CACH;CACA,OAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AACrD;AAaA,SAAS,YAAmB;CAC1B,OAAO;EAAE,YAAY;EAAG,QAAQ,eAAe;EAAG,KAAK;EAAG,IAAI;CAAE;AAClE;;AAGA,SAAS,gBAAgB,GAAU,GAAiB;CAClD,OAAO;EACL,YAAY,EAAE,aAAa,EAAE;EAC7B,QAAQ;GAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,OAAO;GAAO,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO;EAAO;EAC5F,KAAK,EAAE,MAAM,EAAE;EACf,GAAI,EAAE,gBAAgB,SAAS,EAAE,gBAAgB,QAAQ,EAAE,aAAa,MAAM,IAAI,CAAC;EACnF,GAAI,EAAE,aAAa,SAAS,EAAE,aAAa,QAAQ,EAAE,UAAU,MAAM,IAAI,CAAC;EAC1E,IAAI,EAAE,KAAK,EAAE;CACf;AACF;AAEA,SAAS,YAAY,OAAqC,IAA6B;CACrF,MAAM,OAAO,MAAM,IAAI,EAAE;CACzB,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,oDAAoD,GAAG,sBAAsB;CAE/F,OAAO;AACT;AAEA,SAAS,eAAe,MAAqC;CAC3D,OAAO;EACL,IAAI,KAAK;EACT,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,QAAQ,KAAK;EACb,SAAS,KAAK;EACd,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,QAAQ,KAAK;CACf;AACF;AAEA,SAAS,aAAa,KAAuB;CAC3C,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,UAAU,OACT,IAA0B,SAAS;AAExC"}
|