@tangle-network/agent-runtime 0.53.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 +1 -1
- 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-K5M3SHEU.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-K6WP7PYW.js → chunk-JTH2FPCK.js} +3231 -424
- package/dist/chunk-JTH2FPCK.js.map +1 -0
- package/dist/{chunk-P4QNEXFC.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-JFIYKDXF.js → chunk-XRYEZPR6.js} +2 -1
- package/dist/chunk-XRYEZPR6.js.map +1 -0
- package/dist/{coder-LKm3Mczw.d.ts → coder-CEkUFv8h.d.ts} +1 -1
- package/dist/{types-CNDJCL_0.d.ts → coordination-BMzskrUR.d.ts} +268 -2
- package/dist/{kb-gate-CKfykcYQ.d.ts → delegates-BPLIl8EC.d.ts} +491 -566
- package/dist/index.d.ts +7 -7
- package/dist/index.js +23 -80
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +2 -2
- package/dist/kb-gate-CuzMYGYM.d.ts +76 -0
- package/dist/{loop-runner-bin-D4Ir7b00.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 -6
- 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 +9 -272
- package/dist/mcp/index.js +45 -365
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-CKfR3EMh.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-B0Qi1NiN.d.ts → router-client-30Y_pca8.d.ts} +1 -1
- package/dist/{run-loop-DgVhucoR.d.ts → run-loop-DluzfJ2h.d.ts} +1 -1
- package/dist/runtime.d.ts +424 -23
- 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-CklkW4Eh.d.ts → types-C8rNlxfV.d.ts} +4 -4
- 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-5M2WDWBI.js +0 -908
- package/dist/chunk-5M2WDWBI.js.map +0 -1
- package/dist/chunk-AYRQZRDV.js +0 -623
- package/dist/chunk-AYRQZRDV.js.map +0 -1
- package/dist/chunk-FO4DCM7R.js +0 -1356
- package/dist/chunk-FO4DCM7R.js.map +0 -1
- package/dist/chunk-JFIYKDXF.js.map +0 -1
- package/dist/chunk-K5M3SHEU.js.map +0 -1
- package/dist/chunk-K6WP7PYW.js.map +0 -1
- package/dist/driver-B2RKkVJW.d.ts +0 -221
- /package/dist/{chunk-P4QNEXFC.js.map → chunk-KJH62YEK.js.map} +0 -0
- /package/dist/{chunk-HNUXAZIJ.js.map → chunk-P5OKDSLB.js.map} +0 -0
package/dist/runtime.js
CHANGED
|
@@ -8,17 +8,19 @@ import {
|
|
|
8
8
|
acquireSandbox,
|
|
9
9
|
adaptiveRefine,
|
|
10
10
|
anytimeReport,
|
|
11
|
+
asAuthoredProfile,
|
|
11
12
|
assertStrategyContract,
|
|
12
13
|
assertTraceDerivedFindings,
|
|
13
14
|
auditIntent,
|
|
14
15
|
authorStrategy,
|
|
16
|
+
authoredWorker,
|
|
15
17
|
breadthDriver,
|
|
16
18
|
buildSteerContext,
|
|
17
19
|
builtinShapes,
|
|
18
20
|
completionAuthorizes,
|
|
19
21
|
contentAddress,
|
|
22
|
+
coordinationDriverAgent,
|
|
20
23
|
createBudgetPool,
|
|
21
|
-
createDriver,
|
|
22
24
|
createExecutor,
|
|
23
25
|
createExecutorRegistry,
|
|
24
26
|
createMcpEnvironment,
|
|
@@ -39,17 +41,23 @@ import {
|
|
|
39
41
|
depthDriver,
|
|
40
42
|
deterministicCompletion,
|
|
41
43
|
discriminatingMeans,
|
|
44
|
+
driverChild,
|
|
45
|
+
driverExecutorFactory,
|
|
46
|
+
driverRuntime,
|
|
42
47
|
equalKOnCost,
|
|
43
48
|
fanout,
|
|
44
49
|
flatWidenGate,
|
|
50
|
+
gateOnDeliverable,
|
|
45
51
|
gitWorkspace,
|
|
46
52
|
harvestCorpus,
|
|
47
53
|
inlineSandboxClient,
|
|
54
|
+
isDriverSpec,
|
|
48
55
|
jjWorkspace,
|
|
49
56
|
localShell,
|
|
50
57
|
loopDispatch,
|
|
51
58
|
loopUntil,
|
|
52
59
|
materializeTreeView,
|
|
60
|
+
nestedScopeSeamKey,
|
|
53
61
|
observe,
|
|
54
62
|
openSandboxRun,
|
|
55
63
|
panel,
|
|
@@ -61,7 +69,6 @@ import {
|
|
|
61
69
|
refine,
|
|
62
70
|
registerShape,
|
|
63
71
|
registryScopeAnalyst,
|
|
64
|
-
renderAnalyses,
|
|
65
72
|
renderAnytimeTable,
|
|
66
73
|
renderCorpusToInstructions,
|
|
67
74
|
renderReport,
|
|
@@ -72,6 +79,7 @@ import {
|
|
|
72
79
|
routerToolLoop,
|
|
73
80
|
runAgentic,
|
|
74
81
|
runBenchmark,
|
|
82
|
+
runInWorkspace,
|
|
75
83
|
runLoop,
|
|
76
84
|
runPersonified,
|
|
77
85
|
runStrategyEvolution,
|
|
@@ -79,18 +87,23 @@ import {
|
|
|
79
87
|
sampleThenRefine,
|
|
80
88
|
selectChampion,
|
|
81
89
|
sentinelCompletion,
|
|
90
|
+
serveCoordinationMcp,
|
|
82
91
|
settledToIteration,
|
|
83
92
|
spendFromUsageEvents,
|
|
84
93
|
stopSentinel,
|
|
85
94
|
strategyAuthorContract,
|
|
95
|
+
supervisorSkill,
|
|
86
96
|
trajectoryReport,
|
|
87
97
|
verify,
|
|
88
|
-
widen
|
|
89
|
-
|
|
98
|
+
widen,
|
|
99
|
+
withDriverExecutor
|
|
100
|
+
} from "./chunk-JTH2FPCK.js";
|
|
101
|
+
import "./chunk-WIR4HOOJ.js";
|
|
90
102
|
import {
|
|
91
103
|
extractLlmCallEvent,
|
|
92
104
|
mapSandboxEvent
|
|
93
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-VLF5RHEQ.js";
|
|
106
|
+
import "./chunk-G3RGMA7C.js";
|
|
94
107
|
import "./chunk-DGUM43GV.js";
|
|
95
108
|
export {
|
|
96
109
|
FileCorpus,
|
|
@@ -102,17 +115,19 @@ export {
|
|
|
102
115
|
acquireSandbox,
|
|
103
116
|
adaptiveRefine,
|
|
104
117
|
anytimeReport,
|
|
118
|
+
asAuthoredProfile,
|
|
105
119
|
assertStrategyContract,
|
|
106
120
|
assertTraceDerivedFindings,
|
|
107
121
|
auditIntent,
|
|
108
122
|
authorStrategy,
|
|
123
|
+
authoredWorker,
|
|
109
124
|
breadthDriver,
|
|
110
125
|
buildSteerContext,
|
|
111
126
|
builtinShapes,
|
|
112
127
|
completionAuthorizes,
|
|
113
128
|
contentAddress,
|
|
129
|
+
coordinationDriverAgent,
|
|
114
130
|
createBudgetPool,
|
|
115
|
-
createDriver,
|
|
116
131
|
createExecutor,
|
|
117
132
|
createExecutorRegistry,
|
|
118
133
|
createMcpEnvironment,
|
|
@@ -133,19 +148,25 @@ export {
|
|
|
133
148
|
depthDriver,
|
|
134
149
|
deterministicCompletion,
|
|
135
150
|
discriminatingMeans,
|
|
151
|
+
driverChild,
|
|
152
|
+
driverExecutorFactory,
|
|
153
|
+
driverRuntime,
|
|
136
154
|
equalKOnCost,
|
|
137
155
|
extractLlmCallEvent,
|
|
138
156
|
fanout,
|
|
139
157
|
flatWidenGate,
|
|
158
|
+
gateOnDeliverable,
|
|
140
159
|
gitWorkspace,
|
|
141
160
|
harvestCorpus,
|
|
142
161
|
inlineSandboxClient,
|
|
162
|
+
isDriverSpec,
|
|
143
163
|
jjWorkspace,
|
|
144
164
|
localShell,
|
|
145
165
|
loopDispatch,
|
|
146
166
|
loopUntil,
|
|
147
167
|
mapSandboxEvent,
|
|
148
168
|
materializeTreeView,
|
|
169
|
+
nestedScopeSeamKey,
|
|
149
170
|
observe,
|
|
150
171
|
openSandboxRun,
|
|
151
172
|
panel,
|
|
@@ -157,7 +178,6 @@ export {
|
|
|
157
178
|
refine,
|
|
158
179
|
registerShape,
|
|
159
180
|
registryScopeAnalyst,
|
|
160
|
-
renderAnalyses,
|
|
161
181
|
renderAnytimeTable,
|
|
162
182
|
renderCorpusToInstructions,
|
|
163
183
|
renderReport,
|
|
@@ -168,6 +188,7 @@ export {
|
|
|
168
188
|
routerToolLoop,
|
|
169
189
|
runAgentic,
|
|
170
190
|
runBenchmark,
|
|
191
|
+
runInWorkspace,
|
|
171
192
|
runLoop,
|
|
172
193
|
runPersonified,
|
|
173
194
|
runStrategyEvolution,
|
|
@@ -175,12 +196,15 @@ export {
|
|
|
175
196
|
sampleThenRefine,
|
|
176
197
|
selectChampion,
|
|
177
198
|
sentinelCompletion,
|
|
199
|
+
serveCoordinationMcp,
|
|
178
200
|
settledToIteration,
|
|
179
201
|
spendFromUsageEvents,
|
|
180
202
|
stopSentinel,
|
|
181
203
|
strategyAuthorContract,
|
|
204
|
+
supervisorSkill,
|
|
182
205
|
trajectoryReport,
|
|
183
206
|
verify,
|
|
184
|
-
widen
|
|
207
|
+
widen,
|
|
208
|
+
withDriverExecutor
|
|
185
209
|
};
|
|
186
210
|
//# sourceMappingURL=runtime.js.map
|
package/dist/topology.d.ts
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
import { R as RuntimeHooks, a as RuntimeHookEvent } from './runtime-hooks-C7JwKb9E.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @experimental
|
|
5
|
+
*
|
|
6
|
+
* Run replay — the visual, animated record of a recursive agent run.
|
|
7
|
+
*
|
|
8
|
+
* The runtime emits ONE event stream (`agent.spawn`/`agent.child`/`agent.run`/`agent.turn`)
|
|
9
|
+
* through `RuntimeHooks`; the topology tree + waterfall already fold it into ASCII. This module
|
|
10
|
+
* folds the SAME stream into a normalized, timestamped `ReplayEvent[]` (the recorder) and renders
|
|
11
|
+
* a self-contained, animated HTML player (`renderReplayHtml`) — a timeline scrubber over the live
|
|
12
|
+
* recursive tree where every node colors by the completion-oracle: delivered (valid) green, ran-
|
|
13
|
+
* but-not-delivered amber, failed red. No server, no build, no external deps — one HTML file you
|
|
14
|
+
* open in a browser. The same `ReplayEvent[]` is the portable timeline a hosted plane viewer reads.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** One normalized animation frame — a node appearing, settling, or stepping, at a wall-clock ms. */
|
|
18
|
+
interface ReplayEvent {
|
|
19
|
+
t: number;
|
|
20
|
+
kind: 'root' | 'spawn' | 'settle' | 'step';
|
|
21
|
+
id: string;
|
|
22
|
+
parentId?: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
runtime?: string;
|
|
25
|
+
depth?: number;
|
|
26
|
+
status?: 'running' | 'done' | 'down';
|
|
27
|
+
/** The completion-oracle signal: delivered ⟺ a deployable check passed (not self-report). */
|
|
28
|
+
valid?: boolean;
|
|
29
|
+
score?: number;
|
|
30
|
+
reason?: string;
|
|
31
|
+
tokens?: number;
|
|
32
|
+
usd?: number;
|
|
33
|
+
}
|
|
34
|
+
interface ReplayTimeline {
|
|
35
|
+
runId: string;
|
|
36
|
+
events: ReplayEvent[];
|
|
37
|
+
/** Wall-clock window [t0, t1] the player scrubs over. */
|
|
38
|
+
t0: number;
|
|
39
|
+
t1: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A `RuntimeHooks` sink that records every lifecycle event in arrival order as `ReplayEvent`s.
|
|
43
|
+
* Attach it to `SupervisorOpts.hooks` (or merge with another hooks object) and read `timeline()`
|
|
44
|
+
* after the run. Pure capture — no I/O, no throwing; an unrecognized event is ignored.
|
|
45
|
+
*/
|
|
46
|
+
declare function createReplayRecorder(): {
|
|
47
|
+
hooks: RuntimeHooks;
|
|
48
|
+
events: ReplayEvent[];
|
|
49
|
+
timeline(runId?: string): ReplayTimeline;
|
|
50
|
+
};
|
|
51
|
+
/** Render a self-contained animated HTML replay player for a timeline. Open the file in a browser. */
|
|
52
|
+
declare function renderReplayHtml(timeline: ReplayTimeline, opts?: {
|
|
53
|
+
title?: string;
|
|
54
|
+
}): string;
|
|
55
|
+
|
|
3
56
|
/**
|
|
4
57
|
* @experimental
|
|
5
58
|
*
|
|
@@ -70,4 +123,4 @@ declare function renderTopologyTree(tree: {
|
|
|
70
123
|
node: (id: string) => TopologyNode | undefined;
|
|
71
124
|
}, opts?: RenderOptions): string;
|
|
72
125
|
|
|
73
|
-
export { type RenderOptions, type TopologyNode, type TopologyStatus, type TopologyView, createTopologyView, renderTopologyTree };
|
|
126
|
+
export { type RenderOptions, type ReplayEvent, type ReplayTimeline, type TopologyNode, type TopologyStatus, type TopologyView, createReplayRecorder, createTopologyView, renderReplayHtml, renderTopologyTree };
|
package/dist/topology.js
CHANGED
|
@@ -1,5 +1,225 @@
|
|
|
1
1
|
import "./chunk-DGUM43GV.js";
|
|
2
2
|
|
|
3
|
+
// src/topology/replay.ts
|
|
4
|
+
function spendTokens(s) {
|
|
5
|
+
if (!s?.tokens) return 0;
|
|
6
|
+
return (s.tokens.input ?? 0) + (s.tokens.output ?? 0);
|
|
7
|
+
}
|
|
8
|
+
function createReplayRecorder() {
|
|
9
|
+
const events = [];
|
|
10
|
+
let runId = "run";
|
|
11
|
+
const onEvent = (e) => {
|
|
12
|
+
if (e.runId) runId = e.runId;
|
|
13
|
+
const t = e.timestamp;
|
|
14
|
+
const p = e.payload ?? {};
|
|
15
|
+
switch (e.target) {
|
|
16
|
+
case "agent.run": {
|
|
17
|
+
if (e.phase === "before") {
|
|
18
|
+
events.push({
|
|
19
|
+
t,
|
|
20
|
+
kind: "root",
|
|
21
|
+
id: e.runId,
|
|
22
|
+
label: String(p.driver ?? "root"),
|
|
23
|
+
depth: 0,
|
|
24
|
+
status: "running"
|
|
25
|
+
});
|
|
26
|
+
} else if (e.phase === "after" || e.phase === "error") {
|
|
27
|
+
events.push({
|
|
28
|
+
t,
|
|
29
|
+
kind: "settle",
|
|
30
|
+
id: e.runId,
|
|
31
|
+
status: e.phase === "error" ? "down" : "done"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case "agent.spawn": {
|
|
37
|
+
events.push({
|
|
38
|
+
t,
|
|
39
|
+
kind: "spawn",
|
|
40
|
+
id: String(p.childId ?? e.id),
|
|
41
|
+
...e.parentId ? { parentId: e.parentId } : {},
|
|
42
|
+
label: String(p.label ?? p.childId ?? "?"),
|
|
43
|
+
...p.runtime ? { runtime: String(p.runtime) } : {},
|
|
44
|
+
...typeof p.depth === "number" ? { depth: p.depth } : {},
|
|
45
|
+
status: "running"
|
|
46
|
+
});
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case "agent.child": {
|
|
50
|
+
const spent = p.spent;
|
|
51
|
+
events.push({
|
|
52
|
+
t,
|
|
53
|
+
kind: "settle",
|
|
54
|
+
id: String(p.childId ?? e.id),
|
|
55
|
+
status: p.status ?? "done",
|
|
56
|
+
...typeof p.valid === "boolean" ? { valid: p.valid } : {},
|
|
57
|
+
...typeof p.score === "number" ? { score: p.score } : {},
|
|
58
|
+
...p.reason ? { reason: String(p.reason) } : {},
|
|
59
|
+
tokens: spendTokens(spent),
|
|
60
|
+
usd: spent?.usd ?? 0
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
default: {
|
|
65
|
+
const owner = e.parentId ?? e.runId;
|
|
66
|
+
if (owner) events.push({ t, kind: "step", id: owner });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
hooks: { onEvent },
|
|
72
|
+
events,
|
|
73
|
+
timeline(rid) {
|
|
74
|
+
const ts = events.map((e) => e.t);
|
|
75
|
+
const t0 = ts.length ? Math.min(...ts) : 0;
|
|
76
|
+
const t1 = ts.length ? Math.max(...ts) : 0;
|
|
77
|
+
const defined = new Set(
|
|
78
|
+
events.filter((e) => e.kind === "spawn" || e.kind === "root").map((e) => e.id)
|
|
79
|
+
);
|
|
80
|
+
const synthetic = [];
|
|
81
|
+
for (const id of new Set(events.map((e) => e.parentId).filter((p) => !!p))) {
|
|
82
|
+
if (!defined.has(id))
|
|
83
|
+
synthetic.push({
|
|
84
|
+
t: t0,
|
|
85
|
+
kind: "root",
|
|
86
|
+
id,
|
|
87
|
+
label: shortRoot(id),
|
|
88
|
+
depth: 0,
|
|
89
|
+
status: "running"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return { runId: rid ?? runId, events: [...synthetic, ...events], t0, t1 };
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function renderReplayHtml(timeline, opts) {
|
|
97
|
+
const title = opts?.title ?? `agent replay \xB7 ${timeline.runId}`;
|
|
98
|
+
const data = JSON.stringify(timeline);
|
|
99
|
+
return `<!doctype html>
|
|
100
|
+
<html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
101
|
+
<title>${escapeHtml(title)}</title>
|
|
102
|
+
<style>
|
|
103
|
+
:root { --bg:#0b0e14; --panel:#11161f; --line:#2a3340; --txt:#c9d4e3; --dim:#6b7787;
|
|
104
|
+
--run:#e0a73a; --done:#3ad17a; --undeliv:#e0a73a; --down:#e25d5d; --edge:#33405200; }
|
|
105
|
+
* { box-sizing:border-box; }
|
|
106
|
+
body { margin:0; background:var(--bg); color:var(--txt); font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
107
|
+
header { padding:10px 16px; border-bottom:1px solid var(--line); display:flex; gap:18px; align-items:center; flex-wrap:wrap; }
|
|
108
|
+
header b { color:#fff; font-weight:600; } .stat { color:var(--dim); } .stat span { color:var(--txt); }
|
|
109
|
+
#wrap { display:flex; height:calc(100vh - 112px); }
|
|
110
|
+
#stage { flex:1; overflow:auto; }
|
|
111
|
+
#side { width:280px; border-left:1px solid var(--line); padding:14px; overflow:auto; background:var(--panel); }
|
|
112
|
+
#side h3 { margin:0 0 8px; font-size:12px; color:var(--dim); text-transform:uppercase; letter-spacing:.08em; }
|
|
113
|
+
#side .k { color:var(--dim); } #side .row { margin:3px 0; }
|
|
114
|
+
svg { display:block; } .node circle { stroke:#0b0e14; stroke-width:2; cursor:pointer; }
|
|
115
|
+
.node text { fill:var(--txt); font-size:11px; } .node .sub { fill:var(--dim); font-size:9px; }
|
|
116
|
+
.edge { stroke:#2a3340; stroke-width:1.5; fill:none; }
|
|
117
|
+
.hidden { opacity:0; } .node, .edge { transition:opacity .25s ease, fill .25s, stroke .25s; }
|
|
118
|
+
footer { padding:10px 16px; border-top:1px solid var(--line); display:flex; gap:14px; align-items:center; }
|
|
119
|
+
button { background:#1b2433; color:var(--txt); border:1px solid var(--line); border-radius:6px; padding:6px 14px; cursor:pointer; font:inherit; }
|
|
120
|
+
button:hover { background:#243047; } input[type=range] { flex:1; accent-color:#3ad17a; }
|
|
121
|
+
select { background:#1b2433; color:var(--txt); border:1px solid var(--line); border-radius:6px; padding:5px; }
|
|
122
|
+
.legend i { display:inline-block; width:9px; height:9px; border-radius:50%; margin:0 4px 0 10px; vertical-align:middle; }
|
|
123
|
+
</style></head><body>
|
|
124
|
+
<header>
|
|
125
|
+
<b>${escapeHtml(title)}</b>
|
|
126
|
+
<span class="legend"><i style="background:var(--run)"></i>running<i style="background:var(--done)"></i>delivered<i style="background:var(--down)"></i>failed</span>
|
|
127
|
+
<span class="stat">t <span id="clock">0.0s</span></span>
|
|
128
|
+
<span class="stat">nodes <span id="cNodes">0</span></span>
|
|
129
|
+
<span class="stat">delivered <span id="cDeliv">0</span></span>
|
|
130
|
+
<span class="stat">tokens <span id="cTok">0</span></span>
|
|
131
|
+
<span class="stat">$<span id="cUsd">0.000</span></span>
|
|
132
|
+
</header>
|
|
133
|
+
<div id="wrap"><div id="stage"><svg id="svg"></svg></div>
|
|
134
|
+
<div id="side"><h3>node</h3><div id="detail" class="stat">hover a node\u2026</div></div></div>
|
|
135
|
+
<footer>
|
|
136
|
+
<button id="play">\u25B6 play</button>
|
|
137
|
+
<button id="restart">\u27F2</button>
|
|
138
|
+
<input type="range" id="scrub" min="0" max="1000" value="0"/>
|
|
139
|
+
<select id="speed"><option value="1">1\xD7</option><option value="2">2\xD7</option><option value="4" selected>4\xD7</option><option value="8">8\xD7</option><option value="20">20\xD7</option></select>
|
|
140
|
+
</footer>
|
|
141
|
+
<script>
|
|
142
|
+
const TL = ${data};
|
|
143
|
+
const EV = TL.events.slice().sort((a,b)=>a.t-b.t);
|
|
144
|
+
const span = Math.max(1, TL.t1 - TL.t0);
|
|
145
|
+
// \u2500\u2500 build the full tree (nodes known from spawn/root events) \u2500\u2500
|
|
146
|
+
const nodes = new Map();
|
|
147
|
+
for (const e of EV) {
|
|
148
|
+
if (e.kind==='root' || e.kind==='spawn') {
|
|
149
|
+
if (!nodes.has(e.id)) nodes.set(e.id, { id:e.id, parentId:e.parentId, label:e.label||e.id, runtime:e.runtime, depth:e.depth??0, tSpawn:e.t, children:[] });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const n of nodes.values()) { if (n.parentId && nodes.has(n.parentId)) nodes.get(n.parentId).children.push(n); }
|
|
153
|
+
const roots = [...nodes.values()].filter(n=>!n.parentId || !nodes.has(n.parentId));
|
|
154
|
+
// \u2500\u2500 tidy layout: post-order leaf slotting; x=avg(children), y=depth \u2500\u2500
|
|
155
|
+
let leaf=0; const VGAP=78, HGAP=120;
|
|
156
|
+
function layout(n, d){ n.y = 40 + d*VGAP; if(!n.children.length){ n.x = 40 + (leaf++)*HGAP; } else { n.children.forEach(c=>layout(c,d+1)); n.x = (n.children[0].x + n.children[n.children.length-1].x)/2; } }
|
|
157
|
+
roots.forEach(r=>layout(r,0));
|
|
158
|
+
const W = Math.max(900, 80 + leaf*HGAP), H = 80 + (Math.max(0,...[...nodes.values()].map(n=>n.depth||0))+1)*VGAP;
|
|
159
|
+
const svg = document.getElementById('svg'); svg.setAttribute('viewBox','0 0 '+W+' '+H); svg.setAttribute('width',W); svg.setAttribute('height',H);
|
|
160
|
+
const NS='http://www.w3.org/2000/svg';
|
|
161
|
+
function el(t,a){ const x=document.createElementNS(NS,t); for(const k in a) x.setAttribute(k,a[k]); return x; }
|
|
162
|
+
// edges
|
|
163
|
+
const edgeEls=new Map();
|
|
164
|
+
for (const n of nodes.values()) if (n.parentId && nodes.has(n.parentId)) { const p=nodes.get(n.parentId);
|
|
165
|
+
const path=el('path',{class:'edge hidden', d:'M'+p.x+','+(p.y)+' C'+p.x+','+((p.y+n.y)/2)+' '+n.x+','+((p.y+n.y)/2)+' '+n.x+','+n.y}); svg.appendChild(path); edgeEls.set(n.id,path); }
|
|
166
|
+
// nodes
|
|
167
|
+
const nodeEls=new Map();
|
|
168
|
+
for (const n of nodes.values()) { const g=el('g',{class:'node hidden'});
|
|
169
|
+
const c=el('circle',{cx:n.x,cy:n.y,r:14,fill:'var(--dim)'});
|
|
170
|
+
const t=el('text',{x:n.x,y:n.y-20,'text-anchor':'middle'}); t.textContent=n.label.slice(0,18);
|
|
171
|
+
const s=el('text',{class:'sub',x:n.x,y:n.y+26,'text-anchor':'middle'}); s.textContent=n.runtime||'';
|
|
172
|
+
g.appendChild(c); g.appendChild(t); g.appendChild(s); svg.appendChild(g); nodeEls.set(n.id,{g,c,s});
|
|
173
|
+
g.addEventListener('mouseenter',()=>showDetail(n)); }
|
|
174
|
+
function showDetail(n){ const st=state.get(n.id)||{}; document.getElementById('detail').innerHTML =
|
|
175
|
+
'<div class="row"><span class="k">id</span> '+n.id+'</div>'+
|
|
176
|
+
'<div class="row"><span class="k">label</span> '+esc(n.label)+'</div>'+
|
|
177
|
+
'<div class="row"><span class="k">runtime</span> '+(n.runtime||'\u2014')+'</div>'+
|
|
178
|
+
'<div class="row"><span class="k">depth</span> '+(n.depth??0)+'</div>'+
|
|
179
|
+
'<div class="row"><span class="k">status</span> '+(st.status||'\u2014')+(st.valid===false?' (not delivered)':st.valid?' (delivered)':'')+'</div>'+
|
|
180
|
+
'<div class="row"><span class="k">score</span> '+(st.score!=null?st.score.toFixed(2):'\u2014')+'</div>'+
|
|
181
|
+
(st.reason?'<div class="row"><span class="k">reason</span> '+esc(st.reason)+'</div>':'')+
|
|
182
|
+
'<div class="row"><span class="k">tokens</span> '+(st.tokens||0)+'</div>'; }
|
|
183
|
+
function esc(x){ return String(x).replace(/[<>&]/g,c=>({'<':'<','>':'>','&':'&'}[c])); }
|
|
184
|
+
// \u2500\u2500 playback \u2500\u2500
|
|
185
|
+
const state=new Map();
|
|
186
|
+
function colorOf(st){ if(!st||st.status==='running'||!st.status) return 'var(--run)'; if(st.status==='down') return 'var(--down)'; return st.valid===false ? 'var(--undeliv)' : 'var(--done)'; }
|
|
187
|
+
function applyTo(ms){ const clk=TL.t0+ms; state.clear();
|
|
188
|
+
let deliv=0, tok=0, usd=0, shown=0;
|
|
189
|
+
// reset visibility
|
|
190
|
+
for (const [,v] of nodeEls){ v.g.classList.add('hidden'); }
|
|
191
|
+
for (const [,p] of edgeEls){ p.classList.add('hidden'); }
|
|
192
|
+
for (const e of EV){ if (e.t>clk) break;
|
|
193
|
+
if (e.kind==='spawn'||e.kind==='root'){ const ne=nodeEls.get(e.id); if(ne){ ne.g.classList.remove('hidden'); shown++; const ed=edgeEls.get(e.id); if(ed) ed.classList.remove('hidden'); } state.set(e.id, Object.assign(state.get(e.id)||{}, {status:'running'})); }
|
|
194
|
+
else if (e.kind==='settle'){ const st=Object.assign(state.get(e.id)||{}, {status:e.status, valid:e.valid, score:e.score, reason:e.reason, tokens:e.tokens, usd:e.usd}); state.set(e.id,st); }
|
|
195
|
+
}
|
|
196
|
+
for (const [id,st] of state){ const ne=nodeEls.get(id); if(ne) ne.c.setAttribute('fill', colorOf(st)); if(st.status&&st.status!=='running'){ if(st.status==='done'&&st.valid!==false) deliv++; tok+=st.tokens||0; usd+=st.usd||0; } }
|
|
197
|
+
document.getElementById('clock').textContent=(ms/1000).toFixed(1)+'s';
|
|
198
|
+
document.getElementById('cNodes').textContent=shown;
|
|
199
|
+
document.getElementById('cDeliv').textContent=deliv;
|
|
200
|
+
document.getElementById('cTok').textContent=tok;
|
|
201
|
+
document.getElementById('cUsd').textContent=usd.toFixed(3);
|
|
202
|
+
}
|
|
203
|
+
const scrub=document.getElementById('scrub'); let playing=false, raf=0, last=0;
|
|
204
|
+
function setMs(ms){ ms=Math.max(0,Math.min(span,ms)); scrub.value=(ms/span*1000)|0; applyTo(ms); }
|
|
205
|
+
scrub.addEventListener('input',()=>setMs(scrub.value/1000*span));
|
|
206
|
+
document.getElementById('play').addEventListener('click',()=>{ playing=!playing; document.getElementById('play').textContent=playing?'\u23F8 pause':'\u25B6 play'; if(playing){ last=performance.now(); if((scrub.value/1000*span)>=span) setMs(0); tick(); } });
|
|
207
|
+
document.getElementById('restart').addEventListener('click',()=>{ setMs(0); });
|
|
208
|
+
function tick(){ if(!playing) return; const now=performance.now(); const sp=+document.getElementById('speed').value; let ms=scrub.value/1000*span + (now-last)*sp; last=now; if(ms>=span){ ms=span; playing=false; document.getElementById('play').textContent='\u25B6 play'; } setMs(ms); if(playing) raf=requestAnimationFrame(tick); }
|
|
209
|
+
setMs(0);
|
|
210
|
+
</script></body></html>`;
|
|
211
|
+
}
|
|
212
|
+
function shortRoot(id) {
|
|
213
|
+
const seg = id.split("/").pop() ?? id;
|
|
214
|
+
return seg.length > 22 ? `\u2026${seg.slice(-21)}` : seg;
|
|
215
|
+
}
|
|
216
|
+
function escapeHtml(s) {
|
|
217
|
+
return s.replace(
|
|
218
|
+
/[<>&"]/g,
|
|
219
|
+
(c) => ({ "<": "<", ">": ">", "&": "&", '"': """ })[c] ?? c
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
3
223
|
// src/topology/tree.ts
|
|
4
224
|
var stepTargets = /* @__PURE__ */ new Set(["agent.turn", "agent.tool_call", "agent.plan", "agent.decision"]);
|
|
5
225
|
function createTopologyView() {
|
|
@@ -105,7 +325,9 @@ function renderTopologyTree(tree, opts = {}) {
|
|
|
105
325
|
return lines.join("\n");
|
|
106
326
|
}
|
|
107
327
|
export {
|
|
328
|
+
createReplayRecorder,
|
|
108
329
|
createTopologyView,
|
|
330
|
+
renderReplayHtml,
|
|
109
331
|
renderTopologyTree
|
|
110
332
|
};
|
|
111
333
|
//# sourceMappingURL=topology.js.map
|
package/dist/topology.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/topology/tree.ts"],"sourcesContent":["/**\n * @experimental\n *\n * TOPOLOGY VIEW — the live recursive agent tree, folded from the ONE lifecycle stream\n * (`src/runtime-hooks.ts`). It is a pure projection: feed it the `RuntimeHookEvent`s that\n * `runLoop`, `toolLoop`, and the keystone `Scope` already emit, and it maintains the tree of\n * agents + renders it. Attach `view.hooks` to a `Supervisor`/`runLoop` and the tree updates live.\n *\n * Two node sources, ONE stream:\n * - an agent node is born from `agent.spawn` (id = `childId`, parent = the spawner) or the root\n * `agent.run` (id = `runId`); `agent.child`/`agent.run:after` settle it (status + score/reason).\n * - a STEP (`agent.{turn,tool_call,plan,decision}`) is not a node — it advances the step count of\n * the agent it belongs to (matched by `runId`/`parentId`).\n *\n * No I/O, no timers, no backend coupling — the same projection drives a CLI render, a TUI, or a\n * web tree. Rendering is deterministic given the event order (the stream is the source of truth).\n */\n\nimport type { RuntimeHookEvent, RuntimeHooks } from '../runtime-hooks'\n\nexport type TopologyStatus = 'running' | 'done' | 'down'\n\n/** One agent in the tree. A leaf never spawns; a driver's `childIds` is non-empty. */\nexport interface TopologyNode {\n readonly id: string\n /** Display label (spawn `label`, or the driver name on the root). */\n label: string\n /** Leaf runtime (`router`/`sandbox`/`cli`) when known. */\n runtime?: string\n /** Parent agent id; undefined ⇒ a root. */\n parentId?: string\n /** Recursion depth (root = 0). */\n depth: number\n status: TopologyStatus\n /** Count of in-agent steps (turns + tool calls + plan/decision rounds) folded so far. */\n steps: number\n /** Deployable score in [0,1] once settled `done`. */\n score?: number\n /** Failure reason once settled `down`. */\n reason?: string\n /** Children in spawn order. */\n readonly childIds: string[]\n}\n\nexport interface RenderOptions {\n /** Cap the rendered depth (deeper nodes collapse to a `… N more` line). Default: no cap. */\n readonly maxDepth?: number\n /** Drop the per-node detail suffix (steps/children/score) — labels only. Default: false. */\n readonly compact?: boolean\n}\n\nexport interface TopologyView {\n /** The `RuntimeHooks` sink — attach to `SupervisorOpts.hooks` / `runLoop` options. */\n readonly hooks: RuntimeHooks\n /** Fold one event into the tree (the same call `hooks.onEvent` makes — exposed for replay). */\n ingest(event: RuntimeHookEvent): void\n /** Every node, insertion order. */\n nodes(): TopologyNode[]\n /** Nodes with no in-tree parent (the run roots). */\n roots(): TopologyNode[]\n /** One node by id. */\n node(id: string): TopologyNode | undefined\n /** Render the tree as an aligned ASCII forest. */\n render(opts?: RenderOptions): string\n}\n\nconst stepTargets = new Set(['agent.turn', 'agent.tool_call', 'agent.plan', 'agent.decision'])\n\n/** Build a live topology view. Stateful — one per run (or per replay). */\nexport function createTopologyView(): TopologyView {\n // Insertion-ordered so render is stable and roots() reflects spawn order.\n const byId = new Map<string, TopologyNode>()\n\n const ensure = (id: string, seed: Partial<TopologyNode> = {}): TopologyNode => {\n const existing = byId.get(id)\n if (existing) return existing\n const node: TopologyNode = {\n id,\n label: seed.label ?? id,\n depth: seed.depth ?? 0,\n status: 'running',\n steps: 0,\n childIds: [],\n ...seed,\n }\n byId.set(id, node)\n return node\n }\n\n const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined)\n const num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined)\n\n const ingest = (event: RuntimeHookEvent): void => {\n const p = (event.payload ?? {}) as Record<string, unknown>\n\n if (event.target === 'agent.spawn' && event.phase === 'after') {\n const id = str(p.childId)\n if (!id) return\n const parent = event.parentId ? ensure(event.parentId) : undefined\n const node = ensure(id, {\n label: str(p.label) ?? id,\n runtime: str(p.runtime),\n parentId: event.parentId,\n depth: num(p.depth) ?? (parent ? parent.depth + 1 : 0),\n })\n if (parent && !parent.childIds.includes(id)) parent.childIds.push(id)\n node.status = 'running'\n return\n }\n\n if (event.target === 'agent.child' && event.phase === 'after') {\n const id = str(p.childId)\n if (!id) return\n const node = ensure(id, { parentId: event.parentId })\n node.status = str(p.status) === 'down' ? 'down' : 'done'\n node.score = num(p.score)\n node.reason = str(p.reason)\n return\n }\n\n if (event.target === 'agent.run') {\n const node = ensure(event.runId, {\n label: str(p.driver) ?? event.runId,\n parentId: event.parentId,\n })\n if (event.phase === 'before') node.status = 'running'\n else if (event.phase === 'error') node.status = 'down'\n else if (event.phase === 'after' && node.status === 'running') node.status = 'done'\n return\n }\n\n // A step advances the agent it belongs to: tool-loop/run-loop key the agent by `runId`,\n // a within-agent sub-event names it via `parentId`. Only count `after`/`event` phases so a\n // before/after pair is one step, not two. An unknown owner is dropped (no phantom node).\n if (stepTargets.has(event.target) && (event.phase === 'after' || event.phase === 'event')) {\n const owner =\n (event.parentId && byId.get(event.parentId)) || byId.get(event.runId) || undefined\n if (owner) owner.steps += 1\n }\n }\n\n const nodes = (): TopologyNode[] => [...byId.values()]\n const roots = (): TopologyNode[] =>\n nodes().filter((n) => n.parentId === undefined || !byId.has(n.parentId))\n\n return {\n hooks: { onEvent: (e) => ingest(e) },\n ingest,\n nodes,\n roots,\n node: (id) => byId.get(id),\n render: (opts) => renderTopologyTree({ roots: roots(), node: (id) => byId.get(id) }, opts),\n }\n}\n\nconst glyph: Record<TopologyStatus, string> = { running: '◐', done: '✓', down: '✗' }\n\n/** Render a forest of `TopologyNode`s to an aligned ASCII tree. Pure — given the same roots +\n * node lookup it returns the same string. Exposed so a caller can render a tree it folded\n * itself (e.g. from a journal replay) without the live view. */\nexport function renderTopologyTree(\n tree: { roots: TopologyNode[]; node: (id: string) => TopologyNode | undefined },\n opts: RenderOptions = {},\n): string {\n const lines: string[] = []\n\n const detail = (n: TopologyNode): string => {\n if (opts.compact) return ''\n const parts: string[] = []\n if (n.runtime) parts.push(n.runtime)\n if (n.steps > 0) parts.push(`${n.steps} ${n.steps === 1 ? 'step' : 'steps'}`)\n if (n.childIds.length > 0) parts.push(`${n.childIds.length} children`)\n if (n.status === 'done' && n.score !== undefined) parts.push(`score ${n.score.toFixed(2)}`)\n if (n.status === 'down' && n.reason) parts.push(`down: ${n.reason}`)\n return parts.length ? ` (${parts.join(' · ')})` : ''\n }\n\n const walk = (n: TopologyNode, prefix: string, isLast: boolean, depth: number): void => {\n const branch = depth === 0 ? '' : isLast ? '└─ ' : '├─ '\n lines.push(`${prefix}${branch}${glyph[n.status]} ${n.label}${detail(n)}`)\n\n if (opts.maxDepth !== undefined && depth >= opts.maxDepth && n.childIds.length > 0) {\n const childPrefix = prefix + (depth === 0 ? '' : isLast ? ' ' : '│ ')\n lines.push(`${childPrefix}└─ … ${n.childIds.length} more`)\n return\n }\n const childPrefix = prefix + (depth === 0 ? '' : isLast ? ' ' : '│ ')\n const kids = n.childIds.map((id) => tree.node(id)).filter((c): c is TopologyNode => !!c)\n kids.forEach((c, i) => {\n walk(c, childPrefix, i === kids.length - 1, depth + 1)\n })\n }\n\n tree.roots.forEach((r, i) => {\n walk(r, '', i === tree.roots.length - 1, 0)\n })\n return lines.join('\\n')\n}\n"],"mappings":";;;AAkEA,IAAM,cAAc,oBAAI,IAAI,CAAC,cAAc,mBAAmB,cAAc,gBAAgB,CAAC;AAGtF,SAAS,qBAAmC;AAEjD,QAAM,OAAO,oBAAI,IAA0B;AAE3C,QAAM,SAAS,CAAC,IAAY,OAA8B,CAAC,MAAoB;AAC7E,UAAM,WAAW,KAAK,IAAI,EAAE;AAC5B,QAAI,SAAU,QAAO;AACrB,UAAM,OAAqB;AAAA,MACzB;AAAA,MACA,OAAO,KAAK,SAAS;AAAA,MACrB,OAAO,KAAK,SAAS;AAAA,MACrB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU,CAAC;AAAA,MACX,GAAG;AAAA,IACL;AACA,SAAK,IAAI,IAAI,IAAI;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,CAAC,MAAoC,OAAO,MAAM,WAAW,IAAI;AAC7E,QAAM,MAAM,CAAC,MAAoC,OAAO,MAAM,WAAW,IAAI;AAE7E,QAAM,SAAS,CAAC,UAAkC;AAChD,UAAM,IAAK,MAAM,WAAW,CAAC;AAE7B,QAAI,MAAM,WAAW,iBAAiB,MAAM,UAAU,SAAS;AAC7D,YAAM,KAAK,IAAI,EAAE,OAAO;AACxB,UAAI,CAAC,GAAI;AACT,YAAM,SAAS,MAAM,WAAW,OAAO,MAAM,QAAQ,IAAI;AACzD,YAAM,OAAO,OAAO,IAAI;AAAA,QACtB,OAAO,IAAI,EAAE,KAAK,KAAK;AAAA,QACvB,SAAS,IAAI,EAAE,OAAO;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,OAAO,IAAI,EAAE,KAAK,MAAM,SAAS,OAAO,QAAQ,IAAI;AAAA,MACtD,CAAC;AACD,UAAI,UAAU,CAAC,OAAO,SAAS,SAAS,EAAE,EAAG,QAAO,SAAS,KAAK,EAAE;AACpE,WAAK,SAAS;AACd;AAAA,IACF;AAEA,QAAI,MAAM,WAAW,iBAAiB,MAAM,UAAU,SAAS;AAC7D,YAAM,KAAK,IAAI,EAAE,OAAO;AACxB,UAAI,CAAC,GAAI;AACT,YAAM,OAAO,OAAO,IAAI,EAAE,UAAU,MAAM,SAAS,CAAC;AACpD,WAAK,SAAS,IAAI,EAAE,MAAM,MAAM,SAAS,SAAS;AAClD,WAAK,QAAQ,IAAI,EAAE,KAAK;AACxB,WAAK,SAAS,IAAI,EAAE,MAAM;AAC1B;AAAA,IACF;AAEA,QAAI,MAAM,WAAW,aAAa;AAChC,YAAM,OAAO,OAAO,MAAM,OAAO;AAAA,QAC/B,OAAO,IAAI,EAAE,MAAM,KAAK,MAAM;AAAA,QAC9B,UAAU,MAAM;AAAA,MAClB,CAAC;AACD,UAAI,MAAM,UAAU,SAAU,MAAK,SAAS;AAAA,eACnC,MAAM,UAAU,QAAS,MAAK,SAAS;AAAA,eACvC,MAAM,UAAU,WAAW,KAAK,WAAW,UAAW,MAAK,SAAS;AAC7E;AAAA,IACF;AAKA,QAAI,YAAY,IAAI,MAAM,MAAM,MAAM,MAAM,UAAU,WAAW,MAAM,UAAU,UAAU;AACzF,YAAM,QACH,MAAM,YAAY,KAAK,IAAI,MAAM,QAAQ,KAAM,KAAK,IAAI,MAAM,KAAK,KAAK;AAC3E,UAAI,MAAO,OAAM,SAAS;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,QAAQ,MAAsB,CAAC,GAAG,KAAK,OAAO,CAAC;AACrD,QAAM,QAAQ,MACZ,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,UAAa,CAAC,KAAK,IAAI,EAAE,QAAQ,CAAC;AAEzE,SAAO;AAAA,IACL,OAAO,EAAE,SAAS,CAAC,MAAM,OAAO,CAAC,EAAE;AAAA,IACnC;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE;AAAA,IACzB,QAAQ,CAAC,SAAS,mBAAmB,EAAE,OAAO,MAAM,GAAG,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE,EAAE,GAAG,IAAI;AAAA,EAC3F;AACF;AAEA,IAAM,QAAwC,EAAE,SAAS,UAAK,MAAM,UAAK,MAAM,SAAI;AAK5E,SAAS,mBACd,MACA,OAAsB,CAAC,GACf;AACR,QAAM,QAAkB,CAAC;AAEzB,QAAM,SAAS,CAAC,MAA4B;AAC1C,QAAI,KAAK,QAAS,QAAO;AACzB,UAAM,QAAkB,CAAC;AACzB,QAAI,EAAE,QAAS,OAAM,KAAK,EAAE,OAAO;AACnC,QAAI,EAAE,QAAQ,EAAG,OAAM,KAAK,GAAG,EAAE,KAAK,IAAI,EAAE,UAAU,IAAI,SAAS,OAAO,EAAE;AAC5E,QAAI,EAAE,SAAS,SAAS,EAAG,OAAM,KAAK,GAAG,EAAE,SAAS,MAAM,WAAW;AACrE,QAAI,EAAE,WAAW,UAAU,EAAE,UAAU,OAAW,OAAM,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC,CAAC,EAAE;AAC1F,QAAI,EAAE,WAAW,UAAU,EAAE,OAAQ,OAAM,KAAK,SAAS,EAAE,MAAM,EAAE;AACnE,WAAO,MAAM,SAAS,MAAM,MAAM,KAAK,QAAK,CAAC,MAAM;AAAA,EACrD;AAEA,QAAM,OAAO,CAAC,GAAiB,QAAgB,QAAiB,UAAwB;AACtF,UAAM,SAAS,UAAU,IAAI,KAAK,SAAS,kBAAQ;AACnD,UAAM,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,CAAC,EAAE;AAExE,QAAI,KAAK,aAAa,UAAa,SAAS,KAAK,YAAY,EAAE,SAAS,SAAS,GAAG;AAClF,YAAMA,eAAc,UAAU,UAAU,IAAI,KAAK,SAAS,QAAQ;AAClE,YAAM,KAAK,GAAGA,YAAW,uBAAQ,EAAE,SAAS,MAAM,OAAO;AACzD;AAAA,IACF;AACA,UAAM,cAAc,UAAU,UAAU,IAAI,KAAK,SAAS,QAAQ;AAClE,UAAM,OAAO,EAAE,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,MAAyB,CAAC,CAAC,CAAC;AACvF,SAAK,QAAQ,CAAC,GAAG,MAAM;AACrB,WAAK,GAAG,aAAa,MAAM,KAAK,SAAS,GAAG,QAAQ,CAAC;AAAA,IACvD,CAAC;AAAA,EACH;AAEA,OAAK,MAAM,QAAQ,CAAC,GAAG,MAAM;AAC3B,SAAK,GAAG,IAAI,MAAM,KAAK,MAAM,SAAS,GAAG,CAAC;AAAA,EAC5C,CAAC;AACD,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["childPrefix"]}
|
|
1
|
+
{"version":3,"sources":["../src/topology/replay.ts","../src/topology/tree.ts"],"sourcesContent":["/**\n * @experimental\n *\n * Run replay — the visual, animated record of a recursive agent run.\n *\n * The runtime emits ONE event stream (`agent.spawn`/`agent.child`/`agent.run`/`agent.turn`)\n * through `RuntimeHooks`; the topology tree + waterfall already fold it into ASCII. This module\n * folds the SAME stream into a normalized, timestamped `ReplayEvent[]` (the recorder) and renders\n * a self-contained, animated HTML player (`renderReplayHtml`) — a timeline scrubber over the live\n * recursive tree where every node colors by the completion-oracle: delivered (valid) green, ran-\n * but-not-delivered amber, failed red. No server, no build, no external deps — one HTML file you\n * open in a browser. The same `ReplayEvent[]` is the portable timeline a hosted plane viewer reads.\n */\n\nimport type { RuntimeHookEvent, RuntimeHooks } from '../runtime-hooks'\n\n/** One normalized animation frame — a node appearing, settling, or stepping, at a wall-clock ms. */\nexport interface ReplayEvent {\n t: number\n kind: 'root' | 'spawn' | 'settle' | 'step'\n id: string\n parentId?: string\n label?: string\n runtime?: string\n depth?: number\n status?: 'running' | 'done' | 'down'\n /** The completion-oracle signal: delivered ⟺ a deployable check passed (not self-report). */\n valid?: boolean\n score?: number\n reason?: string\n tokens?: number\n usd?: number\n}\n\nexport interface ReplayTimeline {\n runId: string\n events: ReplayEvent[]\n /** Wall-clock window [t0, t1] the player scrubs over. */\n t0: number\n t1: number\n}\n\ninterface RecordedSpend {\n tokens?: { input?: number; output?: number }\n usd?: number\n}\n\nfunction spendTokens(s: RecordedSpend | undefined): number {\n if (!s?.tokens) return 0\n return (s.tokens.input ?? 0) + (s.tokens.output ?? 0)\n}\n\n/**\n * A `RuntimeHooks` sink that records every lifecycle event in arrival order as `ReplayEvent`s.\n * Attach it to `SupervisorOpts.hooks` (or merge with another hooks object) and read `timeline()`\n * after the run. Pure capture — no I/O, no throwing; an unrecognized event is ignored.\n */\nexport function createReplayRecorder(): {\n hooks: RuntimeHooks\n events: ReplayEvent[]\n timeline(runId?: string): ReplayTimeline\n} {\n const events: ReplayEvent[] = []\n let runId = 'run'\n\n const onEvent = (e: RuntimeHookEvent): void => {\n if (e.runId) runId = e.runId\n const t = e.timestamp\n const p = (e.payload ?? {}) as Record<string, unknown>\n switch (e.target) {\n case 'agent.run': {\n // The root driver's lifecycle. `before` plants the root node; `after`/`error` settle it.\n if (e.phase === 'before') {\n events.push({\n t,\n kind: 'root',\n id: e.runId,\n label: String(p.driver ?? 'root'),\n depth: 0,\n status: 'running',\n })\n } else if (e.phase === 'after' || e.phase === 'error') {\n events.push({\n t,\n kind: 'settle',\n id: e.runId,\n status: e.phase === 'error' ? 'down' : 'done',\n })\n }\n break\n }\n case 'agent.spawn': {\n events.push({\n t,\n kind: 'spawn',\n id: String(p.childId ?? e.id),\n ...(e.parentId ? { parentId: e.parentId } : {}),\n label: String(p.label ?? p.childId ?? '?'),\n ...(p.runtime ? { runtime: String(p.runtime) } : {}),\n ...(typeof p.depth === 'number' ? { depth: p.depth } : {}),\n status: 'running',\n })\n break\n }\n case 'agent.child': {\n const spent = p.spent as RecordedSpend | undefined\n events.push({\n t,\n kind: 'settle',\n id: String(p.childId ?? e.id),\n status: (p.status as 'done' | 'down') ?? 'done',\n ...(typeof p.valid === 'boolean' ? { valid: p.valid } : {}),\n ...(typeof p.score === 'number' ? { score: p.score } : {}),\n ...(p.reason ? { reason: String(p.reason) } : {}),\n tokens: spendTokens(spent),\n usd: spent?.usd ?? 0,\n })\n break\n }\n default: {\n // agent.turn / agent.plan / agent.decision / agent.tool_call → a step pulse on the owner.\n const owner = e.parentId ?? e.runId\n if (owner) events.push({ t, kind: 'step', id: owner })\n }\n }\n }\n\n return {\n hooks: { onEvent },\n events,\n timeline(rid?: string): ReplayTimeline {\n const ts = events.map((e) => e.t)\n const t0 = ts.length ? Math.min(...ts) : 0\n const t1 = ts.length ? Math.max(...ts) : 0\n // Synthesize any node referenced as a parent but never spawned (the supervisor's root\n // driver, and each nested driver's tree root, run via `act`, not `spawn`) so the player\n // renders the WHOLE recursion — driver → worker — not just the spawned leaves.\n const defined = new Set(\n events.filter((e) => e.kind === 'spawn' || e.kind === 'root').map((e) => e.id),\n )\n const synthetic: ReplayEvent[] = []\n for (const id of new Set(events.map((e) => e.parentId).filter((p): p is string => !!p))) {\n if (!defined.has(id))\n synthetic.push({\n t: t0,\n kind: 'root',\n id,\n label: shortRoot(id),\n depth: 0,\n status: 'running',\n })\n }\n return { runId: rid ?? runId, events: [...synthetic, ...events], t0, t1 }\n },\n }\n}\n\n/** Render a self-contained animated HTML replay player for a timeline. Open the file in a browser. */\nexport function renderReplayHtml(timeline: ReplayTimeline, opts?: { title?: string }): string {\n const title = opts?.title ?? `agent replay · ${timeline.runId}`\n const data = JSON.stringify(timeline)\n return `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\n<title>${escapeHtml(title)}</title>\n<style>\n :root { --bg:#0b0e14; --panel:#11161f; --line:#2a3340; --txt:#c9d4e3; --dim:#6b7787;\n --run:#e0a73a; --done:#3ad17a; --undeliv:#e0a73a; --down:#e25d5d; --edge:#33405200; }\n * { box-sizing:border-box; }\n body { margin:0; background:var(--bg); color:var(--txt); font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; }\n header { padding:10px 16px; border-bottom:1px solid var(--line); display:flex; gap:18px; align-items:center; flex-wrap:wrap; }\n header b { color:#fff; font-weight:600; } .stat { color:var(--dim); } .stat span { color:var(--txt); }\n #wrap { display:flex; height:calc(100vh - 112px); }\n #stage { flex:1; overflow:auto; }\n #side { width:280px; border-left:1px solid var(--line); padding:14px; overflow:auto; background:var(--panel); }\n #side h3 { margin:0 0 8px; font-size:12px; color:var(--dim); text-transform:uppercase; letter-spacing:.08em; }\n #side .k { color:var(--dim); } #side .row { margin:3px 0; }\n svg { display:block; } .node circle { stroke:#0b0e14; stroke-width:2; cursor:pointer; }\n .node text { fill:var(--txt); font-size:11px; } .node .sub { fill:var(--dim); font-size:9px; }\n .edge { stroke:#2a3340; stroke-width:1.5; fill:none; }\n .hidden { opacity:0; } .node, .edge { transition:opacity .25s ease, fill .25s, stroke .25s; }\n footer { padding:10px 16px; border-top:1px solid var(--line); display:flex; gap:14px; align-items:center; }\n button { background:#1b2433; color:var(--txt); border:1px solid var(--line); border-radius:6px; padding:6px 14px; cursor:pointer; font:inherit; }\n button:hover { background:#243047; } input[type=range] { flex:1; accent-color:#3ad17a; }\n select { background:#1b2433; color:var(--txt); border:1px solid var(--line); border-radius:6px; padding:5px; }\n .legend i { display:inline-block; width:9px; height:9px; border-radius:50%; margin:0 4px 0 10px; vertical-align:middle; }\n</style></head><body>\n<header>\n <b>${escapeHtml(title)}</b>\n <span class=\"legend\"><i style=\"background:var(--run)\"></i>running<i style=\"background:var(--done)\"></i>delivered<i style=\"background:var(--down)\"></i>failed</span>\n <span class=\"stat\">t <span id=\"clock\">0.0s</span></span>\n <span class=\"stat\">nodes <span id=\"cNodes\">0</span></span>\n <span class=\"stat\">delivered <span id=\"cDeliv\">0</span></span>\n <span class=\"stat\">tokens <span id=\"cTok\">0</span></span>\n <span class=\"stat\">$<span id=\"cUsd\">0.000</span></span>\n</header>\n<div id=\"wrap\"><div id=\"stage\"><svg id=\"svg\"></svg></div>\n<div id=\"side\"><h3>node</h3><div id=\"detail\" class=\"stat\">hover a node…</div></div></div>\n<footer>\n <button id=\"play\">▶ play</button>\n <button id=\"restart\">⟲</button>\n <input type=\"range\" id=\"scrub\" min=\"0\" max=\"1000\" value=\"0\"/>\n <select id=\"speed\"><option value=\"1\">1×</option><option value=\"2\">2×</option><option value=\"4\" selected>4×</option><option value=\"8\">8×</option><option value=\"20\">20×</option></select>\n</footer>\n<script>\nconst TL = ${data};\nconst EV = TL.events.slice().sort((a,b)=>a.t-b.t);\nconst span = Math.max(1, TL.t1 - TL.t0);\n// ── build the full tree (nodes known from spawn/root events) ──\nconst nodes = new Map();\nfor (const e of EV) {\n if (e.kind==='root' || e.kind==='spawn') {\n if (!nodes.has(e.id)) nodes.set(e.id, { id:e.id, parentId:e.parentId, label:e.label||e.id, runtime:e.runtime, depth:e.depth??0, tSpawn:e.t, children:[] });\n }\n}\nfor (const n of nodes.values()) { if (n.parentId && nodes.has(n.parentId)) nodes.get(n.parentId).children.push(n); }\nconst roots = [...nodes.values()].filter(n=>!n.parentId || !nodes.has(n.parentId));\n// ── tidy layout: post-order leaf slotting; x=avg(children), y=depth ──\nlet leaf=0; const VGAP=78, HGAP=120;\nfunction layout(n, d){ n.y = 40 + d*VGAP; if(!n.children.length){ n.x = 40 + (leaf++)*HGAP; } else { n.children.forEach(c=>layout(c,d+1)); n.x = (n.children[0].x + n.children[n.children.length-1].x)/2; } }\nroots.forEach(r=>layout(r,0));\nconst W = Math.max(900, 80 + leaf*HGAP), H = 80 + (Math.max(0,...[...nodes.values()].map(n=>n.depth||0))+1)*VGAP;\nconst svg = document.getElementById('svg'); svg.setAttribute('viewBox','0 0 '+W+' '+H); svg.setAttribute('width',W); svg.setAttribute('height',H);\nconst NS='http://www.w3.org/2000/svg';\nfunction el(t,a){ const x=document.createElementNS(NS,t); for(const k in a) x.setAttribute(k,a[k]); return x; }\n// edges\nconst edgeEls=new Map();\nfor (const n of nodes.values()) if (n.parentId && nodes.has(n.parentId)) { const p=nodes.get(n.parentId);\n const path=el('path',{class:'edge hidden', d:'M'+p.x+','+(p.y)+' C'+p.x+','+((p.y+n.y)/2)+' '+n.x+','+((p.y+n.y)/2)+' '+n.x+','+n.y}); svg.appendChild(path); edgeEls.set(n.id,path); }\n// nodes\nconst nodeEls=new Map();\nfor (const n of nodes.values()) { const g=el('g',{class:'node hidden'});\n const c=el('circle',{cx:n.x,cy:n.y,r:14,fill:'var(--dim)'});\n const t=el('text',{x:n.x,y:n.y-20,'text-anchor':'middle'}); t.textContent=n.label.slice(0,18);\n const s=el('text',{class:'sub',x:n.x,y:n.y+26,'text-anchor':'middle'}); s.textContent=n.runtime||'';\n g.appendChild(c); g.appendChild(t); g.appendChild(s); svg.appendChild(g); nodeEls.set(n.id,{g,c,s});\n g.addEventListener('mouseenter',()=>showDetail(n)); }\nfunction showDetail(n){ const st=state.get(n.id)||{}; document.getElementById('detail').innerHTML =\n '<div class=\"row\"><span class=\"k\">id</span> '+n.id+'</div>'+\n '<div class=\"row\"><span class=\"k\">label</span> '+esc(n.label)+'</div>'+\n '<div class=\"row\"><span class=\"k\">runtime</span> '+(n.runtime||'—')+'</div>'+\n '<div class=\"row\"><span class=\"k\">depth</span> '+(n.depth??0)+'</div>'+\n '<div class=\"row\"><span class=\"k\">status</span> '+(st.status||'—')+(st.valid===false?' (not delivered)':st.valid?' (delivered)':'')+'</div>'+\n '<div class=\"row\"><span class=\"k\">score</span> '+(st.score!=null?st.score.toFixed(2):'—')+'</div>'+\n (st.reason?'<div class=\"row\"><span class=\"k\">reason</span> '+esc(st.reason)+'</div>':'')+\n '<div class=\"row\"><span class=\"k\">tokens</span> '+(st.tokens||0)+'</div>'; }\nfunction esc(x){ return String(x).replace(/[<>&]/g,c=>({'<':'<','>':'>','&':'&'}[c])); }\n// ── playback ──\nconst state=new Map();\nfunction colorOf(st){ if(!st||st.status==='running'||!st.status) return 'var(--run)'; if(st.status==='down') return 'var(--down)'; return st.valid===false ? 'var(--undeliv)' : 'var(--done)'; }\nfunction applyTo(ms){ const clk=TL.t0+ms; state.clear();\n let deliv=0, tok=0, usd=0, shown=0;\n // reset visibility\n for (const [,v] of nodeEls){ v.g.classList.add('hidden'); }\n for (const [,p] of edgeEls){ p.classList.add('hidden'); }\n for (const e of EV){ if (e.t>clk) break;\n if (e.kind==='spawn'||e.kind==='root'){ const ne=nodeEls.get(e.id); if(ne){ ne.g.classList.remove('hidden'); shown++; const ed=edgeEls.get(e.id); if(ed) ed.classList.remove('hidden'); } state.set(e.id, Object.assign(state.get(e.id)||{}, {status:'running'})); }\n else if (e.kind==='settle'){ const st=Object.assign(state.get(e.id)||{}, {status:e.status, valid:e.valid, score:e.score, reason:e.reason, tokens:e.tokens, usd:e.usd}); state.set(e.id,st); }\n }\n for (const [id,st] of state){ const ne=nodeEls.get(id); if(ne) ne.c.setAttribute('fill', colorOf(st)); if(st.status&&st.status!=='running'){ if(st.status==='done'&&st.valid!==false) deliv++; tok+=st.tokens||0; usd+=st.usd||0; } }\n document.getElementById('clock').textContent=(ms/1000).toFixed(1)+'s';\n document.getElementById('cNodes').textContent=shown;\n document.getElementById('cDeliv').textContent=deliv;\n document.getElementById('cTok').textContent=tok;\n document.getElementById('cUsd').textContent=usd.toFixed(3);\n}\nconst scrub=document.getElementById('scrub'); let playing=false, raf=0, last=0;\nfunction setMs(ms){ ms=Math.max(0,Math.min(span,ms)); scrub.value=(ms/span*1000)|0; applyTo(ms); }\nscrub.addEventListener('input',()=>setMs(scrub.value/1000*span));\ndocument.getElementById('play').addEventListener('click',()=>{ playing=!playing; document.getElementById('play').textContent=playing?'⏸ pause':'▶ play'; if(playing){ last=performance.now(); if((scrub.value/1000*span)>=span) setMs(0); tick(); } });\ndocument.getElementById('restart').addEventListener('click',()=>{ setMs(0); });\nfunction tick(){ if(!playing) return; const now=performance.now(); const sp=+document.getElementById('speed').value; let ms=scrub.value/1000*span + (now-last)*sp; last=now; if(ms>=span){ ms=span; playing=false; document.getElementById('play').textContent='▶ play'; } setMs(ms); if(playing) raf=requestAnimationFrame(tick); }\nsetMs(0);\n</script></body></html>`\n}\n\n/** A readable label for a synthesized root node (the last path segment of a nested tree key). */\nfunction shortRoot(id: string): string {\n const seg = id.split('/').pop() ?? id\n return seg.length > 22 ? `…${seg.slice(-21)}` : seg\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(\n /[<>&\"]/g,\n (c) => ({ '<': '<', '>': '>', '&': '&', '\"': '"' })[c] ?? c,\n )\n}\n","/**\n * @experimental\n *\n * TOPOLOGY VIEW — the live recursive agent tree, folded from the ONE lifecycle stream\n * (`src/runtime-hooks.ts`). It is a pure projection: feed it the `RuntimeHookEvent`s that\n * `runLoop`, `toolLoop`, and the keystone `Scope` already emit, and it maintains the tree of\n * agents + renders it. Attach `view.hooks` to a `Supervisor`/`runLoop` and the tree updates live.\n *\n * Two node sources, ONE stream:\n * - an agent node is born from `agent.spawn` (id = `childId`, parent = the spawner) or the root\n * `agent.run` (id = `runId`); `agent.child`/`agent.run:after` settle it (status + score/reason).\n * - a STEP (`agent.{turn,tool_call,plan,decision}`) is not a node — it advances the step count of\n * the agent it belongs to (matched by `runId`/`parentId`).\n *\n * No I/O, no timers, no backend coupling — the same projection drives a CLI render, a TUI, or a\n * web tree. Rendering is deterministic given the event order (the stream is the source of truth).\n */\n\nimport type { RuntimeHookEvent, RuntimeHooks } from '../runtime-hooks'\n\nexport type TopologyStatus = 'running' | 'done' | 'down'\n\n/** One agent in the tree. A leaf never spawns; a driver's `childIds` is non-empty. */\nexport interface TopologyNode {\n readonly id: string\n /** Display label (spawn `label`, or the driver name on the root). */\n label: string\n /** Leaf runtime (`router`/`sandbox`/`cli`) when known. */\n runtime?: string\n /** Parent agent id; undefined ⇒ a root. */\n parentId?: string\n /** Recursion depth (root = 0). */\n depth: number\n status: TopologyStatus\n /** Count of in-agent steps (turns + tool calls + plan/decision rounds) folded so far. */\n steps: number\n /** Deployable score in [0,1] once settled `done`. */\n score?: number\n /** Failure reason once settled `down`. */\n reason?: string\n /** Children in spawn order. */\n readonly childIds: string[]\n}\n\nexport interface RenderOptions {\n /** Cap the rendered depth (deeper nodes collapse to a `… N more` line). Default: no cap. */\n readonly maxDepth?: number\n /** Drop the per-node detail suffix (steps/children/score) — labels only. Default: false. */\n readonly compact?: boolean\n}\n\nexport interface TopologyView {\n /** The `RuntimeHooks` sink — attach to `SupervisorOpts.hooks` / `runLoop` options. */\n readonly hooks: RuntimeHooks\n /** Fold one event into the tree (the same call `hooks.onEvent` makes — exposed for replay). */\n ingest(event: RuntimeHookEvent): void\n /** Every node, insertion order. */\n nodes(): TopologyNode[]\n /** Nodes with no in-tree parent (the run roots). */\n roots(): TopologyNode[]\n /** One node by id. */\n node(id: string): TopologyNode | undefined\n /** Render the tree as an aligned ASCII forest. */\n render(opts?: RenderOptions): string\n}\n\nconst stepTargets = new Set(['agent.turn', 'agent.tool_call', 'agent.plan', 'agent.decision'])\n\n/** Build a live topology view. Stateful — one per run (or per replay). */\nexport function createTopologyView(): TopologyView {\n // Insertion-ordered so render is stable and roots() reflects spawn order.\n const byId = new Map<string, TopologyNode>()\n\n const ensure = (id: string, seed: Partial<TopologyNode> = {}): TopologyNode => {\n const existing = byId.get(id)\n if (existing) return existing\n const node: TopologyNode = {\n id,\n label: seed.label ?? id,\n depth: seed.depth ?? 0,\n status: 'running',\n steps: 0,\n childIds: [],\n ...seed,\n }\n byId.set(id, node)\n return node\n }\n\n const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined)\n const num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined)\n\n const ingest = (event: RuntimeHookEvent): void => {\n const p = (event.payload ?? {}) as Record<string, unknown>\n\n if (event.target === 'agent.spawn' && event.phase === 'after') {\n const id = str(p.childId)\n if (!id) return\n const parent = event.parentId ? ensure(event.parentId) : undefined\n const node = ensure(id, {\n label: str(p.label) ?? id,\n runtime: str(p.runtime),\n parentId: event.parentId,\n depth: num(p.depth) ?? (parent ? parent.depth + 1 : 0),\n })\n if (parent && !parent.childIds.includes(id)) parent.childIds.push(id)\n node.status = 'running'\n return\n }\n\n if (event.target === 'agent.child' && event.phase === 'after') {\n const id = str(p.childId)\n if (!id) return\n const node = ensure(id, { parentId: event.parentId })\n node.status = str(p.status) === 'down' ? 'down' : 'done'\n node.score = num(p.score)\n node.reason = str(p.reason)\n return\n }\n\n if (event.target === 'agent.run') {\n const node = ensure(event.runId, {\n label: str(p.driver) ?? event.runId,\n parentId: event.parentId,\n })\n if (event.phase === 'before') node.status = 'running'\n else if (event.phase === 'error') node.status = 'down'\n else if (event.phase === 'after' && node.status === 'running') node.status = 'done'\n return\n }\n\n // A step advances the agent it belongs to: tool-loop/run-loop key the agent by `runId`,\n // a within-agent sub-event names it via `parentId`. Only count `after`/`event` phases so a\n // before/after pair is one step, not two. An unknown owner is dropped (no phantom node).\n if (stepTargets.has(event.target) && (event.phase === 'after' || event.phase === 'event')) {\n const owner =\n (event.parentId && byId.get(event.parentId)) || byId.get(event.runId) || undefined\n if (owner) owner.steps += 1\n }\n }\n\n const nodes = (): TopologyNode[] => [...byId.values()]\n const roots = (): TopologyNode[] =>\n nodes().filter((n) => n.parentId === undefined || !byId.has(n.parentId))\n\n return {\n hooks: { onEvent: (e) => ingest(e) },\n ingest,\n nodes,\n roots,\n node: (id) => byId.get(id),\n render: (opts) => renderTopologyTree({ roots: roots(), node: (id) => byId.get(id) }, opts),\n }\n}\n\nconst glyph: Record<TopologyStatus, string> = { running: '◐', done: '✓', down: '✗' }\n\n/** Render a forest of `TopologyNode`s to an aligned ASCII tree. Pure — given the same roots +\n * node lookup it returns the same string. Exposed so a caller can render a tree it folded\n * itself (e.g. from a journal replay) without the live view. */\nexport function renderTopologyTree(\n tree: { roots: TopologyNode[]; node: (id: string) => TopologyNode | undefined },\n opts: RenderOptions = {},\n): string {\n const lines: string[] = []\n\n const detail = (n: TopologyNode): string => {\n if (opts.compact) return ''\n const parts: string[] = []\n if (n.runtime) parts.push(n.runtime)\n if (n.steps > 0) parts.push(`${n.steps} ${n.steps === 1 ? 'step' : 'steps'}`)\n if (n.childIds.length > 0) parts.push(`${n.childIds.length} children`)\n if (n.status === 'done' && n.score !== undefined) parts.push(`score ${n.score.toFixed(2)}`)\n if (n.status === 'down' && n.reason) parts.push(`down: ${n.reason}`)\n return parts.length ? ` (${parts.join(' · ')})` : ''\n }\n\n const walk = (n: TopologyNode, prefix: string, isLast: boolean, depth: number): void => {\n const branch = depth === 0 ? '' : isLast ? '└─ ' : '├─ '\n lines.push(`${prefix}${branch}${glyph[n.status]} ${n.label}${detail(n)}`)\n\n if (opts.maxDepth !== undefined && depth >= opts.maxDepth && n.childIds.length > 0) {\n const childPrefix = prefix + (depth === 0 ? '' : isLast ? ' ' : '│ ')\n lines.push(`${childPrefix}└─ … ${n.childIds.length} more`)\n return\n }\n const childPrefix = prefix + (depth === 0 ? '' : isLast ? ' ' : '│ ')\n const kids = n.childIds.map((id) => tree.node(id)).filter((c): c is TopologyNode => !!c)\n kids.forEach((c, i) => {\n walk(c, childPrefix, i === kids.length - 1, depth + 1)\n })\n }\n\n tree.roots.forEach((r, i) => {\n walk(r, '', i === tree.roots.length - 1, 0)\n })\n return lines.join('\\n')\n}\n"],"mappings":"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","names":["childPrefix"]}
|
|
@@ -762,8 +762,8 @@ interface Driver<Task, Output, Decision> {
|
|
|
762
762
|
* the `loop.plan` event so a topology viewer can render the agent's chosen
|
|
763
763
|
* move + rationale (not just the inferred fan-width). Drivers whose topology
|
|
764
764
|
* is a pure function of count (refine/fanout-vote) omit it — the kernel
|
|
765
|
-
* infers `moveKind` from the planned-task count.
|
|
766
|
-
*
|
|
765
|
+
* infers `moveKind` from the planned-task count. A driver that authors its
|
|
766
|
+
* own topology returns its chosen move's kind + rationale here.
|
|
767
767
|
*/
|
|
768
768
|
describePlan?(): LoopPlanDescription | undefined;
|
|
769
769
|
/**
|
|
@@ -857,8 +857,8 @@ interface SandboxClient {
|
|
|
857
857
|
* loop end. When the driver's branch point is kernel-inferred (no
|
|
858
858
|
* `describePlan` — refine, fanout-vote), the kernel prunes boxes no future
|
|
859
859
|
* round can reach after each round, so the live set tracks the active frontier.
|
|
860
|
-
* When the driver authors its own branch point (`describePlan().parentIndex`
|
|
861
|
-
*
|
|
860
|
+
* When the driver authors its own branch point (`describePlan().parentIndex`),
|
|
861
|
+
* it may descend from any prior
|
|
862
862
|
* iteration, so no box is pruned and the live-box count rises to the total
|
|
863
863
|
* iterations across all rounds. Size `forkFanout` runs accordingly (CRIU forks
|
|
864
864
|
* are copy-on-write, but each is still a live box until loop end).
|
package/dist/workflow.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentProfile, CreateSandboxOptions, PromptOptions, TaskOptions, SandboxEvent } from '@tangle-network/sandbox';
|
|
2
|
-
import { S as SandboxClient, O as OutputAdapter, f as LoopSandboxPlacement, b as LoopResult } from './types-
|
|
3
|
-
import { R as RunLoopOptions } from './run-loop-
|
|
2
|
+
import { S as SandboxClient, O as OutputAdapter, f as LoopSandboxPlacement, b as LoopResult } from './types-C8rNlxfV.js';
|
|
3
|
+
import { R as RunLoopOptions } from './run-loop-DluzfJ2h.js';
|
|
4
4
|
import '@tangle-network/agent-eval';
|
|
5
5
|
import './runtime-hooks-C7JwKb9E.js';
|
|
6
6
|
|
package/dist/workflow.js
CHANGED
|
@@ -2,11 +2,13 @@ import {
|
|
|
2
2
|
createSandboxForSpec,
|
|
3
3
|
describeSandboxPlacement,
|
|
4
4
|
runLoop
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-JTH2FPCK.js";
|
|
6
|
+
import "./chunk-WIR4HOOJ.js";
|
|
6
7
|
import {
|
|
7
8
|
ValidationError,
|
|
8
9
|
extractLlmCallEvent
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-VLF5RHEQ.js";
|
|
11
|
+
import "./chunk-G3RGMA7C.js";
|
|
10
12
|
import "./chunk-DGUM43GV.js";
|
|
11
13
|
|
|
12
14
|
// src/workflow/agent-delegate.ts
|