@tangle-network/agent-runtime 0.196.0 → 0.198.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 (102) hide show
  1. package/README.md +1 -2
  2. package/dist/{activation-BxMZybuo.js → activation-CwdO0ml2.js} +2 -2
  3. package/dist/{activation-BxMZybuo.js.map → activation-CwdO0ml2.js.map} +1 -1
  4. package/dist/{improve-DZs0KXhK.d.ts → activation-DFRTurvU.d.ts} +99 -5
  5. package/dist/agent.d.ts +1 -1
  6. package/dist/agent.js +2 -2
  7. package/dist/candidate-execution/index.d.ts +370 -2
  8. package/dist/candidate-execution/index.js +828 -4
  9. package/dist/candidate-execution/index.js.map +1 -0
  10. package/dist/{coordination-driver-zkCrfEbS.js → coordination-driver-Cf638-6q.js} +2 -5
  11. package/dist/{coordination-driver-zkCrfEbS.js.map → coordination-driver-Cf638-6q.js.map} +1 -1
  12. package/dist/{delegate-D7sonS78.js → delegate-Bez07Dlh.js} +2 -2
  13. package/dist/{delegate-D7sonS78.js.map → delegate-Bez07Dlh.js.map} +1 -1
  14. package/dist/durable.d.ts +110 -6
  15. package/dist/durable.js +404 -47
  16. package/dist/durable.js.map +1 -1
  17. package/dist/{graph-BuS-NxXD.js → graph-DBk3Unto.js} +3 -3
  18. package/dist/{graph-BuS-NxXD.js.map → graph-DBk3Unto.js.map} +1 -1
  19. package/dist/{improvement-cycle-Tw5nYT5R.js → improvement-cycle-Bma8TJs-.js} +8 -10
  20. package/dist/{improvement-cycle-Tw5nYT5R.js.map → improvement-cycle-Bma8TJs-.js.map} +1 -1
  21. package/dist/{index-CYOJsxSg.d.ts → index-D8LWK3Ha.d.ts} +6 -7
  22. package/dist/index.d.ts +694 -16
  23. package/dist/index.js +1780 -17
  24. package/dist/index.js.map +1 -1
  25. package/dist/intelligence.d.ts +3 -7
  26. package/dist/intelligence.js +5 -6
  27. package/dist/intelligence.js.map +1 -1
  28. package/dist/kernel.d.ts +2 -5
  29. package/dist/kernel.js +8 -10
  30. package/dist/{loop-runner-bin-XKMc3Ld-.js → loop-runner-bin-Bd3dHQuw.js} +3 -3
  31. package/dist/{loop-runner-bin-XKMc3Ld-.js.map → loop-runner-bin-Bd3dHQuw.js.map} +1 -1
  32. package/dist/{loop-runner-bin-DwrSB04m.d.ts → loop-runner-bin-Bn-0o_4k.d.ts} +3 -3
  33. package/dist/loop-runner-bin.d.ts +1 -1
  34. package/dist/loop-runner-bin.js +1 -1
  35. package/dist/mcp/bin.js +3 -3
  36. package/dist/mcp/index.d.ts +2 -3
  37. package/dist/mcp/index.js +4 -5
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/{prepare-DDGp0-rW.js → prepare-CAO1yXov.js} +2407 -2407
  40. package/dist/prepare-CAO1yXov.js.map +1 -0
  41. package/dist/{protected-model-port-DxFN8DLS.js → protected-model-port-B5avcRiQ.js} +2 -2
  42. package/dist/{protected-model-port-DxFN8DLS.js.map → protected-model-port-B5avcRiQ.js.map} +1 -1
  43. package/dist/{provision-supervisor-CpMMShE_.js → provision-supervisor-C97q5NN_.js} +3 -6
  44. package/dist/{provision-supervisor-CpMMShE_.js.map → provision-supervisor-C97q5NN_.js.map} +1 -1
  45. package/dist/{redact-Cbl2O-4N.js → redact-CVyxp2AU.js} +5232 -2525
  46. package/dist/redact-CVyxp2AU.js.map +1 -0
  47. package/dist/{runtime-DqRieJ6e.js → runtime-BrTBBSfC.js} +427 -23
  48. package/dist/runtime-BrTBBSfC.js.map +1 -0
  49. package/dist/{server-BjB6ay4N.js → server-CO4bvAwe.js} +4 -4
  50. package/dist/{server-BjB6ay4N.js.map → server-CO4bvAwe.js.map} +1 -1
  51. package/dist/{types-DFLZMaeh.d.ts → stream-agent-turn-CLOQr497.d.ts} +1986 -6
  52. package/dist/{structural-rollout-DPbZWgEm.js → structural-rollout-D__lmWXe.js} +1101 -8
  53. package/dist/structural-rollout-D__lmWXe.js.map +1 -0
  54. package/dist/{supervise-D9aNi8_f.js → supervise-KSe4JEcg.js} +30 -7
  55. package/dist/supervise-KSe4JEcg.js.map +1 -0
  56. package/dist/testing.d.ts +2 -2
  57. package/dist/testing.js +13 -13
  58. package/dist/tui/index.d.ts +1 -1
  59. package/dist/tui/index.js +1 -1
  60. package/dist/{workspace-archive-Ybomp7AN.js → workspace-archive-BMOnloFf.js} +3 -3
  61. package/dist/{workspace-archive-Ybomp7AN.js.map → workspace-archive-BMOnloFf.js.map} +1 -1
  62. package/package.json +4 -31
  63. package/dist/activation-DyWB0K6E.d.ts +0 -98
  64. package/dist/authored-code-URmkdgjv.js +0 -37
  65. package/dist/authored-code-URmkdgjv.js.map +0 -1
  66. package/dist/candidate-execution-nvqVIMyS.js +0 -829
  67. package/dist/candidate-execution-nvqVIMyS.js.map +0 -1
  68. package/dist/conversation-BxJ0SIBM.js +0 -1363
  69. package/dist/conversation-BxJ0SIBM.js.map +0 -1
  70. package/dist/conversation.d.ts +0 -2
  71. package/dist/conversation.js +0 -2
  72. package/dist/environment-provider-1fKZh2zl.js +0 -2281
  73. package/dist/environment-provider-1fKZh2zl.js.map +0 -1
  74. package/dist/environment-provider-B-I2jlQy.d.ts +0 -143
  75. package/dist/environment-provider.d.ts +0 -2
  76. package/dist/environment-provider.js +0 -2
  77. package/dist/graph.d.ts +0 -753
  78. package/dist/graph.js +0 -2111
  79. package/dist/graph.js.map +0 -1
  80. package/dist/index-CUosKU4N.d.ts +0 -372
  81. package/dist/index-D9mb6fn2.d.ts +0 -691
  82. package/dist/index-ZnxSe6iK.d.ts +0 -138
  83. package/dist/jsonl-file-BEpaEYjT.js +0 -141
  84. package/dist/jsonl-file-BEpaEYjT.js.map +0 -1
  85. package/dist/knowledge-CPzT_Ve7.js +0 -428
  86. package/dist/knowledge-CPzT_Ve7.js.map +0 -1
  87. package/dist/knowledge.d.ts +0 -2
  88. package/dist/knowledge.js +0 -2
  89. package/dist/materialization-Cy0oM8tb.js +0 -672
  90. package/dist/materialization-Cy0oM8tb.js.map +0 -1
  91. package/dist/prepare-DDGp0-rW.js.map +0 -1
  92. package/dist/primeintellect/index.d.ts +0 -218
  93. package/dist/primeintellect/index.js +0 -739
  94. package/dist/primeintellect/index.js.map +0 -1
  95. package/dist/redact-Cbl2O-4N.js.map +0 -1
  96. package/dist/runtime-0xNaV6TJ.d.ts +0 -1699
  97. package/dist/runtime-DqRieJ6e.js.map +0 -1
  98. package/dist/stream-agent-turn-Dt5mZpc3.js +0 -1103
  99. package/dist/stream-agent-turn-Dt5mZpc3.js.map +0 -1
  100. package/dist/stream-agent-turn-urHpmO_Z.d.ts +0 -160
  101. package/dist/structural-rollout-DPbZWgEm.js.map +0 -1
  102. package/dist/supervise-D9aNi8_f.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,27 +1,1374 @@
1
+ import { $n as createRuntimeEventCollector, $r as executableAgentProfileSnapshot, Bn as INTELLIGENCE_WIRE_VERSION, Gn as createOtelExporter, Hn as buildLoopSpanNodes, Jn as generateSpanId, Kn as exportEvalRuns, Mr as parseCommittedJsonLines, Nr as prepareJsonlAppend, Pr as writeAllBytes, Qn as toOtelAttributes, Un as buildRuntimeEventOtelSpans, Vn as buildLoopOtelSpans, Wn as createOpenInferenceFileExporter, Xn as padSpanId, Yn as loopEventToOtelSpan, Zn as padTraceId, er as createRuntimeStreamEventCollector, jr as isNoEntError, li as providerMessageText, n as defaultRedactorIdentityMaterial, nr as sanitizeKnowledgeReadinessReport, r as resolveRedactor, rr as sanitizeRuntimeStreamEvent, t as defaultRedactor, tr as sanitizeAgentRuntimeEvent } from "./redact-CVyxp2AU.js";
1
2
  import { a as JudgeError, c as RetainedInteractiveAdmissionError, d as RetainedRunDispatchBindingError, f as RuntimeRunStateError, i as ConfigError, l as RetainedInteractiveBindingError, m as ValidationError, o as NotFoundError, r as BackendTransportError, s as PlannerError, t as AgentEvalError, u as RetainedRunAdmissionError } from "./errors-DodWX-cb.js";
2
- import { a as normalizeBackendStreamEvent, c as nowIso, i as createSandboxPromptBackend, l as startOrResumeRuntimeSession, o as InMemoryRuntimeSessionStore, r as createIterableBackend, u as touchSession } from "./stream-agent-turn-Dt5mZpc3.js";
3
- import { o as canonicalCandidateDigest$1, u as immutableCandidateValue } from "./protected-redaction-wGo44k2K.js";
4
- import { $ as parseExactAgentProfile, G as applyExactAgentProfileDiff, K as assertCandidateProfileBinding, S as CANDIDATE_TRACE_TAGS, W as agentCandidateProfileAsAgentProfile, Y as freezeGenericAgentCandidateProfile, Z as omitUndefinedObjectFields, _ as candidateExecutionClaim, a as persistCandidateOutputArtifact, at as scanMaterializedWorkspaceManifest, c as CANDIDATE_KNOWLEDGE_ROOT_ENV, et as parseExactAgentProfileDiff, f as verifyAgentCandidateBundle, h as InMemoryAgentCandidateExecutionClaimStore, l as candidateKnowledgeExecutionPaths, n as executePreparedAgentCandidate, nt as candidateWorkspaceManifest, s as CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV, st as verifyMaterializedWorkspace, t as prepareAgentCandidateExecution, tt as parseExactCandidateProfile, u as AGENT_CANDIDATE_EXECUTION_SUPPORT, x as CANDIDATE_TRACE_ENV } from "./prepare-DDGp0-rW.js";
5
- import { i as sealAgentCandidateBundle, n as captureAgentCandidateWorkspaceFiles, r as createAgentCandidateWorkspacePort, t as captureAgentCandidateWorkspace } from "./workspace-archive-Ybomp7AN.js";
6
- import { a as disposePreparedAgentCandidateExecution, i as runProtectedAgentCandidateModelGrant, n as seedWorkspaceTree, o as FileAgentCandidateExecutionClaimStore, r as recoverExpiredAgentCandidateExecution, s as buildAgentCandidateBundle, t as describeWorkspaceTree } from "./candidate-execution-nvqVIMyS.js";
7
- import { n as exactProcessProviderAsCandidateExecutor, t as createProtectedAgentCandidateModelPort } from "./protected-model-port-DxFN8DLS.js";
8
- import { C as makePerAttemptSignal, S as defaultIsRetryable, T as createProfileExecutionBackend, _ as readDepth, a as FileConversationJournal, b as DeadlineExceededError, c as createConversationBackend, d as slugifySpeaker, f as turnId, g as isDepthExceeded, h as buildForwardHeaders, i as d1ToSqlAdapter, l as runConversation, m as FORWARD_HEADERS, n as runPersonaDispatch, o as InMemoryConversationJournal, p as DEFAULT_MAX_DEPTH, r as SqlConversationJournal, s as defineConversation, t as runPersonaConversation, u as runConversationStream, v as CircuitBreakerState, w as sleep, x as computeBackoff, y as CircuitOpenError } from "./conversation-BxJ0SIBM.js";
9
- import { Bn as sanitizeRuntimeStreamEvent, Cn as INTELLIGENCE_WIRE_VERSION, Dn as createOpenInferenceFileExporter, En as buildRuntimeEventOtelSpans, Fn as toOtelAttributes, In as createRuntimeEventCollector, Ln as createRuntimeStreamEventCollector, Mn as loopEventToOtelSpan, Nn as padSpanId, On as createOtelExporter, Pn as padTraceId, Rn as sanitizeAgentRuntimeEvent, Tn as buildLoopSpanNodes, jn as generateSpanId, kn as exportEvalRuns, n as defaultRedactorIdentityMaterial, r as resolveRedactor, t as defaultRedactor, wn as buildLoopOtelSpans, zn as sanitizeKnowledgeReadinessReport } from "./redact-Cbl2O-4N.js";
3
+ import { A as streamAgentTurn, D as optimizerMethod, F as newRuntimeSession, I as nowIso, L as startOrResumeRuntimeSession, M as createSandboxPromptBackend, N as normalizeBackendStreamEvent, O as strategyAuthorMethod, P as InMemoryRuntimeSessionStore, R as touchSession, j as createIterableBackend } from "./structural-rollout-D__lmWXe.js";
4
+ import { t as assertExecutableAgentProfile } from "./model-policy-DKDyr-fc.js";
5
+ import { Ft as connectStdioMcp, Pt as McpSpawnFault } from "./runtime-BrTBBSfC.js";
10
6
  import { i as notifyRuntimeHookEvent, n as defineRuntimeHooks, r as notifyRuntimeDecisionPoint, t as composeRuntimeHooks } from "./runtime-hooks-tXpAarhW.js";
