@tangle-network/agent-runtime 0.52.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -16
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +5 -3
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/analyst-loop.js +2 -2
- package/dist/{chunk-COAVO6QB.js → chunk-2BDXWZUC.js} +5 -34
- package/dist/chunk-2BDXWZUC.js.map +1 -0
- package/dist/chunk-A73RADPB.js +281 -0
- package/dist/chunk-A73RADPB.js.map +1 -0
- package/dist/chunk-FRBWCJLP.js +396 -0
- package/dist/chunk-FRBWCJLP.js.map +1 -0
- package/dist/chunk-GRAGM4MC.js +72 -0
- package/dist/chunk-GRAGM4MC.js.map +1 -0
- package/dist/{chunk-2OU7ZQPD.js → chunk-JTH2FPCK.js} +3248 -456
- package/dist/chunk-JTH2FPCK.js.map +1 -0
- package/dist/{chunk-V2K35HF2.js → chunk-KJH62YEK.js} +2 -2
- package/dist/{chunk-HNUXAZIJ.js → chunk-P5OKDSLB.js} +2 -2
- package/dist/{chunk-GSUO5QS6.js → chunk-VLF5RHEQ.js} +2 -5
- package/dist/{chunk-GSUO5QS6.js.map → chunk-VLF5RHEQ.js.map} +1 -1
- package/dist/{chunk-7JITYN6T.js → chunk-XRYEZPR6.js} +18 -2
- package/dist/chunk-XRYEZPR6.js.map +1 -0
- package/dist/{coder-_YCf3BAK.d.ts → coder-CEkUFv8h.d.ts} +1 -1
- package/dist/{types-5MGt5KTY.d.ts → coordination-BMzskrUR.d.ts} +268 -2
- package/dist/{kb-gate-CHAyt4aI.d.ts → delegates-BPLIl8EC.d.ts} +491 -566
- package/dist/{delegation-profile-1GbW5yA3.d.ts → delegation-profile-Bvfro2m1.d.ts} +28 -2
- package/dist/index.d.ts +7 -7
- package/dist/index.js +25 -80
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +475 -5
- package/dist/intelligence.js +547 -3
- package/dist/intelligence.js.map +1 -1
- package/dist/kb-gate-CuzMYGYM.d.ts +76 -0
- package/dist/{loop-runner-bin-DFUNgpeK.d.ts → loop-runner-bin-BGpVVyXp.d.ts} +6 -27
- package/dist/loop-runner-bin.d.ts +5 -5
- package/dist/loop-runner-bin.js +6 -5
- package/dist/loops.d.ts +8 -5
- package/dist/loops.js +32 -8
- package/dist/mcp/bin.js +94 -30
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +10 -273
- package/dist/mcp/index.js +45 -365
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-D4HLDWgw.d.ts → openai-tools-CoeLQ7Uo.d.ts} +1 -1
- package/dist/profiles.d.ts +2 -2
- package/dist/profiles.js +4 -4
- package/dist/router-client-30Y_pca8.d.ts +120 -0
- package/dist/{run-loop-BIineL1T.d.ts → run-loop-DluzfJ2h.d.ts} +1 -1
- package/dist/runtime.d.ts +429 -131
- package/dist/runtime.js +32 -8
- package/dist/topology.d.ts +54 -1
- package/dist/topology.js +222 -0
- package/dist/topology.js.map +1 -1
- package/dist/{types-BEQsBhOE.d.ts → types-C8rNlxfV.d.ts} +6 -5
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +4 -2
- package/dist/workflow.js.map +1 -1
- package/package.json +5 -5
- package/skills/agent-runtime-adoption/SKILL.md +54 -46
- package/skills/build-with-agent-runtime/SKILL.md +12 -8
- package/skills/loop-writer/SKILL.md +9 -1
- package/skills/supervise/SKILL.md +24 -0
- package/dist/chunk-2OU7ZQPD.js.map +0 -1
- package/dist/chunk-4JI4BCBI.js +0 -623
- package/dist/chunk-4JI4BCBI.js.map +0 -1
- package/dist/chunk-7JITYN6T.js.map +0 -1
- package/dist/chunk-7SP2OVYZ.js +0 -908
- package/dist/chunk-7SP2OVYZ.js.map +0 -1
- package/dist/chunk-BERLUBAP.js +0 -1356
- package/dist/chunk-BERLUBAP.js.map +0 -1
- package/dist/chunk-COAVO6QB.js.map +0 -1
- package/dist/driver-DLI1io57.d.ts +0 -221
- /package/dist/{chunk-V2K35HF2.js.map → chunk-KJH62YEK.js.map} +0 -0
- /package/dist/{chunk-HNUXAZIJ.js.map → chunk-P5OKDSLB.js.map} +0 -0
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
UI_LENSES
|
|
3
|
+
} from "./chunk-WIR4HOOJ.js";
|
|
4
|
+
import {
|
|
5
|
+
AgentEvalError,
|
|
2
6
|
AnalystError,
|
|
7
|
+
NotFoundError,
|
|
3
8
|
PlannerError,
|
|
4
9
|
RuntimeRunStateError,
|
|
5
10
|
ValidationError,
|
|
6
11
|
extractLlmCallEvent
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-VLF5RHEQ.js";
|
|
13
|
+
import {
|
|
14
|
+
buildLoopSpanNodes
|
|
15
|
+
} from "./chunk-G3RGMA7C.js";
|
|
8
16
|
|
|
9
17
|
// src/durable/spawn-journal.ts
|
|
10
18
|
import { createHash } from "crypto";
|
|
@@ -206,8 +214,8 @@ var InMemorySpawnJournal = class {
|
|
|
206
214
|
}
|
|
207
215
|
};
|
|
208
216
|
var FileSpawnJournal = class {
|
|
209
|
-
constructor(
|
|
210
|
-
this.path =
|
|
217
|
+
constructor(path2) {
|
|
218
|
+
this.path = path2;
|
|
211
219
|
}
|
|
212
220
|
path;
|
|
213
221
|
async loadTree(root) {
|
|
@@ -277,8 +285,8 @@ var FileSpawnJournal = class {
|
|
|
277
285
|
}
|
|
278
286
|
async appendRecord(record) {
|
|
279
287
|
const fs = await import("fs/promises");
|
|
280
|
-
const
|
|
281
|
-
await fs.mkdir(
|
|
288
|
+
const path2 = await import("path");
|
|
289
|
+
await fs.mkdir(path2.dirname(this.path), { recursive: true });
|
|
282
290
|
const fh = await fs.open(this.path, "a");
|
|
283
291
|
try {
|
|
284
292
|
await fh.write(`${JSON.stringify(record)}
|
|
@@ -648,246 +656,6 @@ function deterministicCompletion(check) {
|
|
|
648
656
|
};
|
|
649
657
|
}
|
|
650
658
|
|
|
651
|
-
// src/runtime/personify/analyst.ts
|
|
652
|
-
var judgeEvidenceUri = /^(verdict|judge|score)\b/i;
|
|
653
|
-
var assertTraceDerivedFindings = (findings) => {
|
|
654
|
-
for (const f of findings) {
|
|
655
|
-
for (const ref of f.evidence_refs ?? []) {
|
|
656
|
-
if (ref.kind === "metric" && judgeEvidenceUri.test(ref.uri)) {
|
|
657
|
-
throw new PlannerError(
|
|
658
|
-
`steer-firewall: finding ${stringifySafe(f.finding_id)} cites judge-derived evidence (${stringifySafe(ref.uri)}); findings fed to a combinator's steer decision must be trace-derived, not judge-derived (selector \u2260 judge)`
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
function createScopeAnalyst(scope, options) {
|
|
665
|
-
if (!options.analyst || typeof options.analyst.act !== "function") {
|
|
666
|
-
throw new AnalystError("createScopeAnalyst: analyst must be an Agent with an act() method");
|
|
667
|
-
}
|
|
668
|
-
const label = options.label ?? "analyst";
|
|
669
|
-
return {
|
|
670
|
-
async analyze(input) {
|
|
671
|
-
const task = options.buildTask(input);
|
|
672
|
-
const spawned = scope.spawn(options.analyst, task, {
|
|
673
|
-
budget: options.budget,
|
|
674
|
-
label
|
|
675
|
-
});
|
|
676
|
-
if (!spawned.ok) {
|
|
677
|
-
throw new AnalystError(
|
|
678
|
-
`createScopeAnalyst: analyst spawn refused by the conserved pool (${spawned.reason}); cannot steer node ${stringifySafe(input.nodeId)} on an unrun analyst`
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
const settled = await drainAnalystSettlement(scope, spawned.handle.id);
|
|
682
|
-
const findings = readAnalystFindings(settled);
|
|
683
|
-
assertTraceDerivedFindings(findings);
|
|
684
|
-
return findings;
|
|
685
|
-
}
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
async function drainAnalystSettlement(scope, analystId) {
|
|
689
|
-
for (; ; ) {
|
|
690
|
-
const settled = await scope.next();
|
|
691
|
-
if (settled === null) {
|
|
692
|
-
throw new AnalystError(
|
|
693
|
-
`createScopeAnalyst: scope drained before analyst ${stringifySafe(analystId)} settled`
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
if (settled.handle.id === analystId) return settled;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
function readAnalystFindings(settled) {
|
|
700
|
-
if (settled.kind === "down") {
|
|
701
|
-
throw new AnalystError(
|
|
702
|
-
`createScopeAnalyst: analyst ${stringifySafe(settled.handle.id)} settled down (${settled.infra ? "infra" : "result"}): ${stringifySafe(settled.reason)}`
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
const out = settled.out;
|
|
706
|
-
if (!Array.isArray(out)) {
|
|
707
|
-
throw new PlannerError(
|
|
708
|
-
`createScopeAnalyst: analyst ${stringifySafe(settled.handle.id)} must return AnalystFinding[], got ${stringifySafe(out)}`
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
return out;
|
|
712
|
-
}
|
|
713
|
-
function registryScopeAnalyst(registry, buildInputs) {
|
|
714
|
-
return {
|
|
715
|
-
async analyze(input) {
|
|
716
|
-
const projection = buildInputs(input);
|
|
717
|
-
const result = await registry.run(projection.runId, projection.inputs, projection.opts);
|
|
718
|
-
const findings = result.findings;
|
|
719
|
-
assertTraceDerivedFindings(findings);
|
|
720
|
-
return findings;
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
function buildSteerContext(findings, settledSoFar) {
|
|
725
|
-
assertTraceDerivedFindings(findings);
|
|
726
|
-
const lastValidScore = observedBestScore(settledSoFar);
|
|
727
|
-
return {
|
|
728
|
-
findings,
|
|
729
|
-
settledSoFar,
|
|
730
|
-
...lastValidScore !== void 0 ? { lastValidScore } : {}
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
function observedBestScore(settledSoFar) {
|
|
734
|
-
let best;
|
|
735
|
-
for (const s of settledSoFar) {
|
|
736
|
-
if (s.kind !== "done") continue;
|
|
737
|
-
const v = s.verdict;
|
|
738
|
-
if (!v || v.valid !== true || typeof v.score !== "number") continue;
|
|
739
|
-
if (best === void 0 || v.score > best) best = v.score;
|
|
740
|
-
}
|
|
741
|
-
return best;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// src/runtime/driver.ts
|
|
745
|
-
function createDriver(options) {
|
|
746
|
-
if (typeof options.planner !== "function") {
|
|
747
|
-
throw new ValidationError("createDriver: planner must be a function");
|
|
748
|
-
}
|
|
749
|
-
const maxIterations = options.maxIterations ?? 8;
|
|
750
|
-
if (!Number.isFinite(maxIterations) || maxIterations <= 0) {
|
|
751
|
-
throw new ValidationError("createDriver: maxIterations must be > 0");
|
|
752
|
-
}
|
|
753
|
-
const maxFanout = options.maxFanout ?? 4;
|
|
754
|
-
if (!Number.isFinite(maxFanout) || maxFanout < 1) {
|
|
755
|
-
throw new ValidationError("createDriver: maxFanout must be >= 1");
|
|
756
|
-
}
|
|
757
|
-
let pending;
|
|
758
|
-
return {
|
|
759
|
-
name: options.name ?? "dynamic",
|
|
760
|
-
async plan(task, history) {
|
|
761
|
-
if (history.length >= maxIterations) {
|
|
762
|
-
pending = { kind: "stop", rationale: `maxIterations (${maxIterations}) reached` };
|
|
763
|
-
return [];
|
|
764
|
-
}
|
|
765
|
-
const analyses = options.analyze && history.length > 0 ? await runAnalyze(options.analyze, task, history) : void 0;
|
|
766
|
-
if (options.complete && history.length > 0) {
|
|
767
|
-
const verdict = await runComplete(options.complete, task, history);
|
|
768
|
-
if (completionAuthorizes(verdict, options.completionPolicy)) {
|
|
769
|
-
pending = {
|
|
770
|
-
kind: "stop",
|
|
771
|
-
rationale: `complete (${verdict.determinism}): ${verdict.reasons ?? "satisfied"}`
|
|
772
|
-
};
|
|
773
|
-
return [];
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
const move = await options.planner({
|
|
777
|
-
task,
|
|
778
|
-
history,
|
|
779
|
-
iterationsSpent: history.length,
|
|
780
|
-
iterationsRemaining: maxIterations - history.length,
|
|
781
|
-
...analyses ? { analyses } : {}
|
|
782
|
-
});
|
|
783
|
-
pending = validateMove(move, maxFanout);
|
|
784
|
-
if (pending.kind === "select") {
|
|
785
|
-
const iter = history[pending.index];
|
|
786
|
-
if (!iter || iter.output === void 0) {
|
|
787
|
-
throw new PlannerError(
|
|
788
|
-
`dynamic planner select.index ${pending.index} is not a completed iteration with output (history length ${history.length})`
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
switch (pending.kind) {
|
|
793
|
-
case "refine":
|
|
794
|
-
return [pending.task];
|
|
795
|
-
case "fanout":
|
|
796
|
-
return pending.tasks;
|
|
797
|
-
case "stop":
|
|
798
|
-
case "select":
|
|
799
|
-
return [];
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
decide() {
|
|
803
|
-
return pending?.kind === "stop" || pending?.kind === "select" ? "done" : "continue";
|
|
804
|
-
},
|
|
805
|
-
describePlan() {
|
|
806
|
-
if (!pending) return void 0;
|
|
807
|
-
const out = { kind: pending.kind };
|
|
808
|
-
if (pending.rationale !== void 0) out.rationale = pending.rationale;
|
|
809
|
-
if ((pending.kind === "refine" || pending.kind === "fanout") && pending.parentIndex !== void 0) {
|
|
810
|
-
out.parentIndex = pending.parentIndex;
|
|
811
|
-
}
|
|
812
|
-
return out;
|
|
813
|
-
},
|
|
814
|
-
selectWinner(history) {
|
|
815
|
-
if (pending?.kind !== "select") return void 0;
|
|
816
|
-
const iter = history[pending.index];
|
|
817
|
-
if (!iter || iter.output === void 0) return void 0;
|
|
818
|
-
return {
|
|
819
|
-
task: iter.task,
|
|
820
|
-
output: iter.output,
|
|
821
|
-
verdict: iter.verdict,
|
|
822
|
-
iterationIndex: iter.index,
|
|
823
|
-
agentRunName: iter.agentRunName
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
function validateMove(move, maxFanout) {
|
|
829
|
-
if (!move || typeof move !== "object" || typeof move.kind !== "string") {
|
|
830
|
-
throw new PlannerError(`dynamic planner returned a non-move value: ${stringifySafe(move)}`);
|
|
831
|
-
}
|
|
832
|
-
switch (move.kind) {
|
|
833
|
-
case "refine":
|
|
834
|
-
return move;
|
|
835
|
-
case "stop":
|
|
836
|
-
return move;
|
|
837
|
-
case "select": {
|
|
838
|
-
if (!Number.isInteger(move.index) || move.index < 0) {
|
|
839
|
-
throw new PlannerError(
|
|
840
|
-
`dynamic planner select move must carry a non-negative integer index, got ${stringifySafe(move.index)}`
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
return move;
|
|
844
|
-
}
|
|
845
|
-
case "fanout": {
|
|
846
|
-
if (!Array.isArray(move.tasks) || move.tasks.length === 0) {
|
|
847
|
-
throw new PlannerError("dynamic planner fanout move must carry a non-empty tasks[]");
|
|
848
|
-
}
|
|
849
|
-
if (move.tasks.length <= maxFanout) return move;
|
|
850
|
-
return {
|
|
851
|
-
kind: "fanout",
|
|
852
|
-
tasks: move.tasks.slice(0, maxFanout),
|
|
853
|
-
rationale: `${move.rationale ?? ""} [clamped ${move.tasks.length}\u2192${maxFanout}]`.trim()
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
default:
|
|
857
|
-
throw new PlannerError(
|
|
858
|
-
`dynamic planner returned unknown move kind: ${stringifySafe(move.kind)}`
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
async function runAnalyze(analyze2, task, history) {
|
|
863
|
-
const findings = await analyze2({ task, history });
|
|
864
|
-
if (!Array.isArray(findings)) {
|
|
865
|
-
throw new PlannerError(
|
|
866
|
-
`createDriver: analyze hook must return AnalystFinding[], got ${stringifySafe(findings)}`
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
assertTraceDerivedFindings(findings);
|
|
870
|
-
return findings;
|
|
871
|
-
}
|
|
872
|
-
async function runComplete(complete, task, history) {
|
|
873
|
-
const verdict = await complete.assess({ task, history });
|
|
874
|
-
if (!verdict || typeof verdict.done !== "boolean" || verdict.determinism !== "deterministic" && verdict.determinism !== "probabilistic") {
|
|
875
|
-
throw new PlannerError(
|
|
876
|
-
`createDriver: complete.assess must return a CompletionVerdict {done, determinism}, got ${stringifySafe(verdict)}`
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
return verdict;
|
|
880
|
-
}
|
|
881
|
-
function renderAnalyses(findings) {
|
|
882
|
-
if (findings.length === 0) return "";
|
|
883
|
-
const rows = findings.map((f) => {
|
|
884
|
-
const action = f.recommended_action ? ` \u2192 ${f.recommended_action}` : "";
|
|
885
|
-
return ` - [${f.severity}/${f.area}] ${f.claim}${action} (conf ${f.confidence.toFixed(2)})`;
|
|
886
|
-
});
|
|
887
|
-
return `Trace-analyst findings (diagnosis of the attempts so far \u2014 steer from these, not the verdict score alone):
|
|
888
|
-
${rows.join("\n")}`;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
659
|
// src/runtime/observe.ts
|
|
892
660
|
import { makeFinding } from "@tangle-network/agent-eval";
|
|
893
661
|
var observerId = "observe/trace";
|
|
@@ -2231,7 +1999,126 @@ function createMcpEnvironment(opts) {
|
|
|
2231
1999
|
};
|
|
2232
2000
|
}
|
|
2233
2001
|
|
|
2002
|
+
// src/runtime/personify/analyst.ts
|
|
2003
|
+
var judgeEvidenceUri = /^(verdict|judge|score)\b/i;
|
|
2004
|
+
var assertTraceDerivedFindings = (findings) => {
|
|
2005
|
+
for (const f of findings) {
|
|
2006
|
+
for (const ref of f.evidence_refs ?? []) {
|
|
2007
|
+
if (ref.kind === "metric" && judgeEvidenceUri.test(ref.uri)) {
|
|
2008
|
+
throw new PlannerError(
|
|
2009
|
+
`steer-firewall: finding ${stringifySafe(f.finding_id)} cites judge-derived evidence (${stringifySafe(ref.uri)}); findings fed to a combinator's steer decision must be trace-derived, not judge-derived (selector \u2260 judge)`
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
function createScopeAnalyst(scope, options) {
|
|
2016
|
+
if (!options.analyst || typeof options.analyst.act !== "function") {
|
|
2017
|
+
throw new AnalystError("createScopeAnalyst: analyst must be an Agent with an act() method");
|
|
2018
|
+
}
|
|
2019
|
+
const label = options.label ?? "analyst";
|
|
2020
|
+
return {
|
|
2021
|
+
async analyze(input) {
|
|
2022
|
+
const task = options.buildTask(input);
|
|
2023
|
+
const spawned = scope.spawn(options.analyst, task, {
|
|
2024
|
+
budget: options.budget,
|
|
2025
|
+
label
|
|
2026
|
+
});
|
|
2027
|
+
if (!spawned.ok) {
|
|
2028
|
+
throw new AnalystError(
|
|
2029
|
+
`createScopeAnalyst: analyst spawn refused by the conserved pool (${spawned.reason}); cannot steer node ${stringifySafe(input.nodeId)} on an unrun analyst`
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
const settled = await drainAnalystSettlement(scope, spawned.handle.id);
|
|
2033
|
+
const findings = readAnalystFindings(settled);
|
|
2034
|
+
assertTraceDerivedFindings(findings);
|
|
2035
|
+
return findings;
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
async function drainAnalystSettlement(scope, analystId) {
|
|
2040
|
+
for (; ; ) {
|
|
2041
|
+
const settled = await scope.next();
|
|
2042
|
+
if (settled === null) {
|
|
2043
|
+
throw new AnalystError(
|
|
2044
|
+
`createScopeAnalyst: scope drained before analyst ${stringifySafe(analystId)} settled`
|
|
2045
|
+
);
|
|
2046
|
+
}
|
|
2047
|
+
if (settled.handle.id === analystId) return settled;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
function readAnalystFindings(settled) {
|
|
2051
|
+
if (settled.kind === "down") {
|
|
2052
|
+
throw new AnalystError(
|
|
2053
|
+
`createScopeAnalyst: analyst ${stringifySafe(settled.handle.id)} settled down (${settled.infra ? "infra" : "result"}): ${stringifySafe(settled.reason)}`
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
const out = settled.out;
|
|
2057
|
+
if (!Array.isArray(out)) {
|
|
2058
|
+
throw new PlannerError(
|
|
2059
|
+
`createScopeAnalyst: analyst ${stringifySafe(settled.handle.id)} must return AnalystFinding[], got ${stringifySafe(out)}`
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
return out;
|
|
2063
|
+
}
|
|
2064
|
+
function registryScopeAnalyst(registry, buildInputs) {
|
|
2065
|
+
return {
|
|
2066
|
+
async analyze(input) {
|
|
2067
|
+
const projection = buildInputs(input);
|
|
2068
|
+
const result = await registry.run(projection.runId, projection.inputs, projection.opts);
|
|
2069
|
+
const findings = result.findings;
|
|
2070
|
+
assertTraceDerivedFindings(findings);
|
|
2071
|
+
return findings;
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
function buildSteerContext(findings, settledSoFar) {
|
|
2076
|
+
assertTraceDerivedFindings(findings);
|
|
2077
|
+
const lastValidScore = observedBestScore(settledSoFar);
|
|
2078
|
+
return {
|
|
2079
|
+
findings,
|
|
2080
|
+
settledSoFar,
|
|
2081
|
+
...lastValidScore !== void 0 ? { lastValidScore } : {}
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
function observedBestScore(settledSoFar) {
|
|
2085
|
+
let best;
|
|
2086
|
+
for (const s of settledSoFar) {
|
|
2087
|
+
if (s.kind !== "done") continue;
|
|
2088
|
+
const v = s.verdict;
|
|
2089
|
+
if (!v || v.valid !== true || typeof v.score !== "number") continue;
|
|
2090
|
+
if (best === void 0 || v.score > best) best = v.score;
|
|
2091
|
+
}
|
|
2092
|
+
return best;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2234
2095
|
// src/runtime/supervise/scope.ts
|
|
2096
|
+
var nestedScopeSeamKey = "nested-scope";
|
|
2097
|
+
function makeNestedScopeSeam(args, childNodeId) {
|
|
2098
|
+
return {
|
|
2099
|
+
depth: args.depth,
|
|
2100
|
+
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
2101
|
+
journalRoot: args.root,
|
|
2102
|
+
mount(nestedRoot, signal) {
|
|
2103
|
+
return createScope({
|
|
2104
|
+
parentId: childNodeId,
|
|
2105
|
+
root: nestedRoot,
|
|
2106
|
+
pool: args.pool,
|
|
2107
|
+
journal: args.journal,
|
|
2108
|
+
blobs: args.blobs,
|
|
2109
|
+
executors: args.executors,
|
|
2110
|
+
// Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
|
|
2111
|
+
// the nested scope adds its OWN nested-scope seam per child in `spawn`.
|
|
2112
|
+
seams: args.seams,
|
|
2113
|
+
depth: args.depth + 1,
|
|
2114
|
+
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
2115
|
+
signal,
|
|
2116
|
+
...args.now ? { now: args.now } : {},
|
|
2117
|
+
...args.hooks ? { hooks: args.hooks } : {}
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2235
2122
|
function createScope(args) {
|
|
2236
2123
|
const children = /* @__PURE__ */ new Map();
|
|
2237
2124
|
let spawnOrdinal = 0;
|
|
@@ -2258,7 +2145,10 @@ function createScope(args) {
|
|
|
2258
2145
|
const cascadeAbort = () => childAbort.abort();
|
|
2259
2146
|
if (args.signal.aborted) childAbort.abort();
|
|
2260
2147
|
else args.signal.addEventListener("abort", cascadeAbort, { once: true });
|
|
2261
|
-
const ctx = {
|
|
2148
|
+
const ctx = {
|
|
2149
|
+
signal: childAbort.signal,
|
|
2150
|
+
seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
|
|
2151
|
+
};
|
|
2262
2152
|
const executor = resolved.value(spec, ctx);
|
|
2263
2153
|
const handle = {
|
|
2264
2154
|
id,
|
|
@@ -3020,8 +2910,8 @@ var InMemoryCorpus = class {
|
|
|
3020
2910
|
}
|
|
3021
2911
|
};
|
|
3022
2912
|
var FileCorpus = class {
|
|
3023
|
-
constructor(
|
|
3024
|
-
this.path =
|
|
2913
|
+
constructor(path2) {
|
|
2914
|
+
this.path = path2;
|
|
3025
2915
|
}
|
|
3026
2916
|
path;
|
|
3027
2917
|
async append(record) {
|
|
@@ -3085,8 +2975,8 @@ var FileCorpus = class {
|
|
|
3085
2975
|
}
|
|
3086
2976
|
async appendLine(record) {
|
|
3087
2977
|
const fs = await import("fs/promises");
|
|
3088
|
-
const
|
|
3089
|
-
await fs.mkdir(
|
|
2978
|
+
const path2 = await import("path");
|
|
2979
|
+
await fs.mkdir(path2.dirname(this.path), { recursive: true });
|
|
3090
2980
|
const fh = await fs.open(this.path, "a");
|
|
3091
2981
|
try {
|
|
3092
2982
|
await fh.write(`${JSON.stringify(record)}
|
|
@@ -3135,16 +3025,170 @@ function isNoEntError2(err) {
|
|
|
3135
3025
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
3136
3026
|
}
|
|
3137
3027
|
|
|
3138
|
-
// src/runtime/supervise/
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3028
|
+
// src/runtime/supervise/driver-executor.ts
|
|
3029
|
+
var driverRuntime = "driver";
|
|
3030
|
+
var driverRole = "driver";
|
|
3031
|
+
function driverChild(name, driver, journal) {
|
|
3032
|
+
const spec = {
|
|
3033
|
+
profile: { name, metadata: { role: driverRole } },
|
|
3034
|
+
harness: null,
|
|
3035
|
+
driver,
|
|
3036
|
+
journal
|
|
3037
|
+
};
|
|
3038
|
+
return {
|
|
3039
|
+
name,
|
|
3040
|
+
executorSpec: spec,
|
|
3041
|
+
act() {
|
|
3042
|
+
throw new ValidationError(
|
|
3043
|
+
`driverChild: "${name}" was run directly; a driver child runs through its nested-scope executor`
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
function isDriverSpec(spec) {
|
|
3049
|
+
const role = spec.profile.metadata?.role;
|
|
3050
|
+
if (role !== driverRole) return false;
|
|
3051
|
+
const driver = spec.driver;
|
|
3052
|
+
if (!isAgent(driver)) {
|
|
3053
|
+
throw new ValidationError(
|
|
3054
|
+
"driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope"
|
|
3055
|
+
);
|
|
3056
|
+
}
|
|
3057
|
+
return true;
|
|
3058
|
+
}
|
|
3059
|
+
var driverExecutorFactory = (spec, ctx) => {
|
|
3060
|
+
if (!isDriverSpec(spec)) {
|
|
3061
|
+
throw new ValidationError(
|
|
3062
|
+
'driverExecutorFactory: spec is not a driver child (no role:"driver" marker)'
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
const driver = spec.driver;
|
|
3066
|
+
const journal = spec.journal;
|
|
3067
|
+
const seam = readNestedScopeSeam(ctx);
|
|
3068
|
+
let artifact;
|
|
3069
|
+
return {
|
|
3070
|
+
runtime: driverRuntime,
|
|
3071
|
+
async execute(task, signal) {
|
|
3072
|
+
const nestedRoot = nestedTreeKey(seam, journal);
|
|
3073
|
+
await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3074
|
+
const nestedScope = seam.mount(nestedRoot, signal);
|
|
3075
|
+
const out = await driver.act(task, nestedScope);
|
|
3076
|
+
const settled = await loadSettled(journal, nestedRoot);
|
|
3077
|
+
const spent = sumSpend(settled);
|
|
3078
|
+
const verdict = deriveDeliveryVerdict(settled);
|
|
3079
|
+
artifact = {
|
|
3080
|
+
outRef: `${driverRuntime}:${nestedRoot}`,
|
|
3081
|
+
out,
|
|
3082
|
+
spent,
|
|
3083
|
+
...verdict ? { verdict } : {}
|
|
3084
|
+
};
|
|
3085
|
+
return artifact;
|
|
3086
|
+
},
|
|
3087
|
+
teardown() {
|
|
3088
|
+
return Promise.resolve({ destroyed: true });
|
|
3089
|
+
},
|
|
3090
|
+
resultArtifact() {
|
|
3091
|
+
if (!artifact) {
|
|
3092
|
+
throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
|
|
3093
|
+
}
|
|
3094
|
+
return artifact;
|
|
3095
|
+
}
|
|
3096
|
+
};
|
|
3097
|
+
};
|
|
3098
|
+
function withDriverExecutor(base) {
|
|
3099
|
+
return {
|
|
3100
|
+
register: base.register.bind(base),
|
|
3101
|
+
resolve(spec) {
|
|
3102
|
+
const role = spec.profile.metadata?.role;
|
|
3103
|
+
if (role === driverRole && !spec.executor) {
|
|
3104
|
+
return { succeeded: true, value: driverExecutorFactory };
|
|
3105
|
+
}
|
|
3106
|
+
return base.resolve(spec);
|
|
3107
|
+
}
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
function nestedTreeKey(seam, journal) {
|
|
3111
|
+
return `${seam.journalRoot}/d${nextNestOrdinal(journal)}`;
|
|
3112
|
+
}
|
|
3113
|
+
var nestCounters = /* @__PURE__ */ new WeakMap();
|
|
3114
|
+
function nextNestOrdinal(journal) {
|
|
3115
|
+
let c = nestCounters.get(journal);
|
|
3116
|
+
if (!c) {
|
|
3117
|
+
c = { n: 0 };
|
|
3118
|
+
nestCounters.set(journal, c);
|
|
3119
|
+
}
|
|
3120
|
+
return c.n++;
|
|
3121
|
+
}
|
|
3122
|
+
async function loadSettled(journal, nestedRoot) {
|
|
3123
|
+
const events = await journal.loadTree(nestedRoot);
|
|
3124
|
+
if (events === void 0) {
|
|
3125
|
+
throw new ValidationError(
|
|
3126
|
+
`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
return events.filter(
|
|
3130
|
+
(ev) => ev.kind === "settled"
|
|
3131
|
+
);
|
|
3132
|
+
}
|
|
3133
|
+
function sumSpend(settled) {
|
|
3134
|
+
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3135
|
+
for (const ev of settled) {
|
|
3136
|
+
total.iterations += ev.spent.iterations;
|
|
3137
|
+
total.tokens.input += ev.spent.tokens.input;
|
|
3138
|
+
total.tokens.output += ev.spent.tokens.output;
|
|
3139
|
+
total.usd += ev.spent.usd;
|
|
3140
|
+
total.ms += ev.spent.ms;
|
|
3141
|
+
}
|
|
3142
|
+
return total;
|
|
3143
|
+
}
|
|
3144
|
+
function deriveDeliveryVerdict(settled) {
|
|
3145
|
+
let sawChild = false;
|
|
3146
|
+
let anyValid = false;
|
|
3147
|
+
let bestValidScore;
|
|
3148
|
+
let bestDoneScore;
|
|
3149
|
+
for (const ev of settled) {
|
|
3150
|
+
sawChild = true;
|
|
3151
|
+
if (ev.status !== "done") continue;
|
|
3152
|
+
const score = ev.verdict?.score;
|
|
3153
|
+
if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) {
|
|
3154
|
+
bestDoneScore = score;
|
|
3155
|
+
}
|
|
3156
|
+
if (ev.verdict?.valid === true) {
|
|
3157
|
+
anyValid = true;
|
|
3158
|
+
if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) {
|
|
3159
|
+
bestValidScore = score;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (!sawChild) return void 0;
|
|
3164
|
+
return {
|
|
3165
|
+
valid: anyValid,
|
|
3166
|
+
score: anyValid ? bestValidScore ?? 1 : bestDoneScore ?? 0
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
function readNestedScopeSeam(ctx) {
|
|
3170
|
+
const seam = ctx.seams[nestedScopeSeamKey];
|
|
3171
|
+
if (!seam || typeof seam.mount !== "function") {
|
|
3172
|
+
throw new ValidationError(
|
|
3173
|
+
`driverExecutor: missing required seam "${nestedScopeSeamKey}" \u2014 a driver child must be spawned through a Scope that seeds it (the keystone scope does)`
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
return seam;
|
|
3177
|
+
}
|
|
3178
|
+
function isAgent(value) {
|
|
3179
|
+
return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// src/runtime/supervise/runtime.ts
|
|
3183
|
+
import { spawn } from "child_process";
|
|
3184
|
+
import { estimateCost as estimateCost2, isModelPriced as isModelPriced2 } from "@tangle-network/agent-eval";
|
|
3185
|
+
|
|
3186
|
+
// src/runtime/router-client.ts
|
|
3187
|
+
import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
|
|
3188
|
+
async function routerChatWithUsage(cfg, messages, opts) {
|
|
3189
|
+
const url = `${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
3190
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` };
|
|
3191
|
+
let temperature = opts?.temperature ?? 0.2;
|
|
3148
3192
|
let lastErr = "";
|
|
3149
3193
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
3150
3194
|
const res = await fetch(url, {
|
|
@@ -3194,7 +3238,8 @@ async function routerChatWithTools(cfg, messages, tools, opts) {
|
|
|
3194
3238
|
messages,
|
|
3195
3239
|
tools,
|
|
3196
3240
|
tool_choice: opts?.toolChoice ?? "auto",
|
|
3197
|
-
temperature: opts?.temperature ?? 0.3
|
|
3241
|
+
temperature: opts?.temperature ?? 0.3,
|
|
3242
|
+
...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
|
|
3198
3243
|
}),
|
|
3199
3244
|
...opts?.signal ? { signal: opts.signal } : {}
|
|
3200
3245
|
});
|
|
@@ -3218,7 +3263,7 @@ async function routerChatWithTools(cfg, messages, tools, opts) {
|
|
|
3218
3263
|
}
|
|
3219
3264
|
async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
3220
3265
|
const maxTurns = opts?.maxTurns ?? 4;
|
|
3221
|
-
const messages = [
|
|
3266
|
+
const messages = opts?.initialMessages ? [...opts.initialMessages] : [
|
|
3222
3267
|
{ role: "system", content: system },
|
|
3223
3268
|
{ role: "user", content: user }
|
|
3224
3269
|
];
|
|
@@ -3229,6 +3274,7 @@ async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
|
3229
3274
|
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
3230
3275
|
const r = await routerChatWithTools(cfg, messages, tools, {
|
|
3231
3276
|
...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {},
|
|
3277
|
+
...opts?.maxTokens ? { maxTokens: opts.maxTokens } : {},
|
|
3232
3278
|
...opts?.signal ? { signal: opts.signal } : {}
|
|
3233
3279
|
});
|
|
3234
3280
|
if (r.usage) {
|
|
@@ -3237,7 +3283,7 @@ async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
|
3237
3283
|
}
|
|
3238
3284
|
if (r.content) lastText = r.content;
|
|
3239
3285
|
if (r.toolCalls.length === 0)
|
|
3240
|
-
return { final: lastText, turns: turn, toolCalls, toolTrace, usage };
|
|
3286
|
+
return { final: lastText, turns: turn, toolCalls, toolTrace, usage, messages };
|
|
3241
3287
|
messages.push({
|
|
3242
3288
|
role: "assistant",
|
|
3243
3289
|
content: r.content ?? "",
|
|
@@ -3265,7 +3311,7 @@ async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
|
3265
3311
|
toolTrace.push({ name: tc.name, args: tc.arguments, result: out });
|
|
3266
3312
|
}
|
|
3267
3313
|
}
|
|
3268
|
-
return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage };
|
|
3314
|
+
return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
|
|
3269
3315
|
}
|
|
3270
3316
|
|
|
3271
3317
|
// src/runtime/supervise/runtime.ts
|
|
@@ -4037,14 +4083,22 @@ function createSupervisor() {
|
|
|
4037
4083
|
if (actOutcome.ok) {
|
|
4038
4084
|
pool2.assertNoOpenTickets();
|
|
4039
4085
|
const out = actOutcome.out;
|
|
4040
|
-
|
|
4041
|
-
|
|
4086
|
+
if (out !== void 0) {
|
|
4087
|
+
const outRef = contentAddress(out);
|
|
4088
|
+
await opts.blobs.put(outRef, out);
|
|
4089
|
+
return {
|
|
4090
|
+
kind: "winner",
|
|
4091
|
+
out,
|
|
4092
|
+
outRef,
|
|
4093
|
+
tree,
|
|
4094
|
+
spentTotal: await spentTotalFromJournal(journal, opts.runId)
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
4042
4097
|
return {
|
|
4043
|
-
kind: "winner",
|
|
4044
|
-
|
|
4045
|
-
outRef,
|
|
4098
|
+
kind: "no-winner",
|
|
4099
|
+
reason: classifyNoWinner(controller, pool2, opts, breaker),
|
|
4046
4100
|
tree,
|
|
4047
|
-
|
|
4101
|
+
downCount: breaker.downCount()
|
|
4048
4102
|
};
|
|
4049
4103
|
}
|
|
4050
4104
|
return {
|
|
@@ -4245,7 +4299,7 @@ function createShapeContext(persona, budget, analyst) {
|
|
|
4245
4299
|
executorSpec: spec,
|
|
4246
4300
|
act() {
|
|
4247
4301
|
throw new ValidationError(
|
|
4248
|
-
`personify: spawned child "${name}" was run
|
|
4302
|
+
`personify: spawned child "${name}" was run directly; its executorSpec drives it (a leaf, or \u2014 for a driver child \u2014 a nested scope through the recursive driver-executor)`
|
|
4249
4303
|
);
|
|
4250
4304
|
}
|
|
4251
4305
|
};
|
|
@@ -4279,7 +4333,8 @@ async function runPersonified(options) {
|
|
|
4279
4333
|
...options.maxRestarts !== void 0 ? { maxRestarts: options.maxRestarts } : {},
|
|
4280
4334
|
...options.withinMs !== void 0 ? { withinMs: options.withinMs } : {},
|
|
4281
4335
|
...options.now ? { now: options.now } : {},
|
|
4282
|
-
...options.signal ? { signal: options.signal } : {}
|
|
4336
|
+
...options.signal ? { signal: options.signal } : {},
|
|
4337
|
+
...options.hooks ? { hooks: options.hooks } : {}
|
|
4283
4338
|
};
|
|
4284
4339
|
return supervisor.run(rootAgent, options.task, supervisorOpts);
|
|
4285
4340
|
}
|
|
@@ -4309,13 +4364,13 @@ function resolveShapeBudget(root, over) {
|
|
|
4309
4364
|
var defaultFanout = 3;
|
|
4310
4365
|
function personaRegistry(persona) {
|
|
4311
4366
|
const { registry, seams } = persona.executors;
|
|
4312
|
-
if (registry) return registry;
|
|
4367
|
+
if (registry) return withDriverExecutor(registry);
|
|
4313
4368
|
if (!seams) {
|
|
4314
4369
|
throw new ValidationError(
|
|
4315
4370
|
`personify: persona "${persona.name}" supplies neither a registry nor seams`
|
|
4316
4371
|
);
|
|
4317
4372
|
}
|
|
4318
|
-
return withSeams(createExecutorRegistry(), seams);
|
|
4373
|
+
return withDriverExecutor(withSeams(createExecutorRegistry(), seams));
|
|
4319
4374
|
}
|
|
4320
4375
|
function withSeams(base, seams) {
|
|
4321
4376
|
return {
|
|
@@ -4682,59 +4737,41 @@ import { pairedBootstrap, paretoFrontier } from "@tangle-network/agent-eval";
|
|
|
4682
4737
|
import { createChatClient, estimateCost as estimateCost3, isModelPriced as isModelPriced3 } from "@tangle-network/agent-eval";
|
|
4683
4738
|
var taskNudge = "Use the available tools to bring the artifact to the required final state. Address EVERY distinct change the request implies. After each tool result, check what remains and continue. Re-read the values you set to confirm they took. Reply DONE only once every required change is made and verified.";
|
|
4684
4739
|
async function runShot(surface, _task, handle, tools, messages, opts, modelOverride) {
|
|
4685
|
-
const innerTurns = opts.innerTurns ?? 4;
|
|
4686
|
-
let completions = 0;
|
|
4687
|
-
let toolCalls = 0;
|
|
4688
4740
|
let toolErrors = 0;
|
|
4689
|
-
const
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
tools,
|
|
4698
|
-
tool_choice: "auto",
|
|
4699
|
-
temperature: opts.temperature ?? 0.7,
|
|
4700
|
-
...opts.maxTokens ? { max_tokens: opts.maxTokens } : {}
|
|
4701
|
-
})
|
|
4702
|
-
});
|
|
4703
|
-
if (!res.ok) throw new Error(`router ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
4704
|
-
completions += 1;
|
|
4705
|
-
const data = await res.json();
|
|
4706
|
-
if (typeof data.usage?.prompt_tokens === "number") tokens.input += data.usage.prompt_tokens;
|
|
4707
|
-
if (typeof data.usage?.completion_tokens === "number")
|
|
4708
|
-
tokens.output += data.usage.completion_tokens;
|
|
4709
|
-
const msg = data.choices?.[0]?.message;
|
|
4710
|
-
if (!msg) break;
|
|
4711
|
-
const calls = msg.tool_calls ?? [];
|
|
4712
|
-
messages.push({
|
|
4713
|
-
role: "assistant",
|
|
4714
|
-
content: msg.content ?? "",
|
|
4715
|
-
...calls.length ? { tool_calls: calls } : {}
|
|
4716
|
-
});
|
|
4717
|
-
if (calls.length === 0) break;
|
|
4718
|
-
for (const call of calls) {
|
|
4719
|
-
toolCalls += 1;
|
|
4720
|
-
let args = {};
|
|
4721
|
-
try {
|
|
4722
|
-
args = JSON.parse(call.function.arguments || "{}");
|
|
4723
|
-
} catch {
|
|
4724
|
-
toolErrors += 1;
|
|
4725
|
-
}
|
|
4726
|
-
let out;
|
|
4727
|
-
try {
|
|
4728
|
-
out = await surface.call(handle, call.function.name, args);
|
|
4729
|
-
if (out.startsWith("ERROR:")) toolErrors += 1;
|
|
4730
|
-
} catch (e) {
|
|
4731
|
-
toolErrors += 1;
|
|
4732
|
-
out = `ERROR: ${e instanceof Error ? e.message : String(e)}`;
|
|
4733
|
-
}
|
|
4734
|
-
messages.push({ role: "tool", tool_call_id: call.id, content: out });
|
|
4741
|
+
const execute = async (name, args) => {
|
|
4742
|
+
try {
|
|
4743
|
+
const out = await surface.call(handle, name, args);
|
|
4744
|
+
if (out.startsWith("ERROR:")) toolErrors += 1;
|
|
4745
|
+
return out;
|
|
4746
|
+
} catch (e) {
|
|
4747
|
+
toolErrors += 1;
|
|
4748
|
+
return `ERROR: ${e instanceof Error ? e.message : String(e)}`;
|
|
4735
4749
|
}
|
|
4736
|
-
}
|
|
4737
|
-
|
|
4750
|
+
};
|
|
4751
|
+
const r = await routerToolLoop(
|
|
4752
|
+
{
|
|
4753
|
+
routerBaseUrl: opts.routerBaseUrl,
|
|
4754
|
+
routerKey: opts.routerKey,
|
|
4755
|
+
model: modelOverride ?? opts.model
|
|
4756
|
+
},
|
|
4757
|
+
"",
|
|
4758
|
+
"",
|
|
4759
|
+
tools,
|
|
4760
|
+
execute,
|
|
4761
|
+
{
|
|
4762
|
+
maxTurns: opts.innerTurns ?? 4,
|
|
4763
|
+
temperature: opts.temperature ?? 0.7,
|
|
4764
|
+
initialMessages: messages,
|
|
4765
|
+
...opts.maxTokens ? { maxTokens: opts.maxTokens } : {}
|
|
4766
|
+
}
|
|
4767
|
+
);
|
|
4768
|
+
return {
|
|
4769
|
+
messages: r.messages,
|
|
4770
|
+
completions: r.turns,
|
|
4771
|
+
toolCalls: r.toolCalls,
|
|
4772
|
+
toolErrors,
|
|
4773
|
+
tokens: r.usage
|
|
4774
|
+
};
|
|
4738
4775
|
}
|
|
4739
4776
|
function compactTrajectory(messages) {
|
|
4740
4777
|
return messages.filter((m) => m.role === "assistant" || m.role === "tool").map((m) => {
|
|
@@ -4915,7 +4952,7 @@ function analystExecutor(opts) {
|
|
|
4915
4952
|
};
|
|
4916
4953
|
}
|
|
4917
4954
|
function agenticRegistry(surface, opts) {
|
|
4918
|
-
|
|
4955
|
+
const leaves = {
|
|
4919
4956
|
register() {
|
|
4920
4957
|
throw new Error("agenticRegistry: register unsupported");
|
|
4921
4958
|
},
|
|
@@ -4925,13 +4962,14 @@ function agenticRegistry(surface, opts) {
|
|
|
4925
4962
|
return { succeeded: true, value: factory };
|
|
4926
4963
|
}
|
|
4927
4964
|
};
|
|
4965
|
+
return withDriverExecutor(leaves);
|
|
4928
4966
|
}
|
|
4929
4967
|
function leaf(name, role) {
|
|
4930
4968
|
const agent = {
|
|
4931
4969
|
name,
|
|
4932
4970
|
executorSpec: { profile: { name, metadata: { role } }, harness: null },
|
|
4933
4971
|
act() {
|
|
4934
|
-
throw new Error(`agentic: spawned
|
|
4972
|
+
throw new Error(`agentic: spawned child "${name}" was run directly (the executor drives it)`);
|
|
4935
4973
|
}
|
|
4936
4974
|
};
|
|
4937
4975
|
return agent;
|
|
@@ -6186,135 +6224,2812 @@ ${summary}`,
|
|
|
6186
6224
|
};
|
|
6187
6225
|
}
|
|
6188
6226
|
|
|
6189
|
-
// src/runtime/
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
required: ["answer"]
|
|
6227
|
+
// src/runtime/supervise/completion-gate.ts
|
|
6228
|
+
function gateOnDeliverable(inner, deliverable) {
|
|
6229
|
+
let gated;
|
|
6230
|
+
const check = async (out, baseScore) => {
|
|
6231
|
+
let delivered;
|
|
6232
|
+
try {
|
|
6233
|
+
delivered = await deliverable.check(out) === true;
|
|
6234
|
+
} catch {
|
|
6235
|
+
delivered = false;
|
|
6199
6236
|
}
|
|
6200
|
-
|
|
6201
|
-
};
|
|
6202
|
-
function createVerifierEnvironment(opts) {
|
|
6203
|
-
if (opts.extraTools?.length && !opts.callExtra) {
|
|
6204
|
-
throw new Error(`${opts.name}: extraTools requires callExtra`);
|
|
6205
|
-
}
|
|
6206
|
-
const states = /* @__PURE__ */ new Map();
|
|
6207
|
-
let seq = 0;
|
|
6237
|
+
return { valid: delivered, score: baseScore ?? (delivered ? 1 : 0) };
|
|
6238
|
+
};
|
|
6208
6239
|
return {
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
const state = states.get(handle.id);
|
|
6221
|
-
if (!state) return "ERROR: workspace closed";
|
|
6222
|
-
if (name === "submit_answer") {
|
|
6223
|
-
const answer = String(args.answer ?? "").trim();
|
|
6224
|
-
if (!answer) return "ERROR: empty answer";
|
|
6225
|
-
state.submissions.push(answer);
|
|
6226
|
-
return `submission ${state.submissions.length} recorded`;
|
|
6227
|
-
}
|
|
6228
|
-
if (opts.callExtra && opts.extraTools?.some((t) => t.function.name === name)) {
|
|
6229
|
-
try {
|
|
6230
|
-
return await opts.callExtra(state.task, name, args);
|
|
6231
|
-
} catch (e) {
|
|
6232
|
-
return `ERROR: ${e instanceof Error ? e.message : String(e)}`;
|
|
6233
|
-
}
|
|
6234
|
-
}
|
|
6235
|
-
return `ERROR: unknown tool ${name}`;
|
|
6236
|
-
},
|
|
6237
|
-
// Keep-best across submissions — the measured law (workers reach correct answers,
|
|
6238
|
-
// then revise past them; final-state scoring undersells every strategy).
|
|
6239
|
-
async score(task, handle) {
|
|
6240
|
-
const state = states.get(handle.id);
|
|
6241
|
-
if (!state || state.submissions.length === 0) return { passes: 0, total: 1, errored: 0 };
|
|
6242
|
-
let best = { passes: 0, total: 1, errored: 0 };
|
|
6243
|
-
const ratio = (s) => s.total > 0 ? s.passes / s.total : 0;
|
|
6244
|
-
for (const answer of state.submissions) {
|
|
6245
|
-
const s = await opts.check(task, answer);
|
|
6246
|
-
if (ratio(s) > ratio(best)) best = s;
|
|
6240
|
+
runtime: inner.runtime,
|
|
6241
|
+
...inner.budgetExempt !== void 0 ? { budgetExempt: inner.budgetExempt } : {},
|
|
6242
|
+
...inner.deliver ? { deliver: (m) => inner.deliver?.(m) } : {},
|
|
6243
|
+
execute(task, signal) {
|
|
6244
|
+
const r = inner.execute(task, signal);
|
|
6245
|
+
if (isAsyncIterable3(r)) {
|
|
6246
|
+
return (async function* () {
|
|
6247
|
+
for await (const ev of r) yield ev;
|
|
6248
|
+
const art = inner.resultArtifact();
|
|
6249
|
+
gated = await check(art.out, art.verdict?.score);
|
|
6250
|
+
})();
|
|
6247
6251
|
}
|
|
6248
|
-
return
|
|
6252
|
+
return (async () => {
|
|
6253
|
+
const res = await r;
|
|
6254
|
+
gated = await check(res.out, res.verdict?.score);
|
|
6255
|
+
return { ...res, verdict: gated };
|
|
6256
|
+
})();
|
|
6249
6257
|
},
|
|
6250
|
-
|
|
6251
|
-
|
|
6258
|
+
teardown: (grace) => inner.teardown(grace),
|
|
6259
|
+
resultArtifact() {
|
|
6260
|
+
const art = inner.resultArtifact();
|
|
6261
|
+
return { ...art, verdict: gated ?? art.verdict };
|
|
6252
6262
|
}
|
|
6253
6263
|
};
|
|
6254
6264
|
}
|
|
6265
|
+
function isAsyncIterable3(v) {
|
|
6266
|
+
return v != null && typeof v[Symbol.asyncIterator] === "function";
|
|
6267
|
+
}
|
|
6255
6268
|
|
|
6256
|
-
// src/runtime/
|
|
6257
|
-
function
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6269
|
+
// src/runtime/supervise/authoring.ts
|
|
6270
|
+
function asAuthoredProfile(raw) {
|
|
6271
|
+
const p = raw;
|
|
6272
|
+
if (!p || typeof p.systemPrompt !== "string" || p.systemPrompt.trim().length === 0) return null;
|
|
6273
|
+
return {
|
|
6274
|
+
name: typeof p.name === "string" && p.name.length > 0 ? p.name : "worker",
|
|
6275
|
+
systemPrompt: p.systemPrompt,
|
|
6276
|
+
...typeof p.model === "string" ? { model: p.model } : {}
|
|
6277
|
+
};
|
|
6278
|
+
}
|
|
6279
|
+
function supervisorSkill(opts) {
|
|
6280
|
+
return [
|
|
6281
|
+
"You are a SUPERVISOR. You do NOT do the work yourself \u2014 your job is to DESIGN and DRIVE specialist worker agents.",
|
|
6282
|
+
"",
|
|
6283
|
+
"For the task you are given:",
|
|
6284
|
+
"1. DECOMPOSE it into the smallest set of sub-tasks a single focused worker can each deliver.",
|
|
6285
|
+
"2. For EACH sub-task, AUTHOR a worker by calling spawn_worker with a COMPLETE `profile`:",
|
|
6286
|
+
" \u2022 name: a short id for the worker.",
|
|
6287
|
+
' \u2022 systemPrompt: rich, specific instructions for THIS sub-task \u2014 tell the worker exactly what to produce, how to use its tools fully, and what "done" means. Never a one-liner; write the prompt a power-user would write.',
|
|
6288
|
+
" \u2022 model: the model best suited to this sub-task (omit to use the default).",
|
|
6289
|
+
" NEVER spawn a worker with an empty profile. The quality of the worker IS the quality of the profile you write.",
|
|
6290
|
+
"3. await_next to collect each worker. Its result says valid:true only if the deployable check passed.",
|
|
6291
|
+
"4. If a worker did NOT deliver, AUTHOR A NEW worker whose systemPrompt names the SPECIFIC failure and how to fix it \u2014 never just retry the same prompt.",
|
|
6292
|
+
"5. Stop (reply with no tool call) once the work is delivered. You cannot declare done yourself \u2014 only a delivered (valid:true) worker counts.",
|
|
6293
|
+
...opts?.goal ? ["", `The goal: ${opts.goal}`] : []
|
|
6294
|
+
].join("\n");
|
|
6295
|
+
}
|
|
6296
|
+
function authoredWorker(profile, opts) {
|
|
6297
|
+
let artifact;
|
|
6298
|
+
const model = profile.model ?? opts.cfg.model;
|
|
6299
|
+
const inner = {
|
|
6300
|
+
runtime: "router",
|
|
6301
|
+
async execute(_t, signal) {
|
|
6302
|
+
const res = await routerChatWithUsage(
|
|
6303
|
+
{ ...opts.cfg, model },
|
|
6304
|
+
[
|
|
6305
|
+
{ role: "system", content: profile.systemPrompt },
|
|
6306
|
+
{ role: "user", content: opts.taskPrompt }
|
|
6307
|
+
],
|
|
6308
|
+
{ temperature: opts.temperature ?? 0.4, ...signal ? { signal } : {} }
|
|
6309
|
+
);
|
|
6310
|
+
artifact = {
|
|
6311
|
+
outRef: contentAddress(res.content),
|
|
6312
|
+
out: res.content,
|
|
6313
|
+
spent: {
|
|
6314
|
+
iterations: 1,
|
|
6315
|
+
tokens: res.usage ?? { input: 0, output: 0 },
|
|
6316
|
+
usd: res.costUsd ?? 0,
|
|
6317
|
+
ms: 0
|
|
6318
|
+
}
|
|
6287
6319
|
};
|
|
6288
|
-
|
|
6320
|
+
return artifact;
|
|
6321
|
+
},
|
|
6322
|
+
teardown: () => Promise.resolve({ destroyed: true }),
|
|
6323
|
+
resultArtifact: () => {
|
|
6324
|
+
if (!artifact) throw new Error("authoredWorker: resultArtifact read before execute");
|
|
6325
|
+
return artifact;
|
|
6289
6326
|
}
|
|
6290
6327
|
};
|
|
6291
|
-
const
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
let totalUsd = 0;
|
|
6297
|
-
const totalTokens2 = { input: 0, output: 0 };
|
|
6298
|
-
for (const s of all) {
|
|
6299
|
-
totalUsd += s.usd;
|
|
6300
|
-
totalTokens2.input += s.tokens.input;
|
|
6301
|
-
totalTokens2.output += s.tokens.output;
|
|
6302
|
-
const kind = s.label.includes(":") ? s.label.split(":")[0] : s.label;
|
|
6303
|
-
const k = byKind[kind] ??= { count: 0, ms: 0, usd: 0, tokens: { input: 0, output: 0 } };
|
|
6304
|
-
k.count += 1;
|
|
6305
|
-
k.ms += (s.endMs ?? s.startMs) - s.startMs;
|
|
6306
|
-
k.usd += s.usd;
|
|
6307
|
-
k.tokens.input += s.tokens.input;
|
|
6308
|
-
k.tokens.output += s.tokens.output;
|
|
6309
|
-
}
|
|
6310
|
-
return { spans: all, totalMs: end - start, totalUsd, totalTokens: totalTokens2, byKind };
|
|
6328
|
+
const gated = gateOnDeliverable(inner, opts.deliverable);
|
|
6329
|
+
const spec = {
|
|
6330
|
+
profile: { name: profile.name },
|
|
6331
|
+
harness: null,
|
|
6332
|
+
executor: gated
|
|
6311
6333
|
};
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6334
|
+
return { name: profile.name, act: async () => "", executorSpec: spec };
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6337
|
+
// src/mcp/tools/coordination.ts
|
|
6338
|
+
var idArg = { type: "string", description: "The workerId returned by spawn_worker." };
|
|
6339
|
+
function createCoordinationTools(opts) {
|
|
6340
|
+
let stopped = false;
|
|
6341
|
+
let reason;
|
|
6342
|
+
let questionSeq = 0;
|
|
6343
|
+
const ledger = [];
|
|
6344
|
+
const questions = [];
|
|
6345
|
+
const questionPolicy = opts.questionPolicy ?? "auto";
|
|
6346
|
+
const str = (v, field) => {
|
|
6347
|
+
if (typeof v !== "string" || v.length === 0)
|
|
6348
|
+
throw new Error(`coordination tools: "${field}" must be a non-empty string`);
|
|
6349
|
+
return v;
|
|
6350
|
+
};
|
|
6351
|
+
const obj = (raw) => {
|
|
6352
|
+
if (!raw || typeof raw !== "object")
|
|
6353
|
+
throw new Error("coordination tools: arguments must be an object");
|
|
6354
|
+
return raw;
|
|
6355
|
+
};
|
|
6356
|
+
const level = (v) => {
|
|
6357
|
+
if (v === "worker" || v === "driver" || v === "loop") return v;
|
|
6358
|
+
throw new Error('coordination tools: "level" must be worker, driver, or loop');
|
|
6359
|
+
};
|
|
6360
|
+
const urgency = (v) => {
|
|
6361
|
+
if (v === "continue-without" || v === "blocks-step" || v === "blocks-run") return v;
|
|
6362
|
+
throw new Error(
|
|
6363
|
+
'coordination tools: "urgency" must be continue-without, blocks-step, or blocks-run'
|
|
6364
|
+
);
|
|
6365
|
+
};
|
|
6366
|
+
const recordSettled = (s) => {
|
|
6367
|
+
const w = s.kind === "done" ? {
|
|
6368
|
+
id: s.handle.id,
|
|
6369
|
+
status: "done",
|
|
6370
|
+
score: s.verdict?.score ?? 0,
|
|
6371
|
+
valid: s.verdict?.valid ?? false,
|
|
6372
|
+
outRef: s.outRef
|
|
6373
|
+
} : { id: s.handle.id, status: "down", reason: s.reason };
|
|
6374
|
+
ledger.push(w);
|
|
6375
|
+
return w;
|
|
6376
|
+
};
|
|
6377
|
+
const nextQuestionId = (from) => `${from}:q${questionSeq++}`;
|
|
6378
|
+
const normalizeQuestion = (q, fallbackFrom) => {
|
|
6379
|
+
const from = str(q.from ?? fallbackFrom, "from");
|
|
6380
|
+
return {
|
|
6381
|
+
id: typeof q.id === "string" && q.id.length > 0 ? q.id : nextQuestionId(from),
|
|
6382
|
+
from,
|
|
6383
|
+
level: level(q.level),
|
|
6384
|
+
question: str(q.question, "question"),
|
|
6385
|
+
reason: str(q.reason, "reason"),
|
|
6386
|
+
...q.options ? { options: q.options } : {},
|
|
6387
|
+
urgency: urgency(q.urgency)
|
|
6388
|
+
};
|
|
6389
|
+
};
|
|
6390
|
+
const addQuestion = (raw, fallbackFrom, decision) => {
|
|
6391
|
+
const q = normalizeQuestion(raw, fallbackFrom);
|
|
6392
|
+
const existing = questions.find((x) => x.id === q.id);
|
|
6393
|
+
if (existing) return { question: existing, added: false };
|
|
6394
|
+
const effectiveDecision = decision ?? (questionPolicy === "bubble" ? {
|
|
6395
|
+
kind: "escalate",
|
|
6396
|
+
to: "parent",
|
|
6397
|
+
reason: "question policy bubbled to parent"
|
|
6398
|
+
} : void 0);
|
|
6399
|
+
const status = effectiveDecision?.kind === "answer" ? "answered" : effectiveDecision?.kind === "defer" ? "deferred" : effectiveDecision?.kind === "escalate" ? "escalated" : "open";
|
|
6400
|
+
const record = {
|
|
6401
|
+
...q,
|
|
6402
|
+
status,
|
|
6403
|
+
openedAt: Date.now(),
|
|
6404
|
+
...effectiveDecision ? { decision: effectiveDecision } : {}
|
|
6405
|
+
};
|
|
6406
|
+
questions.push(record);
|
|
6407
|
+
return { question: record, added: true };
|
|
6408
|
+
};
|
|
6409
|
+
const emitNewQuestion = async (record) => {
|
|
6410
|
+
if (record.added) await opts.onEvent?.({ type: "question", question: record.question });
|
|
6411
|
+
return record.question;
|
|
6412
|
+
};
|
|
6413
|
+
const decideQuestion = (questionId, decision) => {
|
|
6414
|
+
const idx = questions.findIndex((q) => q.id === questionId);
|
|
6415
|
+
if (idx < 0) throw new Error(`unknown questionId ${JSON.stringify(questionId)}`);
|
|
6416
|
+
const prior = questions[idx];
|
|
6417
|
+
const status = decision.kind === "answer" ? "answered" : decision.kind === "defer" ? "deferred" : "escalated";
|
|
6418
|
+
const next = { ...prior, status, decision };
|
|
6419
|
+
questions[idx] = next;
|
|
6420
|
+
return next;
|
|
6421
|
+
};
|
|
6422
|
+
const blockingQuestionsForStop = () => {
|
|
6423
|
+
if (questionPolicy === "auto" || questionPolicy === "bubble") return [];
|
|
6424
|
+
return questions.filter((q) => {
|
|
6425
|
+
const blocking = q.urgency === "blocks-step" || q.urgency === "blocks-run";
|
|
6426
|
+
if (!blocking) return false;
|
|
6427
|
+
if (questionPolicy === "mustDecide") return q.status === "open";
|
|
6428
|
+
return q.status !== "answered" && q.status !== "deferred";
|
|
6429
|
+
});
|
|
6430
|
+
};
|
|
6431
|
+
const tools = [
|
|
6432
|
+
{
|
|
6433
|
+
name: "spawn_worker",
|
|
6434
|
+
description: "Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed.",
|
|
6435
|
+
inputSchema: {
|
|
6436
|
+
type: "object",
|
|
6437
|
+
properties: {
|
|
6438
|
+
profile: { description: "The worker/driver profile to run." },
|
|
6439
|
+
task: { description: "The task the worker should perform." },
|
|
6440
|
+
label: { type: "string", description: "Optional trace label." }
|
|
6441
|
+
},
|
|
6442
|
+
required: ["profile", "task"]
|
|
6443
|
+
},
|
|
6444
|
+
handler: (raw) => {
|
|
6445
|
+
const a = obj(raw);
|
|
6446
|
+
const agent = opts.makeWorkerAgent(a.profile);
|
|
6447
|
+
const res = opts.scope.spawn(agent, a.task, {
|
|
6448
|
+
budget: opts.perWorker,
|
|
6449
|
+
label: typeof a.label === "string" ? a.label : "worker"
|
|
6450
|
+
});
|
|
6451
|
+
return Promise.resolve(res.ok ? { workerId: res.handle.id } : { error: res.reason });
|
|
6452
|
+
}
|
|
6453
|
+
},
|
|
6454
|
+
{
|
|
6455
|
+
name: "observe_worker",
|
|
6456
|
+
description: "Inspect a worker status, spend, and settled output artifact when available.",
|
|
6457
|
+
inputSchema: { type: "object", properties: { workerId: idArg }, required: ["workerId"] },
|
|
6458
|
+
handler: async (raw) => {
|
|
6459
|
+
const id = str(obj(raw).workerId, "workerId");
|
|
6460
|
+
const node = opts.scope.view.nodes.find((n) => n.id === id);
|
|
6461
|
+
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
6462
|
+
const output = node.outRef ? await opts.blobs.get(node.outRef) : void 0;
|
|
6463
|
+
return {
|
|
6464
|
+
status: node.status,
|
|
6465
|
+
spent: node.spent,
|
|
6466
|
+
outRef: node.outRef ?? null,
|
|
6467
|
+
output: output ?? null
|
|
6468
|
+
};
|
|
6469
|
+
}
|
|
6470
|
+
},
|
|
6471
|
+
{
|
|
6472
|
+
name: "steer_worker",
|
|
6473
|
+
description: "Deliver an out-of-band instruction to a running worker inbox.",
|
|
6474
|
+
inputSchema: {
|
|
6475
|
+
type: "object",
|
|
6476
|
+
properties: {
|
|
6477
|
+
workerId: idArg,
|
|
6478
|
+
instruction: { type: "string", description: "What the worker should do next." }
|
|
6479
|
+
},
|
|
6480
|
+
required: ["workerId", "instruction"]
|
|
6481
|
+
},
|
|
6482
|
+
handler: (raw) => {
|
|
6483
|
+
const a = obj(raw);
|
|
6484
|
+
const delivered = opts.scope.send(str(a.workerId, "workerId"), {
|
|
6485
|
+
steer: str(a.instruction, "instruction")
|
|
6486
|
+
});
|
|
6487
|
+
return Promise.resolve({ delivered });
|
|
6488
|
+
}
|
|
6489
|
+
},
|
|
6490
|
+
{
|
|
6491
|
+
name: "await_next",
|
|
6492
|
+
description: "Wait for the next spawned worker to settle. Returns { idle: true } when none are live.",
|
|
6493
|
+
inputSchema: { type: "object", properties: {} },
|
|
6494
|
+
handler: async () => {
|
|
6495
|
+
const s = await opts.scope.next();
|
|
6496
|
+
if (!s) return { idle: true };
|
|
6497
|
+
const w = recordSettled(s);
|
|
6498
|
+
return w.status === "done" ? {
|
|
6499
|
+
settled: w.id,
|
|
6500
|
+
status: "done",
|
|
6501
|
+
score: w.score,
|
|
6502
|
+
valid: w.valid,
|
|
6503
|
+
outRef: w.outRef
|
|
6504
|
+
} : { settled: w.id, status: "down", reason: w.reason };
|
|
6505
|
+
}
|
|
6506
|
+
},
|
|
6507
|
+
{
|
|
6508
|
+
name: "list_questions",
|
|
6509
|
+
description: "List questions raised by workers, drivers, or analysts. Blocking stop behavior follows questionPolicy.",
|
|
6510
|
+
inputSchema: { type: "object", properties: {} },
|
|
6511
|
+
handler: () => Promise.resolve({ questions })
|
|
6512
|
+
},
|
|
6513
|
+
{
|
|
6514
|
+
name: "answer_question",
|
|
6515
|
+
description: "Record an answer, deferral, or escalation for a loop question.",
|
|
6516
|
+
inputSchema: {
|
|
6517
|
+
type: "object",
|
|
6518
|
+
properties: {
|
|
6519
|
+
questionId: { type: "string" },
|
|
6520
|
+
answer: { type: "string" },
|
|
6521
|
+
by: { type: "string", description: 'Node id or "user".' },
|
|
6522
|
+
deferReason: { type: "string" },
|
|
6523
|
+
escalateTo: { type: "string", enum: ["parent", "user"] },
|
|
6524
|
+
escalateReason: { type: "string" }
|
|
6525
|
+
},
|
|
6526
|
+
required: ["questionId"]
|
|
6527
|
+
},
|
|
6528
|
+
handler: (raw) => {
|
|
6529
|
+
const a = obj(raw);
|
|
6530
|
+
const questionId = str(a.questionId, "questionId");
|
|
6531
|
+
if (typeof a.answer === "string" && a.answer.length > 0) {
|
|
6532
|
+
return Promise.resolve({
|
|
6533
|
+
question: decideQuestion(questionId, {
|
|
6534
|
+
kind: "answer",
|
|
6535
|
+
answer: a.answer,
|
|
6536
|
+
by: typeof a.by === "string" && a.by.length > 0 ? a.by : "user"
|
|
6537
|
+
})
|
|
6538
|
+
});
|
|
6539
|
+
}
|
|
6540
|
+
if (typeof a.deferReason === "string" && a.deferReason.length > 0) {
|
|
6541
|
+
return Promise.resolve({
|
|
6542
|
+
question: decideQuestion(questionId, {
|
|
6543
|
+
kind: "defer",
|
|
6544
|
+
reason: a.deferReason
|
|
6545
|
+
})
|
|
6546
|
+
});
|
|
6547
|
+
}
|
|
6548
|
+
if (a.escalateTo === "parent" || a.escalateTo === "user") {
|
|
6549
|
+
const escalateReason = typeof a.escalateReason === "string" && a.escalateReason.length > 0 ? a.escalateReason : "driver escalated";
|
|
6550
|
+
return Promise.resolve({
|
|
6551
|
+
question: decideQuestion(questionId, {
|
|
6552
|
+
kind: "escalate",
|
|
6553
|
+
to: a.escalateTo,
|
|
6554
|
+
reason: escalateReason
|
|
6555
|
+
})
|
|
6556
|
+
});
|
|
6557
|
+
}
|
|
6558
|
+
throw new Error("answer_question: provide answer, deferReason, or escalateTo");
|
|
6559
|
+
}
|
|
6560
|
+
},
|
|
6561
|
+
{
|
|
6562
|
+
name: "ask_parent",
|
|
6563
|
+
description: "Raise a question to the parent driver/Pi/user when this driver cannot decide.",
|
|
6564
|
+
inputSchema: {
|
|
6565
|
+
type: "object",
|
|
6566
|
+
properties: {
|
|
6567
|
+
from: { type: "string" },
|
|
6568
|
+
level: { type: "string", enum: ["worker", "driver", "loop"] },
|
|
6569
|
+
question: { type: "string" },
|
|
6570
|
+
reason: { type: "string" },
|
|
6571
|
+
urgency: { type: "string", enum: ["continue-without", "blocks-step", "blocks-run"] }
|
|
6572
|
+
},
|
|
6573
|
+
required: ["from", "level", "question", "reason", "urgency"]
|
|
6574
|
+
},
|
|
6575
|
+
handler: async (raw) => {
|
|
6576
|
+
const a = obj(raw);
|
|
6577
|
+
const from = str(a.from, "from");
|
|
6578
|
+
const q = await emitNewQuestion(
|
|
6579
|
+
addQuestion(
|
|
6580
|
+
{
|
|
6581
|
+
from,
|
|
6582
|
+
level: level(a.level),
|
|
6583
|
+
question: str(a.question, "question"),
|
|
6584
|
+
reason: str(a.reason, "reason"),
|
|
6585
|
+
urgency: urgency(a.urgency)
|
|
6586
|
+
},
|
|
6587
|
+
from,
|
|
6588
|
+
{ kind: "escalate", to: "parent", reason: "asked parent" }
|
|
6589
|
+
)
|
|
6590
|
+
);
|
|
6591
|
+
return { question: q };
|
|
6592
|
+
}
|
|
6593
|
+
},
|
|
6594
|
+
{
|
|
6595
|
+
name: "stop",
|
|
6596
|
+
description: "Declare the run complete.",
|
|
6597
|
+
inputSchema: {
|
|
6598
|
+
type: "object",
|
|
6599
|
+
properties: { reason: { type: "string", description: "Why you are stopping." } }
|
|
6600
|
+
},
|
|
6601
|
+
handler: (raw) => {
|
|
6602
|
+
const blocking = blockingQuestionsForStop();
|
|
6603
|
+
if (blocking.length) {
|
|
6604
|
+
return Promise.resolve({
|
|
6605
|
+
stopped: false,
|
|
6606
|
+
error: "unresolved-blocking-questions",
|
|
6607
|
+
questions: blocking
|
|
6608
|
+
});
|
|
6609
|
+
}
|
|
6610
|
+
stopped = true;
|
|
6611
|
+
const r = obj(raw).reason;
|
|
6612
|
+
reason = typeof r === "string" ? r : void 0;
|
|
6613
|
+
return Promise.resolve({ stopped: true });
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
];
|
|
6617
|
+
if (opts.analysts) {
|
|
6618
|
+
tools.push({
|
|
6619
|
+
name: "list_analysts",
|
|
6620
|
+
description: "List trace-analyst lenses available to run over a settled worker.",
|
|
6621
|
+
inputSchema: { type: "object", properties: {} },
|
|
6622
|
+
handler: () => Promise.resolve({ analysts: opts.analysts?.kinds })
|
|
6623
|
+
});
|
|
6624
|
+
tools.push({
|
|
6625
|
+
name: "run_analyst",
|
|
6626
|
+
description: "Apply an analyst lens to a settled worker trace.",
|
|
6627
|
+
inputSchema: {
|
|
6628
|
+
type: "object",
|
|
6629
|
+
properties: {
|
|
6630
|
+
kind: { type: "string", description: "The analyst kind id." },
|
|
6631
|
+
workerId: idArg
|
|
6632
|
+
},
|
|
6633
|
+
required: ["kind", "workerId"]
|
|
6634
|
+
},
|
|
6635
|
+
handler: async (raw) => {
|
|
6636
|
+
const a = obj(raw);
|
|
6637
|
+
const id = str(a.workerId, "workerId");
|
|
6638
|
+
const node = opts.scope.view.nodes.find((n) => n.id === id);
|
|
6639
|
+
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
6640
|
+
if (!node.outRef)
|
|
6641
|
+
return { error: `worker ${JSON.stringify(id)} has not settled \u2014 no trace to analyze yet` };
|
|
6642
|
+
const trace = await opts.blobs.get(node.outRef);
|
|
6643
|
+
return { findings: await opts.analysts?.run(str(a.kind, "kind"), trace) };
|
|
6644
|
+
}
|
|
6645
|
+
});
|
|
6646
|
+
}
|
|
6647
|
+
return {
|
|
6648
|
+
tools,
|
|
6649
|
+
isStopped: () => stopped,
|
|
6650
|
+
stopReason: () => reason,
|
|
6651
|
+
settled: () => ledger,
|
|
6652
|
+
questions: () => questions
|
|
6653
|
+
};
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6656
|
+
// src/runtime/supervise/coordination-driver.ts
|
|
6657
|
+
function coordinationDriverAgent(opts) {
|
|
6658
|
+
if (typeof opts.chat?.next !== "function") {
|
|
6659
|
+
throw new ValidationError("coordinationDriverAgent: opts.chat.next must be a function");
|
|
6660
|
+
}
|
|
6661
|
+
const maxTurns = opts.maxTurns ?? 16;
|
|
6662
|
+
return {
|
|
6663
|
+
name: opts.name,
|
|
6664
|
+
async act(task, scope) {
|
|
6665
|
+
const coord = createCoordinationTools({
|
|
6666
|
+
scope,
|
|
6667
|
+
blobs: opts.blobs,
|
|
6668
|
+
makeWorkerAgent: opts.makeWorkerAgent,
|
|
6669
|
+
perWorker: opts.perWorker
|
|
6670
|
+
});
|
|
6671
|
+
const byName = new Map(coord.tools.map((t) => [t.name, t]));
|
|
6672
|
+
const toolSpecs = coord.tools.map((t) => ({
|
|
6673
|
+
name: t.name,
|
|
6674
|
+
description: t.description,
|
|
6675
|
+
parameters: t.inputSchema
|
|
6676
|
+
}));
|
|
6677
|
+
const system = typeof opts.systemPrompt === "function" ? opts.systemPrompt(task) : opts.systemPrompt;
|
|
6678
|
+
const messages = [{ role: "user", content: stringifyTask(task) }];
|
|
6679
|
+
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
6680
|
+
if (coord.isStopped()) break;
|
|
6681
|
+
const res = await opts.chat.next({ system, messages, tools: toolSpecs });
|
|
6682
|
+
const calls = res.toolCalls ?? [];
|
|
6683
|
+
if (calls.length === 0) {
|
|
6684
|
+
return finalize2(coord, opts.blobs);
|
|
6685
|
+
}
|
|
6686
|
+
messages.push({ role: "assistant", content: res.content ?? "", toolCalls: calls });
|
|
6687
|
+
for (const tc of calls) {
|
|
6688
|
+
const tool = byName.get(tc.name);
|
|
6689
|
+
const result = tool ? await runTool(tool, tc.arguments) : { error: `unknown tool: ${tc.name}` };
|
|
6690
|
+
messages.push({
|
|
6691
|
+
role: "tool",
|
|
6692
|
+
...tc.id ? { toolCallId: tc.id } : {},
|
|
6693
|
+
name: tc.name,
|
|
6694
|
+
content: safeJson(result)
|
|
6695
|
+
});
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
return finalize2(coord, opts.blobs);
|
|
6699
|
+
}
|
|
6700
|
+
};
|
|
6701
|
+
}
|
|
6702
|
+
async function runTool(tool, args) {
|
|
6703
|
+
try {
|
|
6704
|
+
return await tool.handler(args);
|
|
6705
|
+
} catch (e) {
|
|
6706
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
async function finalize2(coord, blobs) {
|
|
6710
|
+
const delivered = coord.settled().filter((w) => w.status === "done" && w.valid === true);
|
|
6711
|
+
if (delivered.length === 0) return void 0;
|
|
6712
|
+
let best = delivered[0];
|
|
6713
|
+
for (const w of delivered) if ((w.score ?? 0) > (best.score ?? 0)) best = w;
|
|
6714
|
+
return best.outRef ? await blobs.get(best.outRef) : void 0;
|
|
6715
|
+
}
|
|
6716
|
+
function stringifyTask(task) {
|
|
6717
|
+
return typeof task === "string" ? task : safeJson(task);
|
|
6718
|
+
}
|
|
6719
|
+
function safeJson(v) {
|
|
6720
|
+
try {
|
|
6721
|
+
return JSON.stringify(v) ?? String(v);
|
|
6722
|
+
} catch {
|
|
6723
|
+
return String(v);
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
|
|
6727
|
+
// src/runtime/supervise/coordination-mcp.ts
|
|
6728
|
+
import { createServer } from "http";
|
|
6729
|
+
|
|
6730
|
+
// src/mcp/server.ts
|
|
6731
|
+
import { createInterface } from "readline";
|
|
6732
|
+
import { Readable, Writable } from "stream";
|
|
6733
|
+
|
|
6734
|
+
// src/mcp/feedback-store.ts
|
|
6735
|
+
var InMemoryFeedbackStore = class {
|
|
6736
|
+
events = [];
|
|
6737
|
+
async put(event) {
|
|
6738
|
+
this.events.push({ ...event });
|
|
6739
|
+
}
|
|
6740
|
+
async list(filter = {}) {
|
|
6741
|
+
let out = this.events;
|
|
6742
|
+
if (filter.namespace !== void 0) {
|
|
6743
|
+
out = out.filter((event) => event.namespace === filter.namespace);
|
|
6744
|
+
}
|
|
6745
|
+
if (filter.refersToRef !== void 0) {
|
|
6746
|
+
out = out.filter((event) => event.refersTo.ref === filter.refersToRef);
|
|
6747
|
+
}
|
|
6748
|
+
return out.map((event) => ({ ...event }));
|
|
6749
|
+
}
|
|
6750
|
+
};
|
|
6751
|
+
function eventToSnapshot(event) {
|
|
6752
|
+
const snap = {
|
|
6753
|
+
id: event.id,
|
|
6754
|
+
score: event.rating.score,
|
|
6755
|
+
by: event.by,
|
|
6756
|
+
notes: event.rating.notes,
|
|
6757
|
+
capturedAt: event.capturedAt
|
|
6758
|
+
};
|
|
6759
|
+
if (event.rating.label) snap.label = event.rating.label;
|
|
6760
|
+
return snap;
|
|
6761
|
+
}
|
|
6762
|
+
|
|
6763
|
+
// src/mcp/delegation-store.ts
|
|
6764
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
6765
|
+
import { dirname } from "path";
|
|
6766
|
+
var DelegationStateCorruptError = class extends AgentEvalError {
|
|
6767
|
+
constructor(message, options) {
|
|
6768
|
+
super("validation", message, options);
|
|
6769
|
+
}
|
|
6770
|
+
};
|
|
6771
|
+
var DelegationPersistenceError = class extends AgentEvalError {
|
|
6772
|
+
constructor(message, options) {
|
|
6773
|
+
super("config", message, options);
|
|
6774
|
+
}
|
|
6775
|
+
};
|
|
6776
|
+
var InMemoryDelegationStore = class {
|
|
6777
|
+
records = /* @__PURE__ */ new Map();
|
|
6778
|
+
async loadAll() {
|
|
6779
|
+
return [...this.records.values()].map(cloneRecord);
|
|
6780
|
+
}
|
|
6781
|
+
async upsert(record) {
|
|
6782
|
+
this.records.set(record.taskId, cloneRecord(record));
|
|
6783
|
+
}
|
|
6784
|
+
async lookupIdempotencyKey(key) {
|
|
6785
|
+
for (const record of this.records.values()) {
|
|
6786
|
+
if (record.idempotencyKey === key) return record.taskId;
|
|
6787
|
+
}
|
|
6788
|
+
return void 0;
|
|
6789
|
+
}
|
|
6790
|
+
async remove(taskIds) {
|
|
6791
|
+
for (const taskId of taskIds) this.records.delete(taskId);
|
|
6792
|
+
}
|
|
6793
|
+
};
|
|
6794
|
+
var STATE_FORMAT_VERSION = 1;
|
|
6795
|
+
var FileDelegationStore = class {
|
|
6796
|
+
filePath;
|
|
6797
|
+
recoverCorrupt;
|
|
6798
|
+
records = /* @__PURE__ */ new Map();
|
|
6799
|
+
loaded = false;
|
|
6800
|
+
writeTail = Promise.resolve();
|
|
6801
|
+
tmpSeq = 0;
|
|
6802
|
+
constructor(options) {
|
|
6803
|
+
this.filePath = options.filePath;
|
|
6804
|
+
this.recoverCorrupt = options.recoverCorrupt ?? false;
|
|
6805
|
+
}
|
|
6806
|
+
async loadAll() {
|
|
6807
|
+
let raw;
|
|
6808
|
+
try {
|
|
6809
|
+
raw = await readFile(this.filePath, "utf8");
|
|
6810
|
+
} catch (err) {
|
|
6811
|
+
if (err.code === "ENOENT") {
|
|
6812
|
+
this.loaded = true;
|
|
6813
|
+
return [];
|
|
6814
|
+
}
|
|
6815
|
+
throw new DelegationPersistenceError(
|
|
6816
|
+
`FileDelegationStore: failed to read ${this.filePath}: ${errorMessage2(err)}`,
|
|
6817
|
+
{ cause: err }
|
|
6818
|
+
);
|
|
6819
|
+
}
|
|
6820
|
+
let state;
|
|
6821
|
+
try {
|
|
6822
|
+
state = parsePersistedState(raw);
|
|
6823
|
+
} catch (err) {
|
|
6824
|
+
if (!this.recoverCorrupt) {
|
|
6825
|
+
throw new DelegationStateCorruptError(
|
|
6826
|
+
`FileDelegationStore: state file ${this.filePath} is corrupt (${errorMessage2(err)}). Repair or archive the file, or opt into automatic recovery (recoverCorrupt / AGENT_RUNTIME_DELEGATION_STATE_RECOVER=1) to archive it and start empty.`,
|
|
6827
|
+
{ cause: err }
|
|
6828
|
+
);
|
|
6829
|
+
}
|
|
6830
|
+
const archivePath = `${this.filePath}.corrupt-${Date.now()}`;
|
|
6831
|
+
await rename(this.filePath, archivePath);
|
|
6832
|
+
this.loaded = true;
|
|
6833
|
+
return [];
|
|
6834
|
+
}
|
|
6835
|
+
this.records.clear();
|
|
6836
|
+
for (const record of state.records) this.records.set(record.taskId, record);
|
|
6837
|
+
this.loaded = true;
|
|
6838
|
+
return [...this.records.values()].map(cloneRecord);
|
|
6839
|
+
}
|
|
6840
|
+
async upsert(record) {
|
|
6841
|
+
this.assertLoaded("upsert");
|
|
6842
|
+
this.records.set(record.taskId, cloneRecord(record));
|
|
6843
|
+
await this.enqueueWrite();
|
|
6844
|
+
}
|
|
6845
|
+
async lookupIdempotencyKey(key) {
|
|
6846
|
+
this.assertLoaded("lookupIdempotencyKey");
|
|
6847
|
+
for (const record of this.records.values()) {
|
|
6848
|
+
if (record.idempotencyKey === key) return record.taskId;
|
|
6849
|
+
}
|
|
6850
|
+
return void 0;
|
|
6851
|
+
}
|
|
6852
|
+
async remove(taskIds) {
|
|
6853
|
+
this.assertLoaded("remove");
|
|
6854
|
+
let changed = false;
|
|
6855
|
+
for (const taskId of taskIds) {
|
|
6856
|
+
if (this.records.delete(taskId)) changed = true;
|
|
6857
|
+
}
|
|
6858
|
+
if (changed) await this.enqueueWrite();
|
|
6859
|
+
}
|
|
6860
|
+
assertLoaded(op) {
|
|
6861
|
+
if (this.loaded) return;
|
|
6862
|
+
throw new DelegationPersistenceError(
|
|
6863
|
+
`FileDelegationStore: ${op} called before loadAll() \u2014 the on-disk state has not been read yet`
|
|
6864
|
+
);
|
|
6865
|
+
}
|
|
6866
|
+
enqueueWrite() {
|
|
6867
|
+
const write = this.writeTail.then(() => this.writeSnapshot());
|
|
6868
|
+
this.writeTail = write.catch(() => {
|
|
6869
|
+
});
|
|
6870
|
+
return write;
|
|
6871
|
+
}
|
|
6872
|
+
async writeSnapshot() {
|
|
6873
|
+
const state = {
|
|
6874
|
+
version: STATE_FORMAT_VERSION,
|
|
6875
|
+
records: [...this.records.values()]
|
|
6876
|
+
};
|
|
6877
|
+
const payload = `${JSON.stringify(state)}
|
|
6878
|
+
`;
|
|
6879
|
+
this.tmpSeq += 1;
|
|
6880
|
+
const tmpPath = `${this.filePath}.tmp-${process.pid}-${this.tmpSeq}`;
|
|
6881
|
+
try {
|
|
6882
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
6883
|
+
await writeFile(tmpPath, payload, "utf8");
|
|
6884
|
+
await rename(tmpPath, this.filePath);
|
|
6885
|
+
} catch (err) {
|
|
6886
|
+
throw new DelegationPersistenceError(
|
|
6887
|
+
`FileDelegationStore: failed to write ${this.filePath}: ${errorMessage2(err)}`,
|
|
6888
|
+
{ cause: err }
|
|
6889
|
+
);
|
|
6890
|
+
}
|
|
6891
|
+
}
|
|
6892
|
+
};
|
|
6893
|
+
function parsePersistedState(raw) {
|
|
6894
|
+
const parsed = JSON.parse(raw);
|
|
6895
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
6896
|
+
throw new Error("top-level value is not an object");
|
|
6897
|
+
}
|
|
6898
|
+
const state = parsed;
|
|
6899
|
+
if (state.version !== STATE_FORMAT_VERSION) {
|
|
6900
|
+
throw new Error(`unsupported state version ${JSON.stringify(state.version)}`);
|
|
6901
|
+
}
|
|
6902
|
+
if (!Array.isArray(state.records)) {
|
|
6903
|
+
throw new Error("`records` is not an array");
|
|
6904
|
+
}
|
|
6905
|
+
for (const record of state.records) {
|
|
6906
|
+
if (record === null || typeof record !== "object") {
|
|
6907
|
+
throw new Error("a record entry is not an object");
|
|
6908
|
+
}
|
|
6909
|
+
const candidate = record;
|
|
6910
|
+
if (typeof candidate.taskId !== "string" || typeof candidate.status !== "string") {
|
|
6911
|
+
throw new Error("a record entry is missing `taskId`/`status`");
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
return { version: STATE_FORMAT_VERSION, records: state.records };
|
|
6915
|
+
}
|
|
6916
|
+
function cloneRecord(record) {
|
|
6917
|
+
return structuredClone(record);
|
|
6918
|
+
}
|
|
6919
|
+
function errorMessage2(err) {
|
|
6920
|
+
return err instanceof Error ? err.message : String(err);
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
// src/mcp/delegation-trace.ts
|
|
6924
|
+
var DELEGATION_TRACE_MAX_SPANS = 512;
|
|
6925
|
+
var DELEGATION_TRACE_MAX_BYTES = 256 * 1024;
|
|
6926
|
+
function buildDelegationTraceSpans(events) {
|
|
6927
|
+
return buildLoopSpanNodes(events).map((node) => ({
|
|
6928
|
+
spanId: node.spanId,
|
|
6929
|
+
...node.parentSpanId !== void 0 ? { parentSpanId: node.parentSpanId } : {},
|
|
6930
|
+
name: node.name,
|
|
6931
|
+
kind: node.kind,
|
|
6932
|
+
startMs: node.startMs,
|
|
6933
|
+
endMs: node.endMs,
|
|
6934
|
+
...Object.keys(node.attrs).length > 0 ? { meta: node.attrs } : {}
|
|
6935
|
+
}));
|
|
6936
|
+
}
|
|
6937
|
+
function capDelegationTrace(spans, caps) {
|
|
6938
|
+
const maxSpans = caps?.maxSpans ?? DELEGATION_TRACE_MAX_SPANS;
|
|
6939
|
+
const maxBytes = caps?.maxBytes ?? DELEGATION_TRACE_MAX_BYTES;
|
|
6940
|
+
let start = Math.max(0, spans.length - maxSpans);
|
|
6941
|
+
const sizes = spans.map((span) => JSON.stringify(span).length + 1);
|
|
6942
|
+
let total = 0;
|
|
6943
|
+
for (let i = start; i < sizes.length; i += 1) total += sizes[i];
|
|
6944
|
+
while (start < spans.length - 1 && total > maxBytes) {
|
|
6945
|
+
total -= sizes[start];
|
|
6946
|
+
start += 1;
|
|
6947
|
+
}
|
|
6948
|
+
return { trace: spans.slice(start), truncated: start > 0 };
|
|
6949
|
+
}
|
|
6950
|
+
function createDelegationTraceCollector(onSpans) {
|
|
6951
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
6952
|
+
const flush = (events) => {
|
|
6953
|
+
const spans = buildDelegationTraceSpans(events);
|
|
6954
|
+
if (spans.length > 0) onSpans(spans);
|
|
6955
|
+
};
|
|
6956
|
+
return {
|
|
6957
|
+
emitter: {
|
|
6958
|
+
emit(event) {
|
|
6959
|
+
const buf = buffers.get(event.runId);
|
|
6960
|
+
if (buf) buf.push(event);
|
|
6961
|
+
else buffers.set(event.runId, [event]);
|
|
6962
|
+
if (event.kind === "loop.ended") {
|
|
6963
|
+
const events = buffers.get(event.runId) ?? [event];
|
|
6964
|
+
buffers.delete(event.runId);
|
|
6965
|
+
flush(events);
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
},
|
|
6969
|
+
settle() {
|
|
6970
|
+
for (const events of buffers.values()) flush(events);
|
|
6971
|
+
buffers.clear();
|
|
6972
|
+
}
|
|
6973
|
+
};
|
|
6974
|
+
}
|
|
6975
|
+
function generateDelegationSpanId() {
|
|
6976
|
+
const bytes = new Uint8Array(8);
|
|
6977
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
6978
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
6979
|
+
} else {
|
|
6980
|
+
for (let i = 0; i < 8; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
6981
|
+
}
|
|
6982
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
6983
|
+
}
|
|
6984
|
+
function composeLoopTraceEmitters(...emitters) {
|
|
6985
|
+
const live = emitters.filter((e) => e !== void 0);
|
|
6986
|
+
if (live.length === 0) return void 0;
|
|
6987
|
+
if (live.length === 1) return live[0];
|
|
6988
|
+
return {
|
|
6989
|
+
emit(event) {
|
|
6990
|
+
const pending = [];
|
|
6991
|
+
for (const emitter of live) {
|
|
6992
|
+
const result = emitter.emit(event);
|
|
6993
|
+
if (result) pending.push(result);
|
|
6994
|
+
}
|
|
6995
|
+
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
6996
|
+
}
|
|
6997
|
+
};
|
|
6998
|
+
}
|
|
6999
|
+
|
|
7000
|
+
// src/mcp/task-queue.ts
|
|
7001
|
+
var DelegationTaskQueue = class _DelegationTaskQueue {
|
|
7002
|
+
records = /* @__PURE__ */ new Map();
|
|
7003
|
+
controllers = /* @__PURE__ */ new Map();
|
|
7004
|
+
byIdempotencyKey = /* @__PURE__ */ new Map();
|
|
7005
|
+
generateId;
|
|
7006
|
+
now;
|
|
7007
|
+
store;
|
|
7008
|
+
resumeDelegate;
|
|
7009
|
+
maxTerminalRecords;
|
|
7010
|
+
onPersistError;
|
|
7011
|
+
traceContext;
|
|
7012
|
+
persistTail = Promise.resolve();
|
|
7013
|
+
persistFailure;
|
|
7014
|
+
constructor(options = {}) {
|
|
7015
|
+
this.generateId = options.generateId ?? randomTaskId;
|
|
7016
|
+
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
7017
|
+
this.store = options.store ?? new InMemoryDelegationStore();
|
|
7018
|
+
this.resumeDelegate = options.resumeDelegate;
|
|
7019
|
+
if (options.maxTerminalRecords !== void 0) {
|
|
7020
|
+
if (!Number.isInteger(options.maxTerminalRecords) || options.maxTerminalRecords < 1) {
|
|
7021
|
+
throw new ValidationError(
|
|
7022
|
+
`DelegationTaskQueue: maxTerminalRecords must be a positive integer, got ${String(options.maxTerminalRecords)}`
|
|
7023
|
+
);
|
|
7024
|
+
}
|
|
7025
|
+
}
|
|
7026
|
+
this.maxTerminalRecords = options.maxTerminalRecords ?? Number.POSITIVE_INFINITY;
|
|
7027
|
+
this.traceContext = options.traceContext;
|
|
7028
|
+
this.onPersistError = options.onPersistError ?? ((error) => {
|
|
7029
|
+
queueMicrotask(() => {
|
|
7030
|
+
throw error;
|
|
7031
|
+
});
|
|
7032
|
+
});
|
|
7033
|
+
}
|
|
7034
|
+
/**
|
|
7035
|
+
* Construct a queue from previously-persisted state. Loads every record
|
|
7036
|
+
* from `options.store`, rebuilds the idempotency index (so a re-submitted
|
|
7037
|
+
* identical task returns the prior taskId and its terminal state), then:
|
|
7038
|
+
*
|
|
7039
|
+
* - terminal records stay queryable via `status()` / `history()`
|
|
7040
|
+
* - in-flight records with a `detachedSessionRef` re-attach through
|
|
7041
|
+
* `options.resumeDelegate` and report `running`
|
|
7042
|
+
* - other in-flight records settle as failed — their driver died with
|
|
7043
|
+
* the previous process and the result is unrecoverable
|
|
7044
|
+
*
|
|
7045
|
+
* The retention cap applies to the loaded set as well.
|
|
7046
|
+
*/
|
|
7047
|
+
static async restore(options = {}) {
|
|
7048
|
+
const queue = new _DelegationTaskQueue(options);
|
|
7049
|
+
const loaded = await queue.store.loadAll();
|
|
7050
|
+
queue.rehydrate(loaded);
|
|
7051
|
+
return queue;
|
|
7052
|
+
}
|
|
7053
|
+
/**
|
|
7054
|
+
* Kick off a delegation in the background. Returns immediately. The
|
|
7055
|
+
* `taskId` is queryable via `status` once this method returns. Throws
|
|
7056
|
+
* the recorded `DelegationPersistenceError` once the store has failed —
|
|
7057
|
+
* the queue does not accept work it cannot journal.
|
|
7058
|
+
*/
|
|
7059
|
+
submit(input) {
|
|
7060
|
+
if (this.persistFailure) throw this.persistFailure;
|
|
7061
|
+
if (input.idempotencyKey) {
|
|
7062
|
+
const existing = this.byIdempotencyKey.get(input.idempotencyKey);
|
|
7063
|
+
if (existing && this.records.has(existing)) {
|
|
7064
|
+
return { taskId: existing, reused: true };
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
7067
|
+
const taskId = this.generateId();
|
|
7068
|
+
const controller = new AbortController();
|
|
7069
|
+
const record = {
|
|
7070
|
+
taskId,
|
|
7071
|
+
profile: input.profile,
|
|
7072
|
+
namespace: input.namespace,
|
|
7073
|
+
args: input.args,
|
|
7074
|
+
status: "pending",
|
|
7075
|
+
startedAt: this.now(),
|
|
7076
|
+
feedback: [],
|
|
7077
|
+
idempotencyKey: input.idempotencyKey,
|
|
7078
|
+
detachedSessionRef: input.detachedSessionRef,
|
|
7079
|
+
...this.traceContext !== void 0 ? {
|
|
7080
|
+
traceId: this.traceContext.traceId,
|
|
7081
|
+
...this.traceContext.parentSpanId !== void 0 ? { parentSpanId: this.traceContext.parentSpanId } : {}
|
|
7082
|
+
} : {}
|
|
7083
|
+
};
|
|
7084
|
+
this.records.set(taskId, record);
|
|
7085
|
+
this.controllers.set(taskId, controller);
|
|
7086
|
+
if (input.idempotencyKey) this.byIdempotencyKey.set(input.idempotencyKey, taskId);
|
|
7087
|
+
this.persist(record);
|
|
7088
|
+
queueMicrotask(() => {
|
|
7089
|
+
this.execute(taskId, input, controller);
|
|
7090
|
+
});
|
|
7091
|
+
return { taskId, reused: false };
|
|
7092
|
+
}
|
|
7093
|
+
/**
|
|
7094
|
+
* Snapshot the current state of a delegation. Returns `undefined` for
|
|
7095
|
+
* unknown ids so callers can distinguish missing from terminal.
|
|
7096
|
+
* `includeTrace` attaches the journaled loop-trace span tree — off by
|
|
7097
|
+
* default so status polls stay light.
|
|
7098
|
+
*/
|
|
7099
|
+
status(taskId, opts) {
|
|
7100
|
+
const record = this.records.get(taskId);
|
|
7101
|
+
if (!record) return void 0;
|
|
7102
|
+
return toStatusResult(record, opts);
|
|
7103
|
+
}
|
|
7104
|
+
/**
|
|
7105
|
+
* Abort an in-flight delegation. Returns `false` if the task is unknown
|
|
7106
|
+
* or already terminal. The underlying `run` function MUST honor the
|
|
7107
|
+
* abort signal for the cancel to take effect; the queue marks the
|
|
7108
|
+
* record `cancelled` regardless so a misbehaving runner cannot pin the
|
|
7109
|
+
* UI on `running` forever.
|
|
7110
|
+
*/
|
|
7111
|
+
cancel(taskId) {
|
|
7112
|
+
const record = this.records.get(taskId);
|
|
7113
|
+
if (!record) return false;
|
|
7114
|
+
if (isTerminal(record.status)) return false;
|
|
7115
|
+
const controller = this.controllers.get(taskId);
|
|
7116
|
+
controller?.abort();
|
|
7117
|
+
record.status = "cancelled";
|
|
7118
|
+
record.completedAt = this.now();
|
|
7119
|
+
record.error = { message: "cancelled by caller", kind: "CancelledError" };
|
|
7120
|
+
this.persist(record);
|
|
7121
|
+
this.enforceRetention();
|
|
7122
|
+
return true;
|
|
7123
|
+
}
|
|
7124
|
+
/**
|
|
7125
|
+
* Append a feedback event to the matching delegation. Returns `false`
|
|
7126
|
+
* when `ref` does not name a known taskId — the caller should still
|
|
7127
|
+
* record the feedback through a different surface (artifact/outcome
|
|
7128
|
+
* kinds are not queue-bound).
|
|
7129
|
+
*/
|
|
7130
|
+
attachFeedback(taskId, snapshot) {
|
|
7131
|
+
const record = this.records.get(taskId);
|
|
7132
|
+
if (!record) return false;
|
|
7133
|
+
record.feedback.push(snapshot);
|
|
7134
|
+
this.persist(record);
|
|
7135
|
+
return true;
|
|
7136
|
+
}
|
|
7137
|
+
/**
|
|
7138
|
+
* Query the recorded delegations. Returns entries newest-first (by
|
|
7139
|
+
* `startedAt`), truncated to `limit`.
|
|
7140
|
+
*/
|
|
7141
|
+
history(args = {}) {
|
|
7142
|
+
const limit = clampLimit(args.limit);
|
|
7143
|
+
const since = args.since ? Date.parse(args.since) : Number.NEGATIVE_INFINITY;
|
|
7144
|
+
const out = [];
|
|
7145
|
+
for (const record of this.records.values()) {
|
|
7146
|
+
if (args.namespace && record.namespace !== args.namespace) continue;
|
|
7147
|
+
if (args.profile && record.profile !== args.profile) continue;
|
|
7148
|
+
if (Number.isFinite(since) && Date.parse(record.startedAt) < since) continue;
|
|
7149
|
+
out.push(toHistoryEntry(record));
|
|
7150
|
+
}
|
|
7151
|
+
out.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
7152
|
+
return out.slice(0, limit);
|
|
7153
|
+
}
|
|
7154
|
+
/**
|
|
7155
|
+
* Await every journal write issued so far. Rejects with the recorded
|
|
7156
|
+
* `DelegationPersistenceError` when any of them failed. Call before
|
|
7157
|
+
* handing the store's backing file to another process.
|
|
7158
|
+
*/
|
|
7159
|
+
async flush() {
|
|
7160
|
+
await this.persistTail;
|
|
7161
|
+
if (this.persistFailure) throw this.persistFailure;
|
|
7162
|
+
}
|
|
7163
|
+
/** Test-only — number of in-flight (non-terminal) records. */
|
|
7164
|
+
inflightCount() {
|
|
7165
|
+
let n = 0;
|
|
7166
|
+
for (const record of this.records.values()) {
|
|
7167
|
+
if (!isTerminal(record.status)) n += 1;
|
|
7168
|
+
}
|
|
7169
|
+
return n;
|
|
7170
|
+
}
|
|
7171
|
+
async execute(taskId, input, controller) {
|
|
7172
|
+
const record = this.records.get(taskId);
|
|
7173
|
+
if (!record) return;
|
|
7174
|
+
record.status = "running";
|
|
7175
|
+
this.persist(record);
|
|
7176
|
+
const traceCollector = createDelegationTraceCollector((spans) => {
|
|
7177
|
+
if (isTerminal(currentStatus(record))) return;
|
|
7178
|
+
this.appendTrace(record, spans);
|
|
7179
|
+
this.persist(record);
|
|
7180
|
+
});
|
|
7181
|
+
try {
|
|
7182
|
+
const output = await input.run({
|
|
7183
|
+
signal: controller.signal,
|
|
7184
|
+
report: (progress) => {
|
|
7185
|
+
if (record.status === "running") {
|
|
7186
|
+
record.progress = progress;
|
|
7187
|
+
this.persist(record);
|
|
7188
|
+
}
|
|
7189
|
+
},
|
|
7190
|
+
traceEmitter: traceCollector.emitter,
|
|
7191
|
+
...record.detachedSessionRef !== void 0 ? { detachedSessionRef: record.detachedSessionRef } : {},
|
|
7192
|
+
updateDetachedSessionRef: (ref) => {
|
|
7193
|
+
if (typeof ref !== "string" || ref.length === 0) {
|
|
7194
|
+
throw new ValidationError(
|
|
7195
|
+
"DelegationTaskQueue: updateDetachedSessionRef requires a non-empty ref"
|
|
7196
|
+
);
|
|
7197
|
+
}
|
|
7198
|
+
if (isTerminal(currentStatus(record))) return;
|
|
7199
|
+
record.detachedSessionRef = ref;
|
|
7200
|
+
this.persist(record);
|
|
7201
|
+
}
|
|
7202
|
+
});
|
|
7203
|
+
traceCollector.settle();
|
|
7204
|
+
if (currentStatus(record) === "cancelled") return;
|
|
7205
|
+
record.status = "completed";
|
|
7206
|
+
record.completedAt = this.now();
|
|
7207
|
+
record.result = { profile: input.profile, output };
|
|
7208
|
+
this.persist(record);
|
|
7209
|
+
this.enforceRetention();
|
|
7210
|
+
} catch (err) {
|
|
7211
|
+
traceCollector.settle();
|
|
7212
|
+
if (currentStatus(record) === "cancelled") return;
|
|
7213
|
+
record.status = "failed";
|
|
7214
|
+
record.completedAt = this.now();
|
|
7215
|
+
record.error = errorToShape(err);
|
|
7216
|
+
this.persist(record);
|
|
7217
|
+
this.enforceRetention();
|
|
7218
|
+
} finally {
|
|
7219
|
+
this.controllers.delete(taskId);
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
appendTrace(record, spans) {
|
|
7223
|
+
if (spans.length === 0) return;
|
|
7224
|
+
const { trace, truncated } = capDelegationTrace([...record.trace ?? [], ...spans]);
|
|
7225
|
+
record.trace = trace;
|
|
7226
|
+
if (truncated) record.traceTruncated = true;
|
|
7227
|
+
}
|
|
7228
|
+
rehydrate(loaded) {
|
|
7229
|
+
const records = [...loaded].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
7230
|
+
for (const record of records) {
|
|
7231
|
+
this.records.set(record.taskId, record);
|
|
7232
|
+
if (record.idempotencyKey) this.byIdempotencyKey.set(record.idempotencyKey, record.taskId);
|
|
7233
|
+
}
|
|
7234
|
+
for (const record of this.records.values()) {
|
|
7235
|
+
if (isTerminal(record.status)) continue;
|
|
7236
|
+
if (record.detachedSessionRef && this.resumeDelegate) {
|
|
7237
|
+
record.status = "running";
|
|
7238
|
+
this.persist(record);
|
|
7239
|
+
this.startResume(record, record.detachedSessionRef, this.resumeDelegate);
|
|
7240
|
+
continue;
|
|
7241
|
+
}
|
|
7242
|
+
record.status = "failed";
|
|
7243
|
+
record.completedAt = this.now();
|
|
7244
|
+
record.error = {
|
|
7245
|
+
message: record.detachedSessionRef ? `delegation driver restarted while the task was in flight; detached session "${record.detachedSessionRef}" needs a resumeDelegate to be resumed` : "delegation driver restarted while the task was in flight; the run was not detached and cannot be resumed",
|
|
7246
|
+
kind: "DriverRestartError"
|
|
7247
|
+
};
|
|
7248
|
+
this.persist(record);
|
|
7249
|
+
}
|
|
7250
|
+
this.enforceRetention();
|
|
7251
|
+
}
|
|
7252
|
+
startResume(record, detachedSessionRef, driver) {
|
|
7253
|
+
const controller = new AbortController();
|
|
7254
|
+
this.controllers.set(record.taskId, controller);
|
|
7255
|
+
void this.driveResume(record, detachedSessionRef, driver, controller);
|
|
7256
|
+
}
|
|
7257
|
+
async driveResume(record, detachedSessionRef, driver, controller) {
|
|
7258
|
+
const intervalMs = driver.intervalMs ?? 5e3;
|
|
7259
|
+
const resumeStartMs = Date.parse(this.now());
|
|
7260
|
+
const ctx = {
|
|
7261
|
+
signal: controller.signal,
|
|
7262
|
+
report: (progress) => {
|
|
7263
|
+
if (currentStatus(record) !== "running") return;
|
|
7264
|
+
record.progress = progress;
|
|
7265
|
+
this.persist(record);
|
|
7266
|
+
}
|
|
7267
|
+
};
|
|
7268
|
+
try {
|
|
7269
|
+
while (!controller.signal.aborted && currentStatus(record) === "running") {
|
|
7270
|
+
const tick = await driver.tick({ record: structuredClone(record), detachedSessionRef }, ctx);
|
|
7271
|
+
if (currentStatus(record) === "cancelled") return;
|
|
7272
|
+
if (tick.state === "completed") {
|
|
7273
|
+
this.appendResumeSpan(record, detachedSessionRef, resumeStartMs);
|
|
7274
|
+
record.status = "completed";
|
|
7275
|
+
record.completedAt = this.now();
|
|
7276
|
+
record.result = {
|
|
7277
|
+
profile: record.profile,
|
|
7278
|
+
output: tick.output
|
|
7279
|
+
};
|
|
7280
|
+
if (tick.costUsd !== void 0) record.costUsd = tick.costUsd;
|
|
7281
|
+
this.persist(record);
|
|
7282
|
+
this.enforceRetention();
|
|
7283
|
+
return;
|
|
7284
|
+
}
|
|
7285
|
+
if (tick.state === "failed") {
|
|
7286
|
+
this.appendResumeSpan(record, detachedSessionRef, resumeStartMs, tick.error.message);
|
|
7287
|
+
record.status = "failed";
|
|
7288
|
+
record.completedAt = this.now();
|
|
7289
|
+
record.error = tick.error;
|
|
7290
|
+
this.persist(record);
|
|
7291
|
+
this.enforceRetention();
|
|
7292
|
+
return;
|
|
7293
|
+
}
|
|
7294
|
+
await abortableDelay(intervalMs, controller.signal);
|
|
7295
|
+
}
|
|
7296
|
+
} catch (err) {
|
|
7297
|
+
if (currentStatus(record) === "cancelled") return;
|
|
7298
|
+
this.appendResumeSpan(record, detachedSessionRef, resumeStartMs, errorToShape(err).message);
|
|
7299
|
+
record.status = "failed";
|
|
7300
|
+
record.completedAt = this.now();
|
|
7301
|
+
record.error = errorToShape(err);
|
|
7302
|
+
this.persist(record);
|
|
7303
|
+
this.enforceRetention();
|
|
7304
|
+
} finally {
|
|
7305
|
+
this.controllers.delete(record.taskId);
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
/**
|
|
7309
|
+
* Journal the resumed segment of a detached run as one compact span. The
|
|
7310
|
+
* resume driver re-attaches after a process restart, so the original
|
|
7311
|
+
* process's loop events are gone — this span records the post-restart
|
|
7312
|
+
* observation window (re-attach → terminal tick) under the
|
|
7313
|
+
* `'detached-resume'` driver tag, keeping restored delegations observable
|
|
7314
|
+
* in the journal alongside trace-carrying live runs.
|
|
7315
|
+
*/
|
|
7316
|
+
appendResumeSpan(record, detachedSessionRef, startMs, error) {
|
|
7317
|
+
this.appendTrace(record, [
|
|
7318
|
+
{
|
|
7319
|
+
spanId: generateDelegationSpanId(),
|
|
7320
|
+
name: "loop",
|
|
7321
|
+
kind: "loop",
|
|
7322
|
+
startMs,
|
|
7323
|
+
endMs: Date.parse(this.now()),
|
|
7324
|
+
meta: {
|
|
7325
|
+
"tangle.loop.driver": "detached-resume",
|
|
7326
|
+
"tangle.loop.detached_session_ref": detachedSessionRef,
|
|
7327
|
+
...error !== void 0 ? { "tangle.loop.error": error } : {}
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
]);
|
|
7331
|
+
}
|
|
7332
|
+
persist(record) {
|
|
7333
|
+
if (this.persistFailure) return;
|
|
7334
|
+
const snapshot = structuredClone(record);
|
|
7335
|
+
this.persistTail = this.persistTail.then(async () => {
|
|
7336
|
+
if (this.persistFailure) return;
|
|
7337
|
+
try {
|
|
7338
|
+
await this.store.upsert(snapshot);
|
|
7339
|
+
} catch (err) {
|
|
7340
|
+
this.failPersistence(err);
|
|
7341
|
+
}
|
|
7342
|
+
});
|
|
7343
|
+
}
|
|
7344
|
+
persistRemoval(taskIds) {
|
|
7345
|
+
if (this.persistFailure || taskIds.length === 0) return;
|
|
7346
|
+
this.persistTail = this.persistTail.then(async () => {
|
|
7347
|
+
if (this.persistFailure) return;
|
|
7348
|
+
try {
|
|
7349
|
+
await this.store.remove(taskIds);
|
|
7350
|
+
} catch (err) {
|
|
7351
|
+
this.failPersistence(err);
|
|
7352
|
+
}
|
|
7353
|
+
});
|
|
7354
|
+
}
|
|
7355
|
+
failPersistence(cause) {
|
|
7356
|
+
if (this.persistFailure) return;
|
|
7357
|
+
const error = cause instanceof DelegationPersistenceError ? cause : new DelegationPersistenceError(
|
|
7358
|
+
`DelegationTaskQueue: store write failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
7359
|
+
{ cause }
|
|
7360
|
+
);
|
|
7361
|
+
this.persistFailure = error;
|
|
7362
|
+
this.onPersistError(error);
|
|
7363
|
+
}
|
|
7364
|
+
enforceRetention() {
|
|
7365
|
+
if (!Number.isFinite(this.maxTerminalRecords)) return;
|
|
7366
|
+
const terminal = [];
|
|
7367
|
+
for (const record of this.records.values()) {
|
|
7368
|
+
if (isTerminal(record.status)) terminal.push(record);
|
|
7369
|
+
}
|
|
7370
|
+
const excess = terminal.length - this.maxTerminalRecords;
|
|
7371
|
+
if (excess <= 0) return;
|
|
7372
|
+
terminal.sort(
|
|
7373
|
+
(a, b) => (a.completedAt ?? a.startedAt).localeCompare(b.completedAt ?? b.startedAt)
|
|
7374
|
+
);
|
|
7375
|
+
const evicted = terminal.slice(0, excess);
|
|
7376
|
+
for (const record of evicted) {
|
|
7377
|
+
this.records.delete(record.taskId);
|
|
7378
|
+
if (record.idempotencyKey && this.byIdempotencyKey.get(record.idempotencyKey) === record.taskId) {
|
|
7379
|
+
this.byIdempotencyKey.delete(record.idempotencyKey);
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
this.persistRemoval(evicted.map((record) => record.taskId));
|
|
7383
|
+
}
|
|
7384
|
+
};
|
|
7385
|
+
function isTerminal(status) {
|
|
7386
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
7387
|
+
}
|
|
7388
|
+
function currentStatus(record) {
|
|
7389
|
+
return record.status;
|
|
7390
|
+
}
|
|
7391
|
+
function clampLimit(raw) {
|
|
7392
|
+
if (!Number.isFinite(raw)) return 50;
|
|
7393
|
+
const n = Math.trunc(raw);
|
|
7394
|
+
if (n <= 0) return 50;
|
|
7395
|
+
return Math.min(n, 500);
|
|
7396
|
+
}
|
|
7397
|
+
function abortableDelay(ms, signal) {
|
|
7398
|
+
return new Promise((resolve) => {
|
|
7399
|
+
if (signal.aborted) {
|
|
7400
|
+
resolve();
|
|
7401
|
+
return;
|
|
7402
|
+
}
|
|
7403
|
+
const onAbort = () => {
|
|
7404
|
+
clearTimeout(timer);
|
|
7405
|
+
resolve();
|
|
7406
|
+
};
|
|
7407
|
+
const timer = setTimeout(() => {
|
|
7408
|
+
signal.removeEventListener("abort", onAbort);
|
|
7409
|
+
resolve();
|
|
7410
|
+
}, ms);
|
|
7411
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
7412
|
+
});
|
|
7413
|
+
}
|
|
7414
|
+
function toStatusResult(record, opts) {
|
|
7415
|
+
const out = {
|
|
7416
|
+
taskId: record.taskId,
|
|
7417
|
+
profile: record.profile,
|
|
7418
|
+
status: record.status,
|
|
7419
|
+
startedAt: record.startedAt
|
|
7420
|
+
};
|
|
7421
|
+
if (record.progress) out.progress = record.progress;
|
|
7422
|
+
if (record.result) out.result = record.result;
|
|
7423
|
+
if (record.error) out.error = record.error;
|
|
7424
|
+
if (record.costUsd !== void 0) out.costUsd = record.costUsd;
|
|
7425
|
+
if (record.completedAt) out.completedAt = record.completedAt;
|
|
7426
|
+
if (record.traceId !== void 0) out.traceId = record.traceId;
|
|
7427
|
+
if (record.parentSpanId !== void 0) out.parentSpanId = record.parentSpanId;
|
|
7428
|
+
if (opts?.includeTrace === true && record.trace && record.trace.length > 0) {
|
|
7429
|
+
out.trace = record.trace.map((span) => ({ ...span }));
|
|
7430
|
+
if (record.traceTruncated) out.traceTruncated = true;
|
|
7431
|
+
}
|
|
7432
|
+
return out;
|
|
7433
|
+
}
|
|
7434
|
+
function toHistoryEntry(record) {
|
|
7435
|
+
const entry = {
|
|
7436
|
+
taskId: record.taskId,
|
|
7437
|
+
profile: record.profile,
|
|
7438
|
+
args: record.args,
|
|
7439
|
+
status: record.status,
|
|
7440
|
+
startedAt: record.startedAt,
|
|
7441
|
+
hasTrace: record.trace !== void 0 && record.trace.length > 0
|
|
7442
|
+
};
|
|
7443
|
+
if (record.namespace) entry.namespace = record.namespace;
|
|
7444
|
+
if (record.completedAt) entry.completedAt = record.completedAt;
|
|
7445
|
+
if (record.costUsd !== void 0) entry.costUsd = record.costUsd;
|
|
7446
|
+
if (record.feedback.length > 0) entry.feedback = [...record.feedback];
|
|
7447
|
+
if (record.traceId !== void 0) entry.traceId = record.traceId;
|
|
7448
|
+
return entry;
|
|
7449
|
+
}
|
|
7450
|
+
function errorToShape(err) {
|
|
7451
|
+
if (err instanceof Error) {
|
|
7452
|
+
return { message: err.message, kind: err.name || "Error" };
|
|
7453
|
+
}
|
|
7454
|
+
return { message: String(err), kind: "NonError" };
|
|
7455
|
+
}
|
|
7456
|
+
function randomTaskId() {
|
|
7457
|
+
const t = Date.now().toString(36);
|
|
7458
|
+
const r = Math.random().toString(36).slice(2, 10);
|
|
7459
|
+
return `dlg-${t}-${r}`;
|
|
7460
|
+
}
|
|
7461
|
+
function hashIdempotencyInput(value) {
|
|
7462
|
+
let str;
|
|
7463
|
+
try {
|
|
7464
|
+
str = JSON.stringify(canonicalize(value));
|
|
7465
|
+
} catch {
|
|
7466
|
+
str = String(value);
|
|
7467
|
+
}
|
|
7468
|
+
let h = 2166136261;
|
|
7469
|
+
for (let i = 0; i < str.length; i += 1) {
|
|
7470
|
+
h ^= str.charCodeAt(i);
|
|
7471
|
+
h = Math.imul(h, 16777619);
|
|
7472
|
+
}
|
|
7473
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
7474
|
+
}
|
|
7475
|
+
function canonicalize(value) {
|
|
7476
|
+
if (value === null || typeof value !== "object") return value;
|
|
7477
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
7478
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b));
|
|
7479
|
+
const out = {};
|
|
7480
|
+
for (const [k, v] of entries) out[k] = canonicalize(v);
|
|
7481
|
+
return out;
|
|
7482
|
+
}
|
|
7483
|
+
|
|
7484
|
+
// src/mcp/detached-turn.ts
|
|
7485
|
+
var DEFAULT_TICK_INTERVAL_MS = 5e3;
|
|
7486
|
+
function formatDetachedSessionRef(parts) {
|
|
7487
|
+
assertRefComponent("sessionId", parts.sessionId);
|
|
7488
|
+
if (parts.sandboxId === void 0) return `session=${parts.sessionId}`;
|
|
7489
|
+
assertRefComponent("sandboxId", parts.sandboxId);
|
|
7490
|
+
return `sandbox=${parts.sandboxId};session=${parts.sessionId}`;
|
|
7491
|
+
}
|
|
7492
|
+
function parseDetachedSessionRef(raw) {
|
|
7493
|
+
const fields = /* @__PURE__ */ new Map();
|
|
7494
|
+
for (const pair of raw.split(";")) {
|
|
7495
|
+
const eq = pair.indexOf("=");
|
|
7496
|
+
const key = eq === -1 ? "" : pair.slice(0, eq);
|
|
7497
|
+
const value = eq === -1 ? "" : pair.slice(eq + 1);
|
|
7498
|
+
if (key !== "session" && key !== "sandbox" || value.length === 0 || fields.has(key)) {
|
|
7499
|
+
throw new ValidationError(
|
|
7500
|
+
`parseDetachedSessionRef: malformed detachedSessionRef ${JSON.stringify(raw)} \u2014 expected "session=<id>" or "sandbox=<id>;session=<id>"`
|
|
7501
|
+
);
|
|
7502
|
+
}
|
|
7503
|
+
fields.set(key, value);
|
|
7504
|
+
}
|
|
7505
|
+
const sessionId = fields.get("session");
|
|
7506
|
+
if (!sessionId) {
|
|
7507
|
+
throw new ValidationError(
|
|
7508
|
+
`parseDetachedSessionRef: detachedSessionRef ${JSON.stringify(raw)} carries no session id`
|
|
7509
|
+
);
|
|
7510
|
+
}
|
|
7511
|
+
const sandboxId = fields.get("sandbox");
|
|
7512
|
+
return { sessionId, ...sandboxId !== void 0 ? { sandboxId } : {} };
|
|
7513
|
+
}
|
|
7514
|
+
function assertRefComponent(name, value) {
|
|
7515
|
+
if (value.length === 0 || value.includes(";") || value.includes("=")) {
|
|
7516
|
+
throw new ValidationError(
|
|
7517
|
+
`formatDetachedSessionRef: ${name} ${JSON.stringify(value)} must be non-empty and free of ";" / "="`
|
|
7518
|
+
);
|
|
7519
|
+
}
|
|
7520
|
+
}
|
|
7521
|
+
function detachedTurnEvents(sessionId, turn) {
|
|
7522
|
+
return [
|
|
7523
|
+
{
|
|
7524
|
+
type: "result",
|
|
7525
|
+
id: sessionId,
|
|
7526
|
+
data: {
|
|
7527
|
+
text: turn.text,
|
|
7528
|
+
finalText: turn.text,
|
|
7529
|
+
success: true,
|
|
7530
|
+
result: turn.result
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
];
|
|
7534
|
+
}
|
|
7535
|
+
async function runDetachedTurn(options) {
|
|
7536
|
+
const intervalMs = options.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS;
|
|
7537
|
+
const trace = createDetachedTurnTrace(options);
|
|
7538
|
+
trace.started();
|
|
7539
|
+
const box = await createSandboxForSpec(options.client, options.spec, options.signal).catch(
|
|
7540
|
+
(err) => {
|
|
7541
|
+
trace.ended(err instanceof Error ? err.message : String(err));
|
|
7542
|
+
throw err;
|
|
7543
|
+
}
|
|
7544
|
+
);
|
|
7545
|
+
const drive = box;
|
|
7546
|
+
const onAbort = () => {
|
|
7547
|
+
void drive._sessionCancel?.(options.sessionId).catch(() => {
|
|
7548
|
+
});
|
|
7549
|
+
};
|
|
7550
|
+
try {
|
|
7551
|
+
if (typeof drive.driveTurn !== "function") {
|
|
7552
|
+
throw new ValidationError(
|
|
7553
|
+
"runDetachedTurn: the acquired sandbox exposes no driveTurn(message, { sessionId }) \u2014 detached dispatch requires @tangle-network/sandbox >= 0.6 and a session-backed placement (sibling/fleet); disable detached dispatch for this executor."
|
|
7554
|
+
);
|
|
7555
|
+
}
|
|
7556
|
+
const sandboxId = box.id;
|
|
7557
|
+
if (typeof sandboxId !== "string" || sandboxId.length === 0) {
|
|
7558
|
+
throw new ValidationError(
|
|
7559
|
+
"runDetachedTurn: the acquired sandbox carries no id \u2014 without it the detached run cannot be resumed after a restart, so refusing to dispatch detached."
|
|
7560
|
+
);
|
|
7561
|
+
}
|
|
7562
|
+
options.bindSandbox(sandboxId);
|
|
7563
|
+
trace.dispatched(sandboxId);
|
|
7564
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
7565
|
+
for (; ; ) {
|
|
7566
|
+
throwIfAborted(options.signal);
|
|
7567
|
+
const tick = await drive.driveTurn(options.prompt, {
|
|
7568
|
+
sessionId: options.sessionId,
|
|
7569
|
+
turnId: options.sessionId,
|
|
7570
|
+
...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
|
|
7571
|
+
});
|
|
7572
|
+
throwIfAborted(options.signal);
|
|
7573
|
+
if (tick.state === "completed") {
|
|
7574
|
+
trace.ended();
|
|
7575
|
+
return { text: tick.text, result: tick.result };
|
|
7576
|
+
}
|
|
7577
|
+
if (tick.state === "failed") {
|
|
7578
|
+
throw new Error(`detached turn ${options.sessionId} failed: ${tick.error}`);
|
|
7579
|
+
}
|
|
7580
|
+
options.report({ iteration: 0, phase: detachedRunningPhase(tick.elapsedMs) });
|
|
7581
|
+
await sleep(intervalMs, options.signal);
|
|
7582
|
+
}
|
|
7583
|
+
} catch (err) {
|
|
7584
|
+
trace.ended(err instanceof Error ? err.message : String(err));
|
|
7585
|
+
throw err;
|
|
7586
|
+
} finally {
|
|
7587
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
7588
|
+
if (options.signal.aborted) onAbort();
|
|
7589
|
+
await deleteBoxSafe(box);
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
function createDetachedTurnTrace(options) {
|
|
7593
|
+
const emitter = options.traceEmitter;
|
|
7594
|
+
if (!emitter) {
|
|
7595
|
+
return { started() {
|
|
7596
|
+
}, dispatched() {
|
|
7597
|
+
}, ended() {
|
|
7598
|
+
} };
|
|
7599
|
+
}
|
|
7600
|
+
const runId = options.sessionId;
|
|
7601
|
+
const agentRunName = options.spec.name ?? options.spec.profile.name ?? "detached-turn";
|
|
7602
|
+
const startMs = Date.now();
|
|
7603
|
+
let done = false;
|
|
7604
|
+
const emit = (event) => {
|
|
7605
|
+
void emitter.emit(event);
|
|
7606
|
+
};
|
|
7607
|
+
return {
|
|
7608
|
+
started() {
|
|
7609
|
+
emit({
|
|
7610
|
+
kind: "loop.started",
|
|
7611
|
+
runId,
|
|
7612
|
+
timestamp: startMs,
|
|
7613
|
+
payload: {
|
|
7614
|
+
driver: "detached-turn",
|
|
7615
|
+
agentRunNames: [agentRunName],
|
|
7616
|
+
maxIterations: 1,
|
|
7617
|
+
maxConcurrency: 1
|
|
7618
|
+
}
|
|
7619
|
+
});
|
|
7620
|
+
emit({
|
|
7621
|
+
kind: "loop.iteration.started",
|
|
7622
|
+
runId,
|
|
7623
|
+
timestamp: startMs,
|
|
7624
|
+
payload: { iterationIndex: 0, agentRunName, taskHash: options.sessionId }
|
|
7625
|
+
});
|
|
7626
|
+
},
|
|
7627
|
+
dispatched(sandboxId) {
|
|
7628
|
+
emit({
|
|
7629
|
+
kind: "loop.iteration.dispatch",
|
|
7630
|
+
runId,
|
|
7631
|
+
timestamp: Date.now(),
|
|
7632
|
+
payload: {
|
|
7633
|
+
iterationIndex: 0,
|
|
7634
|
+
agentRunName,
|
|
7635
|
+
placement: options.placement ?? "sibling",
|
|
7636
|
+
sandboxId
|
|
7637
|
+
}
|
|
7638
|
+
});
|
|
7639
|
+
},
|
|
7640
|
+
ended(error) {
|
|
7641
|
+
if (done) return;
|
|
7642
|
+
done = true;
|
|
7643
|
+
const endMs = Date.now();
|
|
7644
|
+
emit({
|
|
7645
|
+
kind: "loop.iteration.ended",
|
|
7646
|
+
runId,
|
|
7647
|
+
timestamp: endMs,
|
|
7648
|
+
payload: {
|
|
7649
|
+
iterationIndex: 0,
|
|
7650
|
+
agentRunName,
|
|
7651
|
+
costUsd: 0,
|
|
7652
|
+
durationMs: endMs - startMs,
|
|
7653
|
+
...error !== void 0 ? { error } : {}
|
|
7654
|
+
}
|
|
7655
|
+
});
|
|
7656
|
+
emit({
|
|
7657
|
+
kind: "loop.ended",
|
|
7658
|
+
runId,
|
|
7659
|
+
timestamp: endMs,
|
|
7660
|
+
payload: {
|
|
7661
|
+
...error === void 0 ? { winnerIterationIndex: 0 } : {},
|
|
7662
|
+
totalCostUsd: 0,
|
|
7663
|
+
durationMs: endMs - startMs,
|
|
7664
|
+
iterations: 1
|
|
7665
|
+
}
|
|
7666
|
+
});
|
|
7667
|
+
}
|
|
7668
|
+
};
|
|
7669
|
+
}
|
|
7670
|
+
function detachedRunningPhase(elapsedMs) {
|
|
7671
|
+
return elapsedMs === void 0 ? "detached-running" : `detached-running ${Math.round(elapsedMs / 1e3)}s`;
|
|
7672
|
+
}
|
|
7673
|
+
function createDriveTurnResumeDriver(options) {
|
|
7674
|
+
const cancelHooked = /* @__PURE__ */ new Set();
|
|
7675
|
+
return {
|
|
7676
|
+
intervalMs: options.intervalMs ?? DEFAULT_TICK_INTERVAL_MS,
|
|
7677
|
+
async tick({ record, detachedSessionRef }, ctx) {
|
|
7678
|
+
const ref = parseDetachedSessionRef(detachedSessionRef);
|
|
7679
|
+
if (ref.sandboxId === void 0) {
|
|
7680
|
+
return {
|
|
7681
|
+
state: "failed",
|
|
7682
|
+
error: {
|
|
7683
|
+
message: `detached session "${ref.sessionId}" was never bound to a sandbox \u2014 the previous process died before the box was acquired, so the turn was never dispatched and cannot be resumed`,
|
|
7684
|
+
kind: "DetachedSessionUnboundError"
|
|
7685
|
+
}
|
|
7686
|
+
};
|
|
7687
|
+
}
|
|
7688
|
+
const box = await options.resolveSandbox(ref.sandboxId);
|
|
7689
|
+
if (!cancelHooked.has(record.taskId)) {
|
|
7690
|
+
cancelHooked.add(record.taskId);
|
|
7691
|
+
ctx.signal.addEventListener(
|
|
7692
|
+
"abort",
|
|
7693
|
+
() => {
|
|
7694
|
+
void box._sessionCancel?.(ref.sessionId).catch(() => {
|
|
7695
|
+
});
|
|
7696
|
+
},
|
|
7697
|
+
{ once: true }
|
|
7698
|
+
);
|
|
7699
|
+
}
|
|
7700
|
+
if (ctx.signal.aborted) throwAbort();
|
|
7701
|
+
const tick = await box.driveTurn(options.buildMessage(record), {
|
|
7702
|
+
sessionId: ref.sessionId,
|
|
7703
|
+
turnId: ref.sessionId,
|
|
7704
|
+
...options.wallCapMs !== void 0 ? { wallCapMs: options.wallCapMs } : {}
|
|
7705
|
+
});
|
|
7706
|
+
if (tick.state === "completed") {
|
|
7707
|
+
const output = await options.settleOutput(
|
|
7708
|
+
{ text: tick.text, result: tick.result },
|
|
7709
|
+
record,
|
|
7710
|
+
{
|
|
7711
|
+
signal: ctx.signal
|
|
7712
|
+
}
|
|
7713
|
+
);
|
|
7714
|
+
return { state: "completed", output };
|
|
7715
|
+
}
|
|
7716
|
+
if (tick.state === "failed") {
|
|
7717
|
+
return {
|
|
7718
|
+
state: "failed",
|
|
7719
|
+
error: {
|
|
7720
|
+
message: `detached turn ${ref.sessionId} failed: ${tick.error}`,
|
|
7721
|
+
kind: "DetachedTurnFailedError"
|
|
7722
|
+
}
|
|
7723
|
+
};
|
|
7724
|
+
}
|
|
7725
|
+
ctx.report({ iteration: 0, phase: detachedRunningPhase(tick.elapsedMs) });
|
|
7726
|
+
return { state: "running" };
|
|
7727
|
+
}
|
|
7728
|
+
};
|
|
7729
|
+
}
|
|
7730
|
+
|
|
7731
|
+
// src/mcp/tools/delegate-code.ts
|
|
7732
|
+
var DELEGATE_CODE_TOOL_NAME = "delegate_code";
|
|
7733
|
+
var DELEGATE_CODE_DESCRIPTION = [
|
|
7734
|
+
"Delegate a coding task to specialist coder agents that produce a validated patch.",
|
|
7735
|
+
"",
|
|
7736
|
+
"Use when: you need code written, fixed, refactored, or extended to satisfy a",
|
|
7737
|
+
"user goal that touches a real repository. The coder runs in an isolated",
|
|
7738
|
+
"sandbox, opens a fresh branch, keeps the diff minimal, runs the supplied",
|
|
7739
|
+
"test + typecheck commands, and emits a unified-diff patch.",
|
|
7740
|
+
"",
|
|
7741
|
+
"Returns immediately with a taskId. Poll delegation_status to retrieve the",
|
|
7742
|
+
"patch + validator verdict (typically minutes-to-hours, longer for large",
|
|
7743
|
+
"changes). Identical inputs return the same taskId \u2014 safe to retry.",
|
|
7744
|
+
"",
|
|
7745
|
+
"When variants > 1, multiple coder harnesses (claude-code, codex, opencode)",
|
|
7746
|
+
"attempt the task in parallel and the highest-scoring patch wins (smallest",
|
|
7747
|
+
"passing diff). Use variants for high-stakes changes; single variant for",
|
|
7748
|
+
"routine ones.",
|
|
7749
|
+
"",
|
|
7750
|
+
"Capability scope: the coder cannot modify paths outside repoRoot and cannot",
|
|
7751
|
+
"touch paths in config.forbiddenPaths. The validator hard-fails on a",
|
|
7752
|
+
"forbidden-path violation, diff above config.maxDiffLines, test failure, or",
|
|
7753
|
+
"typecheck failure \u2014 none of those make it past the gate."
|
|
7754
|
+
].join("\n");
|
|
7755
|
+
var DELEGATE_CODE_INPUT_SCHEMA = {
|
|
7756
|
+
type: "object",
|
|
7757
|
+
properties: {
|
|
7758
|
+
goal: {
|
|
7759
|
+
type: "string",
|
|
7760
|
+
description: "Natural-language description of what the coder must accomplish."
|
|
7761
|
+
},
|
|
7762
|
+
repoRoot: {
|
|
7763
|
+
type: "string",
|
|
7764
|
+
description: "Absolute path inside the sandbox where the repo lives."
|
|
7765
|
+
},
|
|
7766
|
+
contextHint: {
|
|
7767
|
+
type: "string",
|
|
7768
|
+
description: "Optional free-form context the coder sees in the prompt prelude."
|
|
7769
|
+
},
|
|
7770
|
+
variants: {
|
|
7771
|
+
type: "integer",
|
|
7772
|
+
minimum: 1,
|
|
7773
|
+
maximum: 8,
|
|
7774
|
+
description: "Number of parallel coder harnesses. Default 1."
|
|
7775
|
+
},
|
|
7776
|
+
config: {
|
|
7777
|
+
type: "object",
|
|
7778
|
+
properties: {
|
|
7779
|
+
testCmd: { type: "string" },
|
|
7780
|
+
typecheckCmd: { type: "string" },
|
|
7781
|
+
forbiddenPaths: { type: "array", items: { type: "string" } },
|
|
7782
|
+
maxDiffLines: { type: "integer", minimum: 1 }
|
|
7783
|
+
},
|
|
7784
|
+
additionalProperties: false
|
|
7785
|
+
},
|
|
7786
|
+
namespace: {
|
|
7787
|
+
type: "string",
|
|
7788
|
+
description: "Multi-tenant scope (customer-id, workspace-id)."
|
|
7789
|
+
}
|
|
7790
|
+
},
|
|
7791
|
+
required: ["goal", "repoRoot"],
|
|
7792
|
+
additionalProperties: false
|
|
7793
|
+
};
|
|
7794
|
+
var SINGLE_VARIANT_ESTIMATE_MS = 6 * 60 * 1e3;
|
|
7795
|
+
var FANOUT_PER_VARIANT_ESTIMATE_MS = 8 * 60 * 1e3;
|
|
7796
|
+
function validateDelegateCodeArgs(raw) {
|
|
7797
|
+
if (raw === null || typeof raw !== "object") {
|
|
7798
|
+
throw new TypeError("delegate_code: arguments must be an object");
|
|
7799
|
+
}
|
|
7800
|
+
const value = raw;
|
|
7801
|
+
const goal = value.goal;
|
|
7802
|
+
if (typeof goal !== "string" || goal.trim().length === 0) {
|
|
7803
|
+
throw new TypeError("delegate_code: `goal` must be a non-empty string");
|
|
7804
|
+
}
|
|
7805
|
+
const repoRoot = value.repoRoot;
|
|
7806
|
+
if (typeof repoRoot !== "string" || repoRoot.trim().length === 0) {
|
|
7807
|
+
throw new TypeError("delegate_code: `repoRoot` must be a non-empty string");
|
|
7808
|
+
}
|
|
7809
|
+
const args = { goal: goal.trim(), repoRoot: repoRoot.trim() };
|
|
7810
|
+
if (typeof value.contextHint === "string") args.contextHint = value.contextHint;
|
|
7811
|
+
if (value.variants !== void 0) {
|
|
7812
|
+
const variants = Number(value.variants);
|
|
7813
|
+
if (!Number.isFinite(variants) || variants < 1 || variants > 8) {
|
|
7814
|
+
throw new RangeError("delegate_code: `variants` must be an integer in [1, 8]");
|
|
7815
|
+
}
|
|
7816
|
+
args.variants = Math.trunc(variants);
|
|
7817
|
+
}
|
|
7818
|
+
if (value.config !== void 0) {
|
|
7819
|
+
args.config = validateConfig(value.config);
|
|
7820
|
+
}
|
|
7821
|
+
if (typeof value.namespace === "string") args.namespace = value.namespace;
|
|
7822
|
+
return args;
|
|
7823
|
+
}
|
|
7824
|
+
function validateConfig(raw) {
|
|
7825
|
+
if (raw === null || typeof raw !== "object") {
|
|
7826
|
+
throw new TypeError("delegate_code: `config` must be an object");
|
|
7827
|
+
}
|
|
7828
|
+
const value = raw;
|
|
7829
|
+
const out = {};
|
|
7830
|
+
if (value.testCmd !== void 0) {
|
|
7831
|
+
if (typeof value.testCmd !== "string") {
|
|
7832
|
+
throw new TypeError("delegate_code: `config.testCmd` must be a string");
|
|
7833
|
+
}
|
|
7834
|
+
out.testCmd = value.testCmd;
|
|
7835
|
+
}
|
|
7836
|
+
if (value.typecheckCmd !== void 0) {
|
|
7837
|
+
if (typeof value.typecheckCmd !== "string") {
|
|
7838
|
+
throw new TypeError("delegate_code: `config.typecheckCmd` must be a string");
|
|
7839
|
+
}
|
|
7840
|
+
out.typecheckCmd = value.typecheckCmd;
|
|
7841
|
+
}
|
|
7842
|
+
if (value.forbiddenPaths !== void 0) {
|
|
7843
|
+
if (!Array.isArray(value.forbiddenPaths)) {
|
|
7844
|
+
throw new TypeError("delegate_code: `config.forbiddenPaths` must be a string array");
|
|
7845
|
+
}
|
|
7846
|
+
out.forbiddenPaths = value.forbiddenPaths.map((entry, i) => {
|
|
7847
|
+
if (typeof entry !== "string") {
|
|
7848
|
+
throw new TypeError(`delegate_code: forbiddenPaths[${i}] must be a string`);
|
|
7849
|
+
}
|
|
7850
|
+
return entry;
|
|
7851
|
+
});
|
|
7852
|
+
}
|
|
7853
|
+
if (value.maxDiffLines !== void 0) {
|
|
7854
|
+
const n = Number(value.maxDiffLines);
|
|
7855
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
7856
|
+
throw new RangeError("delegate_code: `config.maxDiffLines` must be a positive integer");
|
|
7857
|
+
}
|
|
7858
|
+
out.maxDiffLines = Math.trunc(n);
|
|
7859
|
+
}
|
|
7860
|
+
return out;
|
|
7861
|
+
}
|
|
7862
|
+
function createDelegateCodeHandler(options) {
|
|
7863
|
+
const estimateDurationMs = options.estimateDurationMs ?? defaultEstimate;
|
|
7864
|
+
return async (raw) => {
|
|
7865
|
+
const args = validateDelegateCodeArgs(raw);
|
|
7866
|
+
const idempotencyKey = hashIdempotencyInput({
|
|
7867
|
+
profile: "coder",
|
|
7868
|
+
goal: args.goal,
|
|
7869
|
+
repoRoot: args.repoRoot,
|
|
7870
|
+
contextHint: args.contextHint,
|
|
7871
|
+
variants: args.variants ?? 1,
|
|
7872
|
+
config: args.config,
|
|
7873
|
+
namespace: args.namespace
|
|
7874
|
+
});
|
|
7875
|
+
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
7876
|
+
const submitted = options.queue.submit({
|
|
7877
|
+
profile: "coder",
|
|
7878
|
+
args,
|
|
7879
|
+
namespace: args.namespace,
|
|
7880
|
+
idempotencyKey,
|
|
7881
|
+
...detached ? {
|
|
7882
|
+
detachedSessionRef: formatDetachedSessionRef({
|
|
7883
|
+
sessionId: `dlg-turn-coder-${idempotencyKey}`
|
|
7884
|
+
})
|
|
7885
|
+
} : {},
|
|
7886
|
+
run: async (ctx) => options.delegate(args, ctx)
|
|
7887
|
+
});
|
|
7888
|
+
return {
|
|
7889
|
+
taskId: submitted.taskId,
|
|
7890
|
+
estimatedDurationMs: estimateDurationMs(args)
|
|
7891
|
+
};
|
|
7892
|
+
};
|
|
7893
|
+
}
|
|
7894
|
+
function defaultEstimate(args) {
|
|
7895
|
+
const variants = Math.max(1, args.variants ?? 1);
|
|
7896
|
+
if (variants === 1) return SINGLE_VARIANT_ESTIMATE_MS;
|
|
7897
|
+
return FANOUT_PER_VARIANT_ESTIMATE_MS;
|
|
7898
|
+
}
|
|
7899
|
+
|
|
7900
|
+
// src/mcp/tools/delegate-feedback.ts
|
|
7901
|
+
var DELEGATE_FEEDBACK_TOOL_NAME = "delegate_feedback";
|
|
7902
|
+
var DELEGATE_FEEDBACK_DESCRIPTION = [
|
|
7903
|
+
"Record feedback on a delegation, artifact, or outcome. Synchronous \u2014 the",
|
|
7904
|
+
"event is durably stored when this call returns.",
|
|
7905
|
+
"",
|
|
7906
|
+
"Use when: you (the agent), the user, or a downstream judge has formed an",
|
|
7907
|
+
"opinion about a piece of work and want it persisted for calibration,",
|
|
7908
|
+
"pricing, or future routing. Every call is a new event \u2014 multiple ratings",
|
|
7909
|
+
"on the same target are expected and never deduped.",
|
|
7910
|
+
"",
|
|
7911
|
+
"`refersTo.kind`:",
|
|
7912
|
+
' - "delegation": ref is a taskId returned by delegate_code/delegate_research',
|
|
7913
|
+
' - "artifact": ref is a URI/path/git-sha \u2014 anything you can dereference',
|
|
7914
|
+
' - "outcome": ref is a free-form description of a downstream result',
|
|
7915
|
+
"",
|
|
7916
|
+
"`by`:",
|
|
7917
|
+
' - "agent": the agent itself rated the work',
|
|
7918
|
+
' - "user": the human user rated it',
|
|
7919
|
+
' - "downstream-judge": an automated evaluator emitted the rating',
|
|
7920
|
+
"",
|
|
7921
|
+
"When ref names a known taskId, the rating is also attached to the",
|
|
7922
|
+
"delegation record so delegation_history surfaces it inline."
|
|
7923
|
+
].join("\n");
|
|
7924
|
+
var DELEGATE_FEEDBACK_INPUT_SCHEMA = {
|
|
7925
|
+
type: "object",
|
|
7926
|
+
properties: {
|
|
7927
|
+
refersTo: {
|
|
7928
|
+
type: "object",
|
|
7929
|
+
properties: {
|
|
7930
|
+
kind: { type: "string", enum: ["delegation", "artifact", "outcome"] },
|
|
7931
|
+
ref: { type: "string" }
|
|
7932
|
+
},
|
|
7933
|
+
required: ["kind", "ref"],
|
|
7934
|
+
additionalProperties: false
|
|
7935
|
+
},
|
|
7936
|
+
rating: {
|
|
7937
|
+
type: "object",
|
|
7938
|
+
properties: {
|
|
7939
|
+
score: { type: "number", minimum: 0, maximum: 1 },
|
|
7940
|
+
label: { type: "string", enum: ["good", "bad", "neutral", "mixed"] },
|
|
7941
|
+
notes: { type: "string" }
|
|
7942
|
+
},
|
|
7943
|
+
required: ["score", "notes"],
|
|
7944
|
+
additionalProperties: false
|
|
7945
|
+
},
|
|
7946
|
+
by: { type: "string", enum: ["agent", "user", "downstream-judge"] },
|
|
7947
|
+
capturedAt: { type: "string" },
|
|
7948
|
+
namespace: { type: "string" }
|
|
7949
|
+
},
|
|
7950
|
+
required: ["refersTo", "rating", "by"],
|
|
7951
|
+
additionalProperties: false
|
|
7952
|
+
};
|
|
7953
|
+
function validateDelegateFeedbackArgs(raw) {
|
|
7954
|
+
if (raw === null || typeof raw !== "object") {
|
|
7955
|
+
throw new TypeError("delegate_feedback: arguments must be an object");
|
|
7956
|
+
}
|
|
7957
|
+
const value = raw;
|
|
7958
|
+
const refersTo = validateRefersTo(value.refersTo);
|
|
7959
|
+
const rating = validateRating(value.rating);
|
|
7960
|
+
const by = value.by;
|
|
7961
|
+
if (by !== "agent" && by !== "user" && by !== "downstream-judge") {
|
|
7962
|
+
throw new TypeError(
|
|
7963
|
+
'delegate_feedback: `by` must be one of "agent" | "user" | "downstream-judge"'
|
|
7964
|
+
);
|
|
7965
|
+
}
|
|
7966
|
+
const args = { refersTo, rating, by };
|
|
7967
|
+
if (value.capturedAt !== void 0) {
|
|
7968
|
+
if (typeof value.capturedAt !== "string" || Number.isNaN(Date.parse(value.capturedAt))) {
|
|
7969
|
+
throw new TypeError("delegate_feedback: `capturedAt` must be an ISO datetime");
|
|
7970
|
+
}
|
|
7971
|
+
args.capturedAt = value.capturedAt;
|
|
7972
|
+
}
|
|
7973
|
+
if (typeof value.namespace === "string") args.namespace = value.namespace;
|
|
7974
|
+
return args;
|
|
7975
|
+
}
|
|
7976
|
+
function validateRefersTo(raw) {
|
|
7977
|
+
if (raw === null || typeof raw !== "object") {
|
|
7978
|
+
throw new TypeError("delegate_feedback: `refersTo` must be an object");
|
|
7979
|
+
}
|
|
7980
|
+
const value = raw;
|
|
7981
|
+
const kind = value.kind;
|
|
7982
|
+
if (kind !== "delegation" && kind !== "artifact" && kind !== "outcome") {
|
|
7983
|
+
throw new TypeError(
|
|
7984
|
+
'delegate_feedback: `refersTo.kind` must be one of "delegation" | "artifact" | "outcome"'
|
|
7985
|
+
);
|
|
7986
|
+
}
|
|
7987
|
+
const ref = value.ref;
|
|
7988
|
+
if (typeof ref !== "string" || ref.trim().length === 0) {
|
|
7989
|
+
throw new TypeError("delegate_feedback: `refersTo.ref` must be a non-empty string");
|
|
7990
|
+
}
|
|
7991
|
+
return { kind, ref: ref.trim() };
|
|
7992
|
+
}
|
|
7993
|
+
function validateRating(raw) {
|
|
7994
|
+
if (raw === null || typeof raw !== "object") {
|
|
7995
|
+
throw new TypeError("delegate_feedback: `rating` must be an object");
|
|
7996
|
+
}
|
|
7997
|
+
const value = raw;
|
|
7998
|
+
const score = Number(value.score);
|
|
7999
|
+
if (!Number.isFinite(score) || score < 0 || score > 1) {
|
|
8000
|
+
throw new RangeError("delegate_feedback: `rating.score` must be a number in [0, 1]");
|
|
8001
|
+
}
|
|
8002
|
+
const notes = value.notes;
|
|
8003
|
+
if (typeof notes !== "string") {
|
|
8004
|
+
throw new TypeError("delegate_feedback: `rating.notes` must be a string");
|
|
8005
|
+
}
|
|
8006
|
+
const rating = { score, notes };
|
|
8007
|
+
const label = value.label;
|
|
8008
|
+
if (label !== void 0) {
|
|
8009
|
+
if (label !== "good" && label !== "bad" && label !== "neutral" && label !== "mixed") {
|
|
8010
|
+
throw new TypeError(
|
|
8011
|
+
'delegate_feedback: `rating.label` must be one of "good" | "bad" | "neutral" | "mixed"'
|
|
8012
|
+
);
|
|
8013
|
+
}
|
|
8014
|
+
rating.label = label;
|
|
8015
|
+
}
|
|
8016
|
+
return rating;
|
|
8017
|
+
}
|
|
8018
|
+
function createDelegateFeedbackHandler(options) {
|
|
8019
|
+
const generateId = options.generateId ?? randomFeedbackId;
|
|
8020
|
+
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
8021
|
+
return async (raw) => {
|
|
8022
|
+
const args = validateDelegateFeedbackArgs(raw);
|
|
8023
|
+
const id = generateId();
|
|
8024
|
+
const event = {
|
|
8025
|
+
id,
|
|
8026
|
+
refersTo: args.refersTo,
|
|
8027
|
+
rating: args.rating,
|
|
8028
|
+
by: args.by,
|
|
8029
|
+
capturedAt: args.capturedAt ?? now(),
|
|
8030
|
+
namespace: args.namespace
|
|
8031
|
+
};
|
|
8032
|
+
await options.store.put(event);
|
|
8033
|
+
if (args.refersTo.kind === "delegation") {
|
|
8034
|
+
options.queue.attachFeedback(args.refersTo.ref, eventToSnapshot(event));
|
|
8035
|
+
}
|
|
8036
|
+
return { recorded: true, id };
|
|
8037
|
+
};
|
|
8038
|
+
}
|
|
8039
|
+
function randomFeedbackId() {
|
|
8040
|
+
const t = Date.now().toString(36);
|
|
8041
|
+
const r = Math.random().toString(36).slice(2, 10);
|
|
8042
|
+
return `fbk-${t}-${r}`;
|
|
8043
|
+
}
|
|
8044
|
+
|
|
8045
|
+
// src/mcp/tools/delegate-research.ts
|
|
8046
|
+
var DELEGATE_RESEARCH_TOOL_NAME = "delegate_research";
|
|
8047
|
+
var DELEGATE_RESEARCH_DESCRIPTION = [
|
|
8048
|
+
"Delegate a research question to specialist researcher agents that produce",
|
|
8049
|
+
"source-grounded, evidence-bearing knowledge items.",
|
|
8050
|
+
"",
|
|
8051
|
+
"Use when: you need to answer a factual question with external evidence \u2014",
|
|
8052
|
+
"audience research, competitive intelligence, recency-bound web searches,",
|
|
8053
|
+
"corpus / docs lookups. The researcher emits items[] with provenance, a",
|
|
8054
|
+
"citations[] index, and proposedWrites[] you decide whether to persist.",
|
|
8055
|
+
"",
|
|
8056
|
+
"Returns immediately with a taskId. Poll delegation_status to retrieve the",
|
|
8057
|
+
"items + verdict. Identical inputs return the same taskId \u2014 safe to retry.",
|
|
8058
|
+
"",
|
|
8059
|
+
"When variants > 1, multiple researcher harnesses run in parallel and the",
|
|
8060
|
+
"highest-scoring valid output wins (citation density \xD7 source diversity \xD7",
|
|
8061
|
+
"recency match \xD7 gap coverage). Use variants when answers might disagree.",
|
|
8062
|
+
"",
|
|
8063
|
+
"Multi-tenant isolation: every item carries `namespace`. The validator",
|
|
8064
|
+
"hard-fails when any item is scoped outside `namespace`. Never pass another",
|
|
8065
|
+
"tenant's namespace."
|
|
8066
|
+
].join("\n");
|
|
8067
|
+
var VALID_SOURCES = ["web", "corpus", "twitter", "github", "docs"];
|
|
8068
|
+
var DELEGATE_RESEARCH_INPUT_SCHEMA = {
|
|
8069
|
+
type: "object",
|
|
8070
|
+
properties: {
|
|
8071
|
+
question: {
|
|
8072
|
+
type: "string",
|
|
8073
|
+
description: "The research question to answer."
|
|
8074
|
+
},
|
|
8075
|
+
namespace: {
|
|
8076
|
+
type: "string",
|
|
8077
|
+
description: "Multi-tenant scope (customer-id, workspace-id). REQUIRED."
|
|
8078
|
+
},
|
|
8079
|
+
scope: { type: "string", description: 'Bound, e.g. "audience for cpg-founder ICP".' },
|
|
8080
|
+
sources: {
|
|
8081
|
+
type: "array",
|
|
8082
|
+
items: { type: "string", enum: [...VALID_SOURCES] }
|
|
8083
|
+
},
|
|
8084
|
+
variants: { type: "integer", minimum: 1, maximum: 8 },
|
|
8085
|
+
config: {
|
|
8086
|
+
type: "object",
|
|
8087
|
+
properties: {
|
|
8088
|
+
recencyWindow: {
|
|
8089
|
+
type: "object",
|
|
8090
|
+
properties: {
|
|
8091
|
+
since: { type: "string", description: "ISO datetime" },
|
|
8092
|
+
until: { type: "string", description: "ISO datetime" }
|
|
8093
|
+
},
|
|
8094
|
+
additionalProperties: false
|
|
8095
|
+
},
|
|
8096
|
+
maxItems: { type: "integer", minimum: 1 },
|
|
8097
|
+
minConfidence: { type: "number", minimum: 0, maximum: 1 }
|
|
8098
|
+
},
|
|
8099
|
+
additionalProperties: false
|
|
8100
|
+
}
|
|
8101
|
+
},
|
|
8102
|
+
required: ["question", "namespace"],
|
|
8103
|
+
additionalProperties: false
|
|
8104
|
+
};
|
|
8105
|
+
var SINGLE_VARIANT_ESTIMATE_MS2 = 4 * 60 * 1e3;
|
|
8106
|
+
var FANOUT_PER_VARIANT_ESTIMATE_MS2 = 6 * 60 * 1e3;
|
|
8107
|
+
function validateDelegateResearchArgs(raw) {
|
|
8108
|
+
if (raw === null || typeof raw !== "object") {
|
|
8109
|
+
throw new TypeError("delegate_research: arguments must be an object");
|
|
8110
|
+
}
|
|
8111
|
+
const value = raw;
|
|
8112
|
+
const question = value.question;
|
|
8113
|
+
if (typeof question !== "string" || question.trim().length === 0) {
|
|
8114
|
+
throw new TypeError("delegate_research: `question` must be a non-empty string");
|
|
8115
|
+
}
|
|
8116
|
+
const namespace = value.namespace;
|
|
8117
|
+
if (typeof namespace !== "string" || namespace.trim().length === 0) {
|
|
8118
|
+
throw new TypeError("delegate_research: `namespace` is required");
|
|
8119
|
+
}
|
|
8120
|
+
const args = { question: question.trim(), namespace: namespace.trim() };
|
|
8121
|
+
if (typeof value.scope === "string") args.scope = value.scope;
|
|
8122
|
+
if (value.sources !== void 0) {
|
|
8123
|
+
if (!Array.isArray(value.sources)) {
|
|
8124
|
+
throw new TypeError("delegate_research: `sources` must be a string array");
|
|
8125
|
+
}
|
|
8126
|
+
const sources = value.sources.map((src, i) => {
|
|
8127
|
+
if (typeof src !== "string" || !VALID_SOURCES.includes(src)) {
|
|
8128
|
+
throw new TypeError(
|
|
8129
|
+
`delegate_research: sources[${i}] must be one of ${VALID_SOURCES.join("|")}`
|
|
8130
|
+
);
|
|
8131
|
+
}
|
|
8132
|
+
return src;
|
|
8133
|
+
});
|
|
8134
|
+
args.sources = sources;
|
|
8135
|
+
}
|
|
8136
|
+
if (value.variants !== void 0) {
|
|
8137
|
+
const variants = Number(value.variants);
|
|
8138
|
+
if (!Number.isFinite(variants) || variants < 1 || variants > 8) {
|
|
8139
|
+
throw new RangeError("delegate_research: `variants` must be an integer in [1, 8]");
|
|
8140
|
+
}
|
|
8141
|
+
args.variants = Math.trunc(variants);
|
|
8142
|
+
}
|
|
8143
|
+
if (value.config !== void 0) {
|
|
8144
|
+
args.config = validateConfig2(value.config);
|
|
8145
|
+
}
|
|
8146
|
+
return args;
|
|
8147
|
+
}
|
|
8148
|
+
function validateConfig2(raw) {
|
|
8149
|
+
if (raw === null || typeof raw !== "object") {
|
|
8150
|
+
throw new TypeError("delegate_research: `config` must be an object");
|
|
8151
|
+
}
|
|
8152
|
+
const value = raw;
|
|
8153
|
+
const out = {};
|
|
8154
|
+
if (value.recencyWindow !== void 0) {
|
|
8155
|
+
if (value.recencyWindow === null || typeof value.recencyWindow !== "object") {
|
|
8156
|
+
throw new TypeError("delegate_research: `config.recencyWindow` must be an object");
|
|
8157
|
+
}
|
|
8158
|
+
const window = value.recencyWindow;
|
|
8159
|
+
const windowOut = {};
|
|
8160
|
+
if (window.since !== void 0) {
|
|
8161
|
+
if (typeof window.since !== "string" || Number.isNaN(Date.parse(window.since))) {
|
|
8162
|
+
throw new TypeError("delegate_research: `recencyWindow.since` must be an ISO datetime");
|
|
8163
|
+
}
|
|
8164
|
+
windowOut.since = window.since;
|
|
8165
|
+
}
|
|
8166
|
+
if (window.until !== void 0) {
|
|
8167
|
+
if (typeof window.until !== "string" || Number.isNaN(Date.parse(window.until))) {
|
|
8168
|
+
throw new TypeError("delegate_research: `recencyWindow.until` must be an ISO datetime");
|
|
8169
|
+
}
|
|
8170
|
+
windowOut.until = window.until;
|
|
8171
|
+
}
|
|
8172
|
+
out.recencyWindow = windowOut;
|
|
8173
|
+
}
|
|
8174
|
+
if (value.maxItems !== void 0) {
|
|
8175
|
+
const n = Number(value.maxItems);
|
|
8176
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
8177
|
+
throw new RangeError("delegate_research: `config.maxItems` must be a positive integer");
|
|
8178
|
+
}
|
|
8179
|
+
out.maxItems = Math.trunc(n);
|
|
8180
|
+
}
|
|
8181
|
+
if (value.minConfidence !== void 0) {
|
|
8182
|
+
const n = Number(value.minConfidence);
|
|
8183
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
8184
|
+
throw new RangeError("delegate_research: `config.minConfidence` must be in [0, 1]");
|
|
8185
|
+
}
|
|
8186
|
+
out.minConfidence = n;
|
|
8187
|
+
}
|
|
8188
|
+
return out;
|
|
8189
|
+
}
|
|
8190
|
+
function createDelegateResearchHandler(options) {
|
|
8191
|
+
const estimateDurationMs = options.estimateDurationMs ?? defaultEstimate2;
|
|
8192
|
+
return async (raw) => {
|
|
8193
|
+
const args = validateDelegateResearchArgs(raw);
|
|
8194
|
+
const idempotencyKey = hashIdempotencyInput({
|
|
8195
|
+
profile: "researcher",
|
|
8196
|
+
question: args.question,
|
|
8197
|
+
namespace: args.namespace,
|
|
8198
|
+
scope: args.scope,
|
|
8199
|
+
sources: args.sources,
|
|
8200
|
+
variants: args.variants ?? 1,
|
|
8201
|
+
config: args.config
|
|
8202
|
+
});
|
|
8203
|
+
const detached = options.detachedDispatch === true && (args.variants ?? 1) <= 1;
|
|
8204
|
+
const submitted = options.queue.submit({
|
|
8205
|
+
profile: "researcher",
|
|
8206
|
+
args,
|
|
8207
|
+
namespace: args.namespace,
|
|
8208
|
+
idempotencyKey,
|
|
8209
|
+
...detached ? {
|
|
8210
|
+
detachedSessionRef: formatDetachedSessionRef({
|
|
8211
|
+
sessionId: `dlg-turn-research-${idempotencyKey}`
|
|
8212
|
+
})
|
|
8213
|
+
} : {},
|
|
8214
|
+
run: async (ctx) => options.delegate(args, ctx)
|
|
8215
|
+
});
|
|
8216
|
+
return {
|
|
8217
|
+
taskId: submitted.taskId,
|
|
8218
|
+
estimatedDurationMs: estimateDurationMs(args)
|
|
8219
|
+
};
|
|
8220
|
+
};
|
|
8221
|
+
}
|
|
8222
|
+
function defaultEstimate2(args) {
|
|
8223
|
+
const variants = Math.max(1, args.variants ?? 1);
|
|
8224
|
+
if (variants === 1) return SINGLE_VARIANT_ESTIMATE_MS2;
|
|
8225
|
+
return FANOUT_PER_VARIANT_ESTIMATE_MS2;
|
|
8226
|
+
}
|
|
8227
|
+
|
|
8228
|
+
// src/mcp/tools/delegate-ui-audit.ts
|
|
8229
|
+
import path from "path";
|
|
8230
|
+
var DELEGATE_UI_AUDIT_TOOL_NAME = "delegate_ui_audit";
|
|
8231
|
+
var DELEGATE_UI_AUDIT_DESCRIPTION = [
|
|
8232
|
+
"Delegate a UI/UX audit to a vision-driven auditor that produces self-contained",
|
|
8233
|
+
"GitHub-issue-ready Markdown findings \u2014 one file per finding, with embedded",
|
|
8234
|
+
"screenshot evidence and a suggested fix.",
|
|
8235
|
+
"",
|
|
8236
|
+
"Use when: you want a thorough pass over a running web app for consistency,",
|
|
8237
|
+
"hierarchy, layout, ux-flow, duplication, accessibility, responsive, states,",
|
|
8238
|
+
"content, interaction, or perceived-performance issues. The auditor iterates",
|
|
8239
|
+
"lens-by-lens so each pass finds new classes of issues; the workspace registry",
|
|
8240
|
+
"deduplicates across iterations.",
|
|
8241
|
+
"",
|
|
8242
|
+
"Returns immediately with a taskId. Poll delegation_status to retrieve the",
|
|
8243
|
+
"workspace path + indexed findings (typically minutes per audited route).",
|
|
8244
|
+
"Identical inputs return the same taskId \u2014 safe to retry.",
|
|
8245
|
+
"",
|
|
8246
|
+
"Output layout under workspaceDir:",
|
|
8247
|
+
" registry.json \u2014 finding index + capture sidecar",
|
|
8248
|
+
" index.md \u2014 human-readable rollup",
|
|
8249
|
+
" issues/NNN--<lens>--<slug>.md \u2014 one self-contained GitHub-issue",
|
|
8250
|
+
" screenshots/<route>--<viewport>.png \u2014 capture archive",
|
|
8251
|
+
"",
|
|
8252
|
+
"Multi-tenant isolation: every finding is scoped to `namespace` when set.",
|
|
8253
|
+
"Never pass another tenant's namespace."
|
|
8254
|
+
].join("\n");
|
|
8255
|
+
var VIEWPORT_SCHEMA = {
|
|
8256
|
+
type: "object",
|
|
8257
|
+
properties: {
|
|
8258
|
+
width: { type: "integer", minimum: 1 },
|
|
8259
|
+
height: { type: "integer", minimum: 1 }
|
|
8260
|
+
},
|
|
8261
|
+
required: ["width", "height"],
|
|
8262
|
+
additionalProperties: false
|
|
8263
|
+
};
|
|
8264
|
+
var ROUTE_SCHEMA = {
|
|
8265
|
+
type: "object",
|
|
8266
|
+
properties: {
|
|
8267
|
+
name: { type: "string", description: "Stable route name (used in screenshot filenames)." },
|
|
8268
|
+
url: { type: "string", description: "Fully-qualified URL." },
|
|
8269
|
+
viewports: {
|
|
8270
|
+
type: "array",
|
|
8271
|
+
items: VIEWPORT_SCHEMA,
|
|
8272
|
+
description: "Viewports to capture at. Default [{1280, 800}]."
|
|
8273
|
+
},
|
|
8274
|
+
fullPage: { type: "boolean" },
|
|
8275
|
+
waitFor: { type: "string", description: "CSS selector to wait for before capturing." }
|
|
8276
|
+
},
|
|
8277
|
+
required: ["name", "url"],
|
|
8278
|
+
additionalProperties: false
|
|
8279
|
+
};
|
|
8280
|
+
var DELEGATE_UI_AUDIT_INPUT_SCHEMA = {
|
|
8281
|
+
type: "object",
|
|
8282
|
+
properties: {
|
|
8283
|
+
workspaceDir: { type: "string", description: "Absolute path for the audit workspace." },
|
|
8284
|
+
routes: { type: "array", items: ROUTE_SCHEMA, minItems: 1 },
|
|
8285
|
+
namespace: { type: "string", description: "Multi-tenant scope." },
|
|
8286
|
+
config: {
|
|
8287
|
+
type: "object",
|
|
8288
|
+
properties: {
|
|
8289
|
+
lenses: {
|
|
8290
|
+
type: "array",
|
|
8291
|
+
items: { type: "string", enum: [...UI_LENSES] },
|
|
8292
|
+
description: 'Lenses to iterate. Default: every lens except "other".'
|
|
8293
|
+
},
|
|
8294
|
+
maxIterations: { type: "integer", minimum: 1 },
|
|
8295
|
+
maxConcurrency: { type: "integer", minimum: 1 },
|
|
8296
|
+
productContext: { type: "string" }
|
|
8297
|
+
},
|
|
8298
|
+
additionalProperties: false
|
|
8299
|
+
}
|
|
8300
|
+
},
|
|
8301
|
+
required: ["workspaceDir", "routes"],
|
|
8302
|
+
additionalProperties: false
|
|
8303
|
+
};
|
|
8304
|
+
var PER_LENS_PER_ROUTE_ESTIMATE_MS = 45e3;
|
|
8305
|
+
function validateDelegateUiAuditArgs(raw) {
|
|
8306
|
+
if (raw === null || typeof raw !== "object") {
|
|
8307
|
+
throw new TypeError("delegate_ui_audit: arguments must be an object");
|
|
8308
|
+
}
|
|
8309
|
+
const value = raw;
|
|
8310
|
+
const workspaceDir = value.workspaceDir;
|
|
8311
|
+
if (typeof workspaceDir !== "string" || workspaceDir.trim().length === 0) {
|
|
8312
|
+
throw new TypeError("delegate_ui_audit: `workspaceDir` must be a non-empty string");
|
|
8313
|
+
}
|
|
8314
|
+
const trimmedWs = workspaceDir.trim();
|
|
8315
|
+
if (!path.isAbsolute(trimmedWs)) {
|
|
8316
|
+
throw new TypeError(
|
|
8317
|
+
`delegate_ui_audit: \`workspaceDir\` must be an absolute path (got ${JSON.stringify(workspaceDir)})`
|
|
8318
|
+
);
|
|
8319
|
+
}
|
|
8320
|
+
if (trimmedWs.split(path.sep).includes("..")) {
|
|
8321
|
+
throw new TypeError(
|
|
8322
|
+
`delegate_ui_audit: \`workspaceDir\` must not contain '..' segments (got ${JSON.stringify(workspaceDir)})`
|
|
8323
|
+
);
|
|
8324
|
+
}
|
|
8325
|
+
const routesRaw = value.routes;
|
|
8326
|
+
if (!Array.isArray(routesRaw) || routesRaw.length === 0) {
|
|
8327
|
+
throw new TypeError("delegate_ui_audit: `routes` must be a non-empty array");
|
|
8328
|
+
}
|
|
8329
|
+
const routes = routesRaw.map((r, i) => validateRoute(r, i));
|
|
8330
|
+
const args = { workspaceDir: workspaceDir.trim(), routes };
|
|
8331
|
+
if (value.namespace !== void 0) {
|
|
8332
|
+
if (typeof value.namespace !== "string" || value.namespace.trim().length === 0) {
|
|
8333
|
+
throw new TypeError("delegate_ui_audit: `namespace` must be a non-empty string when set");
|
|
8334
|
+
}
|
|
8335
|
+
args.namespace = value.namespace.trim();
|
|
8336
|
+
}
|
|
8337
|
+
if (value.config !== void 0) {
|
|
8338
|
+
args.config = validateConfig3(value.config);
|
|
8339
|
+
}
|
|
8340
|
+
return args;
|
|
8341
|
+
}
|
|
8342
|
+
function validateRoute(raw, index) {
|
|
8343
|
+
if (raw === null || typeof raw !== "object") {
|
|
8344
|
+
throw new TypeError(`delegate_ui_audit: routes[${index}] must be an object`);
|
|
8345
|
+
}
|
|
8346
|
+
const v = raw;
|
|
8347
|
+
if (typeof v.name !== "string" || v.name.trim().length === 0) {
|
|
8348
|
+
throw new TypeError(`delegate_ui_audit: routes[${index}].name must be a non-empty string`);
|
|
8349
|
+
}
|
|
8350
|
+
const trimmedName = v.name.trim();
|
|
8351
|
+
if (/[./\\]/.test(trimmedName) || trimmedName.includes("\0")) {
|
|
8352
|
+
throw new TypeError(
|
|
8353
|
+
`delegate_ui_audit: routes[${index}].name must not contain path separators, dots, or NUL (got ${JSON.stringify(v.name)})`
|
|
8354
|
+
);
|
|
8355
|
+
}
|
|
8356
|
+
if (typeof v.url !== "string" || v.url.trim().length === 0) {
|
|
8357
|
+
throw new TypeError(`delegate_ui_audit: routes[${index}].url must be a non-empty string`);
|
|
8358
|
+
}
|
|
8359
|
+
let parsedUrl;
|
|
8360
|
+
try {
|
|
8361
|
+
parsedUrl = new URL(v.url);
|
|
8362
|
+
} catch {
|
|
8363
|
+
throw new TypeError(
|
|
8364
|
+
`delegate_ui_audit: routes[${index}].url is not a parseable URL (got ${JSON.stringify(v.url)})`
|
|
8365
|
+
);
|
|
8366
|
+
}
|
|
8367
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
8368
|
+
throw new TypeError(
|
|
8369
|
+
`delegate_ui_audit: routes[${index}].url must use http or https (got ${parsedUrl.protocol})`
|
|
8370
|
+
);
|
|
8371
|
+
}
|
|
8372
|
+
const out = { name: v.name.trim(), url: v.url.trim() };
|
|
8373
|
+
if (v.viewports !== void 0) {
|
|
8374
|
+
if (!Array.isArray(v.viewports) || v.viewports.length === 0) {
|
|
8375
|
+
throw new TypeError(
|
|
8376
|
+
`delegate_ui_audit: routes[${index}].viewports must be a non-empty array when set`
|
|
8377
|
+
);
|
|
8378
|
+
}
|
|
8379
|
+
out.viewports = v.viewports.map((vp, j) => validateViewport(vp, index, j));
|
|
8380
|
+
}
|
|
8381
|
+
if (v.fullPage !== void 0) {
|
|
8382
|
+
if (typeof v.fullPage !== "boolean") {
|
|
8383
|
+
throw new TypeError(`delegate_ui_audit: routes[${index}].fullPage must be a boolean`);
|
|
8384
|
+
}
|
|
8385
|
+
out.fullPage = v.fullPage;
|
|
8386
|
+
}
|
|
8387
|
+
if (v.waitFor !== void 0) {
|
|
8388
|
+
if (typeof v.waitFor !== "string" || v.waitFor.trim().length === 0) {
|
|
8389
|
+
throw new TypeError(
|
|
8390
|
+
`delegate_ui_audit: routes[${index}].waitFor must be a non-empty string when set`
|
|
8391
|
+
);
|
|
8392
|
+
}
|
|
8393
|
+
out.waitFor = v.waitFor.trim();
|
|
8394
|
+
}
|
|
8395
|
+
return out;
|
|
8396
|
+
}
|
|
8397
|
+
function validateViewport(raw, routeIndex, viewportIndex) {
|
|
8398
|
+
if (raw === null || typeof raw !== "object") {
|
|
8399
|
+
throw new TypeError(
|
|
8400
|
+
`delegate_ui_audit: routes[${routeIndex}].viewports[${viewportIndex}] must be an object`
|
|
8401
|
+
);
|
|
8402
|
+
}
|
|
8403
|
+
const v = raw;
|
|
8404
|
+
const w = Number(v.width);
|
|
8405
|
+
const h = Number(v.height);
|
|
8406
|
+
if (!Number.isInteger(w) || w <= 0 || !Number.isInteger(h) || h <= 0) {
|
|
8407
|
+
throw new RangeError(
|
|
8408
|
+
`delegate_ui_audit: routes[${routeIndex}].viewports[${viewportIndex}] must have positive integer width/height`
|
|
8409
|
+
);
|
|
8410
|
+
}
|
|
8411
|
+
return { width: w, height: h };
|
|
8412
|
+
}
|
|
8413
|
+
function validateConfig3(raw) {
|
|
8414
|
+
if (raw === null || typeof raw !== "object") {
|
|
8415
|
+
throw new TypeError("delegate_ui_audit: `config` must be an object");
|
|
8416
|
+
}
|
|
8417
|
+
const v = raw;
|
|
8418
|
+
const out = {};
|
|
8419
|
+
if (v.lenses !== void 0) {
|
|
8420
|
+
if (!Array.isArray(v.lenses) || v.lenses.length === 0) {
|
|
8421
|
+
throw new TypeError("delegate_ui_audit: `config.lenses` must be a non-empty array when set");
|
|
8422
|
+
}
|
|
8423
|
+
const knownSet = new Set(UI_LENSES);
|
|
8424
|
+
const lenses = [];
|
|
8425
|
+
for (let i = 0; i < v.lenses.length; i += 1) {
|
|
8426
|
+
const lens = v.lenses[i];
|
|
8427
|
+
if (typeof lens !== "string" || !knownSet.has(lens)) {
|
|
8428
|
+
throw new TypeError(
|
|
8429
|
+
`delegate_ui_audit: config.lenses[${i}] must be one of ${UI_LENSES.join("|")}`
|
|
8430
|
+
);
|
|
8431
|
+
}
|
|
8432
|
+
lenses.push(lens);
|
|
8433
|
+
}
|
|
8434
|
+
out.lenses = lenses;
|
|
8435
|
+
}
|
|
8436
|
+
if (v.maxIterations !== void 0) {
|
|
8437
|
+
const n = Number(v.maxIterations);
|
|
8438
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
8439
|
+
throw new RangeError("delegate_ui_audit: `config.maxIterations` must be a positive integer");
|
|
8440
|
+
}
|
|
8441
|
+
out.maxIterations = n;
|
|
8442
|
+
}
|
|
8443
|
+
if (v.maxConcurrency !== void 0) {
|
|
8444
|
+
const n = Number(v.maxConcurrency);
|
|
8445
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
8446
|
+
throw new RangeError("delegate_ui_audit: `config.maxConcurrency` must be a positive integer");
|
|
8447
|
+
}
|
|
8448
|
+
out.maxConcurrency = n;
|
|
8449
|
+
}
|
|
8450
|
+
if (v.productContext !== void 0) {
|
|
8451
|
+
if (typeof v.productContext !== "string") {
|
|
8452
|
+
throw new TypeError("delegate_ui_audit: `config.productContext` must be a string");
|
|
8453
|
+
}
|
|
8454
|
+
out.productContext = v.productContext;
|
|
8455
|
+
}
|
|
8456
|
+
return out;
|
|
8457
|
+
}
|
|
8458
|
+
function createDelegateUiAuditHandler(options) {
|
|
8459
|
+
const estimateDurationMs = options.estimateDurationMs ?? defaultEstimate3;
|
|
8460
|
+
return async (raw) => {
|
|
8461
|
+
const args = validateDelegateUiAuditArgs(raw);
|
|
8462
|
+
const idempotencyKey = hashIdempotencyInput({
|
|
8463
|
+
profile: "ui-auditor",
|
|
8464
|
+
workspaceDir: args.workspaceDir,
|
|
8465
|
+
routes: args.routes,
|
|
8466
|
+
namespace: args.namespace,
|
|
8467
|
+
config: args.config
|
|
8468
|
+
});
|
|
8469
|
+
const submitted = options.queue.submit({
|
|
8470
|
+
profile: "ui-auditor",
|
|
8471
|
+
args,
|
|
8472
|
+
namespace: args.namespace,
|
|
8473
|
+
idempotencyKey,
|
|
8474
|
+
run: async (ctx) => options.delegate(args, ctx)
|
|
8475
|
+
});
|
|
8476
|
+
return {
|
|
8477
|
+
taskId: submitted.taskId,
|
|
8478
|
+
estimatedDurationMs: estimateDurationMs(args)
|
|
8479
|
+
};
|
|
8480
|
+
};
|
|
8481
|
+
}
|
|
8482
|
+
function defaultEstimate3(args) {
|
|
8483
|
+
const lenses = args.config?.lenses?.length ?? UI_LENSES.length - 1;
|
|
8484
|
+
const routes = args.routes.length;
|
|
8485
|
+
return PER_LENS_PER_ROUTE_ESTIMATE_MS * lenses * routes;
|
|
8486
|
+
}
|
|
8487
|
+
|
|
8488
|
+
// src/mcp/tools/delegation-history.ts
|
|
8489
|
+
var DELEGATION_HISTORY_TOOL_NAME = "delegation_history";
|
|
8490
|
+
var DELEGATION_HISTORY_DESCRIPTION = [
|
|
8491
|
+
"Read past delegations newest-first. Each entry carries the original",
|
|
8492
|
+
"arguments, current status, cost, and any feedback attached via",
|
|
8493
|
+
"delegate_feedback.",
|
|
8494
|
+
"",
|
|
8495
|
+
'Use when: you want to introspect prior decisions \u2014 "have I asked this',
|
|
8496
|
+
"question before?",
|
|
8497
|
+
"did the last patch land?",
|
|
8498
|
+
"what's the historical",
|
|
8499
|
+
'success rate of coder delegations on this repo?". Feed the results back',
|
|
8500
|
+
"into your own routing and calibration.",
|
|
8501
|
+
"",
|
|
8502
|
+
"Each entry carries `hasTrace` \u2014 when true, the full loop-trace span tree",
|
|
8503
|
+
"is retrievable via delegation_status { taskId, includeTrace: true }.",
|
|
8504
|
+
"",
|
|
8505
|
+
'Filters: `namespace` (multi-tenant scope), `profile` ("coder" | "researcher"),',
|
|
8506
|
+
"`since` (ISO date \u2014 only delegations started at-or-after). `limit` defaults",
|
|
8507
|
+
"to 50, capped at 500."
|
|
8508
|
+
].join("\n");
|
|
8509
|
+
var DELEGATION_HISTORY_INPUT_SCHEMA = {
|
|
8510
|
+
type: "object",
|
|
8511
|
+
properties: {
|
|
8512
|
+
namespace: { type: "string" },
|
|
8513
|
+
profile: { type: "string", enum: ["coder", "researcher"] },
|
|
8514
|
+
since: { type: "string", description: "ISO datetime \u2014 earliest startedAt to include." },
|
|
8515
|
+
limit: { type: "integer", minimum: 1, maximum: 500 }
|
|
8516
|
+
},
|
|
8517
|
+
additionalProperties: false
|
|
8518
|
+
};
|
|
8519
|
+
function validateDelegationHistoryArgs(raw) {
|
|
8520
|
+
if (raw === void 0 || raw === null) return {};
|
|
8521
|
+
if (typeof raw !== "object") {
|
|
8522
|
+
throw new TypeError("delegation_history: arguments must be an object");
|
|
8523
|
+
}
|
|
8524
|
+
const value = raw;
|
|
8525
|
+
const out = {};
|
|
8526
|
+
if (value.namespace !== void 0) {
|
|
8527
|
+
if (typeof value.namespace !== "string") {
|
|
8528
|
+
throw new TypeError("delegation_history: `namespace` must be a string");
|
|
8529
|
+
}
|
|
8530
|
+
out.namespace = value.namespace;
|
|
8531
|
+
}
|
|
8532
|
+
if (value.profile !== void 0) {
|
|
8533
|
+
if (value.profile !== "coder" && value.profile !== "researcher") {
|
|
8534
|
+
throw new TypeError('delegation_history: `profile` must be "coder" or "researcher"');
|
|
8535
|
+
}
|
|
8536
|
+
out.profile = value.profile;
|
|
8537
|
+
}
|
|
8538
|
+
if (value.since !== void 0) {
|
|
8539
|
+
if (typeof value.since !== "string" || Number.isNaN(Date.parse(value.since))) {
|
|
8540
|
+
throw new TypeError("delegation_history: `since` must be an ISO datetime");
|
|
8541
|
+
}
|
|
8542
|
+
out.since = value.since;
|
|
8543
|
+
}
|
|
8544
|
+
if (value.limit !== void 0) {
|
|
8545
|
+
const n = Number(value.limit);
|
|
8546
|
+
if (!Number.isFinite(n) || n < 1 || n > 500) {
|
|
8547
|
+
throw new RangeError("delegation_history: `limit` must be an integer in [1, 500]");
|
|
8548
|
+
}
|
|
8549
|
+
out.limit = Math.trunc(n);
|
|
8550
|
+
}
|
|
8551
|
+
return out;
|
|
8552
|
+
}
|
|
8553
|
+
function createDelegationHistoryHandler(options) {
|
|
8554
|
+
return async (raw) => {
|
|
8555
|
+
const args = validateDelegationHistoryArgs(raw);
|
|
8556
|
+
return { delegations: options.queue.history(args) };
|
|
8557
|
+
};
|
|
8558
|
+
}
|
|
8559
|
+
|
|
8560
|
+
// src/mcp/tools/delegation-status.ts
|
|
8561
|
+
var DELEGATION_STATUS_TOOL_NAME = "delegation_status";
|
|
8562
|
+
var DELEGATION_STATUS_DESCRIPTION = [
|
|
8563
|
+
"Poll the status of an async delegation. Returns the current state",
|
|
8564
|
+
"(pending | running | completed | failed | cancelled), optional progress,",
|
|
8565
|
+
'and the final result when status === "completed".',
|
|
8566
|
+
"",
|
|
8567
|
+
"Use when: you previously called delegate_code or delegate_research and",
|
|
8568
|
+
"need to know whether the work is done. The agent's right rhythm is to",
|
|
8569
|
+
"call this every minute or two while waiting; do not busy-poll.",
|
|
8570
|
+
"",
|
|
8571
|
+
"For a completed coder task, `result.output` is a CoderOutput with branch,",
|
|
8572
|
+
"patch, test/typecheck results, and diff stats. For a completed research",
|
|
8573
|
+
"task, `result.output` is the items + citations + proposedWrites bundle.",
|
|
8574
|
+
"",
|
|
8575
|
+
"Pass includeTrace: true to also receive the journaled loop-trace span",
|
|
8576
|
+
"tree (loop \u2192 round \u2192 iteration, with placement/cost/verdict metadata).",
|
|
8577
|
+
"Default false \u2014 keep routine polls light.",
|
|
8578
|
+
"",
|
|
8579
|
+
"Throws NotFoundError when taskId is unknown \u2014 never silently returns",
|
|
8580
|
+
"`pending` for a typo."
|
|
8581
|
+
].join("\n");
|
|
8582
|
+
var DELEGATION_STATUS_INPUT_SCHEMA = {
|
|
8583
|
+
type: "object",
|
|
8584
|
+
properties: {
|
|
8585
|
+
taskId: { type: "string", description: "Returned by delegate_code / delegate_research." },
|
|
8586
|
+
includeTrace: {
|
|
8587
|
+
type: "boolean",
|
|
8588
|
+
description: "Also return the journaled loop-trace span tree for this delegation. Default false."
|
|
8589
|
+
}
|
|
8590
|
+
},
|
|
8591
|
+
required: ["taskId"],
|
|
8592
|
+
additionalProperties: false
|
|
8593
|
+
};
|
|
8594
|
+
function validateDelegationStatusArgs(raw) {
|
|
8595
|
+
if (raw === null || typeof raw !== "object") {
|
|
8596
|
+
throw new TypeError("delegation_status: arguments must be an object");
|
|
8597
|
+
}
|
|
8598
|
+
const value = raw;
|
|
8599
|
+
const taskId = value.taskId;
|
|
8600
|
+
if (typeof taskId !== "string" || taskId.trim().length === 0) {
|
|
8601
|
+
throw new TypeError("delegation_status: `taskId` must be a non-empty string");
|
|
8602
|
+
}
|
|
8603
|
+
const out = { taskId: taskId.trim() };
|
|
8604
|
+
if (value.includeTrace !== void 0) {
|
|
8605
|
+
if (typeof value.includeTrace !== "boolean") {
|
|
8606
|
+
throw new TypeError("delegation_status: `includeTrace` must be a boolean");
|
|
8607
|
+
}
|
|
8608
|
+
out.includeTrace = value.includeTrace;
|
|
8609
|
+
}
|
|
8610
|
+
return out;
|
|
8611
|
+
}
|
|
8612
|
+
function createDelegationStatusHandler(options) {
|
|
8613
|
+
return async (raw) => {
|
|
8614
|
+
const args = validateDelegationStatusArgs(raw);
|
|
8615
|
+
const status = options.queue.status(
|
|
8616
|
+
args.taskId,
|
|
8617
|
+
args.includeTrace !== void 0 ? { includeTrace: args.includeTrace } : void 0
|
|
8618
|
+
);
|
|
8619
|
+
if (!status) {
|
|
8620
|
+
throw new NotFoundError(`delegation_status: unknown taskId "${args.taskId}"`);
|
|
8621
|
+
}
|
|
8622
|
+
return status;
|
|
8623
|
+
};
|
|
8624
|
+
}
|
|
8625
|
+
|
|
8626
|
+
// src/mcp/server.ts
|
|
8627
|
+
var PROTOCOL_VERSION = "2024-11-05";
|
|
8628
|
+
var DEFAULT_SERVER_NAME = "agent-runtime-mcp";
|
|
8629
|
+
var DEFAULT_SERVER_VERSION = "0.22.0";
|
|
8630
|
+
function createMcpServer(options = {}) {
|
|
8631
|
+
const queue = options.queue ?? new DelegationTaskQueue(
|
|
8632
|
+
options.traceContext !== void 0 ? { traceContext: options.traceContext } : {}
|
|
8633
|
+
);
|
|
8634
|
+
const feedbackStore = options.feedbackStore ?? new InMemoryFeedbackStore();
|
|
8635
|
+
const serverName = options.serverName ?? DEFAULT_SERVER_NAME;
|
|
8636
|
+
const serverVersion = options.serverVersion ?? DEFAULT_SERVER_VERSION;
|
|
8637
|
+
const tools = /* @__PURE__ */ new Map();
|
|
8638
|
+
if (options.coderDelegate) {
|
|
8639
|
+
tools.set(DELEGATE_CODE_TOOL_NAME, {
|
|
8640
|
+
name: DELEGATE_CODE_TOOL_NAME,
|
|
8641
|
+
description: DELEGATE_CODE_DESCRIPTION,
|
|
8642
|
+
inputSchema: DELEGATE_CODE_INPUT_SCHEMA,
|
|
8643
|
+
handler: createDelegateCodeHandler({
|
|
8644
|
+
queue,
|
|
8645
|
+
delegate: options.coderDelegate,
|
|
8646
|
+
...options.detachedDispatch !== void 0 ? { detachedDispatch: options.detachedDispatch } : {}
|
|
8647
|
+
})
|
|
8648
|
+
});
|
|
8649
|
+
}
|
|
8650
|
+
if (options.researcherDelegate) {
|
|
8651
|
+
tools.set(DELEGATE_RESEARCH_TOOL_NAME, {
|
|
8652
|
+
name: DELEGATE_RESEARCH_TOOL_NAME,
|
|
8653
|
+
description: DELEGATE_RESEARCH_DESCRIPTION,
|
|
8654
|
+
inputSchema: DELEGATE_RESEARCH_INPUT_SCHEMA,
|
|
8655
|
+
handler: createDelegateResearchHandler({
|
|
8656
|
+
queue,
|
|
8657
|
+
delegate: options.researcherDelegate,
|
|
8658
|
+
...options.detachedDispatch !== void 0 ? { detachedDispatch: options.detachedDispatch } : {}
|
|
8659
|
+
})
|
|
8660
|
+
});
|
|
8661
|
+
}
|
|
8662
|
+
if (options.uiAuditorDelegate) {
|
|
8663
|
+
tools.set(DELEGATE_UI_AUDIT_TOOL_NAME, {
|
|
8664
|
+
name: DELEGATE_UI_AUDIT_TOOL_NAME,
|
|
8665
|
+
description: DELEGATE_UI_AUDIT_DESCRIPTION,
|
|
8666
|
+
inputSchema: DELEGATE_UI_AUDIT_INPUT_SCHEMA,
|
|
8667
|
+
handler: createDelegateUiAuditHandler({ queue, delegate: options.uiAuditorDelegate })
|
|
8668
|
+
});
|
|
8669
|
+
}
|
|
8670
|
+
tools.set(DELEGATE_FEEDBACK_TOOL_NAME, {
|
|
8671
|
+
name: DELEGATE_FEEDBACK_TOOL_NAME,
|
|
8672
|
+
description: DELEGATE_FEEDBACK_DESCRIPTION,
|
|
8673
|
+
inputSchema: DELEGATE_FEEDBACK_INPUT_SCHEMA,
|
|
8674
|
+
handler: createDelegateFeedbackHandler({ queue, store: feedbackStore })
|
|
8675
|
+
});
|
|
8676
|
+
tools.set(DELEGATION_STATUS_TOOL_NAME, {
|
|
8677
|
+
name: DELEGATION_STATUS_TOOL_NAME,
|
|
8678
|
+
description: DELEGATION_STATUS_DESCRIPTION,
|
|
8679
|
+
inputSchema: DELEGATION_STATUS_INPUT_SCHEMA,
|
|
8680
|
+
handler: createDelegationStatusHandler({ queue })
|
|
8681
|
+
});
|
|
8682
|
+
tools.set(DELEGATION_HISTORY_TOOL_NAME, {
|
|
8683
|
+
name: DELEGATION_HISTORY_TOOL_NAME,
|
|
8684
|
+
description: DELEGATION_HISTORY_DESCRIPTION,
|
|
8685
|
+
inputSchema: DELEGATION_HISTORY_INPUT_SCHEMA,
|
|
8686
|
+
handler: createDelegationHistoryHandler({ queue })
|
|
8687
|
+
});
|
|
8688
|
+
for (const tool of options.extraTools ?? []) {
|
|
8689
|
+
if (tools.has(tool.name)) {
|
|
8690
|
+
throw new ValidationError(
|
|
8691
|
+
`createMcpServer: extra tool "${tool.name}" shadows a built-in tool`
|
|
8692
|
+
);
|
|
8693
|
+
}
|
|
8694
|
+
tools.set(tool.name, tool);
|
|
8695
|
+
}
|
|
8696
|
+
let stopped = false;
|
|
8697
|
+
let activeReadline;
|
|
8698
|
+
async function handle(message) {
|
|
8699
|
+
if (stopped) {
|
|
8700
|
+
return rpcError(message.id ?? null, -32099, "server stopped");
|
|
8701
|
+
}
|
|
8702
|
+
if (message.method === "initialize") {
|
|
8703
|
+
return rpcResult(message.id ?? null, {
|
|
8704
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
8705
|
+
capabilities: { tools: {} },
|
|
8706
|
+
serverInfo: { name: serverName, version: serverVersion }
|
|
8707
|
+
});
|
|
8708
|
+
}
|
|
8709
|
+
if (message.method === "notifications/initialized") {
|
|
8710
|
+
return null;
|
|
8711
|
+
}
|
|
8712
|
+
if (message.method === "tools/list") {
|
|
8713
|
+
return rpcResult(message.id ?? null, {
|
|
8714
|
+
tools: [...tools.values()].map((tool) => ({
|
|
8715
|
+
name: tool.name,
|
|
8716
|
+
description: tool.description,
|
|
8717
|
+
inputSchema: tool.inputSchema
|
|
8718
|
+
}))
|
|
8719
|
+
});
|
|
8720
|
+
}
|
|
8721
|
+
if (message.method === "tools/call") {
|
|
8722
|
+
const params = message.params ?? {};
|
|
8723
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
8724
|
+
const tool = tools.get(name);
|
|
8725
|
+
if (!tool) {
|
|
8726
|
+
return rpcError(message.id ?? null, -32601, `unknown tool: ${name}`);
|
|
8727
|
+
}
|
|
8728
|
+
try {
|
|
8729
|
+
const output = await tool.handler(params.arguments ?? {});
|
|
8730
|
+
return rpcResult(message.id ?? null, {
|
|
8731
|
+
content: [{ type: "text", text: JSON.stringify(output) }],
|
|
8732
|
+
structuredContent: output,
|
|
8733
|
+
isError: false
|
|
8734
|
+
});
|
|
8735
|
+
} catch (err) {
|
|
8736
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
8737
|
+
const code = err instanceof TypeError || err instanceof RangeError ? -32602 : -32e3;
|
|
8738
|
+
return rpcError(message.id ?? null, code, reason);
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8741
|
+
if (message.id === void 0 || message.id === null) return null;
|
|
8742
|
+
return rpcError(message.id, -32601, `unknown method: ${message.method}`);
|
|
8743
|
+
}
|
|
8744
|
+
async function serve(transport) {
|
|
8745
|
+
const input = transport?.input ?? process.stdin;
|
|
8746
|
+
const output = transport?.output ?? process.stdout;
|
|
8747
|
+
const rl = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
8748
|
+
activeReadline = rl;
|
|
8749
|
+
return new Promise((resolve, reject) => {
|
|
8750
|
+
rl.on("line", (line) => {
|
|
8751
|
+
const trimmed = line.trim();
|
|
8752
|
+
if (!trimmed) return;
|
|
8753
|
+
let parsed;
|
|
8754
|
+
try {
|
|
8755
|
+
parsed = JSON.parse(trimmed);
|
|
8756
|
+
} catch (err) {
|
|
8757
|
+
writeResponse(output, rpcError(null, -32700, `parse error: ${err.message}`));
|
|
8758
|
+
return;
|
|
8759
|
+
}
|
|
8760
|
+
if (!parsed || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
|
|
8761
|
+
writeResponse(output, rpcError(parsed?.id ?? null, -32600, "invalid request"));
|
|
8762
|
+
return;
|
|
8763
|
+
}
|
|
8764
|
+
void handle(parsed).then((response) => {
|
|
8765
|
+
if (response) writeResponse(output, response);
|
|
8766
|
+
});
|
|
8767
|
+
});
|
|
8768
|
+
rl.on("close", () => resolve());
|
|
8769
|
+
rl.on("error", (err) => reject(err));
|
|
8770
|
+
if (stopped) {
|
|
8771
|
+
rl.close();
|
|
8772
|
+
resolve();
|
|
8773
|
+
}
|
|
8774
|
+
});
|
|
8775
|
+
}
|
|
8776
|
+
function stop() {
|
|
8777
|
+
stopped = true;
|
|
8778
|
+
activeReadline?.close();
|
|
8779
|
+
activeReadline = void 0;
|
|
8780
|
+
}
|
|
8781
|
+
return {
|
|
8782
|
+
tools,
|
|
8783
|
+
queue,
|
|
8784
|
+
feedbackStore,
|
|
8785
|
+
handle,
|
|
8786
|
+
serve,
|
|
8787
|
+
stop
|
|
8788
|
+
};
|
|
8789
|
+
}
|
|
8790
|
+
function rpcResult(id, result) {
|
|
8791
|
+
return { jsonrpc: "2.0", id, result };
|
|
8792
|
+
}
|
|
8793
|
+
function rpcError(id, code, message, data) {
|
|
8794
|
+
return {
|
|
8795
|
+
jsonrpc: "2.0",
|
|
8796
|
+
id,
|
|
8797
|
+
error: data === void 0 ? { code, message } : { code, message, data }
|
|
8798
|
+
};
|
|
8799
|
+
}
|
|
8800
|
+
function writeResponse(output, response) {
|
|
8801
|
+
output.write(`${JSON.stringify(response)}
|
|
8802
|
+
`);
|
|
8803
|
+
}
|
|
8804
|
+
function createInProcessTransport() {
|
|
8805
|
+
const responses = [];
|
|
8806
|
+
const input = new Readable({ read() {
|
|
8807
|
+
} });
|
|
8808
|
+
const output = new Writable({
|
|
8809
|
+
write(chunk, _enc, cb) {
|
|
8810
|
+
const text = chunk.toString("utf8");
|
|
8811
|
+
for (const line of text.split("\n")) {
|
|
8812
|
+
const trimmed = line.trim();
|
|
8813
|
+
if (!trimmed) continue;
|
|
8814
|
+
try {
|
|
8815
|
+
responses.push(JSON.parse(trimmed));
|
|
8816
|
+
} catch {
|
|
8817
|
+
}
|
|
8818
|
+
}
|
|
8819
|
+
cb();
|
|
8820
|
+
}
|
|
8821
|
+
});
|
|
8822
|
+
return {
|
|
8823
|
+
transport: { input, output },
|
|
8824
|
+
clientWrite(line) {
|
|
8825
|
+
input.push(`${line}
|
|
8826
|
+
`);
|
|
8827
|
+
},
|
|
8828
|
+
clientClose() {
|
|
8829
|
+
input.push(null);
|
|
8830
|
+
},
|
|
8831
|
+
async readServer() {
|
|
8832
|
+
for (let i = 0; i < 5; i += 1) await new Promise((r) => setImmediate(r));
|
|
8833
|
+
return [...responses];
|
|
8834
|
+
}
|
|
8835
|
+
};
|
|
8836
|
+
}
|
|
8837
|
+
|
|
8838
|
+
// src/runtime/supervise/coordination-mcp.ts
|
|
8839
|
+
async function serveCoordinationMcp(opts) {
|
|
8840
|
+
const coord = createCoordinationTools({
|
|
8841
|
+
scope: opts.scope,
|
|
8842
|
+
blobs: opts.blobs,
|
|
8843
|
+
makeWorkerAgent: opts.makeWorkerAgent,
|
|
8844
|
+
perWorker: opts.perWorker
|
|
8845
|
+
});
|
|
8846
|
+
const mcp = createMcpServer({ extraTools: coord.tools, serverName: "coordination" });
|
|
8847
|
+
const host = opts.host ?? "127.0.0.1";
|
|
8848
|
+
const server = createServer((req, res) => {
|
|
8849
|
+
if (req.method !== "POST") {
|
|
8850
|
+
res.writeHead(405, { allow: "POST" });
|
|
8851
|
+
res.end();
|
|
8852
|
+
return;
|
|
8853
|
+
}
|
|
8854
|
+
let body = "";
|
|
8855
|
+
req.on("data", (c) => {
|
|
8856
|
+
body += c;
|
|
8857
|
+
});
|
|
8858
|
+
req.on("end", () => {
|
|
8859
|
+
void (async () => {
|
|
8860
|
+
try {
|
|
8861
|
+
const message = JSON.parse(body);
|
|
8862
|
+
const response = await mcp.handle(message);
|
|
8863
|
+
if (response === null) {
|
|
8864
|
+
res.writeHead(202).end();
|
|
8865
|
+
return;
|
|
8866
|
+
}
|
|
8867
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
8868
|
+
res.end(JSON.stringify(response));
|
|
8869
|
+
} catch (e) {
|
|
8870
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
8871
|
+
res.end(
|
|
8872
|
+
JSON.stringify({
|
|
8873
|
+
jsonrpc: "2.0",
|
|
8874
|
+
id: null,
|
|
8875
|
+
error: { code: -32700, message: e instanceof Error ? e.message : "parse error" }
|
|
8876
|
+
})
|
|
8877
|
+
);
|
|
8878
|
+
}
|
|
8879
|
+
})();
|
|
8880
|
+
});
|
|
8881
|
+
});
|
|
8882
|
+
const port = await new Promise((resolve, reject) => {
|
|
8883
|
+
server.once("error", reject);
|
|
8884
|
+
server.listen(opts.port ?? 0, host, () => {
|
|
8885
|
+
const addr = server.address();
|
|
8886
|
+
resolve(typeof addr === "object" && addr ? addr.port : opts.port ?? 0);
|
|
8887
|
+
});
|
|
8888
|
+
});
|
|
8889
|
+
return {
|
|
8890
|
+
url: `http://${host}:${port}/mcp`,
|
|
8891
|
+
port,
|
|
8892
|
+
settled: () => coord.settled(),
|
|
8893
|
+
isStopped: () => coord.isStopped(),
|
|
8894
|
+
close: () => new Promise((resolve) => {
|
|
8895
|
+
server.close(() => resolve());
|
|
8896
|
+
})
|
|
8897
|
+
};
|
|
8898
|
+
}
|
|
8899
|
+
|
|
8900
|
+
// src/runtime/verifier-environment.ts
|
|
8901
|
+
var submitTool = {
|
|
8902
|
+
type: "function",
|
|
8903
|
+
function: {
|
|
8904
|
+
name: "submit_answer",
|
|
8905
|
+
description: "Submit your answer for evaluation. You may submit more than once \u2014 the best-scoring submission counts. Submit the COMPLETE final answer, not a fragment.",
|
|
8906
|
+
parameters: {
|
|
8907
|
+
type: "object",
|
|
8908
|
+
properties: { answer: { type: "string", description: "The complete final answer." } },
|
|
8909
|
+
required: ["answer"]
|
|
8910
|
+
}
|
|
8911
|
+
}
|
|
8912
|
+
};
|
|
8913
|
+
function createVerifierEnvironment(opts) {
|
|
8914
|
+
if (opts.extraTools?.length && !opts.callExtra) {
|
|
8915
|
+
throw new Error(`${opts.name}: extraTools requires callExtra`);
|
|
8916
|
+
}
|
|
8917
|
+
const states = /* @__PURE__ */ new Map();
|
|
8918
|
+
let seq = 0;
|
|
8919
|
+
return {
|
|
8920
|
+
name: opts.name,
|
|
8921
|
+
async open(task) {
|
|
8922
|
+
seq += 1;
|
|
8923
|
+
const handle = { id: `${opts.name}-${seq}`, surface: opts.name };
|
|
8924
|
+
states.set(handle.id, { task, submissions: [] });
|
|
8925
|
+
return handle;
|
|
8926
|
+
},
|
|
8927
|
+
async tools() {
|
|
8928
|
+
return [submitTool, ...opts.extraTools ?? []];
|
|
8929
|
+
},
|
|
8930
|
+
async call(handle, name, args) {
|
|
8931
|
+
const state = states.get(handle.id);
|
|
8932
|
+
if (!state) return "ERROR: workspace closed";
|
|
8933
|
+
if (name === "submit_answer") {
|
|
8934
|
+
const answer = String(args.answer ?? "").trim();
|
|
8935
|
+
if (!answer) return "ERROR: empty answer";
|
|
8936
|
+
state.submissions.push(answer);
|
|
8937
|
+
return `submission ${state.submissions.length} recorded`;
|
|
8938
|
+
}
|
|
8939
|
+
if (opts.callExtra && opts.extraTools?.some((t) => t.function.name === name)) {
|
|
8940
|
+
try {
|
|
8941
|
+
return await opts.callExtra(state.task, name, args);
|
|
8942
|
+
} catch (e) {
|
|
8943
|
+
return `ERROR: ${e instanceof Error ? e.message : String(e)}`;
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
return `ERROR: unknown tool ${name}`;
|
|
8947
|
+
},
|
|
8948
|
+
// Keep-best across submissions — the measured law (workers reach correct answers,
|
|
8949
|
+
// then revise past them; final-state scoring undersells every strategy).
|
|
8950
|
+
async score(task, handle) {
|
|
8951
|
+
const state = states.get(handle.id);
|
|
8952
|
+
if (!state || state.submissions.length === 0) return { passes: 0, total: 1, errored: 0 };
|
|
8953
|
+
let best = { passes: 0, total: 1, errored: 0 };
|
|
8954
|
+
const ratio = (s) => s.total > 0 ? s.passes / s.total : 0;
|
|
8955
|
+
for (const answer of state.submissions) {
|
|
8956
|
+
const s = await opts.check(task, answer);
|
|
8957
|
+
if (ratio(s) > ratio(best)) best = s;
|
|
8958
|
+
}
|
|
8959
|
+
return best;
|
|
8960
|
+
},
|
|
8961
|
+
async close(handle) {
|
|
8962
|
+
states.delete(handle.id);
|
|
8963
|
+
}
|
|
8964
|
+
};
|
|
8965
|
+
}
|
|
8966
|
+
|
|
8967
|
+
// src/runtime/waterfall.ts
|
|
8968
|
+
function createWaterfallCollector() {
|
|
8969
|
+
let spans = /* @__PURE__ */ new Map();
|
|
8970
|
+
const onEvent = (event) => {
|
|
8971
|
+
if (event.target === "agent.spawn") {
|
|
8972
|
+
const p = event.payload ?? {};
|
|
8973
|
+
const id = p.childId ?? event.id;
|
|
8974
|
+
spans.set(id, {
|
|
8975
|
+
id,
|
|
8976
|
+
label: p.label ?? id,
|
|
8977
|
+
runId: event.runId,
|
|
8978
|
+
...event.parentId !== void 0 ? { parentId: event.parentId } : {},
|
|
8979
|
+
startMs: event.timestamp,
|
|
8980
|
+
status: "running",
|
|
8981
|
+
usd: 0,
|
|
8982
|
+
tokens: { input: 0, output: 0 }
|
|
8983
|
+
});
|
|
8984
|
+
return;
|
|
8985
|
+
}
|
|
8986
|
+
if (event.target === "agent.child") {
|
|
8987
|
+
const p = event.payload ?? {};
|
|
8988
|
+
const id = p.childId;
|
|
8989
|
+
if (!id) return;
|
|
8990
|
+
const span = spans.get(id);
|
|
8991
|
+
if (!span) return;
|
|
8992
|
+
span.endMs = event.timestamp;
|
|
8993
|
+
span.status = p.status === "down" ? "down" : "done";
|
|
8994
|
+
span.usd = p.spent?.usd ?? 0;
|
|
8995
|
+
span.tokens = {
|
|
8996
|
+
input: p.spent?.tokens?.input ?? 0,
|
|
8997
|
+
output: p.spent?.tokens?.output ?? 0
|
|
8998
|
+
};
|
|
8999
|
+
if (typeof p.score === "number") span.score = p.score;
|
|
9000
|
+
}
|
|
9001
|
+
};
|
|
9002
|
+
const report = () => {
|
|
9003
|
+
const all = [...spans.values()].sort((a, b) => a.startMs - b.startMs);
|
|
9004
|
+
const start = all[0]?.startMs ?? 0;
|
|
9005
|
+
const end = Math.max(start, ...all.map((s) => s.endMs ?? s.startMs));
|
|
9006
|
+
const byKind = {};
|
|
9007
|
+
let totalUsd = 0;
|
|
9008
|
+
const totalTokens2 = { input: 0, output: 0 };
|
|
9009
|
+
for (const s of all) {
|
|
9010
|
+
totalUsd += s.usd;
|
|
9011
|
+
totalTokens2.input += s.tokens.input;
|
|
9012
|
+
totalTokens2.output += s.tokens.output;
|
|
9013
|
+
const kind = s.label.includes(":") ? s.label.split(":")[0] : s.label;
|
|
9014
|
+
let k = byKind[kind];
|
|
9015
|
+
if (!k) {
|
|
9016
|
+
k = { count: 0, ms: 0, usd: 0, tokens: { input: 0, output: 0 } };
|
|
9017
|
+
byKind[kind] = k;
|
|
9018
|
+
}
|
|
9019
|
+
k.count += 1;
|
|
9020
|
+
k.ms += (s.endMs ?? s.startMs) - s.startMs;
|
|
9021
|
+
k.usd += s.usd;
|
|
9022
|
+
k.tokens.input += s.tokens.input;
|
|
9023
|
+
k.tokens.output += s.tokens.output;
|
|
9024
|
+
}
|
|
9025
|
+
return { spans: all, totalMs: end - start, totalUsd, totalTokens: totalTokens2, byKind };
|
|
9026
|
+
};
|
|
9027
|
+
const render = (opts) => {
|
|
9028
|
+
const { spans: all, totalMs, totalUsd, byKind } = report();
|
|
9029
|
+
if (all.length === 0) return "(no spans observed)";
|
|
9030
|
+
const width = opts?.width ?? 48;
|
|
9031
|
+
const maxRows = opts?.maxRows ?? 60;
|
|
9032
|
+
const start = all[0]?.startMs ?? 0;
|
|
6318
9033
|
const scale = totalMs > 0 ? width / totalMs : 0;
|
|
6319
9034
|
const lines = [];
|
|
6320
9035
|
const labelWidth = Math.min(24, Math.max(...all.map((s) => s.label.length)) + 1);
|
|
@@ -6447,15 +9162,29 @@ function jjWorkspace(opts) {
|
|
|
6447
9162
|
}
|
|
6448
9163
|
};
|
|
6449
9164
|
}
|
|
9165
|
+
async function runInWorkspace(ws, body, opts = {}) {
|
|
9166
|
+
const { mkdtempSync, rmSync } = await import("fs");
|
|
9167
|
+
const { tmpdir } = await import("os");
|
|
9168
|
+
const { join: join2 } = await import("path");
|
|
9169
|
+
const dir = mkdtempSync(join2(tmpdir(), opts.tmpPrefix ?? "ws-run-"));
|
|
9170
|
+
try {
|
|
9171
|
+
await ws.materialize(dir);
|
|
9172
|
+
const r = await body(dir);
|
|
9173
|
+
if (r.valid || opts.commitOnInvalid) {
|
|
9174
|
+
const message = r.message ?? (r.valid ? "worker: delivered" : "worker: wip");
|
|
9175
|
+
const commit = await ws.commit(dir, message);
|
|
9176
|
+
return { valid: r.valid, value: r.value, commit };
|
|
9177
|
+
}
|
|
9178
|
+
return { valid: r.valid, value: r.value };
|
|
9179
|
+
} finally {
|
|
9180
|
+
rmSync(dir, { recursive: true, force: true });
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
6450
9183
|
function tail(s) {
|
|
6451
9184
|
return s.slice(-400);
|
|
6452
9185
|
}
|
|
6453
9186
|
|
|
6454
9187
|
export {
|
|
6455
|
-
deleteBoxSafe,
|
|
6456
|
-
throwAbort,
|
|
6457
|
-
throwIfAborted,
|
|
6458
|
-
sleep,
|
|
6459
9188
|
contentAddress,
|
|
6460
9189
|
InMemoryResultBlobStore,
|
|
6461
9190
|
FileResultBlobStore,
|
|
@@ -6471,12 +9200,6 @@ export {
|
|
|
6471
9200
|
stopSentinel,
|
|
6472
9201
|
sentinelCompletion,
|
|
6473
9202
|
deterministicCompletion,
|
|
6474
|
-
assertTraceDerivedFindings,
|
|
6475
|
-
createScopeAnalyst,
|
|
6476
|
-
registryScopeAnalyst,
|
|
6477
|
-
buildSteerContext,
|
|
6478
|
-
createDriver,
|
|
6479
|
-
renderAnalyses,
|
|
6480
9203
|
defaultAnalystInstruction,
|
|
6481
9204
|
observe,
|
|
6482
9205
|
renderReport,
|
|
@@ -6496,6 +9219,11 @@ export {
|
|
|
6496
9219
|
defaultSelectWinner,
|
|
6497
9220
|
loopDispatch,
|
|
6498
9221
|
createMcpEnvironment,
|
|
9222
|
+
assertTraceDerivedFindings,
|
|
9223
|
+
createScopeAnalyst,
|
|
9224
|
+
registryScopeAnalyst,
|
|
9225
|
+
buildSteerContext,
|
|
9226
|
+
nestedScopeSeamKey,
|
|
6499
9227
|
createScope,
|
|
6500
9228
|
settledToIteration,
|
|
6501
9229
|
pipeline,
|
|
@@ -6508,6 +9236,11 @@ export {
|
|
|
6508
9236
|
InMemoryCorpus,
|
|
6509
9237
|
FileCorpus,
|
|
6510
9238
|
renderCorpusToInstructions,
|
|
9239
|
+
driverRuntime,
|
|
9240
|
+
driverChild,
|
|
9241
|
+
isDriverSpec,
|
|
9242
|
+
driverExecutorFactory,
|
|
9243
|
+
withDriverExecutor,
|
|
6511
9244
|
routerChatWithUsage,
|
|
6512
9245
|
routerChatWithTools,
|
|
6513
9246
|
routerToolLoop,
|
|
@@ -6543,10 +9276,69 @@ export {
|
|
|
6543
9276
|
pickChampion,
|
|
6544
9277
|
selectChampion,
|
|
6545
9278
|
runStrategyEvolution,
|
|
9279
|
+
gateOnDeliverable,
|
|
9280
|
+
asAuthoredProfile,
|
|
9281
|
+
supervisorSkill,
|
|
9282
|
+
authoredWorker,
|
|
9283
|
+
createCoordinationTools,
|
|
9284
|
+
coordinationDriverAgent,
|
|
9285
|
+
InMemoryFeedbackStore,
|
|
9286
|
+
eventToSnapshot,
|
|
9287
|
+
DelegationStateCorruptError,
|
|
9288
|
+
DelegationPersistenceError,
|
|
9289
|
+
InMemoryDelegationStore,
|
|
9290
|
+
FileDelegationStore,
|
|
9291
|
+
DELEGATION_TRACE_MAX_SPANS,
|
|
9292
|
+
DELEGATION_TRACE_MAX_BYTES,
|
|
9293
|
+
buildDelegationTraceSpans,
|
|
9294
|
+
capDelegationTrace,
|
|
9295
|
+
createDelegationTraceCollector,
|
|
9296
|
+
composeLoopTraceEmitters,
|
|
9297
|
+
DelegationTaskQueue,
|
|
9298
|
+
hashIdempotencyInput,
|
|
9299
|
+
formatDetachedSessionRef,
|
|
9300
|
+
parseDetachedSessionRef,
|
|
9301
|
+
detachedTurnEvents,
|
|
9302
|
+
runDetachedTurn,
|
|
9303
|
+
createDriveTurnResumeDriver,
|
|
9304
|
+
DELEGATE_CODE_TOOL_NAME,
|
|
9305
|
+
DELEGATE_CODE_DESCRIPTION,
|
|
9306
|
+
DELEGATE_CODE_INPUT_SCHEMA,
|
|
9307
|
+
validateDelegateCodeArgs,
|
|
9308
|
+
createDelegateCodeHandler,
|
|
9309
|
+
DELEGATE_FEEDBACK_TOOL_NAME,
|
|
9310
|
+
DELEGATE_FEEDBACK_DESCRIPTION,
|
|
9311
|
+
DELEGATE_FEEDBACK_INPUT_SCHEMA,
|
|
9312
|
+
validateDelegateFeedbackArgs,
|
|
9313
|
+
createDelegateFeedbackHandler,
|
|
9314
|
+
DELEGATE_RESEARCH_TOOL_NAME,
|
|
9315
|
+
DELEGATE_RESEARCH_DESCRIPTION,
|
|
9316
|
+
DELEGATE_RESEARCH_INPUT_SCHEMA,
|
|
9317
|
+
validateDelegateResearchArgs,
|
|
9318
|
+
createDelegateResearchHandler,
|
|
9319
|
+
DELEGATE_UI_AUDIT_TOOL_NAME,
|
|
9320
|
+
DELEGATE_UI_AUDIT_DESCRIPTION,
|
|
9321
|
+
DELEGATE_UI_AUDIT_INPUT_SCHEMA,
|
|
9322
|
+
validateDelegateUiAuditArgs,
|
|
9323
|
+
createDelegateUiAuditHandler,
|
|
9324
|
+
DELEGATION_HISTORY_TOOL_NAME,
|
|
9325
|
+
DELEGATION_HISTORY_DESCRIPTION,
|
|
9326
|
+
DELEGATION_HISTORY_INPUT_SCHEMA,
|
|
9327
|
+
validateDelegationHistoryArgs,
|
|
9328
|
+
createDelegationHistoryHandler,
|
|
9329
|
+
DELEGATION_STATUS_TOOL_NAME,
|
|
9330
|
+
DELEGATION_STATUS_DESCRIPTION,
|
|
9331
|
+
DELEGATION_STATUS_INPUT_SCHEMA,
|
|
9332
|
+
validateDelegationStatusArgs,
|
|
9333
|
+
createDelegationStatusHandler,
|
|
9334
|
+
createMcpServer,
|
|
9335
|
+
createInProcessTransport,
|
|
9336
|
+
serveCoordinationMcp,
|
|
6546
9337
|
createVerifierEnvironment,
|
|
6547
9338
|
createWaterfallCollector,
|
|
6548
9339
|
localShell,
|
|
6549
9340
|
gitWorkspace,
|
|
6550
|
-
jjWorkspace
|
|
9341
|
+
jjWorkspace,
|
|
9342
|
+
runInWorkspace
|
|
6551
9343
|
};
|
|
6552
|
-
//# sourceMappingURL=chunk-
|
|
9344
|
+
//# sourceMappingURL=chunk-JTH2FPCK.js.map
|