@tangle-network/agent-runtime 0.115.0 → 0.116.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/{activation-BNQQO-bc.js → activation-DuqQhee6.js} +2 -2
- package/dist/{activation-BNQQO-bc.js.map → activation-DuqQhee6.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/{environment-provider-Cygthiy3.d.ts → environment-provider-CWsRh6Uz.d.ts} +365 -4
- package/dist/environment-provider.d.ts +1 -1
- package/dist/{improvement-cycle-CBLOgfsQ.js → improvement-cycle-C1cmjvPD.js} +3 -3
- package/dist/{improvement-cycle-CBLOgfsQ.js.map → improvement-cycle-C1cmjvPD.js.map} +1 -1
- package/dist/{index-BuzkRsOn.d.ts → index-COumPQka.d.ts} +49 -225
- package/dist/{index-I97xACXk.d.ts → index-CYkDeM5L.d.ts} +3 -3
- package/dist/{index-B_vcAAle.d.ts → index-DcLMNnG5.d.ts} +7 -6
- package/dist/index.d.ts +6 -7
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +3 -3
- package/dist/intelligence.js +3 -3
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +5 -5
- package/dist/{knowledge-DcIKJaPd.js → knowledge-DOzbywZT.js} +3 -3
- package/dist/{knowledge-DcIKJaPd.js.map → knowledge-DOzbywZT.js.map} +1 -1
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{local-harness-BnB1E9nM.d.ts → local-harness-t6cDWDQ2.d.ts} +3 -105
- package/dist/{loop-runner-bin-DgAXgQCI.d.ts → loop-runner-bin-BFrhPLKt.d.ts} +3 -3
- package/dist/{loop-runner-bin-C2GIqSUn.js → loop-runner-bin-BuQjc5DR.js} +3 -3
- package/dist/{loop-runner-bin-C2GIqSUn.js.map → loop-runner-bin-BuQjc5DR.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +2 -2
- package/dist/mcp/index.d.ts +3 -2
- package/dist/mcp/index.js +4 -5
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-BWAEIHkZ.js → openai-tools-_Wyp4udO.js} +2 -2
- package/dist/{openai-tools-BWAEIHkZ.js.map → openai-tools-_Wyp4udO.js.map} +1 -1
- package/dist/primeintellect/index.d.ts +1 -1
- package/dist/{redact-BRaHUFMf.d.ts → redact-BEtQtvd6.d.ts} +2 -2
- package/dist/{runtime-C_g_SsXg.js → runtime-Ut1pkd2n.js} +4 -4
- package/dist/{runtime-C_g_SsXg.js.map → runtime-Ut1pkd2n.js.map} +1 -1
- package/dist/{structural-rollout-Cqm1dKa7.js → structural-rollout-CVY_0hJp.js} +2 -2
- package/dist/{structural-rollout-Cqm1dKa7.js.map → structural-rollout-CVY_0hJp.js.map} +1 -1
- package/dist/{supervise-BcCHhLrz.js → supervise-BUR9ByF7.js} +61 -22
- package/dist/supervise-BUR9ByF7.js.map +1 -0
- package/dist/{supervisor-CspDspod.js → supervisor-BBbPBXpe.js} +1019 -10
- package/dist/supervisor-BBbPBXpe.js.map +1 -0
- package/dist/testing.js +8 -8
- package/package.json +1 -1
- package/dist/otel-export-CPZTSADj.js +0 -852
- package/dist/otel-export-CPZTSADj.js.map +0 -1
- package/dist/supervise-BcCHhLrz.js.map +0 -1
- package/dist/supervisor-CspDspod.js.map +0 -1
- package/dist/trace-propagation-B-pL7xn_.js +0 -57
- package/dist/trace-propagation-B-pL7xn_.js.map +0 -1
|
@@ -2438,6 +2438,1002 @@ function defaultSleep(ms, signal) {
|
|
|
2438
2438
|
});
|
|
2439
2439
|
}
|
|
2440
2440
|
//#endregion
|
|
2441
|
+
//#region src/sanitize.ts
|
|
2442
|
+
/** Strip PII and large blobs from a `KnowledgeReadinessReport` for safe telemetry emission. @stable */
|
|
2443
|
+
function sanitizeKnowledgeReadinessReport(report, options = {}) {
|
|
2444
|
+
return {
|
|
2445
|
+
taskId: report.taskId,
|
|
2446
|
+
readinessScore: report.readinessScore,
|
|
2447
|
+
recommendedAction: report.recommendedAction,
|
|
2448
|
+
severity: report.severity,
|
|
2449
|
+
reason: report.reason,
|
|
2450
|
+
blockingMissingRequirements: report.blockingMissingRequirements.map((requirement) => sanitizeKnowledgeRequirement(requirement, options)),
|
|
2451
|
+
nonBlockingGaps: report.nonBlockingGaps.map((requirement) => sanitizeKnowledgeRequirement(requirement, options)),
|
|
2452
|
+
evidenceCount: report.bundle.evidenceIds.length,
|
|
2453
|
+
evidenceIds: options.includeEvidenceIds ? report.bundle.evidenceIds : void 0,
|
|
2454
|
+
missingRequirementIds: report.bundle.missing.map((requirement) => requirement.id)
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
/** Reduce an `AgentRuntimeEvent` to a PII-safe, serializable plain object for telemetry. @stable */
|
|
2458
|
+
function sanitizeAgentRuntimeEvent(event, options = {}) {
|
|
2459
|
+
const base = {
|
|
2460
|
+
type: event.type,
|
|
2461
|
+
task: sanitizeTask(event.task, options)
|
|
2462
|
+
};
|
|
2463
|
+
if (event.type === "readiness_start" || event.type === "task_start" || event.type === "control_start") return event.type === "control_start" ? {
|
|
2464
|
+
...base,
|
|
2465
|
+
knowledge: sanitizeKnowledgeReadinessReport(event.knowledge, options)
|
|
2466
|
+
} : base;
|
|
2467
|
+
if (event.type === "readiness_end") return {
|
|
2468
|
+
...base,
|
|
2469
|
+
knowledge: sanitizeKnowledgeReadinessReport(event.knowledge, options)
|
|
2470
|
+
};
|
|
2471
|
+
if (event.type === "questions_start") return {
|
|
2472
|
+
...base,
|
|
2473
|
+
questions: event.questions.map((question) => sanitizeQuestion(question, options))
|
|
2474
|
+
};
|
|
2475
|
+
if (event.type === "questions_end") return {
|
|
2476
|
+
...base,
|
|
2477
|
+
questions: event.questions.map((question) => sanitizeQuestion(question, options)),
|
|
2478
|
+
userAnswers: options.includeUserAnswers ? event.userAnswers : redactRecord(event.userAnswers)
|
|
2479
|
+
};
|
|
2480
|
+
if (event.type === "acquisition_start") return {
|
|
2481
|
+
...base,
|
|
2482
|
+
acquisitionPlans: event.acquisitionPlans.map(sanitizeAcquisitionPlan)
|
|
2483
|
+
};
|
|
2484
|
+
if (event.type === "acquisition_end") return {
|
|
2485
|
+
...base,
|
|
2486
|
+
acquisitionPlans: event.acquisitionPlans.map(sanitizeAcquisitionPlan),
|
|
2487
|
+
acquiredEvidenceCount: event.acquiredEvidenceIds.length,
|
|
2488
|
+
acquiredEvidenceIds: options.includeEvidenceIds ? event.acquiredEvidenceIds : void 0
|
|
2489
|
+
};
|
|
2490
|
+
if (event.type === "control_step") return {
|
|
2491
|
+
...base,
|
|
2492
|
+
step: sanitizeControlStep(event.step, options)
|
|
2493
|
+
};
|
|
2494
|
+
if (event.type === "control_end") return {
|
|
2495
|
+
...base,
|
|
2496
|
+
control: sanitizeControlRun(event.control, options)
|
|
2497
|
+
};
|
|
2498
|
+
return {
|
|
2499
|
+
...base,
|
|
2500
|
+
status: event.status,
|
|
2501
|
+
reason: event.reason
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
/** Reduce a `RuntimeStreamEvent` to a PII-safe, serializable plain object for telemetry. @stable */
|
|
2505
|
+
function sanitizeRuntimeStreamEvent(event, options = {}) {
|
|
2506
|
+
const withTask = "task" in event && event.task ? { task: sanitizeTask(event.task, options) } : {};
|
|
2507
|
+
const withSession = "session" in event && event.session ? { session: sanitizeRuntimeSession(event.session, options) } : {};
|
|
2508
|
+
if (event.type === "readiness_end") return {
|
|
2509
|
+
type: event.type,
|
|
2510
|
+
...withTask,
|
|
2511
|
+
timestamp: event.timestamp,
|
|
2512
|
+
decision: event.decision,
|
|
2513
|
+
knowledge: sanitizeKnowledgeReadinessReport(event.knowledge, options)
|
|
2514
|
+
};
|
|
2515
|
+
if (event.type === "questions_start") return {
|
|
2516
|
+
type: event.type,
|
|
2517
|
+
...withTask,
|
|
2518
|
+
timestamp: event.timestamp,
|
|
2519
|
+
questions: event.questions.map((question) => sanitizeQuestion(question, options))
|
|
2520
|
+
};
|
|
2521
|
+
if (event.type === "questions_end") return {
|
|
2522
|
+
type: event.type,
|
|
2523
|
+
...withTask,
|
|
2524
|
+
timestamp: event.timestamp,
|
|
2525
|
+
questions: event.questions.map((question) => sanitizeQuestion(question, options)),
|
|
2526
|
+
userAnswers: options.includeUserAnswers ? event.userAnswers : redactRecord(event.userAnswers)
|
|
2527
|
+
};
|
|
2528
|
+
if (event.type === "acquisition_start") return {
|
|
2529
|
+
type: event.type,
|
|
2530
|
+
...withTask,
|
|
2531
|
+
timestamp: event.timestamp,
|
|
2532
|
+
acquisitionPlans: event.acquisitionPlans.map(sanitizeAcquisitionPlan)
|
|
2533
|
+
};
|
|
2534
|
+
if (event.type === "acquisition_end") return {
|
|
2535
|
+
type: event.type,
|
|
2536
|
+
...withTask,
|
|
2537
|
+
timestamp: event.timestamp,
|
|
2538
|
+
acquisitionPlans: event.acquisitionPlans.map(sanitizeAcquisitionPlan),
|
|
2539
|
+
acquiredEvidenceCount: event.acquiredEvidenceIds.length,
|
|
2540
|
+
acquiredEvidenceIds: options.includeEvidenceIds ? event.acquiredEvidenceIds : void 0
|
|
2541
|
+
};
|
|
2542
|
+
if (event.type === "tool_call") return {
|
|
2543
|
+
type: event.type,
|
|
2544
|
+
...withTask,
|
|
2545
|
+
...withSession,
|
|
2546
|
+
timestamp: event.timestamp,
|
|
2547
|
+
toolName: event.toolName,
|
|
2548
|
+
toolCallId: event.toolCallId,
|
|
2549
|
+
args: options.includeControlPayloads ? event.args : void 0
|
|
2550
|
+
};
|
|
2551
|
+
if (event.type === "tool_result") return {
|
|
2552
|
+
type: event.type,
|
|
2553
|
+
...withTask,
|
|
2554
|
+
...withSession,
|
|
2555
|
+
timestamp: event.timestamp,
|
|
2556
|
+
toolName: event.toolName,
|
|
2557
|
+
toolCallId: event.toolCallId,
|
|
2558
|
+
result: options.includeControlPayloads ? event.result : void 0
|
|
2559
|
+
};
|
|
2560
|
+
if (event.type === "llm_call") return {
|
|
2561
|
+
type: event.type,
|
|
2562
|
+
...withTask,
|
|
2563
|
+
...withSession,
|
|
2564
|
+
timestamp: event.timestamp,
|
|
2565
|
+
model: event.model,
|
|
2566
|
+
tokensIn: event.tokensIn,
|
|
2567
|
+
tokensOut: event.tokensOut,
|
|
2568
|
+
costUsd: event.costUsd,
|
|
2569
|
+
latencyMs: event.latencyMs,
|
|
2570
|
+
finishReason: event.finishReason
|
|
2571
|
+
};
|
|
2572
|
+
if (event.type === "artifact") return {
|
|
2573
|
+
type: event.type,
|
|
2574
|
+
...withTask,
|
|
2575
|
+
...withSession,
|
|
2576
|
+
timestamp: event.timestamp,
|
|
2577
|
+
artifactId: event.artifactId,
|
|
2578
|
+
name: event.name,
|
|
2579
|
+
mimeType: event.mimeType,
|
|
2580
|
+
uri: options.includeEvidenceIds ? event.uri : void 0,
|
|
2581
|
+
content: options.includeControlPayloads ? event.content : void 0,
|
|
2582
|
+
metadata: options.includeMetadata ? event.metadata : void 0
|
|
2583
|
+
};
|
|
2584
|
+
if (event.type === "proposal_created") return {
|
|
2585
|
+
type: event.type,
|
|
2586
|
+
...withTask,
|
|
2587
|
+
...withSession,
|
|
2588
|
+
timestamp: event.timestamp,
|
|
2589
|
+
proposalId: event.proposalId,
|
|
2590
|
+
title: options.includeControlPayloads ? event.title : void 0,
|
|
2591
|
+
content: options.includeControlPayloads ? event.content : void 0,
|
|
2592
|
+
status: event.status
|
|
2593
|
+
};
|
|
2594
|
+
if (event.type === "final") {
|
|
2595
|
+
const sanitizedError = event.error !== void 0 ? {
|
|
2596
|
+
kind: event.error.kind,
|
|
2597
|
+
message: event.error.message,
|
|
2598
|
+
status: event.error.status,
|
|
2599
|
+
body: options.includeControlPayloads ? event.error.body : void 0
|
|
2600
|
+
} : void 0;
|
|
2601
|
+
return {
|
|
2602
|
+
type: event.type,
|
|
2603
|
+
...withTask,
|
|
2604
|
+
...withSession,
|
|
2605
|
+
timestamp: event.timestamp,
|
|
2606
|
+
status: event.status,
|
|
2607
|
+
reason: event.reason,
|
|
2608
|
+
text: options.includeControlPayloads ? event.text : void 0,
|
|
2609
|
+
metadata: options.includeMetadata ? event.metadata : void 0,
|
|
2610
|
+
...sanitizedError !== void 0 ? { error: sanitizedError } : {}
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
return {
|
|
2614
|
+
type: event.type,
|
|
2615
|
+
...withTask,
|
|
2616
|
+
...withSession,
|
|
2617
|
+
timestamp: "timestamp" in event ? event.timestamp : void 0,
|
|
2618
|
+
...pickPublicStreamFields(event)
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
function sanitizeTask(task, options) {
|
|
2622
|
+
return {
|
|
2623
|
+
id: task.id,
|
|
2624
|
+
intent: task.intent,
|
|
2625
|
+
domain: task.domain,
|
|
2626
|
+
inputs: options.includeInputs ? task.inputs : task.inputs ? "[redacted]" : void 0,
|
|
2627
|
+
requiredKnowledge: task.requiredKnowledge?.map((requirement) => sanitizeKnowledgeRequirement(requirement, options)),
|
|
2628
|
+
metadata: options.includeMetadata ? task.metadata : task.metadata ? "[redacted]" : void 0
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function sanitizeRuntimeSession(session, options) {
|
|
2632
|
+
return {
|
|
2633
|
+
id: session.id,
|
|
2634
|
+
backend: session.backend,
|
|
2635
|
+
status: session.status,
|
|
2636
|
+
hasResumeToken: Boolean(session.resumeToken),
|
|
2637
|
+
createdAt: session.createdAt,
|
|
2638
|
+
updatedAt: session.updatedAt,
|
|
2639
|
+
metadata: options.includeMetadata ? session.metadata : session.metadata ? "[redacted]" : void 0
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
function sanitizeKnowledgeRequirement(requirement, options) {
|
|
2643
|
+
const includeDescription = options.includeRequirementDescriptions && requirement.sensitivity !== "secret";
|
|
2644
|
+
return {
|
|
2645
|
+
id: requirement.id,
|
|
2646
|
+
description: includeDescription ? requirement.description : void 0,
|
|
2647
|
+
requiredFor: requirement.requiredFor,
|
|
2648
|
+
category: requirement.category,
|
|
2649
|
+
acquisitionMode: requirement.acquisitionMode,
|
|
2650
|
+
importance: requirement.importance,
|
|
2651
|
+
freshness: requirement.freshness,
|
|
2652
|
+
sensitivity: requirement.sensitivity,
|
|
2653
|
+
confidenceNeeded: requirement.confidenceNeeded,
|
|
2654
|
+
currentConfidence: requirement.currentConfidence,
|
|
2655
|
+
evidenceCount: requirement.evidenceIds.length,
|
|
2656
|
+
evidenceIds: options.includeEvidenceIds ? requirement.evidenceIds : void 0,
|
|
2657
|
+
fallbackPolicy: requirement.fallbackPolicy
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
function sanitizeQuestion(question, options) {
|
|
2661
|
+
return {
|
|
2662
|
+
id: question.id,
|
|
2663
|
+
question: options.includeRequirementDescriptions && question.answerType !== "credential" ? question.question : void 0,
|
|
2664
|
+
reason: options.includeRequirementDescriptions ? question.reason : void 0,
|
|
2665
|
+
requirementId: question.requirementId,
|
|
2666
|
+
importance: question.importance,
|
|
2667
|
+
answerType: question.answerType,
|
|
2668
|
+
impactIfUnknown: options.includeRequirementDescriptions ? question.impactIfUnknown : void 0,
|
|
2669
|
+
optionCount: question.options?.length ?? 0
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
function sanitizeAcquisitionPlan(plan) {
|
|
2673
|
+
return {
|
|
2674
|
+
id: plan.id,
|
|
2675
|
+
requirementIds: plan.requirementIds,
|
|
2676
|
+
mode: plan.mode,
|
|
2677
|
+
priority: plan.priority,
|
|
2678
|
+
expectedEvidenceCount: plan.expectedEvidenceIds?.length ?? 0,
|
|
2679
|
+
questionCount: plan.questions?.length ?? 0
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
function sanitizeControlStep(step, options) {
|
|
2683
|
+
const actionOutcome = step.actionOutcome;
|
|
2684
|
+
return {
|
|
2685
|
+
index: step.index,
|
|
2686
|
+
decisionType: step.decision.type,
|
|
2687
|
+
reason: step.decision.reason,
|
|
2688
|
+
action: options.includeControlPayloads && step.decision.type === "continue" ? step.decision.action : void 0,
|
|
2689
|
+
result: options.includeControlPayloads && actionOutcome?.ok ? actionOutcome.result : void 0,
|
|
2690
|
+
actionOk: actionOutcome?.ok,
|
|
2691
|
+
actionError: actionOutcome?.ok === false ? actionOutcome.error : void 0,
|
|
2692
|
+
durationMs: actionOutcome?.durationMs,
|
|
2693
|
+
evalsBefore: summarizeEvals(step.evalsBefore, options),
|
|
2694
|
+
evalsAfter: summarizeEvals(step.evalsAfter, options),
|
|
2695
|
+
startedAt: step.startedAt,
|
|
2696
|
+
endedAt: step.endedAt
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
function sanitizeControlRun(control, options) {
|
|
2700
|
+
return {
|
|
2701
|
+
pass: control.pass,
|
|
2702
|
+
completed: control.completed,
|
|
2703
|
+
reason: control.reason,
|
|
2704
|
+
score: control.score,
|
|
2705
|
+
stepCount: control.steps.length,
|
|
2706
|
+
wallMs: control.wallMs,
|
|
2707
|
+
spentCostUsd: control.spentCostUsd,
|
|
2708
|
+
failureClass: control.failureClass,
|
|
2709
|
+
stoppedBy: control.stoppedBy,
|
|
2710
|
+
runId: control.runId,
|
|
2711
|
+
runtimeErrorCount: control.runtimeErrors.length,
|
|
2712
|
+
finalEvals: summarizeEvals(control.finalEvals, options)
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
function summarizeEvals(evals, options) {
|
|
2716
|
+
return evals.map((evalResult) => ({
|
|
2717
|
+
id: evalResult.id,
|
|
2718
|
+
passed: evalResult.passed,
|
|
2719
|
+
score: evalResult.score,
|
|
2720
|
+
severity: evalResult.severity,
|
|
2721
|
+
objective: evalResult.objective,
|
|
2722
|
+
detail: options.includeEvalDetails ? evalResult.detail : void 0,
|
|
2723
|
+
evidence: options.includeEvalDetails ? evalResult.evidence : void 0
|
|
2724
|
+
}));
|
|
2725
|
+
}
|
|
2726
|
+
function redactRecord(record) {
|
|
2727
|
+
return Object.fromEntries(Object.keys(record).map((key) => [key, "[redacted]"]));
|
|
2728
|
+
}
|
|
2729
|
+
function pickPublicStreamFields(event) {
|
|
2730
|
+
if (event.type === "session_created" || event.type === "session_resumed") return {};
|
|
2731
|
+
if (event.type === "backend_start" || event.type === "backend_end") return { backend: event.backend };
|
|
2732
|
+
if (event.type === "backend_error") {
|
|
2733
|
+
const sanitizedError = event.error !== void 0 ? {
|
|
2734
|
+
kind: event.error.kind,
|
|
2735
|
+
status: event.error.status
|
|
2736
|
+
} : void 0;
|
|
2737
|
+
return {
|
|
2738
|
+
backend: event.backend,
|
|
2739
|
+
message: event.message,
|
|
2740
|
+
recoverable: event.recoverable,
|
|
2741
|
+
...sanitizedError !== void 0 ? { error: sanitizedError } : {}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
if (event.type === "task_end") return {
|
|
2745
|
+
status: event.status,
|
|
2746
|
+
reason: event.reason
|
|
2747
|
+
};
|
|
2748
|
+
if (event.type === "text_delta" || event.type === "reasoning_delta") return { text: event.text };
|
|
2749
|
+
return {};
|
|
2750
|
+
}
|
|
2751
|
+
/** Build an in-memory collector that sanitizes and accumulates `AgentRuntimeEvent`s for inspection. @stable */
|
|
2752
|
+
function createRuntimeEventCollector(options = {}) {
|
|
2753
|
+
const events = [];
|
|
2754
|
+
return {
|
|
2755
|
+
events,
|
|
2756
|
+
onEvent: (event) => {
|
|
2757
|
+
events.push(sanitizeAgentRuntimeEvent(event, options));
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
*
|
|
2763
|
+
* Streaming-event counterpart of `createRuntimeEventCollector`. Pass each
|
|
2764
|
+
* event yielded by `runAgentTaskStream` through `onEvent` and read the
|
|
2765
|
+
* sanitized copies off `events`; the same `RuntimeTelemetryOptions` redaction
|
|
2766
|
+
* flags apply. Kept distinct from `createRuntimeEventCollector` because the
|
|
2767
|
+
* stream and non-stream event shapes overlap on `type` literals — dispatching
|
|
2768
|
+
* on `type` alone would misroute events.
|
|
2769
|
+
*
|
|
2770
|
+
* @stable
|
|
2771
|
+
*/
|
|
2772
|
+
function createRuntimeStreamEventCollector(options = {}) {
|
|
2773
|
+
const events = [];
|
|
2774
|
+
const eventCountsByType = {};
|
|
2775
|
+
let firstSessionId;
|
|
2776
|
+
let finalStatus;
|
|
2777
|
+
let finalReason;
|
|
2778
|
+
let finalText = "";
|
|
2779
|
+
return {
|
|
2780
|
+
events,
|
|
2781
|
+
onEvent: (event) => {
|
|
2782
|
+
events.push(sanitizeRuntimeStreamEvent(event, options));
|
|
2783
|
+
eventCountsByType[event.type] = (eventCountsByType[event.type] ?? 0) + 1;
|
|
2784
|
+
if (event.type === "text_delta") finalText += event.text;
|
|
2785
|
+
if (!firstSessionId && (event.type === "session_created" || event.type === "session_resumed")) firstSessionId = event.session.id;
|
|
2786
|
+
if (event.type === "final") {
|
|
2787
|
+
finalStatus = event.status;
|
|
2788
|
+
finalReason = event.reason;
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
summary() {
|
|
2792
|
+
return {
|
|
2793
|
+
eventCount: events.length,
|
|
2794
|
+
eventCountsByType: { ...eventCountsByType },
|
|
2795
|
+
firstSessionId,
|
|
2796
|
+
finalStatus,
|
|
2797
|
+
finalReason,
|
|
2798
|
+
finalText
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
//#endregion
|
|
2804
|
+
//#region src/otel-export.ts
|
|
2805
|
+
/**
|
|
2806
|
+
* OTEL span exporter — streams LoopTraceEvents to an OTLP/HTTP collector.
|
|
2807
|
+
*
|
|
2808
|
+
* Reads OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_EXPORTER_OTLP_HEADERS from env
|
|
2809
|
+
* when no explicit config is given. Keeps the runtime dep-free from
|
|
2810
|
+
* @opentelemetry/sdk-trace-base — minimal OTLP/JSON serializer.
|
|
2811
|
+
*
|
|
2812
|
+
* The exporter accepts both raw OtelSpan objects and LoopTraceEvents
|
|
2813
|
+
* (which get converted to OTLP spans automatically).
|
|
2814
|
+
*/
|
|
2815
|
+
const SCOPE = {
|
|
2816
|
+
name: "@tangle-network/agent-runtime",
|
|
2817
|
+
version: "0.83.0"
|
|
2818
|
+
};
|
|
2819
|
+
/**
|
|
2820
|
+
* Current (non-deprecated) OpenTelemetry GenAI semantic-convention keys.
|
|
2821
|
+
* Registry: https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/
|
|
2822
|
+
* NB: `gen_ai.system` / `gen_ai.usage.prompt_tokens` / `completion_tokens` are
|
|
2823
|
+
* DEPRECATED — do not emit them. We use `provider.name` + `input/output_tokens`.
|
|
2824
|
+
*/
|
|
2825
|
+
const GEN_AI = {
|
|
2826
|
+
operation: "gen_ai.operation.name",
|
|
2827
|
+
agentName: "gen_ai.agent.name",
|
|
2828
|
+
conversationId: "gen_ai.conversation.id",
|
|
2829
|
+
inputTokens: "gen_ai.usage.input_tokens",
|
|
2830
|
+
outputTokens: "gen_ai.usage.output_tokens"
|
|
2831
|
+
};
|
|
2832
|
+
/**
|
|
2833
|
+
* Create an OTEL exporter. Returns undefined when no endpoint is configured.
|
|
2834
|
+
*/
|
|
2835
|
+
function createOtelExporter(config) {
|
|
2836
|
+
const resolvedEndpoint = config?.endpoint ?? (typeof process !== "undefined" ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : void 0);
|
|
2837
|
+
if (!resolvedEndpoint) return void 0;
|
|
2838
|
+
const endpoint = resolvedEndpoint;
|
|
2839
|
+
const headers = config?.headers ?? parseHeadersFromEnv();
|
|
2840
|
+
const batchSize = config?.batchSize ?? 64;
|
|
2841
|
+
const flushIntervalMs = config?.flushIntervalMs ?? 5e3;
|
|
2842
|
+
const serviceName = config?.serviceName ?? "agent-runtime";
|
|
2843
|
+
const resourceAttrs = config?.resourceAttributes ?? {};
|
|
2844
|
+
const pending = [];
|
|
2845
|
+
let timer;
|
|
2846
|
+
let stopped = false;
|
|
2847
|
+
const exporter = {
|
|
2848
|
+
exportSpan(span) {
|
|
2849
|
+
if (stopped) return;
|
|
2850
|
+
pending.push(span);
|
|
2851
|
+
if (pending.length >= batchSize) doFlush();
|
|
2852
|
+
},
|
|
2853
|
+
async flush() {
|
|
2854
|
+
await doFlush();
|
|
2855
|
+
},
|
|
2856
|
+
async shutdown() {
|
|
2857
|
+
stopped = true;
|
|
2858
|
+
if (timer !== void 0) {
|
|
2859
|
+
clearInterval(timer);
|
|
2860
|
+
timer = void 0;
|
|
2861
|
+
}
|
|
2862
|
+
await doFlush();
|
|
2863
|
+
}
|
|
2864
|
+
};
|
|
2865
|
+
timer = setInterval(() => {
|
|
2866
|
+
if (pending.length > 0) doFlush();
|
|
2867
|
+
}, flushIntervalMs);
|
|
2868
|
+
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
2869
|
+
async function doFlush() {
|
|
2870
|
+
if (pending.length === 0) return;
|
|
2871
|
+
const batch = pending.splice(0);
|
|
2872
|
+
const body = { resourceSpans: [{
|
|
2873
|
+
resource: { attributes: toOtelAttributes({
|
|
2874
|
+
"service.name": serviceName,
|
|
2875
|
+
...resourceAttrs
|
|
2876
|
+
}) },
|
|
2877
|
+
scopeSpans: [{
|
|
2878
|
+
scope: SCOPE,
|
|
2879
|
+
spans: batch
|
|
2880
|
+
}]
|
|
2881
|
+
}] };
|
|
2882
|
+
const url = `${endpoint.replace(/\/+$/, "")}/v1/traces`;
|
|
2883
|
+
try {
|
|
2884
|
+
await fetch(url, {
|
|
2885
|
+
method: "POST",
|
|
2886
|
+
headers: {
|
|
2887
|
+
"content-type": "application/json",
|
|
2888
|
+
...headers
|
|
2889
|
+
},
|
|
2890
|
+
body: JSON.stringify(body)
|
|
2891
|
+
});
|
|
2892
|
+
} catch {}
|
|
2893
|
+
}
|
|
2894
|
+
return exporter;
|
|
2895
|
+
}
|
|
2896
|
+
/**
|
|
2897
|
+
* Convert a LoopTraceEvent into an OtelSpan for export.
|
|
2898
|
+
*/
|
|
2899
|
+
function loopEventToOtelSpan(event, traceId, parentSpanId) {
|
|
2900
|
+
const spanId = generateSpanId();
|
|
2901
|
+
const attrs = {
|
|
2902
|
+
"loop.event_kind": event.kind,
|
|
2903
|
+
"loop.run_id": event.runId
|
|
2904
|
+
};
|
|
2905
|
+
for (const [k, v] of Object.entries(event.payload)) if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") attrs[`loop.${k}`] = v;
|
|
2906
|
+
const ts = msToNs(event.timestamp);
|
|
2907
|
+
return {
|
|
2908
|
+
traceId: padTraceId(traceId),
|
|
2909
|
+
spanId,
|
|
2910
|
+
parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
|
|
2911
|
+
name: event.kind,
|
|
2912
|
+
kind: 1,
|
|
2913
|
+
startTimeUnixNano: ts,
|
|
2914
|
+
endTimeUnixNano: ts,
|
|
2915
|
+
attributes: toOtelAttributes(attrs),
|
|
2916
|
+
status: { code: 1 }
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Build a single flat OtelSpan whose attribute keys are emitted VERBATIM — no
|
|
2921
|
+
* `loop.` namespace. Use for non-loop spans (e.g. the intelligence per-run
|
|
2922
|
+
* span) whose keys ARE the downstream contract (`gen_ai.request.model`,
|
|
2923
|
+
* `tangle.sessionId`) and are exact-matched by readers. `loopEventToOtelSpan`
|
|
2924
|
+
* namespaces payload keys because loop payload fields are free-form; do not
|
|
2925
|
+
* reuse it for spans with contract keys.
|
|
2926
|
+
*/
|
|
2927
|
+
function flatOtelSpan(name, attributes, traceId, timestampMs, parentSpanId, endTimestampMs = timestampMs) {
|
|
2928
|
+
const start = msToNs(timestampMs);
|
|
2929
|
+
const end = msToNs(Math.max(timestampMs, endTimestampMs));
|
|
2930
|
+
return {
|
|
2931
|
+
traceId: padTraceId(traceId),
|
|
2932
|
+
spanId: generateSpanId(),
|
|
2933
|
+
parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
|
|
2934
|
+
name,
|
|
2935
|
+
kind: 1,
|
|
2936
|
+
startTimeUnixNano: start,
|
|
2937
|
+
endTimeUnixNano: end,
|
|
2938
|
+
attributes: toOtelAttributes(attributes),
|
|
2939
|
+
status: { code: 1 }
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function eventTimestampMs(event) {
|
|
2943
|
+
if ("timestamp" in event && typeof event.timestamp === "string") {
|
|
2944
|
+
const parsed = Date.parse(event.timestamp);
|
|
2945
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
2946
|
+
}
|
|
2947
|
+
return Date.now();
|
|
2948
|
+
}
|
|
2949
|
+
function serialized(value) {
|
|
2950
|
+
try {
|
|
2951
|
+
return JSON.stringify(value) ?? String(value);
|
|
2952
|
+
} catch {
|
|
2953
|
+
return String(value);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
function mcpIdentity(toolName) {
|
|
2957
|
+
if (!toolName.startsWith("mcp__")) return {};
|
|
2958
|
+
const [, server, ...toolParts] = toolName.split("__");
|
|
2959
|
+
return {
|
|
2960
|
+
...server ? { server } : {},
|
|
2961
|
+
...toolParts.length > 0 ? { tool: toolParts.join("__") } : {}
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
/** Convert normalized runtime events into lossless, redacted child spans. */
|
|
2965
|
+
function buildRuntimeEventOtelSpans(events, traceId, parentSpanId, options = {}) {
|
|
2966
|
+
return events.map((event) => {
|
|
2967
|
+
const sanitized = sanitizeRuntimeStreamEvent(event, options);
|
|
2968
|
+
const safe = options.redact ? options.redact(sanitized) : sanitized;
|
|
2969
|
+
const record = safe && typeof safe === "object" && !Array.isArray(safe) ? safe : { value: safe };
|
|
2970
|
+
const attrs = {
|
|
2971
|
+
"tangle.runtime.event_type": event.type,
|
|
2972
|
+
"tangle.runtime.event": serialized(record)
|
|
2973
|
+
};
|
|
2974
|
+
let name = `tangle.runtime.${event.type}`;
|
|
2975
|
+
if (event.type === "tool_call" || event.type === "tool_result") {
|
|
2976
|
+
name = `agent.${event.type}`;
|
|
2977
|
+
attrs["tool.name"] = event.toolName;
|
|
2978
|
+
if (event.toolCallId) attrs["tool.call_id"] = event.toolCallId;
|
|
2979
|
+
const mcp = mcpIdentity(event.toolName);
|
|
2980
|
+
if (mcp.server) attrs["mcp.server"] = mcp.server;
|
|
2981
|
+
if (mcp.tool) attrs["mcp.tool.name"] = mcp.tool;
|
|
2982
|
+
const payload = event.type === "tool_call" ? record.args : record.result;
|
|
2983
|
+
if (payload !== void 0) attrs[event.type === "tool_call" ? "tool.input" : "tool.output"] = serialized(payload);
|
|
2984
|
+
} else if (event.type === "llm_call") {
|
|
2985
|
+
name = "gen_ai.client.inference";
|
|
2986
|
+
attrs["gen_ai.request.model"] = event.model;
|
|
2987
|
+
if (event.tokensIn !== void 0) attrs["gen_ai.usage.input_tokens"] = event.tokensIn;
|
|
2988
|
+
if (event.tokensOut !== void 0) attrs["gen_ai.usage.output_tokens"] = event.tokensOut;
|
|
2989
|
+
if (event.costUsd !== void 0) attrs["tangle.cost.usd"] = event.costUsd;
|
|
2990
|
+
if (event.latencyMs !== void 0) attrs["tangle.latency_ms"] = event.latencyMs;
|
|
2991
|
+
if (event.finishReason !== void 0) attrs["gen_ai.response.finish_reasons"] = event.finishReason;
|
|
2992
|
+
} else if (event.type === "backend_error") {
|
|
2993
|
+
attrs["error.type"] = event.error?.kind ?? "backend";
|
|
2994
|
+
attrs["error.message"] = event.message;
|
|
2995
|
+
} else if (event.type === "final") {
|
|
2996
|
+
attrs["tangle.outcome.status"] = event.status;
|
|
2997
|
+
attrs["tangle.outcome.reason"] = event.reason;
|
|
2998
|
+
if (event.error) {
|
|
2999
|
+
attrs["error.type"] = event.error.kind;
|
|
3000
|
+
attrs["error.message"] = event.error.message;
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
const startMs = eventTimestampMs(event);
|
|
3004
|
+
const endMs = event.type === "llm_call" && event.latencyMs !== void 0 && Number.isFinite(event.latencyMs) ? startMs + event.latencyMs : startMs;
|
|
3005
|
+
const span = flatOtelSpan(name, attrs, traceId, startMs, parentSpanId, endMs);
|
|
3006
|
+
if (event.type === "backend_error" || event.type === "final" && event.status !== "completed") span.status = {
|
|
3007
|
+
code: 2,
|
|
3008
|
+
message: attrs["error.message"]?.toString() ?? event.type
|
|
3009
|
+
};
|
|
3010
|
+
return span;
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Build a nested, real-duration OTLP span tree for ONE loop run from its full
|
|
3015
|
+
* ordered `LoopTraceEvent` stream. Unlike `loopEventToOtelSpan` (one flat,
|
|
3016
|
+
* zero-duration span per event), this reconstructs the topology hierarchy a
|
|
3017
|
+
* GenAI trace viewer renders natively:
|
|
3018
|
+
*
|
|
3019
|
+
* loop (invoke_workflow)
|
|
3020
|
+
* └─ loop.round[k] (invoke_workflow) ← tangle.loop.move.{kind,width,rationale}
|
|
3021
|
+
* ├─ loop.iteration[i] (invoke_agent) ← gen_ai.agent.name + usage + verdict + placement
|
|
3022
|
+
* └─ …
|
|
3023
|
+
*
|
|
3024
|
+
* Attributes follow the current GenAI semconv (`gen_ai.*`) where they apply and
|
|
3025
|
+
* a namespaced `tangle.loop.*` / `tangle.cost.usd` extension for topology /
|
|
3026
|
+
* verdict / placement / cost (not yet standardized). Pure: feed it a buffered
|
|
3027
|
+
* per-runId event array (e.g. flushed on `loop.ended`) and export the result.
|
|
3028
|
+
*/
|
|
3029
|
+
function buildLoopOtelSpans(events, traceId, rootParentSpanId) {
|
|
3030
|
+
const tid = padTraceId(traceId);
|
|
3031
|
+
return buildLoopSpanNodes(events).map((node) => ({
|
|
3032
|
+
traceId: tid,
|
|
3033
|
+
spanId: node.spanId,
|
|
3034
|
+
parentSpanId: node.parentSpanId ? padSpanId(node.parentSpanId) : rootParentSpanId ? padSpanId(rootParentSpanId) : void 0,
|
|
3035
|
+
name: node.name,
|
|
3036
|
+
kind: 1,
|
|
3037
|
+
startTimeUnixNano: msToNs(node.startMs),
|
|
3038
|
+
endTimeUnixNano: msToNs(node.endMs),
|
|
3039
|
+
attributes: toOtelAttributes(node.attrs),
|
|
3040
|
+
status: { code: node.error ? 2 : 1 }
|
|
3041
|
+
}));
|
|
3042
|
+
}
|
|
3043
|
+
/**
|
|
3044
|
+
* Sink-neutral core behind {@link buildLoopOtelSpans}: reconstruct the
|
|
3045
|
+
* loop → round → branch span tree from one run's ordered `LoopTraceEvent`
|
|
3046
|
+
* stream. Consumed by the OTEL mapper above and by the MCP delegation
|
|
3047
|
+
* journal's compact trace tee — one topology reconstruction, two sinks.
|
|
3048
|
+
* Tolerates partial streams (a run that never reached `loop.ended` closes
|
|
3049
|
+
* at the last observed event's timestamp).
|
|
3050
|
+
*/
|
|
3051
|
+
function buildLoopSpanNodes(events) {
|
|
3052
|
+
if (events.length === 0) return [];
|
|
3053
|
+
const out = [];
|
|
3054
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
3055
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3056
|
+
const rec = (v) => v && typeof v === "object" ? v : {};
|
|
3057
|
+
const started = events.find((e) => e.kind === "loop.started");
|
|
3058
|
+
const ended = events.find((e) => e.kind === "loop.ended");
|
|
3059
|
+
const runId = events[0]?.runId ?? "";
|
|
3060
|
+
const rootStart = started?.timestamp ?? events[0].timestamp;
|
|
3061
|
+
const rootEnd = ended?.timestamp ?? events[events.length - 1].timestamp;
|
|
3062
|
+
const rootId = generateSpanId();
|
|
3063
|
+
const make = (spanId, parentSpanId, name, kind, startMs, endMs, attrs, error = false) => ({
|
|
3064
|
+
spanId,
|
|
3065
|
+
parentSpanId,
|
|
3066
|
+
name,
|
|
3067
|
+
kind,
|
|
3068
|
+
startMs,
|
|
3069
|
+
endMs,
|
|
3070
|
+
attrs,
|
|
3071
|
+
error
|
|
3072
|
+
});
|
|
3073
|
+
const sp = rec(started?.payload);
|
|
3074
|
+
const rootAttrs = {
|
|
3075
|
+
[GEN_AI.operation]: "invoke_workflow",
|
|
3076
|
+
[GEN_AI.conversationId]: runId,
|
|
3077
|
+
"tangle.run.id": runId,
|
|
3078
|
+
"tangle.loop.driver": str(sp.driver) ?? "driver"
|
|
3079
|
+
};
|
|
3080
|
+
if (Array.isArray(sp.agentRunNames) && sp.agentRunNames.length > 0) {
|
|
3081
|
+
rootAttrs["tangle.loop.agents"] = sp.agentRunNames.map(String).join(",");
|
|
3082
|
+
rootAttrs["tangle.subject.key"] = String(sp.agentRunNames[0]);
|
|
3083
|
+
}
|
|
3084
|
+
if (ended) {
|
|
3085
|
+
const ep = rec(ended.payload);
|
|
3086
|
+
const win = num(ep.winnerIterationIndex);
|
|
3087
|
+
if (win !== void 0) rootAttrs["tangle.loop.winner.iteration_index"] = win;
|
|
3088
|
+
const cost = num(ep.totalCostUsd);
|
|
3089
|
+
if (cost !== void 0) rootAttrs["tangle.cost.usd"] = cost;
|
|
3090
|
+
const dur = num(ep.durationMs);
|
|
3091
|
+
if (dur !== void 0) rootAttrs["tangle.loop.duration_ms"] = dur;
|
|
3092
|
+
const iters = num(ep.iterations);
|
|
3093
|
+
if (iters !== void 0) rootAttrs["tangle.loop.iterations"] = iters;
|
|
3094
|
+
}
|
|
3095
|
+
out.push(make(rootId, void 0, "loop", "loop", rootStart, rootEnd, rootAttrs));
|
|
3096
|
+
const iterStartTs = /* @__PURE__ */ new Map();
|
|
3097
|
+
const placementByIdx = /* @__PURE__ */ new Map();
|
|
3098
|
+
let currentRoundId;
|
|
3099
|
+
let pendingRound;
|
|
3100
|
+
const flushRound = (endMs) => {
|
|
3101
|
+
if (!pendingRound) return;
|
|
3102
|
+
out.push(make(pendingRound.id, rootId, "loop.round", "round", pendingRound.start, endMs, pendingRound.attrs));
|
|
3103
|
+
pendingRound = void 0;
|
|
3104
|
+
};
|
|
3105
|
+
for (const e of events) {
|
|
3106
|
+
const p = rec(e.payload);
|
|
3107
|
+
switch (e.kind) {
|
|
3108
|
+
case "loop.plan": {
|
|
3109
|
+
flushRound(e.timestamp);
|
|
3110
|
+
const id = generateSpanId();
|
|
3111
|
+
const roundIdx = num(p.roundIndex) ?? 0;
|
|
3112
|
+
const attrs = {
|
|
3113
|
+
[GEN_AI.operation]: "invoke_workflow",
|
|
3114
|
+
"tangle.loop.round.index": roundIdx,
|
|
3115
|
+
"tangle.loop.move.kind": str(p.moveKind) ?? "unknown",
|
|
3116
|
+
"tangle.loop.move.round": roundIdx,
|
|
3117
|
+
"tangle.loop.move.width": num(p.plannedCount) ?? 0
|
|
3118
|
+
};
|
|
3119
|
+
const r = str(p.rationale);
|
|
3120
|
+
if (r) attrs["tangle.loop.move.rationale"] = r;
|
|
3121
|
+
const parent = num(p.parentIndex);
|
|
3122
|
+
if (parent !== void 0) attrs["tangle.loop.move.parent_index"] = parent;
|
|
3123
|
+
if (Array.isArray(p.childIndices) && p.childIndices.length > 0) attrs["tangle.loop.move.child_indices"] = p.childIndices.map(String).join(",");
|
|
3124
|
+
pendingRound = {
|
|
3125
|
+
id,
|
|
3126
|
+
start: e.timestamp,
|
|
3127
|
+
attrs
|
|
3128
|
+
};
|
|
3129
|
+
currentRoundId = id;
|
|
3130
|
+
break;
|
|
3131
|
+
}
|
|
3132
|
+
case "loop.iteration.started": {
|
|
3133
|
+
const idx = num(p.iterationIndex);
|
|
3134
|
+
if (idx !== void 0) iterStartTs.set(idx, e.timestamp);
|
|
3135
|
+
break;
|
|
3136
|
+
}
|
|
3137
|
+
case "loop.iteration.dispatch": {
|
|
3138
|
+
const idx = num(p.iterationIndex);
|
|
3139
|
+
if (idx === void 0) break;
|
|
3140
|
+
const place = {};
|
|
3141
|
+
const kind = str(p.placement);
|
|
3142
|
+
if (kind) place["tangle.loop.placement.kind"] = kind;
|
|
3143
|
+
const sid = str(p.sandboxId);
|
|
3144
|
+
if (sid) place["tangle.sandbox.id"] = sid;
|
|
3145
|
+
const fid = str(p.fleetId);
|
|
3146
|
+
if (fid) place["tangle.fleet.id"] = fid;
|
|
3147
|
+
const mid = str(p.machineId);
|
|
3148
|
+
if (mid) place["tangle.machine.id"] = mid;
|
|
3149
|
+
placementByIdx.set(idx, place);
|
|
3150
|
+
break;
|
|
3151
|
+
}
|
|
3152
|
+
case "loop.iteration.ended": {
|
|
3153
|
+
const idx = num(p.iterationIndex) ?? 0;
|
|
3154
|
+
const start = iterStartTs.get(idx) ?? e.timestamp;
|
|
3155
|
+
const err = str(p.error);
|
|
3156
|
+
const attrs = {
|
|
3157
|
+
[GEN_AI.operation]: "invoke_agent",
|
|
3158
|
+
"tangle.loop.iteration.index": idx
|
|
3159
|
+
};
|
|
3160
|
+
const agent = str(p.agentRunName);
|
|
3161
|
+
if (agent) attrs[GEN_AI.agentName] = agent;
|
|
3162
|
+
const tu = rec(p.tokenUsage);
|
|
3163
|
+
const inTok = num(tu.input);
|
|
3164
|
+
if (inTok !== void 0) attrs[GEN_AI.inputTokens] = inTok;
|
|
3165
|
+
const outTok = num(tu.output);
|
|
3166
|
+
if (outTok !== void 0) attrs[GEN_AI.outputTokens] = outTok;
|
|
3167
|
+
const cost = num(p.costUsd);
|
|
3168
|
+
if (cost !== void 0) attrs["tangle.cost.usd"] = cost;
|
|
3169
|
+
const verdict = rec(p.verdict);
|
|
3170
|
+
if (typeof verdict.valid === "boolean") attrs["tangle.loop.verdict.valid"] = verdict.valid;
|
|
3171
|
+
const score = num(verdict.score);
|
|
3172
|
+
if (score !== void 0) attrs["tangle.loop.verdict.score"] = score;
|
|
3173
|
+
if (err) attrs["tangle.loop.error"] = err;
|
|
3174
|
+
const gid = num(p.groupId);
|
|
3175
|
+
if (gid !== void 0) attrs["tangle.loop.iteration.group_id"] = gid;
|
|
3176
|
+
const par = num(p.parentIndex);
|
|
3177
|
+
if (par !== void 0) attrs["tangle.loop.iteration.parent_index"] = par;
|
|
3178
|
+
const dur = num(p.durationMs);
|
|
3179
|
+
if (dur !== void 0) attrs["tangle.loop.iteration.duration_ms"] = dur;
|
|
3180
|
+
const preview = str(p.outputPreview);
|
|
3181
|
+
if (preview) attrs["tangle.loop.iteration.output_preview"] = preview;
|
|
3182
|
+
Object.assign(attrs, placementByIdx.get(idx) ?? {});
|
|
3183
|
+
out.push(make(generateSpanId(), currentRoundId ?? rootId, "loop.iteration", "branch", start, e.timestamp, attrs, err !== void 0));
|
|
3184
|
+
break;
|
|
3185
|
+
}
|
|
3186
|
+
case "loop.decision":
|
|
3187
|
+
if (pendingRound) {
|
|
3188
|
+
const dec = str(p.decision);
|
|
3189
|
+
if (dec) pendingRound.attrs["tangle.loop.decision"] = dec;
|
|
3190
|
+
flushRound(e.timestamp);
|
|
3191
|
+
}
|
|
3192
|
+
currentRoundId = void 0;
|
|
3193
|
+
break;
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
flushRound(rootEnd);
|
|
3197
|
+
return out;
|
|
3198
|
+
}
|
|
3199
|
+
function parseHeadersFromEnv() {
|
|
3200
|
+
if (typeof process === "undefined") return {};
|
|
3201
|
+
const raw = process.env.OTEL_EXPORTER_OTLP_HEADERS;
|
|
3202
|
+
if (!raw) return {};
|
|
3203
|
+
const out = {};
|
|
3204
|
+
for (const pair of raw.split(",")) {
|
|
3205
|
+
const eq = pair.indexOf("=");
|
|
3206
|
+
if (eq < 0) continue;
|
|
3207
|
+
const key = pair.slice(0, eq).trim();
|
|
3208
|
+
const value = pair.slice(eq + 1).trim();
|
|
3209
|
+
if (key) out[key] = value;
|
|
3210
|
+
}
|
|
3211
|
+
return out;
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* Convert a flat record into the OTLP attribute list. Non-finite numbers are DROPPED (an OTLP
|
|
3215
|
+
* `doubleValue` of `NaN`/`Infinity` is not representable), integers ride as `intValue`. Exported so
|
|
3216
|
+
* a producer that mints its own `OtelSpan` (the supervisor span recorder) builds attributes exactly
|
|
3217
|
+
* the way every span in this file does, rather than re-deriving the encoding.
|
|
3218
|
+
*/
|
|
3219
|
+
function toOtelAttributes(record) {
|
|
3220
|
+
return Object.entries(record).flatMap(([key, value]) => {
|
|
3221
|
+
if (typeof value === "number" && !Number.isFinite(value)) return [];
|
|
3222
|
+
return [{
|
|
3223
|
+
key,
|
|
3224
|
+
value: typeof value === "number" ? Number.isInteger(value) ? { intValue: value.toString() } : { doubleValue: value } : typeof value === "boolean" ? { boolValue: value } : { stringValue: value }
|
|
3225
|
+
}];
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
function msToNs(ms) {
|
|
3229
|
+
return (BigInt(Math.floor(Number.isFinite(ms) ? ms : Date.now())) * 1000000n).toString();
|
|
3230
|
+
}
|
|
3231
|
+
function padSpanId(id) {
|
|
3232
|
+
return id.replace(/-/g, "").slice(0, 16).padEnd(16, "0");
|
|
3233
|
+
}
|
|
3234
|
+
function padTraceId(id) {
|
|
3235
|
+
return id.replace(/-/g, "").slice(0, 32).padEnd(32, "0");
|
|
3236
|
+
}
|
|
3237
|
+
/** Mint a fresh 16-hex-character OTLP span id. Exported so a producer that must know a span's id
|
|
3238
|
+
* BEFORE the span closes (a node opened at spawn and parented by its children) uses this one
|
|
3239
|
+
* generator instead of a second copy of it. */
|
|
3240
|
+
function generateSpanId() {
|
|
3241
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
3242
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") globalThis.crypto.getRandomValues(bytes);
|
|
3243
|
+
else for (let i = 0; i < 8; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
3244
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3245
|
+
}
|
|
3246
|
+
/** Wire version the eval-runs ingest enforces (X-Tangle-Wire-Version + body). */
|
|
3247
|
+
const INTELLIGENCE_WIRE_VERSION = "2026-05-26.v1";
|
|
3248
|
+
const DEFAULT_INTELLIGENCE_BASE = "https://intelligence.tangle.tools";
|
|
3249
|
+
/**
|
|
3250
|
+
* Ship self-improvement eval-run events to Tangle Intelligence. Unlike the
|
|
3251
|
+
* best-effort span exporter, this RESOLVES with the ingest verdict (accepted /
|
|
3252
|
+
* rejected per event) so a consumer's loop can assert its provenance landed.
|
|
3253
|
+
* Throws only on a missing key or network failure.
|
|
3254
|
+
*/
|
|
3255
|
+
async function exportEvalRuns(events, config) {
|
|
3256
|
+
if (events.length === 0) return {
|
|
3257
|
+
ok: true,
|
|
3258
|
+
status: 0,
|
|
3259
|
+
accepted: 0,
|
|
3260
|
+
rejected: []
|
|
3261
|
+
};
|
|
3262
|
+
const apiKey = config?.apiKey ?? (typeof process !== "undefined" ? process.env.TANGLE_API_KEY : void 0);
|
|
3263
|
+
if (!apiKey) throw new Error("exportEvalRuns: apiKey required (pass config.apiKey or set TANGLE_API_KEY)");
|
|
3264
|
+
const url = `${(config?.base ?? (typeof process !== "undefined" ? process.env.TANGLE_INTELLIGENCE_URL : void 0) ?? DEFAULT_INTELLIGENCE_BASE).replace(/\/+$/, "")}/v1/ingest/eval-runs`;
|
|
3265
|
+
const res = await fetch(url, {
|
|
3266
|
+
method: "POST",
|
|
3267
|
+
headers: {
|
|
3268
|
+
"content-type": "application/json",
|
|
3269
|
+
authorization: `Bearer ${apiKey}`,
|
|
3270
|
+
"X-Tangle-Wire-Version": INTELLIGENCE_WIRE_VERSION,
|
|
3271
|
+
...config?.idempotencyKey ? { "Idempotency-Key": config.idempotencyKey } : {}
|
|
3272
|
+
},
|
|
3273
|
+
body: JSON.stringify({
|
|
3274
|
+
wireVersion: INTELLIGENCE_WIRE_VERSION,
|
|
3275
|
+
events
|
|
3276
|
+
})
|
|
3277
|
+
});
|
|
3278
|
+
let parsed = {};
|
|
3279
|
+
try {
|
|
3280
|
+
parsed = await res.json();
|
|
3281
|
+
} catch {}
|
|
3282
|
+
return {
|
|
3283
|
+
ok: res.ok,
|
|
3284
|
+
status: res.status,
|
|
3285
|
+
accepted: parsed.accepted ?? (res.ok ? events.length : 0),
|
|
3286
|
+
rejected: parsed.rejected ?? []
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
//#endregion
|
|
3290
|
+
//#region src/mcp/trace-propagation.ts
|
|
3291
|
+
/**
|
|
3292
|
+
* Read trace context from the process environment.
|
|
3293
|
+
* Returns a context with inherited ids or a freshly generated root.
|
|
3294
|
+
*/
|
|
3295
|
+
function readTraceContextFromEnv() {
|
|
3296
|
+
return {
|
|
3297
|
+
traceId: process.env.TRACE_ID || generateTraceId(),
|
|
3298
|
+
parentSpanId: process.env.PARENT_SPAN_ID || void 0
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
/**
|
|
3302
|
+
* Create a LoopTraceEmitter that:
|
|
3303
|
+
* 1. Parents all spans under the inherited PARENT_SPAN_ID.
|
|
3304
|
+
* 2. Exports spans to OTEL when OTEL_EXPORTER_OTLP_ENDPOINT is set.
|
|
3305
|
+
*
|
|
3306
|
+
* Returns both the emitter and the optional exporter handle for shutdown.
|
|
3307
|
+
*/
|
|
3308
|
+
function createPropagatingTraceEmitter(ctx) {
|
|
3309
|
+
const exporter = createOtelExporter();
|
|
3310
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
3311
|
+
return {
|
|
3312
|
+
emitter: { emit(event) {
|
|
3313
|
+
if (!exporter) return;
|
|
3314
|
+
const buf = buffers.get(event.runId);
|
|
3315
|
+
if (buf) buf.push(event);
|
|
3316
|
+
else buffers.set(event.runId, [event]);
|
|
3317
|
+
if (event.kind === "loop.ended") {
|
|
3318
|
+
const events = buffers.get(event.runId) ?? [event];
|
|
3319
|
+
buffers.delete(event.runId);
|
|
3320
|
+
for (const span of buildLoopOtelSpans(events, ctx.traceId, ctx.parentSpanId)) exporter.exportSpan(span);
|
|
3321
|
+
}
|
|
3322
|
+
} },
|
|
3323
|
+
exporter,
|
|
3324
|
+
context: ctx
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Build env vars to pass to a child MCP subprocess so it inherits the
|
|
3329
|
+
* current trace context.
|
|
3330
|
+
*/
|
|
3331
|
+
function traceContextToEnv(ctx) {
|
|
3332
|
+
const env = { TRACE_ID: ctx.traceId };
|
|
3333
|
+
if (ctx.parentSpanId) env.PARENT_SPAN_ID = ctx.parentSpanId;
|
|
3334
|
+
return env;
|
|
3335
|
+
}
|
|
3336
|
+
function generateTraceId() {
|
|
3337
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
3338
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") globalThis.crypto.getRandomValues(bytes);
|
|
3339
|
+
else for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
3340
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3341
|
+
}
|
|
3342
|
+
//#endregion
|
|
3343
|
+
//#region src/runtime/supervise/worker-trace.ts
|
|
3344
|
+
/**
|
|
3345
|
+
* Trace context handed DOWN to a spawned worker, so one supervised tree spans machines.
|
|
3346
|
+
*
|
|
3347
|
+
* WHY. `otel-spans.ts` makes the supervisor's own tree readable by any trace viewer, but it stops at
|
|
3348
|
+
* the process boundary: a worker running on a remote sandbox or in a child process opens its OWN
|
|
3349
|
+
* trace root, so the viewer shows two unrelated trees for one run. A worker that inherits the
|
|
3350
|
+
* parent's trace id and the spawning node's span id emits spans that JOIN the parent's trace, and
|
|
3351
|
+
* the viewer assembles the whole cross-machine tree with no viewer change.
|
|
3352
|
+
*
|
|
3353
|
+
* NO NEW FORMAT. The wire is the env-var convention this package already ships and already reads:
|
|
3354
|
+
* `TRACE_ID` / `PARENT_SPAN_ID`, written by {@link traceContextToEnv} and read back by
|
|
3355
|
+
* `readTraceContextFromEnv()` (`src/mcp/trace-propagation.ts`). Nothing here invents a second
|
|
3356
|
+
* encoding; W3C `traceparent` would be the standard alternative, but adopting it alongside this one
|
|
3357
|
+
* would fork the contract, and the reader for THIS one is already in the package.
|
|
3358
|
+
*
|
|
3359
|
+
* WHICH SPAN. The stamped `PARENT_SPAN_ID` is the span of the node that DID the spawning — the
|
|
3360
|
+
* scope's own `parentId` — not the span of the worker's own node. At depth 0 that is the run's root
|
|
3361
|
+
* span; inside a driver's nested scope it is that driver child's span, so a depth-2 worker joins
|
|
3362
|
+
* under the middle node rather than the root. That is also the only span guaranteed to exist at the
|
|
3363
|
+
* moment the child's `ExecutorContext` is built (the worker's own node span is opened by the
|
|
3364
|
+
* `agent.spawn` hook that fires just after), so the wire needs no ordering assumption.
|
|
3365
|
+
*
|
|
3366
|
+
* OFF WHEN TRACING IS OFF. The context reaches an executor only through the `ExecutorContext` seam
|
|
3367
|
+
* this module names, and that seam is seeded only when a run configured `SuperviseOptions.otel` AND
|
|
3368
|
+
* an exporter resolved. With tracing off there is no seam, {@link workerTraceEnv} returns an empty
|
|
3369
|
+
* record, and every spawn environment is byte-identical to what it was before this module existed.
|
|
3370
|
+
*
|
|
3371
|
+
* TWO SEEDING SITES, because there are two ways a worker's `ExecutorContext` gets built. `Scope`
|
|
3372
|
+
* seeds it per child for anything the executor registry resolves — that is the path that knows the
|
|
3373
|
+
* spawning node and reaches arbitrary depth. `workerFromBackend` (what `supervise({ backend })`
|
|
3374
|
+
* uses) instead builds its leaf executor eagerly and hands it back as a bring-your-own `Executor`,
|
|
3375
|
+
* which the registry resolves WITHOUT consulting the scope's context at all; `supervise()` therefore
|
|
3376
|
+
* passes the seam to `workerFromBackend` itself, resolved against the run root, which is the node
|
|
3377
|
+
* that spawns every front-door worker. Miss that second site and the front door looks wired and
|
|
3378
|
+
* stamps nothing.
|
|
3379
|
+
*
|
|
3380
|
+
* PRECEDENCE, from lowest to highest:
|
|
3381
|
+
* 1. the supervisor process's own `process.env` (ambient inheritance),
|
|
3382
|
+
* 2. the trace context stamped here,
|
|
3383
|
+
* 3. the caller's own seam env (`PiSeam.env`, `CliSeam.env`).
|
|
3384
|
+
* A caller who sets `TRACE_ID` / `PARENT_SPAN_ID` on a seam wins — theirs is a deliberate
|
|
3385
|
+
* declaration about the worker. Ambient `process.env` does NOT win: when the supervisor process was
|
|
3386
|
+
* itself launched as someone's worker, its inherited ids describe the SUPERVISOR's place in an
|
|
3387
|
+
* outer trace, and reusing them for a child would file the child's spans under the wrong parent.
|
|
3388
|
+
* The supported way to join that outer trace is `SupervisorSpanOptions.traceId` / `parentSpanId`
|
|
3389
|
+
* (feed them from `readTraceContextFromEnv()`), which makes the recorder's own ids the outer ones
|
|
3390
|
+
* and so makes every stamped child inherit them too.
|
|
3391
|
+
*
|
|
3392
|
+
* WHICH BACKENDS PROPAGATE. Only a backend with a real environment channel to the worker can carry
|
|
3393
|
+
* this, and the ones that cannot say so here rather than dropping it silently:
|
|
3394
|
+
* - `pi` YES — `PiSeam.env` → the `pi --mode rpc` subprocess.
|
|
3395
|
+
* - `cli` YES — `CliSeam.env` → the spawned subprocess.
|
|
3396
|
+
* - `sandbox` YES — `CreateSandboxOptions.env` on the box the worker runs in (single-shot
|
|
3397
|
+
* and steerable). This is the cross-MACHINE case the feature exists for.
|
|
3398
|
+
* - `router`,
|
|
3399
|
+
* `router-tools` NO — a direct model HTTP call. There is no worker process to inherit anything.
|
|
3400
|
+
* - `bridge`,
|
|
3401
|
+
* `cli-worktree` NO — the work is dispatched over cli-bridge HTTP or through a local harness
|
|
3402
|
+
* transport that exposes no environment channel. Wiring these means adding that
|
|
3403
|
+
* channel to the transport first; until then they are honestly unpropagated.
|
|
3404
|
+
* - `provider` NO — `AgentEnvironmentProvider` has no environment field on its port.
|
|
3405
|
+
*/
|
|
3406
|
+
/**
|
|
3407
|
+
* Seam key the `Scope` seeds a {@link TraceContext} under on each child's `ExecutorContext.seams`.
|
|
3408
|
+
* Single-sourced here so the scope and every backend agree on it without a circular import — the
|
|
3409
|
+
* same arrangement `nestedScopeSeamKey` uses.
|
|
3410
|
+
*/
|
|
3411
|
+
const workerTraceSeamKey = "worker-trace";
|
|
3412
|
+
/**
|
|
3413
|
+
* Read the inherited trace context off an `ExecutorContext`, or `undefined` when the run records no
|
|
3414
|
+
* spans. Fails CLOSED on a malformed seam value (returns `undefined`) rather than stamping a
|
|
3415
|
+
* half-formed id that would produce an unjoinable orphan span downstream.
|
|
3416
|
+
*/
|
|
3417
|
+
function readWorkerTraceContext(ctx) {
|
|
3418
|
+
const seam = ctx.seams[workerTraceSeamKey];
|
|
3419
|
+
if (seam === void 0 || seam === null || typeof seam !== "object") return void 0;
|
|
3420
|
+
const { traceId, parentSpanId } = seam;
|
|
3421
|
+
if (typeof traceId !== "string" || traceId.length === 0) return void 0;
|
|
3422
|
+
return {
|
|
3423
|
+
traceId,
|
|
3424
|
+
...typeof parentSpanId === "string" && parentSpanId.length > 0 ? { parentSpanId } : {}
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* The `TRACE_ID` / `PARENT_SPAN_ID` pair to merge into a worker's environment — EMPTY when the run
|
|
3429
|
+
* records no spans, which is what keeps the untraced path byte-identical. Merge it BELOW the
|
|
3430
|
+
* caller's own seam env so a deliberately-set id wins (see the precedence note above).
|
|
3431
|
+
*/
|
|
3432
|
+
function workerTraceEnv(ctx) {
|
|
3433
|
+
const traceContext = readWorkerTraceContext(ctx);
|
|
3434
|
+
return traceContext ? traceContextToEnv(traceContext) : {};
|
|
3435
|
+
}
|
|
3436
|
+
//#endregion
|
|
2441
3437
|
//#region src/runtime/supervise/scope.ts
|
|
2442
3438
|
/**
|
|
2443
3439
|
*
|
|
@@ -2490,7 +3486,8 @@ function makeNestedScopeSeam(args, childNodeId) {
|
|
|
2490
3486
|
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
2491
3487
|
signal,
|
|
2492
3488
|
...args.now ? { now: args.now } : {},
|
|
2493
|
-
...args.hooks ? { hooks: args.hooks } : {}
|
|
3489
|
+
...args.hooks ? { hooks: args.hooks } : {},
|
|
3490
|
+
...args.workerTrace ? { workerTrace: args.workerTrace } : {}
|
|
2494
3491
|
});
|
|
2495
3492
|
}
|
|
2496
3493
|
};
|
|
@@ -2579,11 +3576,13 @@ function createScope(args) {
|
|
|
2579
3576
|
const cascadeAbort = () => childAbort.abort();
|
|
2580
3577
|
if (args.signal.aborted) childAbort.abort();
|
|
2581
3578
|
else args.signal.addEventListener("abort", cascadeAbort, { once: true });
|
|
3579
|
+
const workerTrace = args.workerTrace?.(args.parentId);
|
|
2582
3580
|
const ctx = {
|
|
2583
3581
|
signal: childAbort.signal,
|
|
2584
3582
|
seams: {
|
|
2585
3583
|
...args.seams,
|
|
2586
|
-
[nestedScopeSeamKey]: makeNestedScopeSeam(args, id)
|
|
3584
|
+
[nestedScopeSeamKey]: makeNestedScopeSeam(args, id),
|
|
3585
|
+
...workerTrace ? { [workerTraceSeamKey]: workerTrace } : {}
|
|
2587
3586
|
}
|
|
2588
3587
|
};
|
|
2589
3588
|
const executor = resolved.value(spec, ctx);
|
|
@@ -3578,7 +4577,12 @@ function isAgent(value) {
|
|
|
3578
4577
|
*
|
|
3579
4578
|
* Pure and deterministic: `now()` is injected, there is no I/O, and no wall-clock or
|
|
3580
4579
|
* RNG read. A `reserve`/`reconcile` ticket is single-use (fail-loud on double or
|
|
3581
|
-
* unknown reconcile) so a child can never refund twice.
|
|
4580
|
+
* unknown reconcile) so a child can never refund twice. Reconciling an OPEN ticket always
|
|
4581
|
+
* closes it: a fail-loud condition settles the reservation first and throws afterwards, so no
|
|
4582
|
+
* error path can strand a reservation past the join barrier's `assertNoOpenTickets`.
|
|
4583
|
+
* A child that declared no `maxUsd` reserved no dollar allocation, so its real dollars are
|
|
4584
|
+
* committed as observed spend and debited from the root's balance rather than treated as an
|
|
4585
|
+
* overspend of a $0 ceiling it never asked for. If dollar cost is unknowable under a
|
|
3582
4586
|
* dollar limit, reconciliation closes the known token/iteration work, marks the dollar channel
|
|
3583
4587
|
* unusable, and refuses later reservations; inventing a numeric dollar total would be worse.
|
|
3584
4588
|
* If a TOKEN count is unknowable (a provider that reported no usage for work that ran), the pool
|
|
@@ -3700,7 +4704,8 @@ function createBudgetPool(root, now = Date.now) {
|
|
|
3700
4704
|
reserved: {
|
|
3701
4705
|
tokens: wantTokens,
|
|
3702
4706
|
usd: wantUsd,
|
|
3703
|
-
iterations: wantIterations
|
|
4707
|
+
iterations: wantIterations,
|
|
4708
|
+
usdBudgeted: b.maxUsd !== void 0
|
|
3704
4709
|
}
|
|
3705
4710
|
}
|
|
3706
4711
|
};
|
|
@@ -3710,9 +4715,12 @@ function createBudgetPool(root, now = Date.now) {
|
|
|
3710
4715
|
const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
|
|
3711
4716
|
const unknownUnderCap = usdCapped && spent.usdKnown === false;
|
|
3712
4717
|
const spentTokens = totalTokens(spent.tokens);
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
if (
|
|
4718
|
+
const usdBudgeted = ticket.reserved.usdBudgeted !== false;
|
|
4719
|
+
let violation;
|
|
4720
|
+
if (spentTokens > rTokens) violation = `ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`;
|
|
4721
|
+
else if (spent.iterations > rIterations) violation = `ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`;
|
|
4722
|
+
else if (usdCapped && usdBudgeted && spent.usd > rUsd) violation = `ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`;
|
|
4723
|
+
else if (unknownUnderCap) violation = `ticket ${ticket.id} reported unknown dollar cost under a dollar-capped budget`;
|
|
3716
4724
|
open.delete(ticket.id);
|
|
3717
4725
|
if (spent.tokensKnown === false) tokensTainted = true;
|
|
3718
4726
|
reservedTokens -= rTokens;
|
|
@@ -3729,7 +4737,7 @@ function createBudgetPool(root, now = Date.now) {
|
|
|
3729
4737
|
freeUsd = 0;
|
|
3730
4738
|
} else freeUsd += rUsd - spent.usd;
|
|
3731
4739
|
} else committedUsd += spent.usd;
|
|
3732
|
-
if (
|
|
4740
|
+
if (violation !== void 0) throw new Error(`budget pool: ${violation}`);
|
|
3733
4741
|
}
|
|
3734
4742
|
function observe(spend) {
|
|
3735
4743
|
if (usdCapped && spend.usdKnown === false) throw new Error("budget pool: cannot observe unknown dollar cost under a dollar-capped budget");
|
|
@@ -4019,6 +5027,7 @@ function createSupervisor() {
|
|
|
4019
5027
|
now,
|
|
4020
5028
|
hooks: opts.hooks,
|
|
4021
5029
|
...opts.probes ? { probes: opts.probes } : {},
|
|
5030
|
+
...opts.workerTrace ? { workerTrace: opts.workerTrace } : {},
|
|
4022
5031
|
...resumeFrom ? { resumeFrom } : {}
|
|
4023
5032
|
});
|
|
4024
5033
|
const openScope = scope;
|
|
@@ -4264,6 +5273,6 @@ function isNonEmptySpend(s) {
|
|
|
4264
5273
|
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false;
|
|
4265
5274
|
}
|
|
4266
5275
|
//#endregion
|
|
4267
|
-
export {
|
|
5276
|
+
export { removeWorktree as $, sanitizeAgentRuntimeEvent as A, createActivityLog as B, exportEvalRuns as C, toOtelAttributes as D, loopEventToOtelSpan as E, pollFor as F, routerToolLoop as G, routerBrain as H, timerAt as I, runSettledCommand as J, streamRouterChatWithTools as K, validateWaitSpec as L, sanitizeRuntimeStreamEvent as M, createWaitProbes as N, createRuntimeEventCollector as O, isWaitOutcome as P, createWorktree as Q, waitUntil as R, createOtelExporter as S, generateSpanId as T, routerChatWithTools as U, readWorkerProgress as V, routerChatWithUsage as W, runWorktreeHarness as X, runWorktreeChecks as Y, captureWorktreeDiff as Z, traceContextToEnv as _, runFinalizer as a, buildLoopSpanNodes as b, spendFromUsageEvents as c, settledToIteration as d, harnessInvocation as et, readWorkerTraceContext as f, readTraceContextFromEnv as g, createPropagatingTraceEmitter as h, pickBestDelivered as i, sanitizeKnowledgeReadinessReport as j, createRuntimeStreamEventCollector as k, withDriverExecutor as l, workerTraceSeamKey as m, bestDelivered as n, runLocalHarness as nt, runTree as o, workerTraceEnv as p, runBrainLoop as q, collectDelivered as r, CodexExecutionDiagnosticError as rt, createBudgetPool as s, createSupervisor as t, parseCodexTokenUsage as tt, createScope as u, INTELLIGENCE_WIRE_VERSION as v, flatOtelSpan as w, buildRuntimeEventOtelSpans as x, buildLoopOtelSpans as y, DEFAULT_STALL_AFTER_MS as z };
|
|
4268
5277
|
|
|
4269
|
-
//# sourceMappingURL=supervisor-
|
|
5278
|
+
//# sourceMappingURL=supervisor-BBbPBXpe.js.map
|