11
- import { D as optimizerMethod, O as strategyAuthorMethod } from "./structural-rollout-DPbZWgEm.js";
12
- import { B as ROLLOUT_POLICY_EXTENSION, G as structuralRolloutPolicyFromProfile, H as normalizeRolloutPolicy, J as defaultBuildPrompt, K as agenticGenerator, L as improve, R as withMethodRuntimeControls, U as parseRolloutPolicy, V as applyRolloutPolicyToProfile, W as serializeRolloutPolicy, q as commandVerifier, z as rawTraceDistiller } from "./improvement-cycle-Tw5nYT5R.js";
13
- import { Ft as connectStdioMcp, Pt as McpSpawnFault } from "./runtime-DqRieJ6e.js";
14
- import { a as createSupervisedKnowledgeUpdater, c as runSupervisedKnowledgeUpdate, i as RESEARCH_SUPERVISOR_SYSTEM_PROMPT, l as createKnowledgeImprovementActivationExecutor, n as createAgentKnowledgeReadinessCheck, o as formatSupervisedKnowledgeTask, r as runKnowledgeImprovementJob, s as knowledgeReadinessDeliverable, t as buildKnowledgeImprovementExperimentBundles } from "./knowledge-CPzT_Ve7.js";
15
- import { a as isDelegatedLoopMode, c as worktreeLoopRunner, i as auditLoopRunner, n as runLoopRunnerCli, o as researchLoopRunner, r as DELEGATED_LOOP_MODES, s as runDelegatedLoop, t as parseLoopRunnerArgv } from "./loop-runner-bin-XKMc3Ld-.js";
7
+ import { B as ROLLOUT_POLICY_EXTENSION, G as structuralRolloutPolicyFromProfile, H as normalizeRolloutPolicy, J as defaultBuildPrompt, K as agenticGenerator, L as improve, R as withMethodRuntimeControls, U as parseRolloutPolicy, V as applyRolloutPolicyToProfile, W as serializeRolloutPolicy, q as commandVerifier, z as rawTraceDistiller } from "./improvement-cycle-Bma8TJs-.js";
8
+ import { a as canonicalCandidateBytes, d as omitTopLevelDigest, l as embeddedCandidateArtifact, o as canonicalCandidateDigest$1, u as immutableCandidateValue } from "./protected-redaction-wGo44k2K.js";
9
+ import { d as persistCandidateOutputArtifact } from "./prepare-CAO1yXov.js";
10
+ import { t as createAgentImprovementActivationResult } from "./activation-CwdO0ml2.js";
11
+ import { i as sealAgentCandidateBundle, t as captureAgentCandidateWorkspace } from "./workspace-archive-BMOnloFf.js";
12
+ import { n as supervise } from "./supervise-KSe4JEcg.js";
13
+ import { a as isDelegatedLoopMode, c as worktreeLoopRunner, i as auditLoopRunner, n as runLoopRunnerCli, o as researchLoopRunner, r as DELEGATED_LOOP_MODES, s as runDelegatedLoop, t as parseLoopRunnerArgv } from "./loop-runner-bin-Bd3dHQuw.js";
16
14
  import { n as mcpToolsForRuntimeMcpSubset, t as mcpToolsForRuntimeMcp } from "./openai-tools-DoQ32vpe.js";
17
- import { agentProfileSchema, canonicalAgentProfileDigest } from "@tangle-network/agent-interface";
15
+ import { agentCandidateKnowledgeSchema, agentProfileSchema, canonicalAgentProfileDigest, sha256DigestSchema } from "@tangle-network/agent-interface";
18
16
  import { FAILURE_CLASSES, acquisitionPlansForKnowledgeGaps, blockingKnowledgeEval, canonicalJson, runAgentControlLoop, scoreKnowledgeReadiness, userQuestionsForKnowledgeGaps } from "@tangle-network/agent-eval";
19
17
  import { createHash } from "node:crypto";
18
+ import { spawnSync } from "node:child_process";
20
19
  import { readFileSync, realpathSync } from "node:fs";
21
20
  import { isAbsolute, relative, resolve, sep } from "node:path";
22
- import { gepaOptimizationMethod, skillOptOptimizationMethod } from "@tangle-network/agent-eval/campaign";
23
- import { spawnSync } from "node:child_process";
21
+ import { realpath } from "node:fs/promises";
24
22
  import { isDeepStrictEqual } from "node:util";
