@tangle-network/agent-runtime 0.70.0 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +4 -4
- package/dist/agent.js +5 -3
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop.d.ts +51 -0
- package/dist/analyst-loop.js +11 -0
- package/dist/analyst-loop.js.map +1 -0
- package/dist/{chunk-EDCVUZZC.js → chunk-4KGQHS7U.js} +2 -2
- package/dist/{chunk-QXWGSDAQ.js → chunk-5ISW5JUF.js} +287 -286
- package/dist/chunk-5ISW5JUF.js.map +1 -0
- package/dist/{chunk-ZNQVMMR5.js → chunk-74UAWZXE.js} +4 -4
- package/dist/{chunk-L5ZFBVT6.js → chunk-INXDNX2W.js} +2 -2
- package/dist/chunk-K3RM4MPM.js +214 -0
- package/dist/chunk-K3RM4MPM.js.map +1 -0
- package/dist/chunk-P5OKDSLB.js +580 -0
- package/dist/chunk-P5OKDSLB.js.map +1 -0
- package/dist/chunk-VLF5RHEQ.js +143 -0
- package/dist/chunk-VLF5RHEQ.js.map +1 -0
- package/dist/{chunk-G3RGMA7C.js → chunk-VMNEQHJR.js} +6 -1
- package/dist/chunk-VMNEQHJR.js.map +1 -0
- package/dist/{coordination-C7WxwHXq.d.ts → coordination-BPQmuwv8.d.ts} +280 -2
- package/dist/{delegates-DqAgo32T.d.ts → delegates-CsXJPZDH.d.ts} +48 -2
- package/dist/{improvement-adapter-BVuMragr.d.ts → improvement-adapter-CioiEE2z.d.ts} +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +20 -17
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +1 -1
- package/dist/intelligence.js +1 -1
- package/dist/{loop-runner-bin-B0NeLTRd.d.ts → loop-runner-bin-DLM_bVQO.d.ts} +4 -4
- package/dist/loop-runner-bin.d.ts +5 -5
- package/dist/loop-runner-bin.js +6 -4
- package/dist/loops.d.ts +121 -242
- package/dist/loops.js +10 -4
- package/dist/mcp/bin.js +8 -7
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +7 -6
- package/dist/mcp/index.js +23 -12
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-DPx9Gzvn.d.ts → openai-tools-kdCS-T12.d.ts} +1 -1
- package/dist/platform.d.ts +255 -0
- package/dist/platform.js +229 -0
- package/dist/platform.js.map +1 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/{types-DJu6TBGp.d.ts → types-BC3bZpH0.d.ts} +15 -1
- package/dist/{types-BYa2ZOAx.d.ts → types-CdnEAE3U.d.ts} +1 -1
- package/dist/{worktree-fanout-gNfl0Byj.d.ts → worktree-fanout-CK2ypmEm.d.ts} +3 -45
- package/package.json +11 -1
- package/dist/chunk-BYZCXQHF.js +0 -474
- package/dist/chunk-BYZCXQHF.js.map +0 -1
- package/dist/chunk-G3RGMA7C.js.map +0 -1
- package/dist/chunk-QXWGSDAQ.js.map +0 -1
- /package/dist/{chunk-EDCVUZZC.js.map → chunk-4KGQHS7U.js.map} +0 -0
- /package/dist/{chunk-ZNQVMMR5.js.map → chunk-74UAWZXE.js.map} +0 -0
- /package/dist/{chunk-L5ZFBVT6.js.map → chunk-INXDNX2W.js.map} +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
import { AgentEvalError } from "@tangle-network/agent-eval";
|
|
3
|
+
import {
|
|
4
|
+
AgentEvalError as AgentEvalError2,
|
|
5
|
+
ConfigError,
|
|
6
|
+
JudgeError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
ValidationError
|
|
9
|
+
} from "@tangle-network/agent-eval";
|
|
10
|
+
var SessionMismatchError = class extends AgentEvalError {
|
|
11
|
+
sessionBackend;
|
|
12
|
+
requestedBackend;
|
|
13
|
+
constructor(sessionBackend, requestedBackend, options) {
|
|
14
|
+
super(
|
|
15
|
+
"validation",
|
|
16
|
+
`Cannot resume ${sessionBackend} session with ${requestedBackend} backend`,
|
|
17
|
+
options
|
|
18
|
+
);
|
|
19
|
+
this.sessionBackend = sessionBackend;
|
|
20
|
+
this.requestedBackend = requestedBackend;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var BackendTransportError = class extends AgentEvalError {
|
|
24
|
+
backend;
|
|
25
|
+
status;
|
|
26
|
+
/**
|
|
27
|
+
* Truncated upstream response body (≤2 KiB) when available. Diagnostic
|
|
28
|
+
* only — surfaces in `backend_error.error.body` and `final.error.body`
|
|
29
|
+
* so operators can see "free_tier_limit", "invalid_api_key", etc. without
|
|
30
|
+
* cracking the log line open.
|
|
31
|
+
*/
|
|
32
|
+
body;
|
|
33
|
+
constructor(backend, message, options) {
|
|
34
|
+
super("config", message, options);
|
|
35
|
+
this.backend = backend;
|
|
36
|
+
this.status = options?.status;
|
|
37
|
+
this.body = options?.body;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var RuntimeRunStateError = class extends AgentEvalError {
|
|
41
|
+
constructor(message, options) {
|
|
42
|
+
super("validation", message, options);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var PlannerError = class extends AgentEvalError {
|
|
46
|
+
constructor(message, options) {
|
|
47
|
+
super("validation", message, options);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var AnalystError = class extends AgentEvalError {
|
|
51
|
+
constructor(message, options) {
|
|
52
|
+
super("validation", message, options);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/runtime/sandbox-events.ts
|
|
57
|
+
function extractLlmCallEvent(event, agentRunName) {
|
|
58
|
+
if (!event || typeof event !== "object") return void 0;
|
|
59
|
+
const type = String(event.type ?? "");
|
|
60
|
+
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
61
|
+
if (type === "llm_call" || type === "cost.usage" || type === "usage") {
|
|
62
|
+
return buildLlmCall(data, agentRunName);
|
|
63
|
+
}
|
|
64
|
+
if (type === "message.completed" || type === "result" || type === "final") {
|
|
65
|
+
const usage = data.usage;
|
|
66
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
67
|
+
return buildLlmCall({ ...usage, model: data.model ?? usage.model }, agentRunName);
|
|
68
|
+
}
|
|
69
|
+
if (type === "done") {
|
|
70
|
+
const usage = data.tokenUsage;
|
|
71
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
72
|
+
const out = pickFiniteNumber(usage, ["outputTokens", "completion_tokens", "tokensOut"]);
|
|
73
|
+
const reasoning = pickFiniteNumber(usage, ["reasoningTokens"]);
|
|
74
|
+
const mergedOut = out !== void 0 || reasoning !== void 0 ? (out ?? 0) + (reasoning ?? 0) : void 0;
|
|
75
|
+
return buildLlmCall(
|
|
76
|
+
{
|
|
77
|
+
inputTokens: usage.inputTokens,
|
|
78
|
+
outputTokens: mergedOut,
|
|
79
|
+
totalCostUsd: data.totalCostUsd,
|
|
80
|
+
model: data.model ?? usage.model
|
|
81
|
+
},
|
|
82
|
+
agentRunName
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
function buildLlmCall(data, agentRunName) {
|
|
88
|
+
const tokensIn = pickFiniteNumber(data, ["tokensIn", "inputTokens", "prompt_tokens"]);
|
|
89
|
+
const tokensOut = pickFiniteNumber(data, ["tokensOut", "outputTokens", "completion_tokens"]);
|
|
90
|
+
const costUsd = pickFiniteNumber(data, ["costUsd", "totalCostUsd", "cost_usd", "cost"]);
|
|
91
|
+
if (tokensIn === void 0 && tokensOut === void 0 && costUsd === void 0) {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
const model = typeof data.model === "string" && data.model.length > 0 ? data.model : agentRunName;
|
|
95
|
+
const event = {
|
|
96
|
+
type: "llm_call",
|
|
97
|
+
model
|
|
98
|
+
};
|
|
99
|
+
if (tokensIn !== void 0) event.tokensIn = tokensIn;
|
|
100
|
+
if (tokensOut !== void 0) event.tokensOut = tokensOut;
|
|
101
|
+
if (costUsd !== void 0) event.costUsd = costUsd;
|
|
102
|
+
return event;
|
|
103
|
+
}
|
|
104
|
+
function pickFiniteNumber(data, keys) {
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const value = data[key];
|
|
107
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
108
|
+
}
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
function mapSandboxEvent(event, opts = {}) {
|
|
112
|
+
if (!event || typeof event !== "object") return void 0;
|
|
113
|
+
const type = String(event.type ?? "");
|
|
114
|
+
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
115
|
+
if (type === "message.part.updated") {
|
|
116
|
+
const part = data.part && typeof data.part === "object" ? data.part : {};
|
|
117
|
+
const partType = String(part.type ?? "");
|
|
118
|
+
const delta = typeof data.delta === "string" ? data.delta : void 0;
|
|
119
|
+
const text = delta ?? (typeof part.text === "string" ? part.text : void 0);
|
|
120
|
+
if (text === void 0) return void 0;
|
|
121
|
+
if (partType === "text") return { type: "text_delta", text };
|
|
122
|
+
if (partType === "reasoning" || partType === "thinking")
|
|
123
|
+
return { type: "reasoning_delta", text };
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
return extractLlmCallEvent(event, opts.agentRunName ?? "agent");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
SessionMismatchError,
|
|
131
|
+
BackendTransportError,
|
|
132
|
+
RuntimeRunStateError,
|
|
133
|
+
PlannerError,
|
|
134
|
+
AnalystError,
|
|
135
|
+
AgentEvalError2 as AgentEvalError,
|
|
136
|
+
ConfigError,
|
|
137
|
+
JudgeError,
|
|
138
|
+
NotFoundError,
|
|
139
|
+
ValidationError,
|
|
140
|
+
extractLlmCallEvent,
|
|
141
|
+
mapSandboxEvent
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=chunk-VLF5RHEQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/runtime/sandbox-events.ts"],"sourcesContent":["/**\n * @stable\n *\n * Error taxonomy for `@tangle-network/agent-runtime`.\n *\n * Public contract: every error this package throws as part of its consumer-\n * facing API either extends `AgentEvalError` (re-exported here for ergonomic\n * `instanceof` checks at the runtime boundary) or extends one of the\n * runtime-specific subclasses below.\n *\n * Internal invariant guards (`throw new Error('this should never happen')`)\n * remain plain `Error` — they are programmer-mistake assertions, not\n * consumer-catchable contract failures.\n *\n * Subclassing strategy: where a runtime-specific failure maps cleanly to an\n * agent-eval code (validation, config, not_found), we re-use the agent-eval\n * subclass. Runtime-only failure modes (session resume against the wrong\n * backend, backend transport errors) get fresh subclasses that still carry an\n * `AgentEvalErrorCode` so cross-package handlers can pattern-match without\n * importing the runtime.\n */\n\nimport { AgentEvalError } from '@tangle-network/agent-eval'\n\nexport {\n AgentEvalError,\n type AgentEvalErrorCode,\n ConfigError,\n JudgeError,\n NotFoundError,\n ValidationError,\n} from '@tangle-network/agent-eval'\n\n/**\n * @stable\n *\n * Caller asked to resume a session against a backend whose `kind` does not\n * match the session's recorded backend. This is a routing bug — the same\n * session id was reused across two different backend implementations — and\n * is not retryable without picking the right backend.\n */\nexport class SessionMismatchError extends AgentEvalError {\n readonly sessionBackend: string\n readonly requestedBackend: string\n\n constructor(sessionBackend: string, requestedBackend: string, options?: { cause?: unknown }) {\n super(\n 'validation',\n `Cannot resume ${sessionBackend} session with ${requestedBackend} backend`,\n options,\n )\n this.sessionBackend = sessionBackend\n this.requestedBackend = requestedBackend\n }\n}\n\n/**\n * @stable\n *\n * A backend transport call (HTTP, gRPC, sidecar IPC) failed with a non-success\n * status. Distinct from `JudgeError` (which is structural / unrecoverable)\n * because backend failures are sometimes retryable and consumers may want to\n * branch on the upstream status code.\n */\nexport class BackendTransportError extends AgentEvalError {\n readonly backend: string\n readonly status?: number\n /**\n * Truncated upstream response body (≤2 KiB) when available. Diagnostic\n * only — surfaces in `backend_error.error.body` and `final.error.body`\n * so operators can see \"free_tier_limit\", \"invalid_api_key\", etc. without\n * cracking the log line open.\n */\n readonly body?: string\n\n constructor(\n backend: string,\n message: string,\n options?: { cause?: unknown; status?: number; body?: string },\n ) {\n super('config', message, options)\n this.backend = backend\n this.status = options?.status\n this.body = options?.body\n }\n}\n\n/**\n * @stable\n *\n * A runtime-run lifecycle method was called in an order the state machine does\n * not allow: `persist()` before `complete()`, `complete()` twice, etc.\n */\nexport class RuntimeRunStateError extends AgentEvalError {\n constructor(message: string, options?: { cause?: unknown }) {\n super('validation', message, options)\n }\n}\n\n/**\n * @stable\n *\n * The dynamic-loop planner returned an unusable topology move — the LLM emitted\n * no parseable envelope, an unknown `kind`, or a structurally-invalid move\n * (e.g. a fanout with zero tasks). This is a structural failure of the\n * agent-authored topology, not a config mistake: the planner ran but its output\n * cannot drive the kernel. Carries `validation` so cross-package handlers can\n * pattern-match without importing the runtime. Fail loud — never substitute a\n * default move, or the loop silently runs a topology nobody chose.\n */\nexport class PlannerError extends AgentEvalError {\n constructor(message: string, options?: { cause?: unknown }) {\n super('validation', message, options)\n }\n}\n\n/**\n * The analyst loop could not read or run over a round's trace — e.g. an empty round\n * (no iterations to analyze) or a malformed trace projection. Fail loud: a silent empty\n * store would mask a broken capture path and the driver would steer on nothing.\n */\nexport class AnalystError extends AgentEvalError {\n constructor(message: string, options?: { cause?: unknown }) {\n super('validation', message, options)\n }\n}\n","/**\n * Sandbox-event → runtime-event mapping.\n *\n * The sandbox SDK emits a polymorphic `SandboxEvent = { type, data, id? }`\n * whose `type` vocabulary is backend-determined (opencode, etc.) rather than\n * enumerated by the SDK. Two consumers project it:\n * - the loop kernel's cost ledger (`extractLlmCallEvent`) — sums usage off\n * every cost-bearing event, regardless of stream shape;\n * - the `AgentRuntime.act` streaming contract (`mapSandboxEvent`) — projects\n * incremental events to the `RuntimeStreamEvent` chat-UX vocabulary.\n *\n * Both live here so the empirically-observed `type` vocabulary has one home.\n */\n\nimport type { SandboxEvent } from '@tangle-network/sandbox'\nimport type { RuntimeStreamEvent } from '../types'\n\n/**\n * Extract a `RuntimeStreamEvent`-shaped `llm_call` from a sandbox event when\n * the event carries usage/cost data. Returns `undefined` for non-cost events\n * so the kernel can iterate the full stream without branching.\n *\n * Canonical cost-carrying types observed in the wild:\n * - `llm_call` — `data: { model, tokensIn, tokensOut, costUsd, ... }`\n * - `message.completed` / `result` — `data: { usage: { inputTokens,\n * outputTokens, totalCostUsd? } }`\n * - `cost.usage` / `usage` — same shape under a dedicated type\n *\n * Numeric coercion is strict: `Number.isFinite` gates every accumulator write\n * so a sentinel `NaN` from a misbehaving backend cannot poison the ledger.\n */\nexport function extractLlmCallEvent(\n event: SandboxEvent,\n agentRunName: string,\n): (RuntimeStreamEvent & { type: 'llm_call' }) | undefined {\n if (!event || typeof event !== 'object') return undefined\n const type = String(event.type ?? '')\n const data =\n event.data && typeof event.data === 'object'\n ? (event.data as Record<string, unknown>)\n : ({} as Record<string, unknown>)\n\n if (type === 'llm_call' || type === 'cost.usage' || type === 'usage') {\n return buildLlmCall(data, agentRunName)\n }\n if (type === 'message.completed' || type === 'result' || type === 'final') {\n const usage = data.usage as Record<string, unknown> | undefined\n if (!usage || typeof usage !== 'object') return undefined\n return buildLlmCall({ ...usage, model: data.model ?? usage.model }, agentRunName)\n }\n // sandbox 0.4.0 terminal event: `data = { tokenUsage: { inputTokens, outputTokens,\n // reasoningTokens, cacheReadInputTokens }, totalCostUsd }`. Usage lives under\n // `tokenUsage` (not `usage`) and the cost is top-level — neither matched the\n // branches above, so an in-process loopDispatch run reported {0,0} and the\n // backend-integrity guard misread a real run as a stub. Reasoning tokens are\n // billed output (reasoning models), so they fold into the output count.\n if (type === 'done') {\n const usage = data.tokenUsage as Record<string, unknown> | undefined\n if (!usage || typeof usage !== 'object') return undefined\n const out = pickFiniteNumber(usage, ['outputTokens', 'completion_tokens', 'tokensOut'])\n const reasoning = pickFiniteNumber(usage, ['reasoningTokens'])\n const mergedOut =\n out !== undefined || reasoning !== undefined ? (out ?? 0) + (reasoning ?? 0) : undefined\n return buildLlmCall(\n {\n inputTokens: usage.inputTokens,\n outputTokens: mergedOut,\n totalCostUsd: data.totalCostUsd,\n model: data.model ?? usage.model,\n },\n agentRunName,\n )\n }\n return undefined\n}\n\nfunction buildLlmCall(\n data: Record<string, unknown>,\n agentRunName: string,\n): (RuntimeStreamEvent & { type: 'llm_call' }) | undefined {\n const tokensIn = pickFiniteNumber(data, ['tokensIn', 'inputTokens', 'prompt_tokens'])\n const tokensOut = pickFiniteNumber(data, ['tokensOut', 'outputTokens', 'completion_tokens'])\n const costUsd = pickFiniteNumber(data, ['costUsd', 'totalCostUsd', 'cost_usd', 'cost'])\n if (tokensIn === undefined && tokensOut === undefined && costUsd === undefined) {\n return undefined\n }\n const model = typeof data.model === 'string' && data.model.length > 0 ? data.model : agentRunName\n const event: RuntimeStreamEvent & { type: 'llm_call' } = {\n type: 'llm_call',\n model,\n }\n if (tokensIn !== undefined) event.tokensIn = tokensIn\n if (tokensOut !== undefined) event.tokensOut = tokensOut\n if (costUsd !== undefined) event.costUsd = costUsd\n return event\n}\n\nfunction pickFiniteNumber(data: Record<string, unknown>, keys: string[]): number | undefined {\n for (const key of keys) {\n const value = data[key]\n if (typeof value === 'number' && Number.isFinite(value)) return value\n }\n return undefined\n}\n\n/**\n * Project one `SandboxEvent` onto the `RuntimeStreamEvent` chat-UX vocabulary,\n * for runtimes that bridge a sandbox `streamPrompt` into the\n * `AgentRuntime.act` streaming contract. Returns `undefined` for events that\n * have no faithful projection — the raw stream is preserved separately for the\n * `OutputAdapter`, so an unmapped event never loses data.\n *\n * Mapped (the task-optional incremental variants — no synthesized task\n * lifecycle, no guessed tool-part shapes):\n * - `message.part.updated` text part → `text_delta`\n * - `message.part.updated` reasoning/thinking part → `reasoning_delta`\n * - cost-bearing events → `llm_call` (shared with the ledger extractor)\n *\n * The opencode backend emits incremental text as\n * `{ type: 'message.part.updated', data: { part: { type, text }, delta } }`;\n * `delta` is the increment, `part.text` the running accumulation.\n */\nexport function mapSandboxEvent(\n event: SandboxEvent,\n opts: { agentRunName?: string } = {},\n): RuntimeStreamEvent | undefined {\n if (!event || typeof event !== 'object') return undefined\n const type = String(event.type ?? '')\n const data =\n event.data && typeof event.data === 'object'\n ? (event.data as Record<string, unknown>)\n : ({} as Record<string, unknown>)\n\n if (type === 'message.part.updated') {\n const part =\n data.part && typeof data.part === 'object' ? (data.part as Record<string, unknown>) : {}\n const partType = String(part.type ?? '')\n const delta = typeof data.delta === 'string' ? data.delta : undefined\n const text = delta ?? (typeof part.text === 'string' ? part.text : undefined)\n if (text === undefined) return undefined\n if (partType === 'text') return { type: 'text_delta', text }\n if (partType === 'reasoning' || partType === 'thinking')\n return { type: 'reasoning_delta', text }\n return undefined\n }\n\n return extractLlmCallEvent(event, opts.agentRunName ?? 'agent')\n}\n"],"mappings":"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","names":["AgentEvalError"]}
|
|
@@ -138,10 +138,15 @@ function buildLoopSpanNodes(events) {
|
|
|
138
138
|
const rootAttrs = {
|
|
139
139
|
[GEN_AI.operation]: "invoke_workflow",
|
|
140
140
|
[GEN_AI.conversationId]: runId,
|
|
141
|
+
// Explicit run identity under the tangle namespace so a consuming system of
|
|
142
|
+
// record (Tangle Intelligence's run spine) maps this span tree to one run
|
|
143
|
+
// deterministically, instead of inferring it from the conversation id.
|
|
144
|
+
"tangle.run.id": runId,
|
|
141
145
|
"tangle.loop.driver": str(sp.driver) ?? "driver"
|
|
142
146
|
};
|
|
143
147
|
if (Array.isArray(sp.agentRunNames) && sp.agentRunNames.length > 0) {
|
|
144
148
|
rootAttrs["tangle.loop.agents"] = sp.agentRunNames.map(String).join(",");
|
|
149
|
+
rootAttrs["tangle.subject.key"] = String(sp.agentRunNames[0]);
|
|
145
150
|
}
|
|
146
151
|
if (ended) {
|
|
147
152
|
const ep = rec(ended.payload);
|
|
@@ -358,4 +363,4 @@ export {
|
|
|
358
363
|
INTELLIGENCE_WIRE_VERSION,
|
|
359
364
|
exportEvalRuns
|
|
360
365
|
};
|
|
361
|
-
//# sourceMappingURL=chunk-
|
|
366
|
+
//# sourceMappingURL=chunk-VMNEQHJR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/otel-export.ts"],"sourcesContent":["/**\n * OTEL span exporter — streams LoopTraceEvents to an OTLP/HTTP collector.\n *\n * Reads OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_EXPORTER_OTLP_HEADERS from env\n * when no explicit config is given. Keeps the runtime dep-free from\n * @opentelemetry/sdk-trace-base — minimal OTLP/JSON serializer.\n *\n * The exporter accepts both raw OtelSpan objects and LoopTraceEvents\n * (which get converted to OTLP spans automatically).\n */\n\nexport interface OtelExportConfig {\n /** OTLP endpoint. Reads OTEL_EXPORTER_OTLP_ENDPOINT env by default. */\n endpoint?: string\n /** OTLP headers. Reads OTEL_EXPORTER_OTLP_HEADERS env by default. */\n headers?: Record<string, string>\n /** Batch size before flush. Default 64. */\n batchSize?: number\n /** Flush interval ms. Default 5000. */\n flushIntervalMs?: number\n /** Resource attributes stamped on every export. */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Service name. Default 'agent-runtime'. */\n serviceName?: string\n}\n\nexport interface OtelExporter {\n /** Export a span. */\n exportSpan(span: OtelSpan): void\n /** Force flush pending spans. */\n flush(): Promise<void>\n /** Shutdown cleanly. */\n shutdown(): Promise<void>\n}\n\nexport interface OtelSpan {\n traceId: string\n spanId: string\n parentSpanId?: string\n name: string\n kind?: number\n startTimeUnixNano: string\n endTimeUnixNano: string\n attributes?: OtelAttribute[]\n status?: { code: number; message?: string }\n}\n\nexport interface OtelAttribute {\n key: string\n value: { stringValue?: string; intValue?: string; doubleValue?: number; boolValue?: boolean }\n}\n\ninterface OtlpResourceSpans {\n resource: { attributes: OtelAttribute[] }\n scopeSpans: Array<{ scope: { name: string; version: string }; spans: OtelSpan[] }>\n}\n\ninterface OtlpExport {\n resourceSpans: OtlpResourceSpans[]\n}\n\nconst SCOPE = { name: '@tangle-network/agent-runtime', version: '0.33.0' }\n\n/**\n * Current (non-deprecated) OpenTelemetry GenAI semantic-convention keys.\n * Registry: https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/\n * NB: `gen_ai.system` / `gen_ai.usage.prompt_tokens` / `completion_tokens` are\n * DEPRECATED — do not emit them. We use `provider.name` + `input/output_tokens`.\n */\nconst GEN_AI = {\n operation: 'gen_ai.operation.name',\n agentName: 'gen_ai.agent.name',\n conversationId: 'gen_ai.conversation.id',\n inputTokens: 'gen_ai.usage.input_tokens',\n outputTokens: 'gen_ai.usage.output_tokens',\n} as const\n\n/**\n * Create an OTEL exporter. Returns undefined when no endpoint is configured.\n */\nexport function createOtelExporter(config?: OtelExportConfig): OtelExporter | undefined {\n const resolvedEndpoint =\n config?.endpoint ??\n (typeof process !== 'undefined' ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : undefined)\n if (!resolvedEndpoint) return undefined\n const endpoint: string = resolvedEndpoint\n\n const headers = config?.headers ?? parseHeadersFromEnv()\n const batchSize = config?.batchSize ?? 64\n const flushIntervalMs = config?.flushIntervalMs ?? 5000\n const serviceName = config?.serviceName ?? 'agent-runtime'\n const resourceAttrs = config?.resourceAttributes ?? {}\n\n const pending: OtelSpan[] = []\n let timer: ReturnType<typeof setInterval> | undefined\n let stopped = false\n\n const exporter: OtelExporter = {\n exportSpan(span: OtelSpan): void {\n if (stopped) return\n pending.push(span)\n if (pending.length >= batchSize) {\n void doFlush()\n }\n },\n\n async flush(): Promise<void> {\n await doFlush()\n },\n\n async shutdown(): Promise<void> {\n stopped = true\n if (timer !== undefined) {\n clearInterval(timer)\n timer = undefined\n }\n await doFlush()\n },\n }\n\n timer = setInterval(() => {\n if (pending.length > 0) void doFlush()\n }, flushIntervalMs)\n if (typeof timer === 'object' && 'unref' in timer) {\n ;(timer as NodeJS.Timeout).unref()\n }\n\n async function doFlush(): Promise<void> {\n if (pending.length === 0) return\n const batch = pending.splice(0)\n const body: OtlpExport = {\n resourceSpans: [\n {\n resource: {\n attributes: toAttributes({\n 'service.name': serviceName,\n ...resourceAttrs,\n }),\n },\n scopeSpans: [{ scope: SCOPE, spans: batch }],\n },\n ],\n }\n const url = `${endpoint.replace(/\\/+$/, '')}/v1/traces`\n try {\n await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(body),\n })\n } catch {\n // Best-effort — telemetry export must not crash the runtime.\n }\n }\n\n return exporter\n}\n\n/**\n * Convert a LoopTraceEvent into an OtelSpan for export.\n */\nexport function loopEventToOtelSpan(\n event: {\n kind: string\n runId: string\n timestamp: number\n payload: object\n },\n traceId: string,\n parentSpanId?: string,\n): OtelSpan {\n const spanId = generateSpanId()\n const attrs: Record<string, string | number | boolean> = {\n 'loop.event_kind': event.kind,\n 'loop.run_id': event.runId,\n }\n for (const [k, v] of Object.entries(event.payload)) {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n attrs[`loop.${k}`] = v\n }\n }\n const ts = msToNs(event.timestamp)\n return {\n traceId: padTraceId(traceId),\n spanId,\n parentSpanId: parentSpanId ? padSpanId(parentSpanId) : undefined,\n name: event.kind,\n kind: 1,\n startTimeUnixNano: ts,\n endTimeUnixNano: ts,\n attributes: toAttributes(attrs),\n status: { code: 1 },\n }\n}\n\n/**\n * Sink-neutral node in a reconstructed loop span tree. The root node's\n * `parentSpanId` is `undefined` — sinks decide how to parent it (the OTEL\n * mapper attaches the inherited delegation span; the delegation journal\n * leaves it as the tree root).\n */\nexport interface LoopSpanNode {\n spanId: string\n parentSpanId?: string\n /** `'loop'` | `'loop.round'` | `'loop.iteration'`. */\n name: string\n /** Topology level: loop root, plan round, or iteration branch. */\n kind: 'loop' | 'round' | 'branch'\n startMs: number\n endMs: number\n attrs: Record<string, string | number | boolean>\n /** True when the iteration carried an error — maps to OTEL status code 2. */\n error: boolean\n}\n\n/**\n * Build a nested, real-duration OTLP span tree for ONE loop run from its full\n * ordered `LoopTraceEvent` stream. Unlike `loopEventToOtelSpan` (one flat,\n * zero-duration span per event), this reconstructs the topology hierarchy a\n * GenAI trace viewer renders natively:\n *\n * loop (invoke_workflow)\n * └─ loop.round[k] (invoke_workflow) ← tangle.loop.move.{kind,width,rationale}\n * ├─ loop.iteration[i] (invoke_agent) ← gen_ai.agent.name + usage + verdict + placement\n * └─ …\n *\n * Attributes follow the current GenAI semconv (`gen_ai.*`) where they apply and\n * a namespaced `tangle.loop.*` / `tangle.cost.usd` extension for topology /\n * verdict / placement / cost (not yet standardized). Pure: feed it a buffered\n * per-runId event array (e.g. flushed on `loop.ended`) and export the result.\n */\nexport function buildLoopOtelSpans(\n events: ReadonlyArray<{ kind: string; runId: string; timestamp: number; payload: object }>,\n traceId: string,\n rootParentSpanId?: string,\n): OtelSpan[] {\n const tid = padTraceId(traceId)\n return buildLoopSpanNodes(events).map((node) => ({\n traceId: tid,\n spanId: node.spanId,\n parentSpanId: node.parentSpanId\n ? padSpanId(node.parentSpanId)\n : rootParentSpanId\n ? padSpanId(rootParentSpanId)\n : undefined,\n name: node.name,\n kind: 1,\n startTimeUnixNano: msToNs(node.startMs),\n endTimeUnixNano: msToNs(node.endMs),\n attributes: toAttributes(node.attrs),\n status: { code: node.error ? 2 : 1 },\n }))\n}\n\n/**\n * Sink-neutral core behind {@link buildLoopOtelSpans}: reconstruct the\n * loop → round → branch span tree from one run's ordered `LoopTraceEvent`\n * stream. Consumed by the OTEL mapper above and by the MCP delegation\n * journal's compact trace tee — one topology reconstruction, two sinks.\n * Tolerates partial streams (a run that never reached `loop.ended` closes\n * at the last observed event's timestamp).\n */\nexport function buildLoopSpanNodes(\n events: ReadonlyArray<{ kind: string; runId: string; timestamp: number; payload: object }>,\n): LoopSpanNode[] {\n if (events.length === 0) return []\n const out: LoopSpanNode[] = []\n const num = (v: unknown): number | undefined =>\n typeof v === 'number' && Number.isFinite(v) ? v : undefined\n const str = (v: unknown): string | undefined =>\n typeof v === 'string' && v.length > 0 ? v : undefined\n const rec = (v: unknown): Record<string, unknown> =>\n v && typeof v === 'object' ? (v as Record<string, unknown>) : {}\n\n const started = events.find((e) => e.kind === 'loop.started')\n const ended = events.find((e) => e.kind === 'loop.ended')\n const runId = events[0]?.runId ?? ''\n const rootStart = started?.timestamp ?? events[0]!.timestamp\n const rootEnd = ended?.timestamp ?? events[events.length - 1]!.timestamp\n const rootId = generateSpanId()\n\n const make = (\n spanId: string,\n parentSpanId: string | undefined,\n name: string,\n kind: LoopSpanNode['kind'],\n startMs: number,\n endMs: number,\n attrs: Record<string, string | number | boolean>,\n error = false,\n ): LoopSpanNode => ({\n spanId,\n parentSpanId,\n name,\n kind,\n startMs,\n endMs,\n attrs,\n error,\n })\n\n // root\n const sp = rec(started?.payload)\n const rootAttrs: Record<string, string | number | boolean> = {\n [GEN_AI.operation]: 'invoke_workflow',\n [GEN_AI.conversationId]: runId,\n // Explicit run identity under the tangle namespace so a consuming system of\n // record (Tangle Intelligence's run spine) maps this span tree to one run\n // deterministically, instead of inferring it from the conversation id.\n 'tangle.run.id': runId,\n 'tangle.loop.driver': str(sp.driver) ?? 'driver',\n }\n if (Array.isArray(sp.agentRunNames) && sp.agentRunNames.length > 0) {\n rootAttrs['tangle.loop.agents'] = sp.agentRunNames.map(String).join(',')\n // Subject grain: the primary agent/profile this run drove. Lets the spine\n // group runs under the thing being worked on rather than one service-wide\n // bucket. `agentRunNames[0]` is the lead agent on a fan-out.\n rootAttrs['tangle.subject.key'] = String(sp.agentRunNames[0])\n }\n if (ended) {\n const ep = rec(ended.payload)\n const win = num(ep.winnerIterationIndex)\n if (win !== undefined) rootAttrs['tangle.loop.winner.iteration_index'] = win\n const cost = num(ep.totalCostUsd)\n if (cost !== undefined) rootAttrs['tangle.cost.usd'] = cost\n const dur = num(ep.durationMs)\n if (dur !== undefined) rootAttrs['tangle.loop.duration_ms'] = dur\n const iters = num(ep.iterations)\n if (iters !== undefined) rootAttrs['tangle.loop.iterations'] = iters\n }\n out.push(make(rootId, undefined, 'loop', 'loop', rootStart, rootEnd, rootAttrs))\n\n // rounds + iterations\n const iterStartTs = new Map<number, number>()\n const placementByIdx = new Map<number, Record<string, string>>()\n let currentRoundId: string | undefined\n let pendingRound:\n | { id: string; start: number; attrs: Record<string, string | number | boolean> }\n | undefined\n const flushRound = (endMs: number) => {\n if (!pendingRound) return\n out.push(\n make(\n pendingRound.id,\n rootId,\n 'loop.round',\n 'round',\n pendingRound.start,\n endMs,\n pendingRound.attrs,\n ),\n )\n pendingRound = undefined\n }\n\n for (const e of events) {\n const p = rec(e.payload)\n switch (e.kind) {\n case 'loop.plan': {\n flushRound(e.timestamp)\n const id = generateSpanId()\n const roundIdx = num(p.roundIndex) ?? 0\n const attrs: Record<string, string | number | boolean> = {\n [GEN_AI.operation]: 'invoke_workflow',\n 'tangle.loop.round.index': roundIdx,\n 'tangle.loop.move.kind': str(p.moveKind) ?? 'unknown',\n 'tangle.loop.move.round': roundIdx,\n 'tangle.loop.move.width': num(p.plannedCount) ?? 0,\n }\n const r = str(p.rationale)\n if (r) attrs['tangle.loop.move.rationale'] = r\n const parent = num(p.parentIndex)\n if (parent !== undefined) attrs['tangle.loop.move.parent_index'] = parent\n if (Array.isArray(p.childIndices) && p.childIndices.length > 0) {\n attrs['tangle.loop.move.child_indices'] = p.childIndices.map(String).join(',')\n }\n pendingRound = { id, start: e.timestamp, attrs }\n currentRoundId = id\n break\n }\n case 'loop.iteration.started': {\n const idx = num(p.iterationIndex)\n if (idx !== undefined) iterStartTs.set(idx, e.timestamp)\n break\n }\n case 'loop.iteration.dispatch': {\n const idx = num(p.iterationIndex)\n if (idx === undefined) break\n const place: Record<string, string> = {}\n const kind = str(p.placement)\n if (kind) place['tangle.loop.placement.kind'] = kind\n const sid = str(p.sandboxId)\n if (sid) place['tangle.sandbox.id'] = sid\n const fid = str(p.fleetId)\n if (fid) place['tangle.fleet.id'] = fid\n const mid = str(p.machineId)\n if (mid) place['tangle.machine.id'] = mid\n placementByIdx.set(idx, place)\n break\n }\n case 'loop.iteration.ended': {\n const idx = num(p.iterationIndex) ?? 0\n const start = iterStartTs.get(idx) ?? e.timestamp\n const err = str(p.error)\n const attrs: Record<string, string | number | boolean> = {\n [GEN_AI.operation]: 'invoke_agent',\n 'tangle.loop.iteration.index': idx,\n }\n const agent = str(p.agentRunName)\n if (agent) attrs[GEN_AI.agentName] = agent\n const tu = rec(p.tokenUsage)\n const inTok = num(tu.input)\n if (inTok !== undefined) attrs[GEN_AI.inputTokens] = inTok\n const outTok = num(tu.output)\n if (outTok !== undefined) attrs[GEN_AI.outputTokens] = outTok\n const cost = num(p.costUsd)\n if (cost !== undefined) attrs['tangle.cost.usd'] = cost\n const verdict = rec(p.verdict)\n if (typeof verdict.valid === 'boolean') attrs['tangle.loop.verdict.valid'] = verdict.valid\n const score = num(verdict.score)\n if (score !== undefined) attrs['tangle.loop.verdict.score'] = score\n if (err) attrs['tangle.loop.error'] = err\n const gid = num(p.groupId)\n if (gid !== undefined) attrs['tangle.loop.iteration.group_id'] = gid\n const par = num(p.parentIndex)\n if (par !== undefined) attrs['tangle.loop.iteration.parent_index'] = par\n const dur = num(p.durationMs)\n if (dur !== undefined) attrs['tangle.loop.iteration.duration_ms'] = dur\n const preview = str(p.outputPreview)\n if (preview) attrs['tangle.loop.iteration.output_preview'] = preview\n Object.assign(attrs, placementByIdx.get(idx) ?? {})\n out.push(\n make(\n generateSpanId(),\n currentRoundId ?? rootId,\n 'loop.iteration',\n 'branch',\n start,\n e.timestamp,\n attrs,\n err !== undefined,\n ),\n )\n break\n }\n case 'loop.decision': {\n if (pendingRound) {\n const dec = str(p.decision)\n if (dec) pendingRound.attrs['tangle.loop.decision'] = dec\n flushRound(e.timestamp)\n }\n currentRoundId = undefined\n break\n }\n }\n }\n flushRound(rootEnd)\n return out\n}\n\nfunction parseHeadersFromEnv(): Record<string, string> {\n if (typeof process === 'undefined') return {}\n const raw = process.env.OTEL_EXPORTER_OTLP_HEADERS\n if (!raw) return {}\n const out: Record<string, string> = {}\n for (const pair of raw.split(',')) {\n const eq = pair.indexOf('=')\n if (eq < 0) continue\n const key = pair.slice(0, eq).trim()\n const value = pair.slice(eq + 1).trim()\n if (key) out[key] = value\n }\n return out\n}\n\nfunction toAttributes(record: Record<string, string | number | boolean>): OtelAttribute[] {\n return Object.entries(record).map(([key, value]) => ({\n key,\n value:\n typeof value === 'number'\n ? Number.isInteger(value)\n ? { intValue: value.toString() }\n : { doubleValue: value }\n : typeof value === 'boolean'\n ? { boolValue: value }\n : { stringValue: value },\n }))\n}\n\nfunction msToNs(ms: number): string {\n return (BigInt(Math.floor(ms)) * 1_000_000n).toString()\n}\n\nfunction padSpanId(id: string): string {\n const cleaned = id.replace(/-/g, '')\n return cleaned.slice(0, 16).padEnd(16, '0')\n}\n\nfunction padTraceId(id: string): string {\n const cleaned = id.replace(/-/g, '')\n return cleaned.slice(0, 32).padEnd(32, '0')\n}\n\nfunction generateSpanId(): string {\n const bytes = new Uint8Array(8)\n if (typeof globalThis.crypto?.getRandomValues === 'function') {\n globalThis.crypto.getRandomValues(bytes)\n } else {\n for (let i = 0; i < 8; i++) bytes[i] = Math.floor(Math.random() * 256)\n }\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── Eval-run ingest (self-improvement provenance) ───────────────────────────\n//\n// Tangle Intelligence has a first-class, non-trace record for self-improvement\n// runs: POST /v1/ingest/eval-runs (\"Mode D\"). Each generation carries a\n// `surfaceHash` (the proposed-change identity) + arbitrary `surface` provenance;\n// a later `gate-decided` event re-emits the same `runId` (idempotent upsert) with\n// a real `gateDecision` + `holdoutLift`, so proposal→verdict is one diffable\n// record. This is how a consumer's RSI loop records WHAT it changed, WHY, from\n// which evidence — the audit trail behind agentic self-improvement.\n\n/** Wire version the eval-runs ingest enforces (X-Tangle-Wire-Version + body). */\nexport const INTELLIGENCE_WIRE_VERSION = '2026-05-26.v1'\n\nexport interface EvalRunGeneration {\n /** 0-based ordinal of this generation within the run (required by ingest). */\n index: number\n /** Identity of the proposed surface change (content-addressed hash). */\n surfaceHash: string\n /** Arbitrary provenance for this generation (rationale, evidence, source). */\n surface?: unknown\n /** Per-scenario results; empty until the generation is measured. */\n cells?: unknown[]\n /** Mean composite score (0 when unmeasured — pair with labels.measured). */\n compositeMean: number\n costUsd: number\n durationMs: number\n}\n\nexport interface EvalRunEvent {\n runId: string\n runDir: string\n /** ISO timestamp. */\n timestamp: string\n status:\n | 'started'\n | 'baseline-complete'\n | 'generation-complete'\n | 'gate-decided'\n | 'finished'\n | 'errored'\n labels?: Record<string, string>\n baseline?: EvalRunGeneration\n generations?: EvalRunGeneration[]\n gateDecision?: 'ship' | 'hold' | 'need_more_work' | 'model_ceiling' | 'arch_ceiling'\n holdoutLift?: number\n totalCostUsd: number\n totalDurationMs: number\n errorMessage?: string\n}\n\nexport interface EvalRunsExportConfig {\n /** Bearer key — tenant is resolved server-side from it. Reads TANGLE_API_KEY. */\n apiKey?: string\n /** Intelligence base. Reads INTELLIGENCE_BASE env, else prod. */\n base?: string\n /** Idempotency-Key header (e.g. the runId) — safe retries + upsert. */\n idempotencyKey?: string\n}\n\nexport interface EvalRunsExportResult {\n ok: boolean\n status: number\n accepted: number\n rejected: Array<{ index: number; reason: string }>\n}\n\nconst DEFAULT_INTELLIGENCE_BASE = 'https://intelligence.tangle.tools'\n\n/**\n * Ship self-improvement eval-run events to Tangle Intelligence. Unlike the\n * best-effort span exporter, this RESOLVES with the ingest verdict (accepted /\n * rejected per event) so a consumer's loop can assert its provenance landed.\n * Throws only on a missing key or network failure.\n */\nexport async function exportEvalRuns(\n events: EvalRunEvent[],\n config?: EvalRunsExportConfig,\n): Promise<EvalRunsExportResult> {\n if (events.length === 0) return { ok: true, status: 0, accepted: 0, rejected: [] }\n const apiKey =\n config?.apiKey ?? (typeof process !== 'undefined' ? process.env.TANGLE_API_KEY : undefined)\n if (!apiKey)\n throw new Error('exportEvalRuns: apiKey required (pass config.apiKey or set TANGLE_API_KEY)')\n const base =\n config?.base ??\n (typeof process !== 'undefined' ? process.env.INTELLIGENCE_BASE : undefined) ??\n DEFAULT_INTELLIGENCE_BASE\n const url = `${base.replace(/\\/+$/, '')}/v1/ingest/eval-runs`\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${apiKey}`,\n 'X-Tangle-Wire-Version': INTELLIGENCE_WIRE_VERSION,\n ...(config?.idempotencyKey ? { 'Idempotency-Key': config.idempotencyKey } : {}),\n },\n body: JSON.stringify({ wireVersion: INTELLIGENCE_WIRE_VERSION, events }),\n })\n let parsed: { accepted?: number; rejected?: Array<{ index: number; reason: string }> } = {}\n try {\n parsed = (await res.json()) as typeof parsed\n } catch {\n // non-JSON body (e.g. 5xx HTML) — leave parsed empty\n }\n return {\n ok: res.ok,\n status: res.status,\n accepted: parsed.accepted ?? (res.ok ? events.length : 0),\n rejected: parsed.rejected ?? [],\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { c as DelegateFeedbackArgs, d as DelegationFeedbackSnapshot, e as
|
|
1
|
+
import { c as DelegateFeedbackArgs, d as DelegationFeedbackSnapshot, L as LocalHarness, E as ExecutorFactory, e as ExecutorRegistry, f as DeliverableSpec, S as Spend, g as DelegationTaskQueue, h as CoderDelegate, R as ResearcherDelegate, U as UiAuditorDelegate, T as TraceContext, A as Agent, i as Scope, j as ResultBlobStore, B as Budget } from './delegates-CsXJPZDH.js';
|
|
2
|
+
import { T as ToolSpec, R as RouterConfig } from './router-client-C7kp_ECN.js';
|
|
3
|
+
import { BackendType } from '@tangle-network/sandbox';
|
|
4
|
+
import { S as SandboxClient, E as ExecCtx } from './types-CdnEAE3U.js';
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* @experimental
|
|
@@ -124,6 +127,274 @@ declare class InMemoryFeedbackStore implements FeedbackStore {
|
|
|
124
127
|
*/
|
|
125
128
|
declare function eventToSnapshot(event: FeedbackEvent): DelegationFeedbackSnapshot;
|
|
126
129
|
|
|
130
|
+
/**
|
|
131
|
+
* @experimental
|
|
132
|
+
*
|
|
133
|
+
* The leaf runtime — the built-in `Executor` IMPLEMENTATIONS behind the ONE
|
|
134
|
+
* open interface frozen in `./types`, plus the open resolver/registry that maps
|
|
135
|
+
* an `AgentSpec` to one of them OR accepts a bring-your-own executor verbatim.
|
|
136
|
+
*
|
|
137
|
+
* The interface is the extension point, not a closed `inline|sandbox|cli` union:
|
|
138
|
+
* - router/inline : a direct OpenAI-compatible Router call, no box (one-shot).
|
|
139
|
+
* - sandbox : COMPOSES the existing `runLoop` kernel as a single-task
|
|
140
|
+
* leaf and surfaces its token/cost usage as `UsageEvent`s;
|
|
141
|
+
* forwards PR #150's optional `lineage` passthrough WITHOUT
|
|
142
|
+
* reinventing checkpoint/fork (streaming).
|
|
143
|
+
* - cli : a Halo/RLM subprocess; `budgetExempt` (no token accounting),
|
|
144
|
+
* excluded from the equal-k arms by construction (streaming).
|
|
145
|
+
* Every metered runtime reports through the SAME normalized `UsageEvent` channel
|
|
146
|
+
* so the conserved budget pool meters them identically. A user's own agent is
|
|
147
|
+
* first-class the moment it implements `Executor` — register it by name or
|
|
148
|
+
* pass it as `AgentSpec.executor`.
|
|
149
|
+
*
|
|
150
|
+
* Layering: `estimateCost`/`isModelPriced` are substrate primitives from
|
|
151
|
+
* `@tangle-network/agent-eval`; `runLoop`/`acquireSandbox` are runtime kernels
|
|
152
|
+
* from this package. No per-vendor adapters live here.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Router/inline connection seam. A direct OpenAI-compatible Router endpoint —
|
|
157
|
+
* the cheapest leaf, no box, no tools. `model` overrides the profile's model
|
|
158
|
+
* hint when present; otherwise the profile's `model.default` is required.
|
|
159
|
+
*/
|
|
160
|
+
interface RouterSeam {
|
|
161
|
+
routerBaseUrl: string;
|
|
162
|
+
routerKey: string;
|
|
163
|
+
model?: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Sandbox executor seam. The `sandboxClient` the composed `runLoop` creates
|
|
167
|
+
* boxes through, plus the optional trace/run/lineage wiring forwarded into the
|
|
168
|
+
* loop. `lineage` is opaque here (PR #150's `RunLoopOptions.lineage`): forwarded
|
|
169
|
+
* forward-compatibly, never inspected — this executor does NOT reinvent
|
|
170
|
+
* checkpoint/fork.
|
|
171
|
+
*/
|
|
172
|
+
interface SandboxSeam {
|
|
173
|
+
sandboxClient: SandboxClient;
|
|
174
|
+
/** Forwarded into the composed `runLoop`'s `ctx` (trace emitter, run handle, etc.). */
|
|
175
|
+
loopCtx?: Partial<Omit<ExecCtx, 'sandboxClient' | 'signal'>>;
|
|
176
|
+
/** PR #150 `RunLoopOptions.lineage` passthrough — opaque; forwarded, not parsed. */
|
|
177
|
+
lineage?: unknown;
|
|
178
|
+
/** Hard cap on the composed loop's iterations. The budget pool reserves against
|
|
179
|
+
* the spawn `Budget.maxIterations`; this is the leaf's own ceiling. Default 1. */
|
|
180
|
+
maxIterations?: number;
|
|
181
|
+
}
|
|
182
|
+
/** CLI subprocess seam. `bin` + `args` describe the Halo/RLM process to spawn. */
|
|
183
|
+
interface CliSeam {
|
|
184
|
+
bin: string;
|
|
185
|
+
args?: string[];
|
|
186
|
+
/** Extra environment for the subprocess (merged over `process.env`). */
|
|
187
|
+
env?: Record<string, string>;
|
|
188
|
+
/** Working directory for the subprocess. */
|
|
189
|
+
cwd?: string;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* cli-worktree seam. A supervisor-authored `AgentProfile` driving a local coding-harness CLI
|
|
193
|
+
* (claude / codex / opencode) on its own git worktree — the leaf `createWorktreeCliExecutor`
|
|
194
|
+
* named as data. `harness` + `repoRoot` + `taskPrompt` are required; the authored
|
|
195
|
+
* `profile.prompt.systemPrompt` + `profile.model.default` reach the harness via the §1.5
|
|
196
|
+
* `harnessInvocation` mapper. Everything else mirrors `WorktreeCliExecutorOptions`.
|
|
197
|
+
*/
|
|
198
|
+
interface CliWorktreeSeam {
|
|
199
|
+
repoRoot: string;
|
|
200
|
+
harness: LocalHarness;
|
|
201
|
+
taskPrompt: string;
|
|
202
|
+
runId?: string;
|
|
203
|
+
baseRef?: string;
|
|
204
|
+
harnessTimeoutMs?: number;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* cli-bridge seam. A local OpenAI-compatible bridge that fronts harness CLIs
|
|
208
|
+
* (claude-code / opencode / kimi / pi) behind one HTTP surface; `model` doubles
|
|
209
|
+
* as the harness selector (e.g. `claude-code/sonnet`, `opencode/<provider>/<model>`).
|
|
210
|
+
* `agentProfile` is the bridge-dialect profile (metadata.disallowedTools, mcp)
|
|
211
|
+
* forwarded verbatim per request — how an arm disables native tools or injects
|
|
212
|
+
* a provider search MCP.
|
|
213
|
+
*
|
|
214
|
+
* The executor opens a RESUMABLE cli-bridge session — structurally identical to the
|
|
215
|
+
* sandbox executor's persistent box, just local. `sessionId` is the stable
|
|
216
|
+
* caller-owned id cli-bridge maps to the harness's internal conversation id; a
|
|
217
|
+
* follow-up steer/resume on the SAME id continues the SAME harness session (opencode
|
|
218
|
+
* `-s`, claude `--resume`, …). Omit it and the executor mints a stable one per spawn.
|
|
219
|
+
*/
|
|
220
|
+
interface BridgeSeam {
|
|
221
|
+
bridgeUrl: string;
|
|
222
|
+
bridgeBearer: string;
|
|
223
|
+
model: string;
|
|
224
|
+
agentProfile?: Record<string, unknown>;
|
|
225
|
+
timeoutMs?: number;
|
|
226
|
+
/** Stable, caller-owned cli-bridge session id for harness-side resume. Defaults
|
|
227
|
+
* to a freshly minted per-spawn id so each worker is its own resumable session. */
|
|
228
|
+
sessionId?: string;
|
|
229
|
+
/** Per-resume-turn inference cap before the worker settles on its last output.
|
|
230
|
+
* Mirrors `routerToolsInlineExecutor.maxTurns`; default 200 (runaway backstop). */
|
|
231
|
+
maxTurns?: number;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Router seam WITH tool use — the tool-using router backend. Same direct
|
|
235
|
+
* OpenAI-compatible endpoint as `RouterSeam`, but each turn passes `tools`; when
|
|
236
|
+
* the model emits tool_calls they run via `executeToolCall` ON THIS HOST and the
|
|
237
|
+
* results fold back as `tool` messages, repeating until the model answers without
|
|
238
|
+
* a tool or `maxTurns` is hit. A real agentic loop, OFF-BOX — no sandbox, so it
|
|
239
|
+
* is unaffected by a box's egress allowlist. One turn = one completion = the
|
|
240
|
+
* equal-compute unit. `executeToolCall` receives the task so per-task tool
|
|
241
|
+
* surfaces (e.g. a gym keyed by task) can dispatch correctly.
|
|
242
|
+
*/
|
|
243
|
+
interface RouterToolsSeam {
|
|
244
|
+
routerBaseUrl: string;
|
|
245
|
+
routerKey: string;
|
|
246
|
+
model?: string;
|
|
247
|
+
tools: ReadonlyArray<ToolSpec>;
|
|
248
|
+
executeToolCall: (name: string, args: Record<string, unknown>, task: unknown) => Promise<string>;
|
|
249
|
+
/** Online observer of each tool step — the seam a `DetectorMonitor` taps to watch the live pipe
|
|
250
|
+
* (raise a `finding` when the worker loops/errors). Called after every tool call resolves, with
|
|
251
|
+
* real per-call wall-clock (`startedAt`/`endedAt`/`durationMs`) so a push `TraceSource` can carry
|
|
252
|
+
* non-zero span durations onto the unified timeline. */
|
|
253
|
+
onToolStep?: (step: {
|
|
254
|
+
toolName: string;
|
|
255
|
+
args: Record<string, unknown>;
|
|
256
|
+
status: 'ok' | 'error';
|
|
257
|
+
startedAt?: number;
|
|
258
|
+
endedAt?: number;
|
|
259
|
+
durationMs?: number;
|
|
260
|
+
}) => void;
|
|
261
|
+
/** Max inference turns. Default 200 (runaway backstop — set far above any
|
|
262
|
+
* legitimate workflow). For tighter per-workflow limits use a cost budget
|
|
263
|
+
* or wall-clock deadline at the call site. */
|
|
264
|
+
maxTurns?: number;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The leaf `createWorktreeCliExecutor` as a backend-as-data factory: a supervisor-authored
|
|
268
|
+
* `AgentProfile` driving claude / codex / opencode on its own worktree. `budgetExempt` like
|
|
269
|
+
* the other CLI leaves; the authored systemPrompt + model reach the harness via §1.5.
|
|
270
|
+
*/
|
|
271
|
+
declare const cliWorktreeExecutor: ExecutorFactory<unknown>;
|
|
272
|
+
/**
|
|
273
|
+
* Config for {@link createExecutor}: the backend is DATA — the cost dial a profile,
|
|
274
|
+
* an experiment config, or a replay journal can name — not an import choice. Each
|
|
275
|
+
* variant carries its backend's seam (router/router-tools/bridge/cli/cli-worktree/sandbox).
|
|
276
|
+
*/
|
|
277
|
+
type ExecutorConfig = ({
|
|
278
|
+
backend: 'router';
|
|
279
|
+
} & RouterSeam) | ({
|
|
280
|
+
backend: 'router-tools';
|
|
281
|
+
} & RouterToolsSeam) | ({
|
|
282
|
+
backend: 'bridge';
|
|
283
|
+
} & BridgeSeam) | ({
|
|
284
|
+
backend: 'cli';
|
|
285
|
+
} & CliSeam) | ({
|
|
286
|
+
backend: 'cli-worktree';
|
|
287
|
+
} & CliWorktreeSeam) | ({
|
|
288
|
+
backend: 'sandbox';
|
|
289
|
+
harness?: BackendType;
|
|
290
|
+
} & SandboxSeam);
|
|
291
|
+
/**
|
|
292
|
+
* The single built-in executor factory. Picks a leaf backend by data (`config.backend`),
|
|
293
|
+
* injects the matching seam, and delegates to that backend's built-in implementation.
|
|
294
|
+
* The `Executor` port stays OPEN: bring-your-own agents implement `Executor` directly
|
|
295
|
+
* and never pass through here. Use this (or `createExecutorRegistry`) instead of a
|
|
296
|
+
* per-vendor adapter or a closed `inline|sandbox|cli` switch — those bypass the
|
|
297
|
+
* `UsageEvent` reporting channel.
|
|
298
|
+
*/
|
|
299
|
+
declare function createExecutor(config: ExecutorConfig): ExecutorFactory<unknown>;
|
|
300
|
+
/**
|
|
301
|
+
* The open resolver/registry. Pre-registers the three built-ins under their
|
|
302
|
+
* runtime tags (`'router'`, `'sandbox'`, `'cli'`) and accepts `register(name,
|
|
303
|
+
* factory)` for any additional runtime — and a BYO `AgentSpec.executor` resolves
|
|
304
|
+
* without touching the registry at all. NOT a closed switch; registration + BYO
|
|
305
|
+
* ARE the extension points.
|
|
306
|
+
*
|
|
307
|
+
* `resolve` precedence (frozen in `ExecutorRegistry`): a BYO `spec.executor` →
|
|
308
|
+
* `harness === null` → the `'router'` factory; else a registered factory for the
|
|
309
|
+
* harness-derived runtime (`'sandbox'` for any `BackendType`); else fail loud.
|
|
310
|
+
*/
|
|
311
|
+
declare function createExecutorRegistry(): ExecutorRegistry;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @experimental
|
|
315
|
+
*
|
|
316
|
+
* `delegate` MCP tool — the ONE generic delegation verb, the agent-facing front door to
|
|
317
|
+
* `delegate()` / `supervise()`. The agent hands it an INTENT (what it wants done); a default
|
|
318
|
+
* authoring supervisor decomposes the intent and AUTHORS the worker profile it needs — there is no
|
|
319
|
+
* hardcoded coder/researcher profile. It is the generic replacement for `delegate_code` /
|
|
320
|
+
* `delegate_research`.
|
|
321
|
+
*
|
|
322
|
+
* Unlike those async, queue-backed tools (kick off → return a taskId → poll `delegation_status`),
|
|
323
|
+
* `delegate` is SYNCHRONOUS: it awaits the full supervised run and returns the delivered output
|
|
324
|
+
* TOGETHER WITH `spentTotal` — the conserved cost of the whole delegation (`iterations` / `tokens` /
|
|
325
|
+
* `usd` / `ms`). Returning the real spend is the whole reason `delegate` beats `delegate_code`,
|
|
326
|
+
* which has no cost channel.
|
|
327
|
+
*
|
|
328
|
+
* The supervisor's substrate (its brain `router`, the worker `backend`, the completion `deliverable`)
|
|
329
|
+
* is INJECTED at server construction — never an agent-supplied arg — exactly as `delegate_code`
|
|
330
|
+
* injects its `CoderDelegate`. The agent supplies only the intent (+ an optional per-call `model` /
|
|
331
|
+
* `runId`).
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
/** @experimental */
|
|
335
|
+
declare const DELEGATE_TOOL_NAME = "delegate";
|
|
336
|
+
/** @experimental */
|
|
337
|
+
declare const DELEGATE_DESCRIPTION: string;
|
|
338
|
+
/** @experimental */
|
|
339
|
+
declare const DELEGATE_INPUT_SCHEMA: {
|
|
340
|
+
readonly type: "object";
|
|
341
|
+
readonly properties: {
|
|
342
|
+
readonly intent: {
|
|
343
|
+
readonly type: "string";
|
|
344
|
+
readonly description: "What you want accomplished, as an outcome. The supervisor authors the worker.";
|
|
345
|
+
};
|
|
346
|
+
readonly model: {
|
|
347
|
+
readonly type: "string";
|
|
348
|
+
readonly description: "Optional per-call override for the supervisor brain model.";
|
|
349
|
+
};
|
|
350
|
+
readonly runId: {
|
|
351
|
+
readonly type: "string";
|
|
352
|
+
readonly description: "Optional trace-correlation id for this delegation.";
|
|
353
|
+
};
|
|
354
|
+
};
|
|
355
|
+
readonly required: readonly ["intent"];
|
|
356
|
+
readonly additionalProperties: false;
|
|
357
|
+
};
|
|
358
|
+
/** Parsed `delegate` tool arguments. */
|
|
359
|
+
interface DelegateArgs {
|
|
360
|
+
intent: string;
|
|
361
|
+
model?: string;
|
|
362
|
+
runId?: string;
|
|
363
|
+
}
|
|
364
|
+
/** @experimental */
|
|
365
|
+
declare function validateDelegateArgs(raw: unknown): DelegateArgs;
|
|
366
|
+
/** The synchronous result the `delegate` tool returns to the calling agent: the delivered output (or
|
|
367
|
+
* the no-winner reason) PLUS the conserved spend of the whole delegation. */
|
|
368
|
+
type DelegateResult = {
|
|
369
|
+
status: 'winner';
|
|
370
|
+
out: unknown;
|
|
371
|
+
outRef: string;
|
|
372
|
+
spentTotal: Spend;
|
|
373
|
+
} | {
|
|
374
|
+
status: 'no-winner';
|
|
375
|
+
reason: string;
|
|
376
|
+
spentTotal: Spend;
|
|
377
|
+
};
|
|
378
|
+
/** @experimental */
|
|
379
|
+
interface DelegateHandlerOptions {
|
|
380
|
+
/** The supervisor brain's router substrate (REQUIRED — the default supervisor is router-brained). */
|
|
381
|
+
router: RouterConfig;
|
|
382
|
+
/** WHERE the authored workers run. Required for `supervise()` to spawn anything. */
|
|
383
|
+
backend: ExecutorConfig;
|
|
384
|
+
/** The completion oracle the authored workers settle against (settled ⟺ delivered). */
|
|
385
|
+
deliverable?: DeliverableSpec;
|
|
386
|
+
/** Default supervisor brain model when a call omits `model`. */
|
|
387
|
+
model?: string;
|
|
388
|
+
/** Restrict the run to this subset of models. */
|
|
389
|
+
allowedModels?: readonly string[];
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Build the `delegate` tool handler. Closes over the injected supervisor substrate (`router` /
|
|
393
|
+
* `backend` / `deliverable`); each call routes the agent's intent to `delegate()` and returns the
|
|
394
|
+
* delivered output with its conserved cost.
|
|
395
|
+
*/
|
|
396
|
+
declare function createDelegateHandler(options: DelegateHandlerOptions): (raw: unknown) => Promise<DelegateResult>;
|
|
397
|
+
|
|
127
398
|
/**
|
|
128
399
|
* @experimental
|
|
129
400
|
*
|
|
@@ -144,6 +415,13 @@ declare function eventToSnapshot(event: FeedbackEvent): DelegationFeedbackSnapsh
|
|
|
144
415
|
|
|
145
416
|
/** @experimental */
|
|
146
417
|
interface McpServerOptions {
|
|
418
|
+
/**
|
|
419
|
+
* Required to enable `delegate` — the ONE generic delegation verb (the replacement for
|
|
420
|
+
* delegate_code / delegate_research). Inject the supervisor substrate: its brain `router`, the
|
|
421
|
+
* worker `backend`, and the completion `deliverable`. The supervisor AUTHORS its own worker from
|
|
422
|
+
* the agent's intent, so there is no worker profile to wire here.
|
|
423
|
+
*/
|
|
424
|
+
delegateSupervisor?: DelegateHandlerOptions;
|
|
147
425
|
/** Required to enable delegate_code. */
|
|
148
426
|
coderDelegate?: CoderDelegate;
|
|
149
427
|
/**
|
|
@@ -385,4 +663,4 @@ interface CoordinationTools {
|
|
|
385
663
|
/** Build the driver's MCP tools over a live scope. */
|
|
386
664
|
declare function createCoordinationTools(opts: CoordinationToolsOptions): CoordinationTools;
|
|
387
665
|
|
|
388
|
-
export { type AnalystRegistry as A, type BusEvent as B, type CoordinationEvent as C, type
|
|
666
|
+
export { type AnalystRegistry as A, type BusEvent as B, type CoordinationEvent as C, DELEGATE_DESCRIPTION as D, type ExecutorConfig as E, type FeedbackStore as F, createEventBus as G, createExecutor as H, InMemoryFeedbackStore as I, type JsonRpcMessage as J, createExecutorRegistry as K, type MakeWorkerAgent as M, type PublishOptions as P, type Question as Q, type SettledWorker as S, type CoordinationTools as a, type CoordinationToolsOptions as b, DELEGATE_INPUT_SCHEMA as c, DELEGATE_TOOL_NAME as d, type DelegateArgs as e, type DelegateHandlerOptions as f, type DelegateResult as g, type FeedbackEvent as h, type JsonRpcResponse as i, type McpServer as j, type McpServerOptions as k, type McpToolDescriptor as l, type McpTransport as m, type QuestionDecision as n, type QuestionPolicy as o, type QuestionRecord as p, createCoordinationTools as q, createDelegateHandler as r, createInProcessTransport as s, createMcpServer as t, eventToSnapshot as u, validateDelegateArgs as v, type BusRecord as w, type BusStats as x, type EventBus as y, cliWorktreeExecutor as z };
|