23
+ import { gepaOptimizationMethod, skillOptOptimizationMethod } from "@tangle-network/agent-eval/campaign";
24
+ import { evaluateKnowledgeBaseReadiness, fromAgentCandidateKnowledgeRef, improveKnowledgeBase, knowledgeImprovementCandidateRef, loadKnowledgeImprovementActivationResult, promoteKnowledgeCandidate, restoreKnowledgeCandidateBaseline, toAgentCandidateKnowledgeRef, withKnowledgeImprovementComparison } from "@tangle-network/agent-knowledge";
25
+ //#region src/runtime/profile-execution-backend.ts
26
+ /**
27
+ * Bind one exact profile and Runtime executor to the stable `AgentExecutionBackend` contract used
28
+ * by `runAgentTaskStream` and conversations.
29
+ *
30
+ * Runtime still owns the model call through `streamAgentTurn`.
31
+ * The adapter only translates the two stream protocols and carries the caller's request headers
32
+ * into `ExecutorContext` so an HTTP executor can preserve authorization, recursion depth, and
33
+ * trace identity.
34
+ *
35
+ * @stable
36
+ */
37
+ function createProfileExecutionBackend(options) {
38
+ const profile = executableAgentProfileSnapshot(options.profile, "createProfileExecutionBackend");
39
+ const executor = options.executor;
40
+ return {
41
+ kind: "runtime-profile",
42
+ async *stream(input, context) {
43
+ const propagatedHeaders = snapshotPropagatedHeaders(context.propagatedHeaders);
44
+ const contextualExecutor = (spec, executorContext) => executor(spec, {
45
+ ...executorContext,
46
+ ...propagatedHeaders === void 0 ? {} : { propagatedHeaders }
47
+ });
48
+ const providerMessages = input.providerOptions?.messages;
49
+ const turnInput = input.messages !== void 0 ? { providerOptions: { messages: input.messages.map((message) => ({ ...message })) } } : Array.isArray(providerMessages) ? { providerOptions: { messages: structuredClone(providerMessages) } } : { prompt: input.message ?? context.task.intent };
50
+ let terminal = false;
51
+ let emittedText = false;
52
+ for await (const event of streamAgentTurn({
53
+ kind: "executor",
54
+ profile,
55
+ factory: contextualExecutor
56
+ }, turnInput, {
57
+ signal: context.signal,
58
+ ...context.turnId ? { callId: context.turnId } : {},
59
+ ...context.runId ? { correlationId: context.runId } : {}
60
+ })) {
61
+ if (event.type === "backend_start") continue;
62
+ if (event.type === "backend_error") throw backendError(event.error, event.message);
63
+ if (event.type === "final") {
64
+ terminal = true;
65
+ if (event.status !== "completed") throw backendError(event.error, event.reason);
66
+ if (!emittedText && event.text) yield {
67
+ type: "text_delta",
68
+ task: context.task,
69
+ session: context.session,
70
+ text: event.text,
71
+ timestamp: event.timestamp
72
+ };
73
+ continue;
74
+ }
75
+ if (event.type === "text_delta") emittedText = true;
76
+ yield rebindEvent(event, context);
77
+ }
78
+ if (!terminal) throw new ValidationError("createProfileExecutionBackend: streamAgentTurn ended without a terminal event");
79
+ }
80
+ };
81
+ }
82
+ function snapshotPropagatedHeaders(headers) {
83
+ if (headers === void 0) return void 0;
84
+ const snapshot = {};
85
+ for (const [name, value] of Object.entries(headers)) {
86
+ if (typeof value !== "string") throw new ValidationError(`createProfileExecutionBackend: propagated header ${JSON.stringify(name)} must be a string`);
87
+ snapshot[name] = value;
88
+ }
89
+ return Object.freeze(snapshot);
90
+ }
91
+ function backendError(detail, message) {
92
+ if (detail?.kind === "transport") return new BackendTransportError("runtime-profile", detail.message, {
93
+ status: detail.status,
94
+ body: detail.body
95
+ });
96
+ return new Error(detail?.message ?? message);
97
+ }
98
+ function rebindEvent(event, context) {
99
+ return {
100
+ ...event,
101
+ task: context.task,
102
+ session: context.session
103
+ };
104
+ }
105
+ //#endregion
106
+ //#region src/conversation/call-policy.ts
107
+ /** Thrown when the circuit breaker is open for a participant and no retry is allowed yet. */
108
+ var CircuitOpenError = class extends Error {
109
+ constructor(participant, retryAfterMs) {
110
+ super(`circuit open for participant '${participant}'; ${retryAfterMs}ms remaining before retry allowed`);
111
+ this.name = "CircuitOpenError";
112
+ }
113
+ };
114
+ /** Thrown when a backend call exceeds its per-attempt deadline. */
115
+ var DeadlineExceededError = class extends Error {
116
+ constructor(deadlineMs) {
117
+ super(`backend call exceeded per-attempt deadline of ${deadlineMs}ms`);
118
+ this.name = "DeadlineExceededError";
119
+ }
120
+ };
121
+ /**
122
+ * Default retryable classification — network/timeout class errors. Errors
123
+ * a model deliberately throws (validation, refusal, 4xx) are not retried;
124
+ * those represent real outcomes, not transient infrastructure faults.
125
+ */
126
+ const defaultIsRetryable = (err) => {
127
+ if (err instanceof DeadlineExceededError) return true;
128
+ if (err instanceof Error) {
129
+ const name = err.name;
130
+ const message = err.message.toLowerCase();
131
+ if (name === "AbortError" || name === "TimeoutError") return true;
132
+ if (message.includes("econnreset") || message.includes("etimedout") || message.includes("econnrefused") || message.includes("socket hang up") || message.includes("network") || message.includes("fetch failed")) return true;
133
+ }
134
+ return false;
135
+ };
136
+ /** Live circuit-breaker state — one instance per (participant, conversation run). */
137
+ var CircuitBreakerState = class {
138
+ config;
139
+ consecutiveFailures = 0;
140
+ openedAt;
141
+ constructor(config) {
142
+ this.config = config;
143
+ }
144
+ /**
145
+ * Check whether the next call is allowed. Throws `CircuitOpenError` when
146
+ * the breaker is open and the cooldown hasn't elapsed.
147
+ */
148
+ preflight(participant, now = Date.now()) {
149
+ if (!this.config || this.openedAt === void 0) return;
150
+ const remaining = this.config.cooldownMs - (now - this.openedAt);
151
+ if (remaining > 0) throw new CircuitOpenError(participant, remaining);
152
+ this.openedAt = void 0;
153
+ this.consecutiveFailures = 0;
154
+ }
155
+ recordSuccess() {
156
+ this.consecutiveFailures = 0;
157
+ this.openedAt = void 0;
158
+ }
159
+ recordFailure(now = Date.now()) {
160
+ if (!this.config) return;
161
+ this.consecutiveFailures += 1;
162
+ if (this.consecutiveFailures >= this.config.failuresToOpen) this.openedAt = now;
163
+ }
164
+ };
165
+ /**
166
+ * Build a per-attempt AbortSignal linked to the parent signal AND fired when
167
+ * the deadline elapses. The returned `dispose()` MUST be called in a
168
+ * `finally` (clears the timer, detaches the listener) so we don't leak.
169
+ *
170
+ * When the deadline fires, the signal's `reason` is a `DeadlineExceededError`
171
+ * — callers can detect timeout-vs-cancel by reading `signal.reason` after
172
+ * the underlying operation throws.
173
+ */
174
+ function makePerAttemptSignal(parentSignal, deadlineMs) {
175
+ const controller = new AbortController();
176
+ let deadlineError;
177
+ const cleanups = [];
178
+ if (parentSignal) if (parentSignal.aborted) controller.abort(parentSignal.reason);
179
+ else {
180
+ const onAbort = () => controller.abort(parentSignal.reason);
181
+ parentSignal.addEventListener("abort", onAbort, { once: true });
182
+ cleanups.push(() => parentSignal.removeEventListener("abort", onAbort));
183
+ }
184
+ if (deadlineMs !== void 0) {
185
+ const ms = deadlineMs;
186
+ const timer = setTimeout(() => {
187
+ deadlineError = new DeadlineExceededError(ms);
188
+ controller.abort(deadlineError);
189
+ }, ms);
190
+ cleanups.push(() => clearTimeout(timer));
191
+ }
192
+ return {
193
+ signal: controller.signal,
194
+ dispose() {
195
+ for (const c of cleanups) c();
196
+ },
197
+ getDeadlineError() {
198
+ return deadlineError;
199
+ }
200
+ };
201
+ }
202
+ /** Compute the delay before the next attempt. Default: 250ms exponential with jitter. */
203
+ function computeBackoff(spec, attempt) {
204
+ if (spec === void 0) {
205
+ const base = 250;
206
+ const jitter = Math.floor(Math.random() * base);
207
+ return base * 2 ** (attempt - 1) + jitter;
208
+ }
209
+ if (typeof spec === "function") return Math.max(0, spec(attempt));
210
+ return Math.max(0, spec);
211
+ }
212
+ /** Resolve after `ms` milliseconds — used for retry backoff in conversation call policy. */
213
+ function sleep(ms) {
214
+ return new Promise((resolve) => setTimeout(resolve, ms));
215
+ }
216
+ //#endregion
217
+ //#region src/conversation/headers.ts
218
+ /**
219
+ *
220
+ * Cross-gateway forwarding headers — the wire-level contract that makes
221
+ * agent-to-agent communication composable across organizational boundaries.
222
+ * Every header here is read on inbound and re-emitted on outbound, so a chain
223
+ * `caller → A's gateway → A's runtime → B's gateway → B's runtime` ends with
224
+ * B billing the original user, the depth counter monotonically incremented,
225
+ * and the run/turn correlation IDs preserved end-to-end.
226
+ *
227
+ * The actual depth refusal (HTTP 413 at MAX_DEPTH) is enforced by
228
+ * `agent-gateway`'s middleware; this module owns the names + the propagation
229
+ * rules so both sides agree.
230
+ *
231
+ * Full protocol: `docs/agent-bus-protocol.md`.
232
+ *
233
+ * @stable
234
+ */
235
+ /** Standard names — lowercased so Headers maps interop on every runtime. */
236
+ const FORWARD_HEADERS = {
237
+ /** Forwarded original-user identity (`Bearer sk-tan-<user>`); downstream gateways bill against this. */
238
+ authorization: "x-tangle-forwarded-authorization",
239
+ /** Monotonically incremented on every gateway hop. Refused at MAX_DEPTH. */
240
+ depth: "x-tangle-forwarded-depth",
241
+ /** Top-level conversation run identifier, propagated through every nested call. */
242
+ runId: "x-tangle-runid",
243
+ /** This call's turn within the run; deterministic + stable across retries. */
244
+ turnId: "x-tangle-turnid",
245
+ /** When the call is *inside* another turn (recursion), the parent turn's id. */
246
+ parentTurnId: "x-tangle-parent-turnid",
247
+ /** Logical conversation peer label at the sending side, for trace stitching. */
248
+ speaker: "x-tangle-speaker"
249
+ };
250
+ /**
251
+ * Lowercase a header lookup so we read the same key regardless of source
252
+ * casing (Hono, fetch's `Headers`, raw Node IncomingMessage, …).
253
+ */
254
+ function lc(name) {
255
+ return name.toLowerCase();
256
+ }
257
+ /**
258
+ * Read the depth counter off an inbound request. Missing → 0 (caller is the
259
+ * origin). Non-integer → throws — silent coercion would let a bad caller
260
+ * reset depth and bypass the limit.
261
+ */
262
+ function readDepth(headers) {
263
+ const raw = pickHeader(headers, FORWARD_HEADERS.depth);
264
+ if (raw === void 0 || raw === "") return 0;
265
+ const n = Number(raw);
266
+ if (!Number.isInteger(n) || n < 0) throw new Error(`invalid ${FORWARD_HEADERS.depth} header value '${raw}' — must be a non-negative integer`);
267
+ return n;
268
+ }
269
+ /**
270
+ * Build the headers to emit on an outbound participant call, given the
271
+ * conversation's propagation context. Depth is incremented from the inbound
272
+ * value; runId / turnId / speaker stamp the current hop; the user's
273
+ * `Authorization` is preserved verbatim so the downstream gateway bills the
274
+ * right wallet.
275
+ */
276
+ function buildForwardHeaders(input) {
277
+ const out = {
278
+ [FORWARD_HEADERS.depth]: String(input.inboundDepth + 1),
279
+ [FORWARD_HEADERS.runId]: input.runId,
280
+ [FORWARD_HEADERS.turnId]: input.turnId,
281
+ [FORWARD_HEADERS.speaker]: input.speaker
282
+ };
283
+ if (input.forwardedAuthorization !== void 0) out[FORWARD_HEADERS.authorization] = input.forwardedAuthorization;
284
+ if (input.parentTurnId !== void 0) out[FORWARD_HEADERS.parentTurnId] = input.parentTurnId;
285
+ return out;
286
+ }
287
+ function pickHeader(headers, name) {
288
+ const target = lc(name);
289
+ for (const key of Object.keys(headers)) if (lc(key) === target) {
290
+ const value = headers[key];
291
+ if (Array.isArray(value)) return value[0];
292
+ return value;
293
+ }
294
+ }
295
+ //#endregion
296
+ //#region src/conversation/turn-id.ts
297
+ /**
298
+ *
299
+ * Deterministic turn identifier. Stable across retries of the same logical
300
+ * turn so backends (and any caching gateway in between) can dedupe on it.
301
+ * A retry triggered by a network blip or deadline timeout MUST produce the
302
+ * same `turn_id`; only the underlying attempt count differs.
303
+ *
304
+ * Shape: `${runId}.t${index}.${speakerSlug}` — readable in logs, sortable by
305
+ * turn index, attributable to a speaker. Slugify keeps the speaker portion
306
+ * URL-safe so it can ride in HTTP headers without escaping.
307
+ *
308
+ * @stable
309
+ */
310
+ function turnId(runId, index, speaker) {
311
+ return `${runId}.t${index}.${slugifySpeaker(speaker)}`;
312
+ }
313
+ /**
314
+ * Reduce a speaker name to ASCII alphanumerics + dashes. Preserves enough
315
+ * substance to read in a log line; collisions between speakers within a
316
+ * single Conversation are prevented by `defineConversation`'s
317
+ * unique-name check, so the slug only needs to be deterministic, not unique.
318
+ */
319
+ function slugifySpeaker(speaker) {
320
+ return speaker.normalize("NFKD").replace(/[^\w-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase() || "anon";
321
+ }
322
+ //#endregion
323
+ //#region src/conversation/run-conversation.ts
324
+ /** Run a conversation to completion and return its terminal result. */
325
+ async function runConversation(conversation, options) {
326
+ let result;
327
+ for await (const event of runConversationStream(conversation, options)) {
328
+ if (options.onEvent) await options.onEvent(event);
329
+ if (event.type === "conversation_end") result = event.result;
330
+ }
331
+ if (!result) throw new BackendTransportError("conversation", "conversation stream ended without a conversation_end event");
332
+ return result;
333
+ }
334
+ /** Streaming conversation orchestrator: drives N participants in turn through their own backends, enforcing `maxTurns` / `maxCreditsCents` / `haltOn`, yielding per-event stream markers. */
335
+ async function* runConversationStream(conversation, options) {
336
+ const runId = options.runId ?? `conv_${crypto.randomUUID()}`;
337
+ const inboundDepth = options.inboundDepth ?? 0;
338
+ const forwardedAuthorization = (options.propagatedHeaders ?? {})[FORWARD_HEADERS.authorization];
339
+ const breakers = /* @__PURE__ */ new Map();
340
+ for (const participant of conversation.participants) {
341
+ const cfg = participant.callPolicy?.circuitBreaker ?? conversation.policy.defaultCallPolicy?.circuitBreaker;
342
+ breakers.set(participant.name, new CircuitBreakerState(cfg));
343
+ }
344
+ let transcript = [];
345
+ let spentCreditsCents = 0;
346
+ let startedAt = nowIso();
347
+ let resumed = false;
348
+ const sessionStore = options.sessionStore ?? new InMemoryRuntimeSessionStore();
349
+ if (options.journal) {
350
+ const prior = await options.journal.loadRun(runId);
351
+ if (prior) {
352
+ if (prior.halted) {
353
+ const replayResult = {
354
+ runId,
355
+ transcript: prior.turns,
356
+ turns: prior.turns.length,
357
+ spentCreditsCents: prior.turns.reduce((sum, t) => sum + centsFromUsd(t.usage?.costUsd ?? 0), 0),
358
+ halted: prior.halted,
359
+ durationMs: 0,
360
+ startedAt: prior.startedAt,
361
+ endedAt: prior.endedAt ?? prior.startedAt
362
+ };
363
+ yield {
364
+ type: "conversation_resumed",
365
+ runId,
366
+ participants: conversation.participants.map((p) => p.name),
367
+ transcript: prior.turns,
368
+ timestamp: nowIso()
369
+ };
370
+ yield {
371
+ type: "conversation_end",
372
+ runId,
373
+ result: replayResult,
374
+ timestamp: nowIso()
375
+ };
376
+ return;
377
+ }
378
+ transcript = [...prior.turns];
379
+ spentCreditsCents = transcript.reduce((sum, t) => sum + centsFromUsd(t.usage?.costUsd ?? 0), 0);
380
+ startedAt = prior.startedAt;
381
+ resumed = true;
382
+ } else await options.journal.beginRun(runId, startedAt);
383
+ }
384
+ const startedAtMs = Date.now();
385
+ const participantSessions = sessionIdsFrom(transcript);
386
+ if (resumed) yield {
387
+ type: "conversation_resumed",
388
+ runId,
389
+ participants: conversation.participants.map((p) => p.name),
390
+ transcript: [...transcript],
391
+ timestamp: nowIso()
392
+ };
393
+ else yield {
394
+ type: "conversation_start",
395
+ runId,
396
+ participants: conversation.participants.map((p) => p.name),
397
+ seed: options.seed,
398
+ timestamp: startedAt
399
+ };
400
+ let currentInput = transcript.length === 0 ? options.seed : transcript[transcript.length - 1]?.text ?? options.seed;
401
+ let halt;
402
+ const initialOffset = transcript.length;
403
+ for (let turnIndex = initialOffset; turnIndex < conversation.policy.maxTurns; turnIndex++) {
404
+ if (options.signal?.aborted) {
405
+ halt = { kind: "abort" };
406
+ break;
407
+ }
408
+ if (conversation.policy.maxCreditsCents !== void 0 && spentCreditsCents >= conversation.policy.maxCreditsCents) {
409
+ halt = {
410
+ kind: "max_credits",
411
+ spentCents: spentCreditsCents,
412
+ capCents: conversation.policy.maxCreditsCents
413
+ };
414
+ break;
415
+ }
416
+ const speakerIdx = selectSpeaker(conversation.policy.turnOrder, conversation.participants.length, {
417
+ transcript,
418
+ turnIndex,
419
+ spentCreditsCents
420
+ });
421
+ const speaker = conversation.participants[speakerIdx];
422
+ if (!speaker) throw new BackendTransportError("conversation", `turnOrder selector returned out-of-range index ${speakerIdx} for ${conversation.participants.length} participants`);
423
+ const tid = turnId(runId, turnIndex, speaker.name);
424
+ const callPolicy = speaker.callPolicy ?? conversation.policy.defaultCallPolicy;
425
+ const breaker = breakers.get(speaker.name);
426
+ if (!breaker) throw new BackendTransportError("conversation", `internal: no circuit-breaker state registered for participant '${speaker.name}'`);
427
+ const isRetryable = callPolicy?.isRetryable ?? defaultIsRetryable;
428
+ const totalAttempts = 1 + (callPolicy?.maxRetries ?? 0);
429
+ yield {
430
+ type: "turn_start",
431
+ runId,
432
+ index: turnIndex,
433
+ speaker: speaker.name,
434
+ turnId: tid,
435
+ attempt: 1,
436
+ timestamp: nowIso()
437
+ };
438
+ let aggregator;
439
+ let attemptCount = 0;
440
+ let lastError;
441
+ let breakerOpenFailure;
442
+ for (let attempt = 1; attempt <= totalAttempts; attempt++) {
443
+ attemptCount = attempt;
444
+ try {
445
+ breaker.preflight(speaker.name);
446
+ } catch (err) {
447
+ breakerOpenFailure = err;
448
+ break;
449
+ }
450
+ if (attempt > 1) yield {
451
+ type: "turn_retry",
452
+ runId,
453
+ index: turnIndex,
454
+ speaker: speaker.name,
455
+ turnId: tid,
456
+ attempt,
457
+ reason: lastError instanceof Error ? lastError.message : String(lastError),
458
+ timestamp: nowIso()
459
+ };
460
+ const perAttempt = makePerAttemptSignal(options.signal, callPolicy?.perAttemptDeadlineMs);
461
+ const localAgg = new TurnAggregator({
462
+ index: turnIndex,
463
+ speaker: speaker.name,
464
+ startedAt: nowIso()
465
+ });
466
+ try {
467
+ for await (const delta of driveSingleAttempt({
468
+ speaker,
469
+ participants: conversation.participants,
470
+ seed: options.seed,
471
+ input: currentInput,
472
+ turnIndex,
473
+ runId,
474
+ turnId: tid,
475
+ transcript,
476
+ participantSessions,
477
+ sessionStore,
478
+ signal: perAttempt.signal,
479
+ aggregator: localAgg,
480
+ propagatedHeaders: buildForwardHeaders({
481
+ inboundDepth,
482
+ forwardedAuthorization: resolveAuthForwarding(speaker, {
483
+ transcript,
484
+ turnIndex,
485
+ spentCreditsCents
486
+ }) ? forwardedAuthorization : void 0,
487
+ runId,
488
+ turnId: tid,
489
+ parentTurnId: options.parentTurnId,
490
+ speaker: speaker.name
491
+ })
492
+ })) yield {
493
+ type: "turn_text_delta",
494
+ runId,
495
+ index: turnIndex,
496
+ speaker: speaker.name,
497
+ turnId: tid,
498
+ text: delta.text,
499
+ timestamp: delta.timestamp
500
+ };
501
+ perAttempt.dispose();
502
+ breaker.recordSuccess();
503
+ aggregator = localAgg;
504
+ break;
505
+ } catch (err) {
506
+ perAttempt.dispose();
507
+ breaker.recordFailure();
508
+ lastError = perAttempt.getDeadlineError() ?? err;
509
+ if (attempt >= totalAttempts || !isRetryable(lastError)) break;
510
+ await sleep(computeBackoff(callPolicy?.retryBackoffMs, attempt));
511
+ }
512
+ }
513
+ if (!aggregator) {
514
+ const failure = breakerOpenFailure ?? lastError;
515
+ const message = failure instanceof Error ? failure.message : String(failure);
516
+ halt = {
517
+ kind: "participant_error",
518
+ participant: speaker.name,
519
+ message
520
+ };
521
+ break;
522
+ }
523
+ const turn = aggregator.toTurn({
524
+ turnId: tid,
525
+ attempts: attemptCount
526
+ });
527
+ if (turn.sessionId) bindParticipantSession(participantSessions, turn.speaker, turn.sessionId);
528
+ transcript.push(turn);
529
+ spentCreditsCents += centsFromUsd(turn.usage?.costUsd ?? 0);
530
+ if (options.journal) await options.journal.appendTurn(runId, turn);
531
+ yield {
532
+ type: "turn_end",
533
+ runId,
534
+ turn,
535
+ timestamp: nowIso()
536
+ };
537
+ if (conversation.policy.haltOn) {
538
+ const haltCtx = {
539
+ transcript,
540
+ lastTurn: turn,
541
+ turnIndex,
542
+ spentCreditsCents
543
+ };
544
+ const decision = await conversation.policy.haltOn(haltCtx);
545
+ if (decision === true) {
546
+ halt = {
547
+ kind: "predicate",
548
+ reason: "predicate_true"
549
+ };
550
+ break;
551
+ }
552
+ if (typeof decision === "object" && decision !== null && decision.halted) {
553
+ halt = {
554
+ kind: "predicate",
555
+ reason: decision.reason
556
+ };
557
+ break;
558
+ }
559
+ }
560
+ currentInput = turn.text;
561
+ }
562
+ if (!halt) halt = {
563
+ kind: "max_turns",
564
+ turns: transcript.length
565
+ };
566
+ const endedAt = nowIso();
567
+ const result = {
568
+ runId,
569
+ transcript,
570
+ turns: transcript.length,
571
+ spentCreditsCents,
572
+ halted: halt,
573
+ durationMs: Date.now() - startedAtMs,
574
+ startedAt,
575
+ endedAt
576
+ };
577
+ if (options.journal) await options.journal.recordHalt(runId, halt, endedAt);
578
+ yield {
579
+ type: "conversation_end",
580
+ runId,
581
+ result,
582
+ timestamp: endedAt
583
+ };
584
+ }
585
+ async function* driveSingleAttempt(args) {
586
+ const task = {
587
+ id: args.turnId,
588
+ intent: args.input,
589
+ metadata: {
590
+ runId: args.runId,
591
+ turnId: args.turnId,
592
+ turnIndex: args.turnIndex,
593
+ speaker: args.speaker.name,
594
+ participants: args.participants.map((p) => p.name)
595
+ }
596
+ };
597
+ const knowledge = passingReadiness(task.id);
598
+ const startCtx = {
599
+ task,
600
+ knowledge,
601
+ signal: args.signal,
602
+ runId: args.runId,
603
+ turnId: args.turnId,
604
+ propagatedHeaders: args.propagatedHeaders
605
+ };
606
+ const previousSessionId = args.participantSessions.get(args.speaker.name);
607
+ const opened = await startOrResumeRuntimeSession({
608
+ backend: args.speaker.backend,
609
+ input: () => fullConversationInput(task, args.speaker.name, args.seed, args.transcript),
610
+ continuationInput: () => continuedConversationInput(task, args.speaker.name, args.transcript),
611
+ context: startCtx,
612
+ store: args.sessionStore,
613
+ sessionId: previousSessionId ?? participantSessionId(args.runId, args.speaker.name, args.participants),
614
+ resume: previousSessionId !== void 0 && args.speaker.backend.resume !== void 0,
615
+ validateSession: (session) => assertParticipantSessionAvailable(args.participantSessions, args.speaker.name, session.id)
616
+ });
617
+ const session = opened.session;
618
+ args.aggregator.recordSession(session.id);
619
+ const streamCtx = {
620
+ task,
621
+ knowledge,
622
+ session,
623
+ signal: args.signal,
624
+ runId: args.runId,
625
+ turnId: args.turnId,
626
+ propagatedHeaders: args.propagatedHeaders
627
+ };
628
+ for await (const event of args.speaker.backend.stream(opened.input, streamCtx)) {
629
+ if (args.signal.aborted) {
630
+ const reason = args.signal.reason;
631
+ throw reason instanceof Error ? reason : /* @__PURE__ */ new Error("aborted");
632
+ }
633
+ if (event.type === "text_delta") {
634
+ args.aggregator.appendText(event.text);
635
+ yield {
636
+ text: event.text,
637
+ timestamp: event.timestamp
638
+ };
639
+ } else if (event.type === "llm_call") args.aggregator.recordUsage(event);
640
+ else if (event.type === "backend_error") throw new BackendTransportError(args.speaker.backend.kind, event.message, {
641
+ status: event.error?.status,
642
+ body: event.error?.body
643
+ });
644
+ else if (event.type === "final") {
645
+ if (event.status !== "completed") throw new BackendTransportError(args.speaker.backend.kind, event.error?.message ?? event.reason, {
646
+ status: event.error?.status,
647
+ body: event.error?.body
648
+ });
649
+ args.aggregator.adoptFinalText(event.text);
650
+ }
651
+ }
652
+ if (!args.aggregator.hasText()) throw new BackendTransportError(args.speaker.backend.kind, `backend '${args.speaker.backend.kind}' completed without text`);
653
+ }
654
+ var TurnAggregator = class {
655
+ base;
656
+ text = "";
657
+ adoptedFinal = false;
658
+ sessionId;
659
+ usage;
660
+ constructor(base) {
661
+ this.base = base;
662
+ }
663
+ appendText(text) {
664
+ if (this.adoptedFinal) return;
665
+ this.text += text;
666
+ }
667
+ /**
668
+ * Use the backend's `final.text` only when no streamed deltas were observed.
669
+ * Some backends emit deltas AND a final summary; treating both as content
670
+ * would double-count.
671
+ */
672
+ adoptFinalText(text) {
673
+ if (!text) return;
674
+ if (this.text.length > 0) return;
675
+ this.text = text;
676
+ this.adoptedFinal = true;
677
+ }
678
+ hasText() {
679
+ return this.text.trim().length > 0;
680
+ }
681
+ recordSession(sessionId) {
682
+ this.sessionId = sessionId;
683
+ }
684
+ recordUsage(event) {
685
+ const u = this.usage ?? {};
686
+ if (event.tokensIn !== void 0) u.tokensIn = (u.tokensIn ?? 0) + event.tokensIn;
687
+ if (event.tokensOut !== void 0) u.tokensOut = (u.tokensOut ?? 0) + event.tokensOut;
688
+ if (event.costUsd !== void 0) u.costUsd = (u.costUsd ?? 0) + event.costUsd;
689
+ if (event.latencyMs !== void 0) u.latencyMs = event.latencyMs;
690
+ if (event.model !== void 0) u.model = event.model;
691
+ this.usage = u;
692
+ }
693
+ toTurn(meta) {
694
+ return {
695
+ index: this.base.index,
696
+ speaker: this.base.speaker,
697
+ turnId: meta.turnId,
698
+ ...this.sessionId ? { sessionId: this.sessionId } : {},
699
+ text: this.text.trim(),
700
+ usage: this.usage,
701
+ attempts: meta.attempts,
702
+ startedAt: this.base.startedAt,
703
+ endedAt: nowIso()
704
+ };
705
+ }
706
+ };
707
+ /**
708
+ * Build the participant's POV of the transcript so an OpenAI-compatible
709
+ * backend sees its own turns as `assistant` and everyone else's as `user`,
710
+ * with explicit speaker tags so 3+ party conversations stay disambiguated.
711
+ */
712
+ function buildMessagesFor(speakerName, seed, transcript) {
713
+ const messages = seed ? [{
714
+ role: "user",
715
+ content: seed
716
+ }] : [];
717
+ for (const turn of transcript) if (turn.speaker === speakerName) messages.push({
718
+ role: "assistant",
719
+ content: turn.text
720
+ });
721
+ else messages.push({
722
+ role: "user",
723
+ content: `[${turn.speaker}] ${turn.text}`
724
+ });
725
+ return messages;
726
+ }
727
+ function fullConversationInput(task, speakerName, seed, transcript) {
728
+ return {
729
+ task,
730
+ message: [seed, ...transcript.map((turn) => `[${turn.speaker}] ${turn.text}`)].filter((part) => part.length > 0).join("\n\n"),
731
+ messages: buildMessagesFor(speakerName, seed, transcript)
732
+ };
733
+ }
734
+ function continuedConversationInput(task, speakerName, transcript) {
735
+ let lastOwnTurn = -1;
736
+ for (let index = transcript.length - 1; index >= 0; index -= 1) if (transcript[index]?.speaker === speakerName) {
737
+ lastOwnTurn = index;
738
+ break;
739
+ }
740
+ const unseen = transcript.slice(lastOwnTurn + 1);
741
+ const message = unseen.length > 0 ? unseen.map((turn) => `[${turn.speaker}] ${turn.text}`).join("\n\n") : "Continue.";
742
+ return {
743
+ task,
744
+ message,
745
+ messages: [{
746
+ role: "user",
747
+ content: message
748
+ }]
749
+ };
750
+ }
751
+ function sessionIdsFrom(transcript) {
752
+ const sessions = /* @__PURE__ */ new Map();
753
+ for (const turn of transcript) if (turn.sessionId) bindParticipantSession(sessions, turn.speaker, turn.sessionId);
754
+ return sessions;
755
+ }
756
+ function bindParticipantSession(sessions, speakerName, sessionId) {
757
+ assertParticipantSessionAvailable(sessions, speakerName, sessionId);
758
+ sessions.set(speakerName, sessionId);
759
+ }
760
+ function assertParticipantSessionAvailable(sessions, speakerName, sessionId) {
761
+ for (const [boundSpeaker, boundSessionId] of sessions) if (boundSpeaker !== speakerName && boundSessionId === sessionId) throw new BackendTransportError("conversation", `session '${sessionId}' is already bound to participant '${boundSpeaker}'`);
762
+ }
763
+ function participantSessionId(runId, speakerName, participants) {
764
+ const participantIndex = participants.findIndex((participant) => participant.name === speakerName);
765
+ if (participantIndex < 0) throw new BackendTransportError("conversation", `participant '${speakerName}' is not registered in this conversation`);
766
+ return `${runId}.participant.${participantIndex}.${slugifySpeaker(speakerName)}`;
767
+ }
768
+ /**
769
+ * True when this participant should forward the caller's
770
+ * `X-Tangle-Forwarded-Authorization` on outbound calls (the "pass-through"
771
+ * commercial mode). False when it elects to pay for its own outbound calls
772
+ * (the "reseller" / "bundle" mode). See ConversationParticipant.authSource.
773
+ */
774
+ function resolveAuthForwarding(participant, state) {
775
+ return (typeof participant.authSource === "function" ? participant.authSource(state) : participant.authSource ?? "forward-user") === "forward-user";
776
+ }
777
+ function selectSpeaker(order, participantCount, state) {
778
+ const resolved = order ?? (participantCount === 2 ? "alternate" : "round-robin");
779
+ if (resolved === "alternate" || resolved === "round-robin") return state.turnIndex % participantCount;
780
+ if (typeof resolved === "function") {
781
+ const idx = resolved(state);
782
+ if (!Number.isInteger(idx) || idx < 0 || idx >= participantCount) throw new BackendTransportError("conversation", `turnOrder function returned invalid index ${String(idx)} for ${participantCount} participants`);
783
+ return idx;
784
+ }
785
+ throw new BackendTransportError("conversation", `unknown turnOrder: ${String(resolved)}`);
786
+ }
787
+ function centsFromUsd(usd) {
788
+ return Math.round(usd * 100);
789
+ }
790
+ /**
791
+ * Synthesize a knowledge-readiness report that *passes* every gate, used to
792
+ * satisfy `AgentBackendContext.knowledge` per turn. Conversations don't apply
793
+ * task-level readiness gating per-turn — that's a `runAgentTask` concern.
794
+ */
795
+ function passingReadiness(taskId) {
796
+ return {
797
+ taskId,
798
+ readinessScore: 1,
799
+ blockingMissingRequirements: [],
800
+ nonBlockingGaps: [],
801
+ recommendedAction: "run_agent",
802
+ bundle: {
803
+ taskId,
804
+ requirements: [],
805
+ evidenceIds: [],
806
+ claimIds: [],
807
+ wikiPageIds: [],
808
+ userAnswers: {},
809
+ missing: [],
810
+ readinessScore: 1
811
+ },
812
+ severity: "info",
813
+ reason: "conversation-mode: readiness gating not applied per-turn"
814
+ };
815
+ }
816
+ //#endregion
817
+ //#region src/conversation/conversation-backend.ts
818
+ /**
819
+ *
820
+ * Wrap a `Conversation` so it satisfies `AgentExecutionBackend`. The result is
821
+ * an addressable "single agent" whose internal behavior is an N-party
822
+ * orchestrated conversation — the recursion primitive that lets a swarm be a
823
+ * participant inside another swarm, or be published behind a single
824
+ * agent-gateway endpoint.
825
+ *
826
+ * Stream events from inner participants are NOT forwarded verbatim. Outer
827
+ * callers see one `text_delta` per inner turn (the turn's full text), tagged
828
+ * with `[speaker] ` prefix so the outer transcript stays attributable. The
829
+ * conversation's `conversation_end` halt reason rides on a `final` event.
830
+ *
831
+ * @stable
832
+ */
833
+ /** Adapt a multi-participant conversation into the standard execution backend contract. */
834
+ function createConversationBackend(options) {
835
+ const kind = options.kind ?? "conversation";
836
+ return {
837
+ kind,
838
+ start(_input, context) {
839
+ return newRuntimeSession(kind, context.requestedSessionId, { participants: options.conversation.participants.map((p) => p.name) });
840
+ },
841
+ async *stream(input, context) {
842
+ const seed = input.message ?? input.messages?.at(-1)?.content ?? providerMessageText(input.providerOptions) ?? context.task.intent;
843
+ const task = context.task;
844
+ const session = context.session;
845
+ yield {
846
+ type: "backend_start",
847
+ task,
848
+ session,
849
+ backend: kind,
850
+ timestamp: nowIso()
851
+ };
852
+ let finalText = "";
853
+ let totalCostUsd = 0;
854
+ let totalTokensIn = 0;
855
+ let totalTokensOut = 0;
856
+ const inboundDepth = parseInboundDepth(context.propagatedHeaders);
857
+ for await (const event of runConversationStream(options.conversation, {
858
+ seed,
859
+ signal: context.signal,
860
+ runId: context.runId,
861
+ propagatedHeaders: context.propagatedHeaders,
862
+ inboundDepth,
863
+ parentTurnId: context.turnId
864
+ })) if (event.type === "turn_end") {
865
+ const tagged = `[${event.turn.speaker}] ${event.turn.text}\n`;
866
+ finalText += tagged;
867
+ yield {
868
+ type: "text_delta",
869
+ task,
870
+ session,
871
+ text: tagged,
872
+ timestamp: event.timestamp
873
+ };
874
+ if (event.turn.usage) {
875
+ const u = event.turn.usage;
876
+ if (u.costUsd !== void 0) totalCostUsd += u.costUsd;
877
+ if (u.tokensIn !== void 0) totalTokensIn += u.tokensIn;
878
+ if (u.tokensOut !== void 0) totalTokensOut += u.tokensOut;
879
+ yield {
880
+ type: "llm_call",
881
+ task,
882
+ session,
883
+ model: u.model ?? `${kind}/${event.turn.speaker}`,
884
+ tokensIn: u.tokensIn,
885
+ tokensOut: u.tokensOut,
886
+ costUsd: u.costUsd,
887
+ latencyMs: u.latencyMs,
888
+ timestamp: event.timestamp
889
+ };
890
+ }
891
+ } else if (event.type === "conversation_end") {
892
+ const halt = event.result.halted;
893
+ yield {
894
+ type: "final",
895
+ task,
896
+ session,
897
+ status: halt.kind === "participant_error" ? "failed" : "completed",
898
+ reason: describeHalt(halt),
899
+ text: finalText.trim(),
900
+ metadata: {
901
+ conversationRunId: event.result.runId,
902
+ turns: event.result.turns,
903
+ spentCreditsCents: event.result.spentCreditsCents,
904
+ halted: halt,
905
+ durationMs: event.result.durationMs,
906
+ tokensIn: totalTokensIn,
907
+ tokensOut: totalTokensOut,
908
+ costUsd: totalCostUsd
909
+ },
910
+ timestamp: event.timestamp
911
+ };
912
+ }
913
+ yield {
914
+ type: "backend_end",
915
+ task,
916
+ session,
917
+ backend: kind,
918
+ timestamp: nowIso()
919
+ };
920
+ }
921
+ };
922
+ }
923
+ function parseInboundDepth(headers) {
924
+ if (!headers) return 0;
925
+ const raw = headers[FORWARD_HEADERS.depth];
926
+ if (raw === void 0) return 0;
927
+ try {
928
+ return readDepth({ [FORWARD_HEADERS.depth]: raw });
929
+ } catch {
930
+ return 0;
931
+ }
932
+ }
933
+ function describeHalt(halt) {
934
+ switch (halt.kind) {
935
+ case "max_turns": return `max_turns (${halt.turns})`;
936
+ case "max_credits": return `max_credits (${halt.spentCents}/${halt.capCents}¢)`;
937
+ case "predicate": return `predicate: ${halt.reason}`;
938
+ case "abort": return "abort";
939
+ case "participant_error": return `participant_error[${halt.participant}]: ${halt.message}`;
940
+ }
941
+ }
942
+ //#endregion
943
+ //#region src/conversation/define-conversation.ts
944
+ /**
945
+ *
946
+ * Declarative constructor for a multi-agent `Conversation`. Validates inputs
947
+ * fail-loud at definition time (duplicate participant names, alternate order
948
+ * with ≠2 participants, non-positive `maxTurns`) so misconfiguration is caught
949
+ * before `runConversation` is called and not buried inside a streaming run.
950
+ *
951
+ * @stable
952
+ */
953
+ /** Validate and define a conversation before execution. */
954
+ function defineConversation(input) {
955
+ if (input.participants.length < 2) throw new ValidationError(`Conversation requires at least 2 participants; received ${input.participants.length}.`);
956
+ const seen = /* @__PURE__ */ new Set();
957
+ for (const p of input.participants) {
958
+ if (!p.name || p.name.trim() === "") throw new ValidationError("Conversation participant.name must be a non-empty string.");
959
+ if (seen.has(p.name)) throw new ValidationError(`Conversation participant names must be unique within a Conversation; '${p.name}' appears more than once.`);
960
+ seen.add(p.name);
961
+ if (!p.backend || typeof p.backend.stream !== "function") throw new ValidationError(`Conversation participant '${p.name}' is missing a backend with a stream() method.`);
962
+ }
963
+ const policy = normalizePolicy(input.policy, input.participants.length);
964
+ return {
965
+ participants: input.participants,
966
+ policy
967
+ };
968
+ }
969
+ function normalizePolicy(policy, participantCount) {
970
+ if (!Number.isInteger(policy.maxTurns) || policy.maxTurns < 1) throw new ValidationError(`ConversationPolicy.maxTurns must be a positive integer; received ${String(policy.maxTurns)}.`);
971
+ if (policy.maxCreditsCents !== void 0 && (!Number.isFinite(policy.maxCreditsCents) || policy.maxCreditsCents < 0)) throw new ValidationError(`ConversationPolicy.maxCreditsCents must be a non-negative finite number when set; received ${String(policy.maxCreditsCents)}.`);
972
+ const turnOrder = policy.turnOrder ?? (participantCount === 2 ? "alternate" : "round-robin");
973
+ if (turnOrder === "alternate" && participantCount !== 2) throw new ValidationError(`ConversationPolicy.turnOrder 'alternate' requires exactly 2 participants; received ${participantCount}. Use 'round-robin' or a custom selector for N-party conversations.`);
974
+ return {
975
+ ...policy,
976
+ turnOrder
977
+ };
978
+ }
979
+ //#endregion
980
+ //#region src/conversation/journal.ts
981
+ /**
982
+ *
983
+ * Durable conversation transcript — survives a driver process crash mid-run.
984
+ * The runner journals every committed turn before yielding `turn_end`, so a
985
+ * resumed run replays the same `runId` against the same journal and picks up
986
+ * from the first un-recorded turn. Combined with the deterministic
987
+ * `turnId(runId, index, speaker)`, a retried turn collides with the prior
988
+ * attempt's id and any caching gateway can dedupe.
989
+ *
990
+ * The interface is small enough that a Cloudflare D1 / R2 / postgres adapter
991
+ * is ~30 lines. The in-memory adapter is the default for tests and scratch.
992
+ * The file adapter (JSONL on disk) is the default-durable choice when no
993
+ * upstream store is wired.
994
+ *
995
+ * @stable
996
+ */
997
+ /** In-memory `ConversationJournal` — suitable for testing and single-process runs. */
998
+ var InMemoryConversationJournal = class {
999
+ entries = /* @__PURE__ */ new Map();
1000
+ async loadRun(runId) {
1001
+ const entry = this.entries.get(runId);
1002
+ if (!entry) return void 0;
1003
+ return {
1004
+ runId: entry.runId,
1005
+ startedAt: entry.startedAt,
1006
+ halted: entry.halted,
1007
+ endedAt: entry.endedAt,
1008
+ turns: [...entry.turns]
1009
+ };
1010
+ }
1011
+ async beginRun(runId, startedAt) {
1012
+ const existing = this.entries.get(runId);
1013
+ if (existing) {
1014
+ if (existing.startedAt !== startedAt) throw new Error(`runId '${runId}' already exists with startedAt=${existing.startedAt}; refusing to overwrite with ${startedAt}`);
1015
+ return;
1016
+ }
1017
+ this.entries.set(runId, {
1018
+ runId,
1019
+ startedAt,
1020
+ turns: []
1021
+ });
1022
+ }
1023
+ async appendTurn(runId, turn) {
1024
+ const entry = this.entries.get(runId);
1025
+ if (!entry) throw new Error(`appendTurn called for unknown runId '${runId}'; call beginRun first or use the runner which handles it`);
1026
+ if (entry.halted) throw new Error(`cannot append turn to halted run '${runId}' (halt reason: ${JSON.stringify(entry.halted)})`);
1027
+ entry.turns.push(turn);
1028
+ }
1029
+ async recordHalt(runId, halt, endedAt) {
1030
+ const entry = this.entries.get(runId);
1031
+ if (!entry) throw new Error(`recordHalt called for unknown runId '${runId}'`);
1032
+ entry.halted = halt;
1033
+ entry.endedAt = endedAt;
1034
+ }
1035
+ };
1036
+ /**
1037
+ * JSONL on disk. One line per record; first line is the `begin`, subsequent
1038
+ * lines are `turn` records, terminal line is `halt`. Replays the whole file
1039
+ * on `loadRun` — cheap for the conversation sizes this is designed for
1040
+ * (thousands of turns, not millions). For huge runs, plug in a real DB
1041
+ * adapter; the interface is small.
1042
+ *
1043
+ * Reads and appends over the shared append-only spine (`durable/jsonl-file`): each
1044
+ * `appendTurn` / `recordHalt` finishes a short write and calls `fsync`, so a process
1045
+ * crash between writes never loses an acknowledged turn, and a crash DURING one leaves
1046
+ * an uncommitted final line that the next read skips and the next append truncates.
1047
+ */
1048
+ var FileConversationJournal = class {
1049
+ path;
1050
+ constructor(path) {
1051
+ this.path = path;
1052
+ }
1053
+ async loadRun(runId) {
1054
+ const fs = await import("node:fs/promises");
1055
+ let text;
1056
+ try {
1057
+ text = await fs.readFile(this.path, "utf8");
1058
+ } catch (err) {
1059
+ if (isNoEntError(err)) return void 0;
1060
+ throw err;
1061
+ }
1062
+ let entry;
1063
+ for (const record of parseCommittedJsonLines(text, this.path)) {
1064
+ if (record.runId !== runId) continue;
1065
+ if (record.kind === "begin") entry = {
1066
+ runId,
1067
+ startedAt: record.startedAt,
1068
+ turns: []
1069
+ };
1070
+ else if (record.kind === "turn") {
1071
+ if (!entry) throw new Error(`journal corrupted: turn record for runId '${runId}' precedes its begin record`);
1072
+ entry.turns.push(record.turn);
1073
+ } else if (record.kind === "halt") {
1074
+ if (!entry) throw new Error(`journal corrupted: halt record for runId '${runId}' precedes its begin record`);
1075
+ entry.halted = record.halted;
1076
+ entry.endedAt = record.endedAt;
1077
+ }
1078
+ }
1079
+ return entry;
1080
+ }
1081
+ async beginRun(runId, startedAt) {
1082
+ const existing = await this.loadRun(runId);
1083
+ if (existing) {
1084
+ if (existing.startedAt !== startedAt) throw new Error(`runId '${runId}' already exists in ${this.path} with startedAt=${existing.startedAt}; refusing to overwrite with ${startedAt}`);
1085
+ return;
1086
+ }
1087
+ await this.appendRecord({
1088
+ kind: "begin",
1089
+ runId,
1090
+ startedAt
1091
+ });
1092
+ }
1093
+ async appendTurn(runId, turn) {
1094
+ await this.appendRecord({
1095
+ kind: "turn",
1096
+ runId,
1097
+ turn
1098
+ });
1099
+ }
1100
+ async recordHalt(runId, halt, endedAt) {
1101
+ await this.appendRecord({
1102
+ kind: "halt",
1103
+ runId,
1104
+ halted: halt,
1105
+ endedAt
1106
+ });
1107
+ }
1108
+ async appendRecord(record) {
1109
+ const fs = await import("node:fs/promises");
1110
+ const path = await import("node:path");
1111
+ await fs.mkdir(path.dirname(this.path), { recursive: true });
1112
+ const needsSeparator = await prepareJsonlAppend(this.path);
1113
+ const fh = await fs.open(this.path, "a");
1114
+ try {
1115
+ await writeAllBytes(fh, `${needsSeparator ? "\n" : ""}${JSON.stringify(record)}\n`);
1116
+ await fh.sync();
1117
+ } finally {
1118
+ await fh.close();
1119
+ }
1120
+ }
1121
+ };
1122
+ //#endregion
1123
+ //#region src/conversation/journal-sql.ts
1124
+ /**
1125
+ * Adapt a Cloudflare D1 binding to the SqlAdapter shape. Lives here so D1
1126
+ * consumers don't have to write the wrapper themselves; the runtime never
1127
+ * imports `@cloudflare/workers-types` directly (peer-style typing).
1128
+ */
1129
+ function d1ToSqlAdapter(db) {
1130
+ return {
1131
+ async exec(sql, params = []) {
1132
+ const stmt = db.prepare(sql);
1133
+ const meta = (await (params.length > 0 ? stmt.bind(...params) : stmt).run()).meta;
1134
+ return { rowsAffected: meta?.rows_written ?? meta?.changes ?? 0 };
1135
+ },
1136
+ async query(sql, params = []) {
1137
+ const stmt = db.prepare(sql);
1138
+ return (await (params.length > 0 ? stmt.bind(...params) : stmt).all()).results ?? [];
1139
+ }
1140
+ };
1141
+ }
1142
+ const RUNS_TABLE_DDL = (table) => `
1143
+ CREATE TABLE IF NOT EXISTS ${table}_runs (
1144
+ run_id TEXT PRIMARY KEY,
1145
+ started_at TEXT NOT NULL,
1146
+ halted_kind TEXT,
1147
+ halted_payload TEXT,
1148
+ ended_at TEXT
1149
+ )
1150
+ `;
1151
+ const TURNS_TABLE_DDL = (table) => `
1152
+ CREATE TABLE IF NOT EXISTS ${table}_turns (
1153
+ run_id TEXT NOT NULL,
1154
+ turn_index INTEGER NOT NULL,
1155
+ payload TEXT NOT NULL,
1156
+ PRIMARY KEY (run_id, turn_index)
1157
+ )
1158
+ `;
1159
+ const TURNS_INDEX_DDL = (table) => `
1160
+ CREATE INDEX IF NOT EXISTS idx_${table}_turns_run ON ${table}_turns (run_id, turn_index)
1161
+ `;
1162
+ /**
1163
+ * SQL-backed ConversationJournal. Two tables — runs (one row per runId, holds
1164
+ * start/halt timestamps + halt reason) and turns (one row per committed turn,
1165
+ * payload is the ConversationTurn JSON). Replays the turns table on
1166
+ * `loadRun` and writes append-only per `appendTurn`.
1167
+ */
1168
+ var SqlConversationJournal = class {
1169
+ db;
1170
+ table;
1171
+ /**
1172
+ * @param db SQL adapter (D1, postgres, sqlite, libSQL — all work)
1173
+ * @param table Table-name prefix; the journal creates `${table}_runs` and
1174
+ * `${table}_turns`. Lets multiple journals share a database
1175
+ * without colliding (e.g. one per product surface).
1176
+ */
1177
+ constructor(db, table = "agent_runtime_journal") {
1178
+ this.db = db;
1179
+ this.table = table;
1180
+ }
1181
+ /**
1182
+ * Create the journal's tables if absent. Idempotent. Call once at deploy
1183
+ * (or at app boot) — running on every request is harmless but adds latency.
1184
+ */
1185
+ async migrate() {
1186
+ await this.db.exec(RUNS_TABLE_DDL(this.table));
1187
+ await this.db.exec(TURNS_TABLE_DDL(this.table));
1188
+ await this.db.exec(TURNS_INDEX_DDL(this.table));
1189
+ }
1190
+ async loadRun(runId) {
1191
+ const row = (await this.db.query(`SELECT run_id, started_at, halted_kind, halted_payload, ended_at FROM ${this.table}_runs WHERE run_id = ?`, [runId]))[0];
1192
+ if (!row) return void 0;
1193
+ const turns = await this.db.query(`SELECT payload, turn_index FROM ${this.table}_turns WHERE run_id = ? ORDER BY turn_index ASC`, [runId]);
1194
+ return {
1195
+ runId: row.run_id,
1196
+ startedAt: row.started_at,
1197
+ halted: row.halted_payload ? JSON.parse(row.halted_payload) : void 0,
1198
+ endedAt: row.ended_at ?? void 0,
1199
+ turns: turns.map((t) => JSON.parse(t.payload))
1200
+ };
1201
+ }
1202
+ async beginRun(runId, startedAt) {
1203
+ const existing = await this.db.query(`SELECT started_at FROM ${this.table}_runs WHERE run_id = ?`, [runId]);
1204
+ if (existing.length > 0) {
1205
+ if (existing[0]?.started_at !== startedAt) throw new Error(`runId '${runId}' already exists with startedAt=${existing[0]?.started_at}; refusing to overwrite with ${startedAt}`);
1206
+ return;
1207
+ }
1208
+ await this.db.exec(`INSERT INTO ${this.table}_runs (run_id, started_at) VALUES (?, ?)`, [runId, startedAt]);
1209
+ }
1210
+ async appendTurn(runId, turn) {
1211
+ const halted = await this.db.query(`SELECT halted_kind FROM ${this.table}_runs WHERE run_id = ?`, [runId]);
1212
+ if (halted.length === 0) throw new Error(`appendTurn called for unknown runId '${runId}'; call beginRun first or use the runner which handles it`);
1213
+ if (halted[0]?.halted_kind) throw new Error(`cannot append turn to halted run '${runId}' (halt kind: ${halted[0]?.halted_kind})`);
1214
+ await this.db.exec(`INSERT INTO ${this.table}_turns (run_id, turn_index, payload) VALUES (?, ?, ?)`, [
1215
+ runId,
1216
+ turn.index,
1217
+ JSON.stringify(turn)
1218
+ ]);
1219
+ }
1220
+ async recordHalt(runId, halt, endedAt) {
1221
+ if ((await this.db.exec(`UPDATE ${this.table}_runs SET halted_kind = ?, halted_payload = ?, ended_at = ? WHERE run_id = ?`, [
1222
+ halt.kind,
1223
+ JSON.stringify(halt),
1224
+ endedAt,
1225
+ runId
1226
+ ])).rowsAffected === 0) throw new Error(`recordHalt called for unknown runId '${runId}'`);
1227
+ }
1228
+ };
1229
+ //#endregion
1230
+ //#region src/conversation/run-persona.ts
1231
+ /** Adapt one exact profile + Runtime executor into the conversation stream protocol. */
1232
+ function profileRuntimeBackend(profile, factory, counter) {
1233
+ const backend = createProfileExecutionBackend({
1234
+ profile,
1235
+ executor: factory
1236
+ });
1237
+ if (!counter) return backend;
1238
+ return {
1239
+ ...backend,
1240
+ async *stream(input, context) {
1241
+ for await (const event of backend.stream(input, context)) {
1242
+ if (event.type === "llm_call") {
1243
+ counter.sawLlmCall = true;
1244
+ counter.tokensIn += event.tokensIn ?? 0;
1245
+ counter.tokensOut += event.tokensOut ?? 0;
1246
+ counter.costUsd += event.costUsd ?? 0;
1247
+ if (event.tokensKnown === false || event.tokensIn === void 0 || event.tokensOut === void 0) counter.tokensKnown = false;
1248
+ if (event.usdKnown === false || event.costUsd === void 0) counter.usdKnown = false;
1249
+ }
1250
+ yield event;
1251
+ }
1252
+ }
1253
+ };
1254
+ }
1255
+ /** A persona participant that replays scripted user turns in order. */
1256
+ function scriptedPersonaBackend(turns) {
1257
+ let idx = 0;
1258
+ return createIterableBackend({
1259
+ kind: "persona-user",
1260
+ async *stream(_input, context) {
1261
+ const text = turns[idx];
1262
+ if (text === void 0) throw new Error(`persona-user: ran out of scripted turns at index ${idx} (had ${turns.length})`);
1263
+ idx += 1;
1264
+ yield {
1265
+ type: "text_delta",
1266
+ task: context.task,
1267
+ session: context.session,
1268
+ text,
1269
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1270
+ };
1271
+ }
1272
+ });
1273
+ }
1274
+ /**
1275
+ * Run one worker profile against one persona as a multi-round conversation.
1276
+ * The persona leads (participant 0): it speaks, the worker answers, repeat,
1277
+ * until `maxTurns`. Returns the persistent transcript + worker-only usage.
1278
+ */
1279
+ async function runPersonaConversation(opts) {
1280
+ const counter = {
1281
+ tokensIn: 0,
1282
+ tokensOut: 0,
1283
+ costUsd: 0,
1284
+ sawLlmCall: false,
1285
+ tokensKnown: true,
1286
+ usdKnown: true
1287
+ };
1288
+ const workerName = opts.workerName ?? "agent";
1289
+ const worker = profileRuntimeBackend(opts.worker, opts.executorFor(opts.worker, "worker"), counter);
1290
+ let persona;
1291
+ let maxTurns;
1292
+ if (opts.persona.kind === "scripted") {
1293
+ if (opts.persona.turns.length === 0) throw new Error("runPersonaConversation: scripted persona has no turns");
1294
+ persona = scriptedPersonaBackend(opts.persona.turns);
1295
+ maxTurns = opts.maxTurns ?? 2 * opts.persona.turns.length;
1296
+ } else {
1297
+ persona = profileRuntimeBackend(opts.persona.profile, opts.executorFor(opts.persona.profile, "persona"));
1298
+ if (opts.maxTurns === void 0) throw new Error("runPersonaConversation: maxTurns is required for a profile-driven persona");
1299
+ maxTurns = opts.maxTurns;
1300
+ }
1301
+ const result = await runConversation(defineConversation({
1302
+ participants: [{
1303
+ name: "user",
1304
+ backend: persona
1305
+ }, {
1306
+ name: workerName,
1307
+ backend: worker
1308
+ }],
1309
+ policy: {
1310
+ maxTurns,
1311
+ turnOrder: "alternate",
1312
+ ...opts.haltOn ? { haltOn: opts.haltOn } : {}
1313
+ }
1314
+ }), {
1315
+ seed: opts.seed ?? "Begin.",
1316
+ signal: opts.signal
1317
+ });
1318
+ const fallbackCostKnown = !counter.sawLlmCall && opts.persona.kind === "scripted" && result.spentCreditsCents > 0;
1319
+ const costUsd = fallbackCostKnown ? result.spentCreditsCents / 100 : counter.costUsd;
1320
+ const tokensKnown = counter.sawLlmCall && counter.tokensKnown;
1321
+ const costUsdKnown = counter.sawLlmCall ? counter.usdKnown : fallbackCostKnown;
1322
+ return {
1323
+ transcript: result.transcript,
1324
+ turns: result.turns,
1325
+ halted: result.halted,
1326
+ costUsd,
1327
+ tokensIn: counter.tokensIn,
1328
+ tokensOut: counter.tokensOut,
1329
+ ...tokensKnown ? {} : { tokensKnown: false },
1330
+ ...costUsdKnown ? {} : { costUsdKnown: false }
1331
+ };
1332
+ }
1333
+ /**
1334
+ * Wrap {@link runPersonaConversation} as a `ProfileDispatchFn` for
1335
+ * `runProfileMatrix`: the profile axis is the worker-under-test, the scenario
1336
+ * axis is the persona, and the runner is the cell. Meters the worker through
1337
+ * `ctx.cost` so the matrix's backend-integrity guard sees real usage.
1338
+ */
1339
+ function runPersonaDispatch(config) {
1340
+ return async (worker, scenario, ctx) => {
1341
+ const model = worker.model?.default ?? "unknown";
1342
+ const maximumCharge = typeof config.maximumCharge === "function" ? config.maximumCharge(worker, scenario) : config.maximumCharge;
1343
+ const paid = await ctx.cost.runPaidCall({
1344
+ channel: "agent",
1345
+ actor: "persona-conversation",
1346
+ model,
1347
+ signal: ctx.signal,
1348
+ ...maximumCharge ? { maximumCharge } : {},
1349
+ execute: (executionSignal) => runPersonaConversation({
1350
+ worker,
1351
+ persona: config.personaOf(scenario),
1352
+ executorFor: config.executorFor,
1353
+ maxTurns: config.maxTurns?.(scenario),
1354
+ seed: config.seed?.(scenario),
1355
+ signal: executionSignal,
1356
+ workerName: config.workerName
1357
+ }),
1358
+ receipt: (result) => ({
1359
+ model,
1360
+ inputTokens: result.tokensIn,
1361
+ outputTokens: result.tokensOut,
1362
+ ...result.tokensKnown === false ? { usageUnknown: true } : {},
1363
+ ...result.costUsdKnown === false ? { costUnknown: true } : { actualCostUsd: result.costUsd }
1364
+ })
1365
+ });
1366
+ if (!paid.succeeded) throw paid.error;
1367
+ const result = paid.value;
1368
+ return config.artifactOf(result.transcript, scenario);
1369
+ };
1370
+ }
1371
+ //#endregion
25
1372
  //#region src/improvement/build-prompts.ts
26
1373
  /** Render findings as the ranked-evidence block every build prompt ends with. */
27
1374
  function findingLines(findings) {
@@ -589,6 +1936,422 @@ function applyPatches(edits, cwd) {
589
1936
  if (result.status !== 0) throw new Error(`reflectiveGenerator: patch batch failed: ${result.stderr.trim()}`);
590
1937
  }
591
1938
  //#endregion
1939
+ //#region src/knowledge/activation.ts
1940
+ /** Apply or restore one local knowledge candidate through the shared activation contract. */
1941
+ function createKnowledgeImprovementActivationExecutor(options) {
1942
+ if (!options.identity.trim()) throw new Error("knowledge activation identity is required");
1943
+ return {
1944
+ reconcile: async (transition) => {
1945
+ const inspected = await inspectKnowledgeActivation(options, transition);
1946
+ return inspected.settled ? inspected.result : void 0;
1947
+ },
1948
+ transition: async (transition) => {
1949
+ if (transition.expired) throw new Error("knowledge write transition cannot run after authorization expires");
1950
+ const inspected = await inspectKnowledgeActivation(options, transition);
1951
+ if (inspected.settled) return inspected.result;
1952
+ const mutationOptions = {
1953
+ root: options.root,
1954
+ candidate: inspected.candidate,
1955
+ activation: {
1956
+ activation: transition.activation,
1957
+ attemptedAt: transition.attemptedAt,
1958
+ identity: options.identity,
1959
+ createResult: (receipt) => inspected.create(knowledgeMutationOutcome(receipt, inspected, transition.activation.intent))
1960
+ },
1961
+ ...options.ownerId ? { ownerId: options.ownerId } : {},
1962
+ ...options.leaseTtlMs === void 0 ? {} : { leaseTtlMs: options.leaseTtlMs },
1963
+ ...options.now ? { now: options.now } : {},
1964
+ ...options.onState ? { onState: options.onState } : {}
1965
+ };
1966
+ const improvement = transition.activation.intent === "activate-candidate" ? await promoteKnowledgeCandidate(mutationOptions) : await restoreKnowledgeCandidateBaseline(mutationOptions);
1967
+ if (!improvement.activationResult) throw new Error("knowledge mutation did not persist its shared activation result");
1968
+ return options.results.putIfAbsent(improvement.activationResult);
1969
+ }
1970
+ };
1971
+ }
1972
+ async function inspectKnowledgeActivation(options, transition) {
1973
+ const stored = await options.results.load(transition.activation.digest);
1974
+ if (stored !== void 0) return {
1975
+ settled: true,
1976
+ result: stored
1977
+ };
1978
+ const create = (outcome) => createAgentImprovementActivationResult(transition, {
1979
+ completedAt: completionTime(transition, options.now),
1980
+ outcome
1981
+ });
1982
+ const store = (outcome) => options.results.putIfAbsent(create(outcome));
1983
+ if (transition.kind !== "sealed-candidate") {
1984
+ if (transition.expired) return {
1985
+ settled: true,
1986
+ result: void 0
1987
+ };
1988
+ return {
1989
+ settled: true,
1990
+ result: await store({
1991
+ status: "unsupported",
1992
+ code: "KNOWLEDGE_PROFILE_TRANSITION_UNSUPPORTED",
1993
+ message: "The knowledge adapter accepts only sealed knowledge candidates."
1994
+ })
1995
+ };
1996
+ }
1997
+ const target = supportedKnowledgeTarget(transition, options.identity);
1998
+ if (!target) {
1999
+ if (transition.expired) return {
2000
+ settled: true,
2001
+ result: void 0
2002
+ };
2003
+ return {
2004
+ settled: true,
2005
+ result: await store({
2006
+ status: "unsupported",
2007
+ code: "KNOWLEDGE_TARGET_SET_UNSUPPORTED",
2008
+ message: "The knowledge adapter requires exactly one matching knowledge target."
2009
+ })
2010
+ };
2011
+ }
2012
+ const knowledge = transition.candidateBundle.knowledge;
2013
+ if (!knowledge) {
2014
+ if (transition.expired) return {
2015
+ settled: true,
2016
+ result: void 0
2017
+ };
2018
+ return {
2019
+ settled: true,
2020
+ result: await store({
2021
+ status: "unsupported",
2022
+ code: "KNOWLEDGE_CANDIDATE_MISSING",
2023
+ message: "The measured candidate bundle does not contain a knowledge candidate."
2024
+ })
2025
+ };
2026
+ }
2027
+ const candidate = fromAgentCandidateKnowledgeRef(knowledge.candidate);
2028
+ const expectedDigest = transition.activation.intent === "activate-candidate" ? knowledge.candidate.baseHash : knowledge.candidate.candidateHash;
2029
+ const desiredDigest = transition.activation.intent === "activate-candidate" ? knowledge.candidate.candidateHash : knowledge.candidate.baseHash;
2030
+ if (target.expectedBaseDigest !== expectedDigest || target.desiredDigest !== desiredDigest) {
2031
+ if (transition.expired) return {
2032
+ settled: true,
2033
+ result: void 0
2034
+ };
2035
+ return {
2036
+ settled: true,
2037
+ result: await store({
2038
+ status: "failed",
2039
+ code: "KNOWLEDGE_TARGET_MISMATCH",
2040
+ message: "The activation target does not match the measured knowledge candidate."
2041
+ })
2042
+ };
2043
+ }
2044
+ const durable = await loadKnowledgeImprovementActivationResult({
2045
+ root: options.root,
2046
+ candidate,
2047
+ activation: transition.activation,
2048
+ identity: options.identity
2049
+ });
2050
+ if (durable) return {
2051
+ settled: true,
2052
+ result: await options.results.putIfAbsent(durable)
2053
+ };
2054
+ if (transition.expired) return {
2055
+ settled: true,
2056
+ result: void 0
2057
+ };
2058
+ return {
2059
+ settled: false,
2060
+ target,
2061
+ candidate,
2062
+ expectedDigest,
2063
+ desiredDigest,
2064
+ create
2065
+ };
2066
+ }
2067
+ function supportedKnowledgeTarget(transition, identity) {
2068
+ if (transition.targets.length !== 1) return void 0;
2069
+ const target = transition.targets[0];
2070
+ return target.surface === "knowledge" && target.identity === identity ? target : void 0;
2071
+ }
2072
+ function knowledgeMutationOutcome(receipt, inspected, intent) {
2073
+ const expectedTarget = intent === "activate-candidate" ? "candidate" : "baseline";
2074
+ const beforeDigest = prefixedDigest(receipt.beforeHash);
2075
+ const afterDigest = prefixedDigest(receipt.afterHash);
2076
+ if (receipt.target !== expectedTarget || receipt.changed !== (beforeDigest !== afterDigest) || !receipt.changed && (receipt.transactionId !== null || receipt.recovered)) throw new Error("knowledge mutation returned inconsistent state evidence");
2077
+ if (receipt.changed) {
2078
+ if (receipt.transactionId === null || beforeDigest !== inspected.expectedDigest || afterDigest !== inspected.desiredDigest) throw new Error("knowledge activation did not apply its authorized content transition");
2079
+ return {
2080
+ status: "applied",
2081
+ transactionId: receipt.transactionId,
2082
+ targets: [{
2083
+ surface: inspected.target.surface,
2084
+ identity: inspected.target.identity,
2085
+ beforeDigest,
2086
+ afterDigest
2087
+ }]
2088
+ };
2089
+ }
2090
+ if (afterDigest === inspected.desiredDigest) return {
2091
+ status: "already-applied",
2092
+ targets: [targetState(inspected.target, afterDigest)]
2093
+ };
2094
+ if (afterDigest === inspected.expectedDigest) throw new Error("knowledge activation did not attempt its authorized content transition");
2095
+ return {
2096
+ status: "conflict",
2097
+ targets: [targetState(inspected.target, afterDigest)]
2098
+ };
2099
+ }
2100
+ function targetState(target, currentDigest) {
2101
+ return {
2102
+ surface: target.surface,
2103
+ identity: target.identity,
2104
+ currentDigest
2105
+ };
2106
+ }
2107
+ function prefixedDigest(hash) {
2108
+ return sha256DigestSchema.parse(`sha256:${hash}`);
2109
+ }
2110
+ function completionTime(transition, now) {
2111
+ const completedAt = (now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
2112
+ return Date.parse(completedAt) < Date.parse(transition.attemptedAt) ? transition.attemptedAt : completedAt;
2113
+ }
2114
+ //#endregion
2115
+ //#region src/knowledge/supervised-update.ts
2116
+ /** Standing prompt for a supervisor that grows a shared knowledge base through spawned researchers. */
2117
+ const RESEARCH_SUPERVISOR_SYSTEM_PROMPT = [
2118
+ "You are a research supervisor. You do not answer the research question yourself.",
2119
+ "You create and manage researcher workers that improve one shared knowledge base until it is ready.",
2120
+ "",
2121
+ "Each round:",
2122
+ "1. Read the goal and the gaps the readiness check still reports.",
2123
+ "2. Decompose the open work into independent sub-topics.",
2124
+ "3. Spawn one researcher per sub-topic; split by sub-topic, never duplicate work.",
2125
+ "4. Wait for researchers to settle, steer or re-spawn thin sub-topics, then stop when the check passes.",
2126
+ "",
2127
+ "Conserve the shared budget. Prefer a small number of well-scoped researchers over a large fanout that re-covers the same ground."
2128
+ ].join("\n");
2129
+ /** Build the completion check a supervised KB update uses to stop only when the KB is ready. */
2130
+ function knowledgeReadinessDeliverable(options) {
2131
+ return {
2132
+ describe: `knowledge base at ${options.root} is ready for: ${options.goal}`,
2133
+ async check() {
2134
+ const result = await options.readiness({
2135
+ root: options.root,
2136
+ goal: options.goal,
2137
+ readinessSpecs: options.readinessSpecs,
2138
+ readinessTaskId: options.readinessTaskId,
2139
+ readiness: options.readinessOptions
2140
+ });
2141
+ return typeof result === "boolean" ? result : result.ready;
2142
+ }
2143
+ };
2144
+ }
2145
+ /** Create an `improveKnowledgeBase` update callback backed by runtime supervision. */
2146
+ function createSupervisedKnowledgeUpdater(options) {
2147
+ return (input) => runSupervisedKnowledgeUpdate({
2148
+ ...options,
2149
+ root: input.candidateRoot ?? input.root ?? options.root,
2150
+ goal: input.goal ?? options.goal,
2151
+ findings: input.findings ?? options.findings,
2152
+ metadata: {
2153
+ ...options.metadata,
2154
+ ...input.metadata
2155
+ }
2156
+ });
2157
+ }
2158
+ /** Run a runtime supervisor that updates one candidate knowledge base and stops on readiness. */
2159
+ async function runSupervisedKnowledgeUpdate(options) {
2160
+ const exactSupervisor = agentProfileSchema.parse(options.supervisorProfile);
2161
+ assertExecutableAgentProfile(exactSupervisor, "runSupervisedKnowledgeUpdate");
2162
+ const supervised = await (options.runSupervised ?? supervise)(exactSupervisor, formatSupervisedKnowledgeTask(options), {
2163
+ ...options.superviseOptions,
2164
+ budget: options.budget,
2165
+ backend: options.backend,
2166
+ deliverable: knowledgeReadinessDeliverable(options),
2167
+ makeWorkerAgent: options.makeWorkerAgent,
2168
+ allowedModels: options.allowedModels
2169
+ });
2170
+ return {
2171
+ applied: supervised.kind === "winner",
2172
+ summary: supervised.kind === "winner" ? "research supervisor completed and the knowledge base passed readiness" : `research supervisor stopped without a ready knowledge base: ${supervised.reason}`,
2173
+ supervised,
2174
+ metadata: {
2175
+ supervised: true,
2176
+ root: options.root,
2177
+ goal: options.goal,
2178
+ result: supervised.kind
2179
+ }
2180
+ };
2181
+ }
2182
+ /** Format the supervisor task with the KB root, readiness requirements, current findings, and metadata. */
2183
+ function formatSupervisedKnowledgeTask(options) {
2184
+ return `${[
2185
+ `Goal: ${options.goal}`,
2186
+ `Knowledge base root: ${options.root}`,
2187
+ options.readinessTaskId ? `Readiness task id: ${options.readinessTaskId}` : void 0,
2188
+ options.readinessSpecs?.length ? `Readiness specs:\n${JSON.stringify(options.readinessSpecs, null, 2)}` : void 0,
2189
+ options.findings?.length ? `Current findings:\n${JSON.stringify(options.findings, null, 2)}` : void 0,
2190
+ options.metadata && Object.keys(options.metadata).length > 0 ? `Metadata:\n${JSON.stringify(options.metadata, null, 2)}` : void 0
2191
+ ].filter((section) => Boolean(section)).join("\n\n")}\n\nUpdate files under the knowledge base root only. Stop when the readiness check passes.`;
2192
+ }
2193
+ //#endregion
2194
+ //#region src/knowledge/improvement-job.ts
2195
+ /** Build the default readiness check backed by `@tangle-network/agent-knowledge` validation and scoring. */
2196
+ function createAgentKnowledgeReadinessCheck(options) {
2197
+ return async (input) => {
2198
+ const readiness = await evaluateKnowledgeBaseReadiness({
2199
+ root: input.root,
2200
+ goal: input.goal ?? options.goal,
2201
+ readinessSpecs: input.readinessSpecs ?? options.readinessSpecs,
2202
+ readinessTaskId: input.readinessTaskId ?? options.readinessTaskId,
2203
+ readiness: input.readiness ?? options.readiness,
2204
+ strict: options.strict,
2205
+ kbQuality: options.kbQuality
2206
+ });
2207
+ return {
2208
+ ready: readiness.ready,
2209
+ summary: readiness.summary,
2210
+ metadata: {
2211
+ dimensions: readiness.dimensions,
2212
+ validationOk: readiness.validation.ok,
2213
+ kbQualityOk: readiness.kbQuality.ok,
2214
+ blockingMissing: readiness.readiness?.report.blockingMissingRequirements.length ?? 0
2215
+ }
2216
+ };
2217
+ };
2218
+ }
2219
+ /** Produce a frozen KB candidate while leaving live knowledge content unchanged. */
2220
+ async function runKnowledgeImprovementJob(options) {
2221
+ const { allowedModels, backend, budget, candidateArtifacts, makeWorkerAgent, onMeasurement, readinessCheck, runSupervised, supervisorProfile, superviseOptions, ...knowledgeOptions } = options;
2222
+ const startedAtMs = Date.now();
2223
+ const startedAt = new Date(startedAtMs).toISOString();
2224
+ const supervisedSpent = emptySpent();
2225
+ let updateCalls = 0;
2226
+ let updateDurationMs = 0;
2227
+ const readiness = readinessCheck ?? createAgentKnowledgeReadinessCheck(options);
2228
+ const updateKnowledge = createSupervisedKnowledgeUpdater({
2229
+ root: options.root,
2230
+ goal: options.goal,
2231
+ readiness,
2232
+ readinessSpecs: options.readinessSpecs,
2233
+ readinessTaskId: options.readinessTaskId,
2234
+ readinessOptions: options.readiness,
2235
+ budget,
2236
+ backend,
2237
+ makeWorkerAgent,
2238
+ supervisorProfile,
2239
+ superviseOptions,
2240
+ allowedModels,
2241
+ runSupervised
2242
+ });
2243
+ const instrumentedUpdateKnowledge = async (input) => {
2244
+ const updateStartedAt = Date.now();
2245
+ updateCalls += 1;
2246
+ const result = await updateKnowledge(input);
2247
+ updateDurationMs += Date.now() - updateStartedAt;
2248
+ addSpent(supervisedSpent, result.supervised);
2249
+ return result;
2250
+ };
2251
+ const resolvedImprovement = await improveKnowledgeBase({
2252
+ ...knowledgeOptions,
2253
+ updateKnowledge: instrumentedUpdateKnowledge
2254
+ });
2255
+ let knowledge;
2256
+ if (resolvedImprovement.candidate?.status === "candidate-ready" || resolvedImprovement.candidate?.status === "promoted") knowledge = await freezeKnowledgeCandidatePair(options.root, resolvedImprovement, candidateArtifacts, knowledgeOptions.signal);
2257
+ const finishedAtMs = Date.now();
2258
+ const measurement = {
2259
+ startedAt,
2260
+ finishedAt: new Date(finishedAtMs).toISOString(),
2261
+ durationMs: finishedAtMs - startedAtMs,
2262
+ updateCalls,
2263
+ updateDurationMs,
2264
+ supervisedSpent
2265
+ };
2266
+ await onMeasurement?.(measurement);
2267
+ return {
2268
+ improvement: resolvedImprovement,
2269
+ ...knowledge ? { knowledge } : {},
2270
+ measurement,
2271
+ blocked: resolvedImprovement.blocked
2272
+ };
2273
+ }
2274
+ /** Attach both frozen knowledge inputs to one otherwise-identical bundle pair. */
2275
+ function buildKnowledgeImprovementExperimentBundles(bundle, knowledge) {
2276
+ const input = omitTopLevelDigest(bundle);
2277
+ const withSnapshot = (snapshot) => agentCandidateKnowledgeSchema.parse({
2278
+ candidate: knowledge.reference,
2279
+ snapshot,
2280
+ evaluation: knowledge.evaluation
2281
+ });
2282
+ return Object.freeze({
2283
+ baseline: sealAgentCandidateBundle({
2284
+ ...input,
2285
+ knowledge: withSnapshot(knowledge.baseline)
2286
+ }),
2287
+ candidate: sealAgentCandidateBundle({
2288
+ ...input,
2289
+ knowledge: withSnapshot(knowledge.candidate)
2290
+ })
2291
+ });
2292
+ }
2293
+ async function freezeKnowledgeCandidatePair(root, improvement, artifacts, signal) {
2294
+ const candidate = knowledgeImprovementCandidateRef(improvement);
2295
+ const candidateRef = toAgentCandidateKnowledgeRef(candidate);
2296
+ return withKnowledgeImprovementComparison({
2297
+ root,
2298
+ candidate
2299
+ }, async (comparison) => {
2300
+ const freeze = async (target) => {
2301
+ const executionId = `knowledge-${candidate.candidateId}-${target}`;
2302
+ return (await captureAgentCandidateWorkspace(await realpath(comparison[target].root), { ...artifacts ? { artifactPersistence: {
2303
+ executionId,
2304
+ outputArtifacts: artifacts,
2305
+ ...signal ? { signal } : {}
2306
+ } } : {} })).snapshot;
2307
+ };
2308
+ const evaluation = await captureKnowledgeEvidence(canonicalCandidateBytes({
2309
+ kind: "agent-knowledge-candidate-evaluation",
2310
+ candidate: candidateRef,
2311
+ metric: comparison.evaluation
2312
+ }), "knowledge-evaluation", `knowledge-${candidate.candidateId}`, artifacts, signal);
2313
+ const baseline = await freeze("baseline");
2314
+ const proposed = await freeze("candidate");
2315
+ return Object.freeze({
2316
+ reference: candidateRef,
2317
+ evaluation,
2318
+ baseline,
2319
+ candidate: proposed
2320
+ });
2321
+ });
2322
+ }
2323
+ async function captureKnowledgeEvidence(bytes, purpose, executionId, artifacts, signal) {
2324
+ if (!artifacts) return embeddedCandidateArtifact(bytes);
2325
+ return persistCandidateOutputArtifact(artifacts, {
2326
+ executionId,
2327
+ purpose,
2328
+ bytes,
2329
+ ...signal ? { signal } : {}
2330
+ });
2331
+ }
2332
+ /** The starting accumulator, before any supervised run has been folded in. Zero iterations means
2333
+ * nothing ran, so a zero dollar total IS the measured amount; `addSpent` lowers `usdKnown` as
2334
+ * soon as it folds a run whose own dollars were not proven. */
2335
+ function emptySpent() {
2336
+ return {
2337
+ iterations: 0,
2338
+ inputTokens: 0,
2339
+ outputTokens: 0,
2340
+ usdKnown: true,
2341
+ usd: 0,
2342
+ ms: 0
2343
+ };
2344
+ }
2345
+ function addSpent(target, result) {
2346
+ const spent = result.spentTotal;
2347
+ target.iterations += spent.iterations;
2348
+ target.inputTokens += spent.tokens.input;
2349
+ target.outputTokens += spent.tokens.output;
2350
+ target.usdKnown = target.usdKnown && spent.usdKnown !== false;
2351
+ target.usd += spent.usd;
2352
+ target.ms += spent.ms;
2353
+ }
2354
+ //#endregion
592
2355
  //#region src/model-resolution.ts
593
2356
  /** Default Tangle Router base URL used when no env override is set. */
594
2357
  const DEFAULT_ROUTER_BASE_URL = "https://router.tangle.tools";
@@ -1371,6 +3134,6 @@ function stripNewlines(value) {
1371
3134
  return value.replace(/[\r\n]/g, " ");
1372
3135
  }
1373
3136
  //#endregion
1374
- export { AGENT_CANDIDATE_EXECUTION_SUPPORT, AgentEvalError, BackendTransportError, CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV, CANDIDATE_KNOWLEDGE_ROOT_ENV, CANDIDATE_TRACE_ENV, CANDIDATE_TRACE_TAGS, CircuitBreakerState, CircuitOpenError, ConfigError, DEFAULT_MAX_DEPTH, DEFAULT_ROUTER_BASE_URL, DELEGATED_LOOP_MODES, DeadlineExceededError, FORWARD_HEADERS, FileAgentCandidateExecutionClaimStore, FileConversationJournal, INTELLIGENCE_WIRE_VERSION, InMemoryAgentCandidateExecutionClaimStore, InMemoryConversationJournal, InMemoryRuntimeSessionStore, JudgeError, NotFoundError, OfficialOptimizerUnavailableError, PROMPT_INSTRUCTION_COMPONENT_PREFIX, PlannerError, RESEARCH_SUPERVISOR_SYSTEM_PROMPT, ROLLOUT_POLICY_EXTENSION, RetainedInteractiveAdmissionError, RetainedInteractiveBindingError, RetainedRunAdmissionError, RetainedRunDispatchBindingError, RuntimeRunStateError, SqlConversationJournal, ValidationError, agentCandidateProfileAsAgentProfile, agenticGenerator, applyExactAgentProfileDiff, applyRolloutPolicyToProfile, applyRunRecordDefaults, assertCandidateProfileBinding, auditLoopRunner, buildAgentCandidateBundle, buildForwardHeaders, buildKnowledgeImprovementExperimentBundles, buildLoopOtelSpans, buildLoopSpanNodes, buildRuntimeEventOtelSpans, candidateExecutionClaim, candidateKnowledgeExecutionPaths, candidateWorkspaceManifest, captureAgentCandidateWorkspace, captureAgentCandidateWorkspaceFiles, cleanModelId, commandVerifier, composeRuntimeHooks, computeBackoff, createAgentCandidateWorkspacePort, createAgentKnowledgeReadinessCheck, createConversationBackend, createIterableBackend, createKnowledgeImprovementActivationExecutor, createOpenInferenceFileExporter, createOtelExporter, createProfileExecutionBackend, createProfileImprovementHarness, createProtectedAgentCandidateModelPort, createRuntimeEventCollector, createRuntimeStreamEventCollector, createSandboxPromptBackend, createSupervisedKnowledgeUpdater, d1ToSqlAdapter, decideKnowledgeReadiness, defaultBuildPrompt, defaultIsRetryable, defineConversation, defineRuntimeHooks, describeWorkspaceTree, disposePreparedAgentCandidateExecution, exactProcessProviderAsCandidateExecutor, executePreparedAgentCandidate, exportEvalRuns, findingLines, formatSupervisedKnowledgeTask, freezeGenericAgentCandidateProfile, generateSpanId, getModels, improve, isDelegatedLoopMode, isDepthExceeded, knowledgeReadinessDeliverable, loopEventToOtelSpan, makePerAttemptSignal, mcpBuildPrompt, mcpServeVerifier, mcpToolsForRuntimeMcp, mcpToolsForRuntimeMcpSubset, normalizeRolloutPolicy, notifyRuntimeDecisionPoint, notifyRuntimeHookEvent, officialGepa, officialSkillOpt, omitUndefinedObjectFields, optimizerMethod, padSpanId, padTraceId, parseExactAgentProfile, parseExactAgentProfileDiff, parseExactCandidateProfile, parseLoopRunnerArgv, parseRolloutPolicy, persistCandidateOutputArtifact, prepareAgentCandidateExecution, promptInstructionsProfileComponents, rawTraceDistiller, readDepth, readinessServerSentEvent, recoverExpiredAgentCandidateExecution, reflectiveGenerator, researchLoopRunner, resolveChatModel, resolveRouterBaseUrl, runAgentTask, runAgentTaskStream, runConversation, runConversationStream, runDelegatedLoop, runKnowledgeImprovementJob, runLoopRunnerCli, runPersonaConversation, runPersonaDispatch, runProtectedAgentCandidateModelGrant, runSupervisedKnowledgeUpdate, runtimeStreamServerSentEvent, sanitizeAgentRuntimeEvent, sanitizeKnowledgeReadinessReport, sanitizeRuntimeStreamEvent, scanMaterializedWorkspaceManifest, sealAgentCandidateBundle, seedWorkspaceTree, serializeRolloutPolicy, sleep, slugifySpeaker, startRuntimeRun, strategyAuthorMethod, structuralRolloutPolicyFromProfile, toOtelAttributes, toolBuildPrompt, turnId, validateChatModelId, verifyAgentCandidateBundle, verifyMaterializedWorkspace, worktreeLoopRunner };
3137
+ export { AgentEvalError, BackendTransportError, ConfigError, DEFAULT_ROUTER_BASE_URL, DELEGATED_LOOP_MODES, FileConversationJournal, INTELLIGENCE_WIRE_VERSION, InMemoryConversationJournal, InMemoryRuntimeSessionStore, JudgeError, NotFoundError, OfficialOptimizerUnavailableError, PROMPT_INSTRUCTION_COMPONENT_PREFIX, PlannerError, RESEARCH_SUPERVISOR_SYSTEM_PROMPT, ROLLOUT_POLICY_EXTENSION, RetainedInteractiveAdmissionError, RetainedInteractiveBindingError, RetainedRunAdmissionError, RetainedRunDispatchBindingError, RuntimeRunStateError, SqlConversationJournal, ValidationError, agenticGenerator, applyRolloutPolicyToProfile, applyRunRecordDefaults, auditLoopRunner, buildKnowledgeImprovementExperimentBundles, buildLoopOtelSpans, buildLoopSpanNodes, buildRuntimeEventOtelSpans, cleanModelId, commandVerifier, composeRuntimeHooks, createAgentKnowledgeReadinessCheck, createConversationBackend, createIterableBackend, createKnowledgeImprovementActivationExecutor, createOpenInferenceFileExporter, createOtelExporter, createProfileExecutionBackend, createProfileImprovementHarness, createRuntimeEventCollector, createRuntimeStreamEventCollector, createSandboxPromptBackend, createSupervisedKnowledgeUpdater, d1ToSqlAdapter, decideKnowledgeReadiness, defaultBuildPrompt, defineConversation, defineRuntimeHooks, exportEvalRuns, findingLines, formatSupervisedKnowledgeTask, generateSpanId, getModels, improve, isDelegatedLoopMode, knowledgeReadinessDeliverable, loopEventToOtelSpan, mcpBuildPrompt, mcpServeVerifier, mcpToolsForRuntimeMcp, mcpToolsForRuntimeMcpSubset, normalizeRolloutPolicy, notifyRuntimeDecisionPoint, notifyRuntimeHookEvent, officialGepa, officialSkillOpt, optimizerMethod, padSpanId, padTraceId, parseLoopRunnerArgv, parseRolloutPolicy, promptInstructionsProfileComponents, rawTraceDistiller, readinessServerSentEvent, reflectiveGenerator, researchLoopRunner, resolveChatModel, resolveRouterBaseUrl, runAgentTask, runAgentTaskStream, runConversation, runConversationStream, runDelegatedLoop, runKnowledgeImprovementJob, runLoopRunnerCli, runPersonaConversation, runPersonaDispatch, runSupervisedKnowledgeUpdate, runtimeStreamServerSentEvent, sanitizeAgentRuntimeEvent, sanitizeKnowledgeReadinessReport, sanitizeRuntimeStreamEvent, serializeRolloutPolicy, startRuntimeRun, strategyAuthorMethod, structuralRolloutPolicyFromProfile, toOtelAttributes, toolBuildPrompt, validateChatModelId, worktreeLoopRunner };
1375
3138
 
1376
3139
  //# sourceMappingURL=index.js.map