@botbotgo/agent-harness 0.0.157 → 0.0.158
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 -0
- package/README.zh.md +19 -0
- package/dist/api.d.ts +1 -0
- package/dist/api.js +1 -0
- package/dist/contracts/runtime.d.ts +7 -0
- package/dist/flow/build-flow-graph.d.ts +2 -0
- package/dist/flow/build-flow-graph.js +737 -0
- package/dist/flow/export-mermaid.d.ts +2 -0
- package/dist/flow/export-mermaid.js +96 -0
- package/dist/flow/export-sequence-mermaid.d.ts +3 -0
- package/dist/flow/export-sequence-mermaid.js +169 -0
- package/dist/flow/index.d.ts +4 -0
- package/dist/flow/index.js +3 -0
- package/dist/flow/types.d.ts +75 -0
- package/dist/flow/types.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/adapter/tool/resolved-tool.d.ts +1 -1
- package/dist/runtime/harness/events/streaming.js +6 -0
- package/dist/runtime/harness/run/stream-run.js +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -450,10 +450,18 @@ const result = await run(runtime, {
|
|
|
450
450
|
onEvent(event) {
|
|
451
451
|
console.log(event.eventType, event.payload);
|
|
452
452
|
},
|
|
453
|
+
onUpstreamEvent(event) {
|
|
454
|
+
console.log("raw upstream event", event);
|
|
455
|
+
},
|
|
456
|
+
onUpstreamItem(item) {
|
|
457
|
+
console.log("upstream agent", item.agentId, item.event);
|
|
458
|
+
},
|
|
453
459
|
},
|
|
454
460
|
});
|
|
455
461
|
```
|
|
456
462
|
|
|
463
|
+
`onUpstreamEvent(...)` preserves the raw upstream event payload. `onUpstreamItem(...)` adds runtime correlation metadata such as `threadId`, `runId`, and the current `agentId`, which is useful for flow-graph capture and delegated sub-agent inspection.
|
|
464
|
+
|
|
457
465
|
`subscribe(...)` is the read-only observer surface over stored lifecycle events.
|
|
458
466
|
|
|
459
467
|
The runtime event stream includes:
|
|
@@ -563,8 +571,14 @@ Discovery rules:
|
|
|
563
571
|
- local tools are auto-discovered from `resources/tools/**/*.js|*.mjs|*.cjs` when they export `tool({...})`
|
|
564
572
|
- skills are auto-discovered from `resources/skills/**/SKILL.md`
|
|
565
573
|
|
|
574
|
+
Example workspaces:
|
|
575
|
+
|
|
576
|
+
- `examples/hello-skill-app/` keeps the smallest local tool + skill workspace
|
|
577
|
+
- `examples/runtime-flow-demo/` runs one real hosted-model request and exports a Mermaid flowchart from runtime plus upstream events
|
|
578
|
+
|
|
566
579
|
Workspace-local tool modules in `resources/tools/` should be exported with `tool({...})`.
|
|
567
580
|
Any other local module shape is not supported, and unsupported shapes are rejected at load time.
|
|
581
|
+
When local tools use Zod-authored schemas, keep the workspace or isolated `resources` package on `zod@^4` so raw-shape validators and runtime parsing stay on one supported major version.
|
|
568
582
|
|
|
569
583
|
Default wiring guidance:
|
|
570
584
|
|
|
@@ -872,3 +886,16 @@ Primary exports:
|
|
|
872
886
|
- `createToolMcpServer`
|
|
873
887
|
- `serveToolsOverStdio`
|
|
874
888
|
- `stop`
|
|
889
|
+
- `createUpstreamTimelineReducer`
|
|
890
|
+
- `buildFlowGraph`
|
|
891
|
+
- `exportFlowGraphToMermaid`
|
|
892
|
+
- `exportFlowGraphToSequenceMermaid`
|
|
893
|
+
|
|
894
|
+
Inspection helpers:
|
|
895
|
+
|
|
896
|
+
- `createUpstreamTimelineReducer()` reduces raw upstream model/tool/chain events into ordered step-like projections for inspection and visualization.
|
|
897
|
+
- `buildFlowGraph(...)` combines persisted runtime timeline items with optional upstream projections into a detailed flow graph utility shape, including best-effort delegated subagent transitions when raw upstream events are available.
|
|
898
|
+
- `exportFlowGraphToMermaid(...)` renders that inspection graph as Mermaid flowchart text. By default it emits the product view: agent and sub-agent delegation plus user-defined model, tool, and skill calls. Use `view: "debug"` to include runtime inspection detail.
|
|
899
|
+
- `exportFlowGraphToSequenceMermaid(...)` renders the same inspection graph as a Mermaid sequence diagram. By default it emits only user-defined participants and calls, while `view: "debug"` includes runtime participants and lifecycle messages.
|
|
900
|
+
|
|
901
|
+
These helpers are visualization and inspection utilities. They do not introduce a canonical harness-owned execution protocol.
|
package/README.zh.md
CHANGED
|
@@ -535,8 +535,14 @@ await stop(runtime);
|
|
|
535
535
|
- 本地工具会从 `resources/tools/**/*.js|*.mjs|*.cjs` 中自动发现,前提是模块导出 `tool({...})`
|
|
536
536
|
- skills 会从 `resources/skills/**/SKILL.md` 自动发现
|
|
537
537
|
|
|
538
|
+
示例工作区:
|
|
539
|
+
|
|
540
|
+
- `examples/hello-skill-app/` 保留最小的本地 tool + skill 工作区
|
|
541
|
+
- `examples/runtime-flow-demo/` 会跑一次真实 hosted model 请求,并把 runtime 与 upstream events 导出为 Mermaid flowchart
|
|
542
|
+
|
|
538
543
|
`resources/tools/` 下的工作区本地工具模块应统一用 `tool({...})` 导出。
|
|
539
544
|
不支持历史/兼容写法,任何不带该导出形式的工具模块都会在工作区加载时被拒绝。
|
|
545
|
+
若本地工具使用 Zod schema,请让工作区或隔离的 `resources` 包统一使用 `zod@^4`,避免 raw shape validator 与 runtime 解析落在不同 major 版本上。
|
|
540
546
|
|
|
541
547
|
主要有三层配置:
|
|
542
548
|
|
|
@@ -839,3 +845,16 @@ spec:
|
|
|
839
845
|
- `createToolMcpServer`
|
|
840
846
|
- `serveToolsOverStdio`
|
|
841
847
|
- `stop`
|
|
848
|
+
- `createUpstreamTimelineReducer`
|
|
849
|
+
- `buildFlowGraph`
|
|
850
|
+
- `exportFlowGraphToMermaid`
|
|
851
|
+
- `exportFlowGraphToSequenceMermaid`
|
|
852
|
+
|
|
853
|
+
Inspection 辅助工具:
|
|
854
|
+
|
|
855
|
+
- `createUpstreamTimelineReducer()` 可把上游 model/tool/chain 原始事件归约成有序的 step-like 投影,便于检查和可视化。
|
|
856
|
+
- `buildFlowGraph(...)` 可把持久化 runtime timeline 与可选的 upstream projections 组合成详细 flow graph utility 数据结构;当提供原始 upstream events 时,也会尽力补出 subagent delegation 转移。
|
|
857
|
+
- `exportFlowGraphToMermaid(...)` 可把该 inspection graph 导出为 Mermaid flowchart 文本。默认输出 product 视图,只保留 agent / sub-agent delegation 以及用户定义的 model、tool、skill 调用;传 `view: "debug"` 才会带上 runtime 检查细节。
|
|
858
|
+
- `exportFlowGraphToSequenceMermaid(...)` 可把同一份 inspection graph 导出为 Mermaid sequence diagram。默认只输出用户定义的参与者与调用;传 `view: "debug"` 才会包含 runtime participant 与生命周期消息。
|
|
859
|
+
|
|
860
|
+
这些 helper 只用于可视化与检查,不代表新的 harness 官方执行协议。
|
package/dist/api.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ToolMcpServerOptions } from "./mcp.js";
|
|
|
6
6
|
export { AgentHarnessAcpServer, createAcpServer } from "./acp.js";
|
|
7
7
|
export type { AcpApproval, AcpArtifact, AcpEventNotification, AcpJsonRpcError, AcpJsonRpcRequest, AcpJsonRpcResponse, AcpJsonRpcSuccess, AcpRequestRecord, AcpRunRequestParams, AcpServerCapabilities, AcpSessionRecord, } from "./acp.js";
|
|
8
8
|
export { AgentHarnessRuntime } from "./runtime/harness.js";
|
|
9
|
+
export { buildFlowGraph, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid } from "./flow/index.js";
|
|
9
10
|
export { createUpstreamTimelineReducer } from "./upstream-events.js";
|
|
10
11
|
export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, UpdateMemoryInput, } from "./contracts/types.js";
|
|
11
12
|
type PublicApprovalRecord = {
|
package/dist/api.js
CHANGED
|
@@ -3,6 +3,7 @@ import { normalizeMessageContent } from "./utils/message-content.js";
|
|
|
3
3
|
import { loadWorkspace } from "./workspace/compile.js";
|
|
4
4
|
export { AgentHarnessAcpServer, createAcpServer } from "./acp.js";
|
|
5
5
|
export { AgentHarnessRuntime } from "./runtime/harness.js";
|
|
6
|
+
export { buildFlowGraph, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid } from "./flow/index.js";
|
|
6
7
|
export { createUpstreamTimelineReducer } from "./upstream-events.js";
|
|
7
8
|
function toSessionSummary(summary) {
|
|
8
9
|
return {
|
|
@@ -335,9 +335,16 @@ export type RunResult = {
|
|
|
335
335
|
metadata?: Record<string, unknown>;
|
|
336
336
|
};
|
|
337
337
|
export type UpstreamRuntimeEvent = unknown;
|
|
338
|
+
export type UpstreamRuntimeEventItem = {
|
|
339
|
+
threadId: string;
|
|
340
|
+
runId: string;
|
|
341
|
+
agentId: string;
|
|
342
|
+
event: UpstreamRuntimeEvent;
|
|
343
|
+
};
|
|
338
344
|
export type RuntimeListeners = {
|
|
339
345
|
onEvent?: (event: HarnessEvent) => void | Promise<void>;
|
|
340
346
|
onUpstreamEvent?: (event: UpstreamRuntimeEvent) => void | Promise<void>;
|
|
347
|
+
onUpstreamItem?: (item: UpstreamRuntimeEventItem) => void | Promise<void>;
|
|
341
348
|
};
|
|
342
349
|
export type RunListeners = RuntimeListeners;
|
|
343
350
|
export type MessageContentPart = {
|
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import { projectRuntimeTimeline } from "../runtime/harness/events/timeline.js";
|
|
2
|
+
import { createUpstreamTimelineReducer } from "../upstream-events.js";
|
|
3
|
+
function asObject(value) {
|
|
4
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
5
|
+
}
|
|
6
|
+
function readString(value) {
|
|
7
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
8
|
+
}
|
|
9
|
+
function extractUpstreamEventEnvelope(value) {
|
|
10
|
+
const typed = asObject(value);
|
|
11
|
+
const nestedEvent = typed && Object.prototype.hasOwnProperty.call(typed, "event") ? typed.event : undefined;
|
|
12
|
+
const event = typed
|
|
13
|
+
&& nestedEvent
|
|
14
|
+
&& typeof nestedEvent === "object"
|
|
15
|
+
&& !Array.isArray(nestedEvent)
|
|
16
|
+
&& Object.prototype.hasOwnProperty.call(typed, "agentId")
|
|
17
|
+
? nestedEvent
|
|
18
|
+
: value;
|
|
19
|
+
const agentId = readString(typed?.agentId);
|
|
20
|
+
return { event, agentId: agentId ?? undefined };
|
|
21
|
+
}
|
|
22
|
+
function collectNestedAgentNames(value, names = new Set()) {
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
for (const item of value) {
|
|
25
|
+
collectNestedAgentNames(item, names);
|
|
26
|
+
}
|
|
27
|
+
return names;
|
|
28
|
+
}
|
|
29
|
+
const typed = asObject(value);
|
|
30
|
+
if (!typed) {
|
|
31
|
+
return names;
|
|
32
|
+
}
|
|
33
|
+
const idParts = Array.isArray(typed.id) ? typed.id.filter((item) => typeof item === "string") : [];
|
|
34
|
+
const kwargs = asObject(typed.kwargs);
|
|
35
|
+
const kwargsName = idParts.includes("AIMessage") ? readString(kwargs?.name) : null;
|
|
36
|
+
if (kwargsName && kwargsName !== "assistant") {
|
|
37
|
+
names.add(kwargsName);
|
|
38
|
+
}
|
|
39
|
+
for (const nested of Object.values(typed)) {
|
|
40
|
+
collectNestedAgentNames(nested, names);
|
|
41
|
+
}
|
|
42
|
+
return names;
|
|
43
|
+
}
|
|
44
|
+
function slugify(value) {
|
|
45
|
+
return value
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
48
|
+
.replace(/^-+|-+$/g, "")
|
|
49
|
+
.slice(0, 80) || "node";
|
|
50
|
+
}
|
|
51
|
+
function titleCase(value) {
|
|
52
|
+
return value
|
|
53
|
+
.replace(/[_-]+/g, " ")
|
|
54
|
+
.replace(/\s+/g, " ")
|
|
55
|
+
.trim()
|
|
56
|
+
.split(" ")
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
59
|
+
.join(" ");
|
|
60
|
+
}
|
|
61
|
+
function normalizeLabel(value) {
|
|
62
|
+
return value.replace(/\s+/g, " ").trim();
|
|
63
|
+
}
|
|
64
|
+
function readProjectionLabel(projection) {
|
|
65
|
+
if (projection.type === "step") {
|
|
66
|
+
return projection.step;
|
|
67
|
+
}
|
|
68
|
+
if (projection.type === "tool-result") {
|
|
69
|
+
return `Tool result ${titleCase(projection.toolName)}`;
|
|
70
|
+
}
|
|
71
|
+
return projection.text;
|
|
72
|
+
}
|
|
73
|
+
function deriveNodeKindFromProjection(projection) {
|
|
74
|
+
if (projection.type === "tool-result") {
|
|
75
|
+
return "result";
|
|
76
|
+
}
|
|
77
|
+
if (projection.type === "thinking") {
|
|
78
|
+
return "thinking";
|
|
79
|
+
}
|
|
80
|
+
return projection.category;
|
|
81
|
+
}
|
|
82
|
+
function deriveStatusFromProjection(projection) {
|
|
83
|
+
if (projection.type === "thinking" || projection.type === "tool-result") {
|
|
84
|
+
return "completed";
|
|
85
|
+
}
|
|
86
|
+
return projection.category === "approval" && projection.status === "started" ? "waiting" : projection.status;
|
|
87
|
+
}
|
|
88
|
+
function deriveRuntimeNode(input) {
|
|
89
|
+
if (input.eventType === "run.created") {
|
|
90
|
+
return {
|
|
91
|
+
kind: "run",
|
|
92
|
+
status: "started",
|
|
93
|
+
label: "Run created",
|
|
94
|
+
detail: input.payload,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (input.eventType === "run.queued") {
|
|
98
|
+
return {
|
|
99
|
+
kind: "queue",
|
|
100
|
+
status: "waiting",
|
|
101
|
+
label: "Queued",
|
|
102
|
+
detail: input.payload,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (input.eventType === "run.dequeued") {
|
|
106
|
+
return {
|
|
107
|
+
kind: "queue",
|
|
108
|
+
status: "completed",
|
|
109
|
+
label: "Dequeued",
|
|
110
|
+
detail: input.payload,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (input.eventType === "approval.requested") {
|
|
114
|
+
return {
|
|
115
|
+
kind: "approval",
|
|
116
|
+
status: "waiting",
|
|
117
|
+
label: "Approval requested",
|
|
118
|
+
detail: input.payload,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (input.eventType === "approval.resolved") {
|
|
122
|
+
return {
|
|
123
|
+
kind: "approval",
|
|
124
|
+
status: "resolved",
|
|
125
|
+
label: "Approval resolved",
|
|
126
|
+
detail: input.payload,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (input.eventType === "run.resumed") {
|
|
130
|
+
return {
|
|
131
|
+
kind: "recovery",
|
|
132
|
+
status: "completed",
|
|
133
|
+
label: "Run resumed",
|
|
134
|
+
detail: input.payload,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (input.eventType === "artifact.created") {
|
|
138
|
+
return {
|
|
139
|
+
kind: "artifact",
|
|
140
|
+
status: "completed",
|
|
141
|
+
label: "Artifact created",
|
|
142
|
+
detail: input.payload,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (input.eventType === "run.state.changed") {
|
|
146
|
+
const state = typeof input.payload.state === "string" ? input.payload.state : "";
|
|
147
|
+
if (state === "waiting_for_approval") {
|
|
148
|
+
return {
|
|
149
|
+
kind: "approval",
|
|
150
|
+
status: "waiting",
|
|
151
|
+
label: "Waiting for approval",
|
|
152
|
+
detail: input.payload,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (state === "failed") {
|
|
156
|
+
return {
|
|
157
|
+
kind: "run",
|
|
158
|
+
status: "failed",
|
|
159
|
+
label: "Run failed",
|
|
160
|
+
detail: input.payload,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (state === "completed") {
|
|
164
|
+
return {
|
|
165
|
+
kind: "run",
|
|
166
|
+
status: "completed",
|
|
167
|
+
label: "Run completed",
|
|
168
|
+
detail: input.payload,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
kind: "run",
|
|
173
|
+
status: "started",
|
|
174
|
+
label: state ? `Run ${state}` : "Run state changed",
|
|
175
|
+
detail: input.payload,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
kind: input.kind,
|
|
180
|
+
status: "completed",
|
|
181
|
+
label: input.eventType,
|
|
182
|
+
detail: input.payload,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function buildRuntimeNodes(timeline) {
|
|
186
|
+
const nodes = [];
|
|
187
|
+
const edges = [];
|
|
188
|
+
const groups = [];
|
|
189
|
+
let previousNode = null;
|
|
190
|
+
let segmentOrdinal = 0;
|
|
191
|
+
let approvalOrdinal = 0;
|
|
192
|
+
let activeExecutionGroup = null;
|
|
193
|
+
let activeApprovalGroup = null;
|
|
194
|
+
for (const item of timeline) {
|
|
195
|
+
const mapped = deriveRuntimeNode(item);
|
|
196
|
+
const node = {
|
|
197
|
+
id: `rt:${item.runId}:${item.sequence}:${slugify(item.eventType)}`,
|
|
198
|
+
layer: "runtime",
|
|
199
|
+
kind: mapped.kind,
|
|
200
|
+
label: mapped.label,
|
|
201
|
+
status: mapped.status,
|
|
202
|
+
threadId: item.threadId,
|
|
203
|
+
runId: item.runId,
|
|
204
|
+
startedAt: item.timestamp,
|
|
205
|
+
endedAt: item.timestamp,
|
|
206
|
+
sequenceStart: item.sequence,
|
|
207
|
+
sequenceEnd: item.sequence,
|
|
208
|
+
sourceEventIds: [item.eventId],
|
|
209
|
+
detail: { ...mapped.detail, eventType: item.eventType, source: item.source },
|
|
210
|
+
};
|
|
211
|
+
nodes.push(node);
|
|
212
|
+
if (previousNode) {
|
|
213
|
+
const kind = item.eventType === "approval.resolved"
|
|
214
|
+
? "approval"
|
|
215
|
+
: item.eventType === "run.resumed"
|
|
216
|
+
? "resume"
|
|
217
|
+
: "sequence";
|
|
218
|
+
edges.push({
|
|
219
|
+
id: `edge:${previousNode.id}->${node.id}`,
|
|
220
|
+
from: previousNode.id,
|
|
221
|
+
to: node.id,
|
|
222
|
+
kind,
|
|
223
|
+
...(kind === "approval" ? { label: typeof item.payload.decision === "string" ? item.payload.decision : "resolved" } : {}),
|
|
224
|
+
sourceEventIds: [previousNode.sourceEventIds[0], item.eventId],
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
previousNode = node;
|
|
228
|
+
if (item.eventType === "run.created" || item.eventType === "run.dequeued" || item.eventType === "run.resumed") {
|
|
229
|
+
segmentOrdinal += 1;
|
|
230
|
+
activeExecutionGroup = {
|
|
231
|
+
id: `group:${item.runId}:segment:${segmentOrdinal}`,
|
|
232
|
+
kind: "segment",
|
|
233
|
+
label: item.eventType === "run.created"
|
|
234
|
+
? `Execution segment ${segmentOrdinal}`
|
|
235
|
+
: item.eventType === "run.resumed"
|
|
236
|
+
? `Resumed segment ${segmentOrdinal}`
|
|
237
|
+
: `Dequeued segment ${segmentOrdinal}`,
|
|
238
|
+
nodeIds: [node.id],
|
|
239
|
+
startedAt: item.timestamp,
|
|
240
|
+
anchorNodeId: node.id,
|
|
241
|
+
};
|
|
242
|
+
groups.push(activeExecutionGroup);
|
|
243
|
+
activeApprovalGroup = null;
|
|
244
|
+
node.groupId = activeExecutionGroup.id;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (item.eventType === "approval.requested") {
|
|
248
|
+
approvalOrdinal += 1;
|
|
249
|
+
activeApprovalGroup = {
|
|
250
|
+
id: `group:${item.runId}:approval:${approvalOrdinal}`,
|
|
251
|
+
kind: "approval-window",
|
|
252
|
+
label: `Approval window ${approvalOrdinal}`,
|
|
253
|
+
nodeIds: [node.id],
|
|
254
|
+
startedAt: item.timestamp,
|
|
255
|
+
anchorNodeId: node.id,
|
|
256
|
+
};
|
|
257
|
+
groups.push(activeApprovalGroup);
|
|
258
|
+
node.groupId = activeApprovalGroup.id;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (item.eventType === "approval.resolved") {
|
|
262
|
+
if (!activeApprovalGroup) {
|
|
263
|
+
approvalOrdinal += 1;
|
|
264
|
+
activeApprovalGroup = {
|
|
265
|
+
id: `group:${item.runId}:approval:${approvalOrdinal}`,
|
|
266
|
+
kind: "approval-window",
|
|
267
|
+
label: `Approval window ${approvalOrdinal}`,
|
|
268
|
+
nodeIds: [],
|
|
269
|
+
anchorNodeId: node.id,
|
|
270
|
+
};
|
|
271
|
+
groups.push(activeApprovalGroup);
|
|
272
|
+
}
|
|
273
|
+
activeApprovalGroup.nodeIds.push(node.id);
|
|
274
|
+
activeApprovalGroup.endedAt = item.timestamp;
|
|
275
|
+
node.groupId = activeApprovalGroup.id;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (activeApprovalGroup && !activeApprovalGroup.endedAt) {
|
|
279
|
+
activeApprovalGroup.nodeIds.push(node.id);
|
|
280
|
+
node.groupId = activeApprovalGroup.id;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (activeExecutionGroup) {
|
|
284
|
+
activeExecutionGroup.nodeIds.push(node.id);
|
|
285
|
+
activeExecutionGroup.endedAt = item.timestamp;
|
|
286
|
+
node.groupId = activeExecutionGroup.id;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { nodes, edges, groups };
|
|
290
|
+
}
|
|
291
|
+
function normalizeAttemptLabel(kind, projection) {
|
|
292
|
+
if (projection.type === "step") {
|
|
293
|
+
const label = normalizeLabel(projection.step);
|
|
294
|
+
if (label) {
|
|
295
|
+
return label;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (projection.type === "tool-result") {
|
|
299
|
+
return `Tool ${titleCase(projection.toolName)}`;
|
|
300
|
+
}
|
|
301
|
+
if (projection.type === "thinking") {
|
|
302
|
+
return "Model reasoning";
|
|
303
|
+
}
|
|
304
|
+
return titleCase(kind);
|
|
305
|
+
}
|
|
306
|
+
function semanticNameFromLabel(kind, label) {
|
|
307
|
+
const normalized = normalizeLabel(label)
|
|
308
|
+
.replace(/^Calling LLM\s+/i, "")
|
|
309
|
+
.replace(/^Completed LLM\s+/i, "")
|
|
310
|
+
.replace(/^Calling tool\s+/i, "")
|
|
311
|
+
.replace(/^Completed tool\s+/i, "")
|
|
312
|
+
.replace(/^Tool\s+/i, "")
|
|
313
|
+
.replace(/\s+failed$/i, "")
|
|
314
|
+
.replace(/^Calling skill\s+/i, "")
|
|
315
|
+
.replace(/^Completed skill\s+/i, "")
|
|
316
|
+
.replace(/^Accessing memory\s+/i, "")
|
|
317
|
+
.replace(/^Completed memory\s+/i, "")
|
|
318
|
+
.replace(/^Running\s+/i, "")
|
|
319
|
+
.replace(/^Completed\s+/i, "");
|
|
320
|
+
return `${kind}:${normalized.toLowerCase()}`;
|
|
321
|
+
}
|
|
322
|
+
function normalizeToolIdentity(value) {
|
|
323
|
+
return value
|
|
324
|
+
.toLowerCase()
|
|
325
|
+
.replace(/[_-]+/g, " ")
|
|
326
|
+
.replace(/\s+/g, " ")
|
|
327
|
+
.trim();
|
|
328
|
+
}
|
|
329
|
+
function buildAttemptKey(kind, label, groupId) {
|
|
330
|
+
return `${semanticNameFromLabel(kind, label)}:${groupId ?? "ungrouped"}`;
|
|
331
|
+
}
|
|
332
|
+
function deriveInitialAgentId(input, runtimeTimeline) {
|
|
333
|
+
const candidate = runtimeTimeline.find((item) => {
|
|
334
|
+
const payload = asObject(item.payload);
|
|
335
|
+
return typeof payload?.agentId === "string" && payload.agentId.trim().length > 0;
|
|
336
|
+
});
|
|
337
|
+
const payload = candidate ? asObject(candidate.payload) : null;
|
|
338
|
+
const metadataAgentId = typeof input.metadata?.currentAgentId === "string" ? input.metadata.currentAgentId : null;
|
|
339
|
+
if (metadataAgentId && metadataAgentId.trim().length > 0) {
|
|
340
|
+
return metadataAgentId.trim();
|
|
341
|
+
}
|
|
342
|
+
if (payload?.agentId && typeof payload.agentId === "string" && payload.agentId.trim().length > 0) {
|
|
343
|
+
return payload.agentId.trim();
|
|
344
|
+
}
|
|
345
|
+
return "agent";
|
|
346
|
+
}
|
|
347
|
+
function extractDelegatedAgentId(event) {
|
|
348
|
+
const typed = asObject(event);
|
|
349
|
+
if (!typed) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
const eventName = typeof typed.event === "string" ? typed.event : "";
|
|
353
|
+
const toolName = typeof typed.name === "string" ? typed.name : "";
|
|
354
|
+
if (toolName !== "task" || (eventName !== "on_tool_start" && !(eventName === "on_chain_start" && typed.run_type === "tool"))) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const data = asObject(typed.data);
|
|
358
|
+
const input = asObject(data?.input);
|
|
359
|
+
const subagentType = typeof input?.subagent_type === "string"
|
|
360
|
+
? input.subagent_type
|
|
361
|
+
: typeof input?.subagentType === "string"
|
|
362
|
+
? input.subagentType
|
|
363
|
+
: null;
|
|
364
|
+
return subagentType && subagentType.trim().length > 0 ? subagentType.trim() : null;
|
|
365
|
+
}
|
|
366
|
+
function extractAgentFromNestedMessages(event, currentAgentId) {
|
|
367
|
+
const typed = asObject(event);
|
|
368
|
+
if (!typed) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const data = asObject(typed.data);
|
|
372
|
+
const input = asObject(data?.input);
|
|
373
|
+
const output = asObject(data?.output);
|
|
374
|
+
const names = new Set();
|
|
375
|
+
collectNestedAgentNames(input?.messages, names);
|
|
376
|
+
collectNestedAgentNames(output?.messages, names);
|
|
377
|
+
return [...names].find((name) => name !== currentAgentId) ?? null;
|
|
378
|
+
}
|
|
379
|
+
function convertUpstreamEventsWithAgents(upstreamEvents, initialAgentId) {
|
|
380
|
+
const reducer = createUpstreamTimelineReducer();
|
|
381
|
+
const projections = [];
|
|
382
|
+
const delegationNodes = [];
|
|
383
|
+
let currentAgentId = initialAgentId;
|
|
384
|
+
let ordinal = 0;
|
|
385
|
+
upstreamEvents.forEach((event, index) => {
|
|
386
|
+
const sourceEventId = `upstream:${index + 1}`;
|
|
387
|
+
const envelope = extractUpstreamEventEnvelope(event);
|
|
388
|
+
if (envelope.agentId && envelope.agentId !== currentAgentId) {
|
|
389
|
+
currentAgentId = envelope.agentId;
|
|
390
|
+
}
|
|
391
|
+
const nestedAgentId = extractAgentFromNestedMessages(envelope.event, currentAgentId);
|
|
392
|
+
if (nestedAgentId && nestedAgentId !== currentAgentId) {
|
|
393
|
+
ordinal += 1;
|
|
394
|
+
delegationNodes.push({
|
|
395
|
+
id: `delegate:${slugify(currentAgentId)}:${slugify(nestedAgentId)}:${ordinal}`,
|
|
396
|
+
layer: "execution",
|
|
397
|
+
kind: "agent",
|
|
398
|
+
label: `Delegate to ${titleCase(nestedAgentId)}`,
|
|
399
|
+
status: "completed",
|
|
400
|
+
threadId: "",
|
|
401
|
+
runId: "",
|
|
402
|
+
agentId: nestedAgentId,
|
|
403
|
+
sourceEventIds: [sourceEventId],
|
|
404
|
+
detail: {
|
|
405
|
+
fromAgentId: currentAgentId,
|
|
406
|
+
toAgentId: nestedAgentId,
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
currentAgentId = nestedAgentId;
|
|
410
|
+
}
|
|
411
|
+
const emitted = reducer.consume(envelope.event);
|
|
412
|
+
for (const projection of emitted) {
|
|
413
|
+
projections.push({
|
|
414
|
+
projection,
|
|
415
|
+
agentId: currentAgentId,
|
|
416
|
+
sourceEventId,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
const delegatedAgentId = extractDelegatedAgentId(envelope.event);
|
|
420
|
+
if (!delegatedAgentId || delegatedAgentId === currentAgentId) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
ordinal += 1;
|
|
424
|
+
delegationNodes.push({
|
|
425
|
+
id: `delegate:${slugify(initialAgentId)}:${slugify(delegatedAgentId)}:${ordinal}`,
|
|
426
|
+
layer: "execution",
|
|
427
|
+
kind: "agent",
|
|
428
|
+
label: `Delegate to ${titleCase(delegatedAgentId)}`,
|
|
429
|
+
status: "completed",
|
|
430
|
+
threadId: "",
|
|
431
|
+
runId: "",
|
|
432
|
+
agentId: delegatedAgentId,
|
|
433
|
+
sourceEventIds: [sourceEventId],
|
|
434
|
+
detail: {
|
|
435
|
+
fromAgentId: currentAgentId,
|
|
436
|
+
toAgentId: delegatedAgentId,
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
currentAgentId = delegatedAgentId;
|
|
440
|
+
});
|
|
441
|
+
return { projections, delegationNodes };
|
|
442
|
+
}
|
|
443
|
+
function selectInitialGroup(groups) {
|
|
444
|
+
return groups.find((group) => group.kind === "segment") ?? groups[0] ?? null;
|
|
445
|
+
}
|
|
446
|
+
function buildAttempts(projectionRecords, delegationNodes, runtimeGroups, threadId, runId) {
|
|
447
|
+
const nodes = [];
|
|
448
|
+
const edges = [];
|
|
449
|
+
const groups = [];
|
|
450
|
+
const attempts = [];
|
|
451
|
+
const activeAttemptsByKey = new Map();
|
|
452
|
+
const toolAttemptsByName = new Map();
|
|
453
|
+
const attemptSetGroups = new Map();
|
|
454
|
+
let currentGroup = selectInitialGroup(runtimeGroups);
|
|
455
|
+
let awaitingResumeAfterApproval = false;
|
|
456
|
+
let previousAttempt = null;
|
|
457
|
+
let ordinal = 0;
|
|
458
|
+
let pendingDelegationNode = null;
|
|
459
|
+
let delegationIndex = 0;
|
|
460
|
+
const segmentGroups = runtimeGroups.filter((group) => group.kind === "segment");
|
|
461
|
+
let nextSegmentIndex = currentGroup && currentGroup.kind === "segment"
|
|
462
|
+
? Math.max(segmentGroups.findIndex((group) => group.id === currentGroup?.id), 0)
|
|
463
|
+
: 0;
|
|
464
|
+
function promoteToNextSegment() {
|
|
465
|
+
if (nextSegmentIndex + 1 < segmentGroups.length) {
|
|
466
|
+
nextSegmentIndex += 1;
|
|
467
|
+
currentGroup = segmentGroups[nextSegmentIndex] ?? currentGroup;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function createAttempt(kind, label, projection, agentId) {
|
|
471
|
+
ordinal += 1;
|
|
472
|
+
const attempt = {
|
|
473
|
+
id: `attempt:${runId}:${slugify(kind)}:${slugify(label)}:${ordinal}`,
|
|
474
|
+
kind,
|
|
475
|
+
label,
|
|
476
|
+
status: deriveStatusFromProjection(projection),
|
|
477
|
+
agentId,
|
|
478
|
+
groupId: currentGroup?.id,
|
|
479
|
+
sourceEventIds: [],
|
|
480
|
+
attemptKey: buildAttemptKey(kind, label, currentGroup?.id),
|
|
481
|
+
detail: {},
|
|
482
|
+
projectionKeys: [],
|
|
483
|
+
...(projection.type === "tool-result" ? { toolName: projection.toolName } : {}),
|
|
484
|
+
};
|
|
485
|
+
attempts.push(attempt);
|
|
486
|
+
if (kind === "tool" || (projection.type === "tool-result" && projection.toolName)) {
|
|
487
|
+
const toolName = projection.type === "tool-result"
|
|
488
|
+
? normalizeToolIdentity(projection.toolName)
|
|
489
|
+
: normalizeToolIdentity(semanticNameFromLabel("tool", label).replace(/^tool:/, ""));
|
|
490
|
+
const existing = toolAttemptsByName.get(toolName) ?? [];
|
|
491
|
+
if (existing.length > 0) {
|
|
492
|
+
const prior = existing[existing.length - 1];
|
|
493
|
+
const edgeKind = prior.status === "failed" ? "retry" : "fallback";
|
|
494
|
+
edges.push({
|
|
495
|
+
id: `edge:${prior.id}->${attempt.id}`,
|
|
496
|
+
from: prior.id,
|
|
497
|
+
to: attempt.id,
|
|
498
|
+
kind: edgeKind,
|
|
499
|
+
sourceEventIds: [],
|
|
500
|
+
confidence: 0.55,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
existing.push(attempt);
|
|
504
|
+
toolAttemptsByName.set(toolName, existing);
|
|
505
|
+
}
|
|
506
|
+
return attempt;
|
|
507
|
+
}
|
|
508
|
+
for (const record of projectionRecords) {
|
|
509
|
+
const projection = record.projection;
|
|
510
|
+
if (previousAttempt
|
|
511
|
+
&& previousAttempt.agentId
|
|
512
|
+
&& record.agentId !== previousAttempt.agentId
|
|
513
|
+
&& delegationIndex < delegationNodes.length) {
|
|
514
|
+
const sourceNode = delegationNodes[delegationIndex];
|
|
515
|
+
delegationIndex += 1;
|
|
516
|
+
pendingDelegationNode = {
|
|
517
|
+
...sourceNode,
|
|
518
|
+
threadId,
|
|
519
|
+
runId,
|
|
520
|
+
groupId: currentGroup?.id,
|
|
521
|
+
};
|
|
522
|
+
nodes.push(pendingDelegationNode);
|
|
523
|
+
edges.push({
|
|
524
|
+
id: `edge:${previousAttempt.id}->${pendingDelegationNode.id}`,
|
|
525
|
+
from: previousAttempt.id,
|
|
526
|
+
to: pendingDelegationNode.id,
|
|
527
|
+
kind: "spawn",
|
|
528
|
+
label: "delegate",
|
|
529
|
+
sourceEventIds: [...pendingDelegationNode.sourceEventIds],
|
|
530
|
+
confidence: 0.9,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (projection.type === "thinking") {
|
|
534
|
+
ordinal += 1;
|
|
535
|
+
const detailNode = {
|
|
536
|
+
id: `detail:${runId}:thinking:${ordinal}`,
|
|
537
|
+
layer: "detail",
|
|
538
|
+
kind: "thinking",
|
|
539
|
+
label: "Model reasoning",
|
|
540
|
+
status: "completed",
|
|
541
|
+
threadId,
|
|
542
|
+
runId,
|
|
543
|
+
groupId: currentGroup?.id,
|
|
544
|
+
sourceEventIds: [],
|
|
545
|
+
detail: { text: projection.text },
|
|
546
|
+
};
|
|
547
|
+
nodes.push(detailNode);
|
|
548
|
+
if (previousAttempt) {
|
|
549
|
+
edges.push({
|
|
550
|
+
id: `edge:${previousAttempt.id}->${detailNode.id}`,
|
|
551
|
+
from: previousAttempt.id,
|
|
552
|
+
to: detailNode.id,
|
|
553
|
+
kind: "result",
|
|
554
|
+
sourceEventIds: [],
|
|
555
|
+
confidence: 0.6,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const kind = deriveNodeKindFromProjection(projection);
|
|
561
|
+
const label = normalizeAttemptLabel(kind, projection);
|
|
562
|
+
if (kind === "approval") {
|
|
563
|
+
const activeApproval = runtimeGroups.find((group) => group.kind === "approval-window");
|
|
564
|
+
if (activeApproval) {
|
|
565
|
+
currentGroup = activeApproval;
|
|
566
|
+
}
|
|
567
|
+
awaitingResumeAfterApproval = true;
|
|
568
|
+
}
|
|
569
|
+
else if (awaitingResumeAfterApproval && currentGroup?.kind === "approval-window") {
|
|
570
|
+
promoteToNextSegment();
|
|
571
|
+
awaitingResumeAfterApproval = false;
|
|
572
|
+
}
|
|
573
|
+
const attemptKey = buildAttemptKey(kind, label, currentGroup?.id);
|
|
574
|
+
let attempt;
|
|
575
|
+
if (projection.type === "step" && projection.status === "started") {
|
|
576
|
+
attempt = createAttempt(kind, label, projection, record.agentId);
|
|
577
|
+
activeAttemptsByKey.set(attemptKey, attempt);
|
|
578
|
+
}
|
|
579
|
+
else if (projection.type === "tool-result") {
|
|
580
|
+
const toolName = normalizeToolIdentity(projection.toolName);
|
|
581
|
+
const existing = toolAttemptsByName.get(toolName) ?? [];
|
|
582
|
+
attempt = [...existing].reverse().find((candidate) => candidate.groupId === currentGroup?.id) ?? existing[existing.length - 1];
|
|
583
|
+
if (!attempt) {
|
|
584
|
+
attempt = createAttempt("tool", `Calling tool ${titleCase(toolName)}`, projection, record.agentId);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
attempt = activeAttemptsByKey.get(attemptKey);
|
|
589
|
+
if (!attempt) {
|
|
590
|
+
attempt = createAttempt(kind, label, projection, record.agentId);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
attempt.agentId = attempt.agentId ?? record.agentId;
|
|
594
|
+
attempt.projectionKeys.push(projection.key);
|
|
595
|
+
attempt.sourceEventIds.push(record.sourceEventId);
|
|
596
|
+
if (projection.type === "step") {
|
|
597
|
+
attempt.status = deriveStatusFromProjection(projection);
|
|
598
|
+
attempt.detail.category = projection.category;
|
|
599
|
+
attempt.detail.step = projection.step;
|
|
600
|
+
}
|
|
601
|
+
else if (projection.type === "tool-result") {
|
|
602
|
+
attempt.status = projection.isError ? "failed" : "completed";
|
|
603
|
+
attempt.toolName = projection.toolName;
|
|
604
|
+
attempt.detail.toolName = projection.toolName;
|
|
605
|
+
attempt.detail.result = projection.output;
|
|
606
|
+
attempt.detail.resultIsError = projection.isError === true;
|
|
607
|
+
if (projection.isError) {
|
|
608
|
+
activeAttemptsByKey.delete(buildAttemptKey("tool", attempt.label, attempt.groupId));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (!attemptSetGroups.has(`${kind}:${currentGroup?.id ?? "ungrouped"}`)) {
|
|
612
|
+
attemptSetGroups.set(`${kind}:${currentGroup?.id ?? "ungrouped"}`, {
|
|
613
|
+
id: `group:${runId}:attempt-set:${slugify(kind)}:${attemptSetGroups.size + 1}`,
|
|
614
|
+
kind: "attempt-set",
|
|
615
|
+
label: `${titleCase(kind)} attempts`,
|
|
616
|
+
nodeIds: [],
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
const attemptGroup = attemptSetGroups.get(`${kind}:${currentGroup?.id ?? "ungrouped"}`);
|
|
620
|
+
if (!attemptGroup.nodeIds.includes(attempt.id)) {
|
|
621
|
+
attemptGroup.nodeIds.push(attempt.id);
|
|
622
|
+
}
|
|
623
|
+
if (pendingDelegationNode && pendingDelegationNode.id !== attempt.id) {
|
|
624
|
+
edges.push({
|
|
625
|
+
id: `edge:${pendingDelegationNode.id}->${attempt.id}`,
|
|
626
|
+
from: pendingDelegationNode.id,
|
|
627
|
+
to: attempt.id,
|
|
628
|
+
kind: "spawn",
|
|
629
|
+
sourceEventIds: [...pendingDelegationNode.sourceEventIds, ...attempt.sourceEventIds],
|
|
630
|
+
confidence: 0.9,
|
|
631
|
+
});
|
|
632
|
+
pendingDelegationNode = null;
|
|
633
|
+
}
|
|
634
|
+
else if (previousAttempt && previousAttempt.id !== attempt.id) {
|
|
635
|
+
edges.push({
|
|
636
|
+
id: `edge:${previousAttempt.id}->${attempt.id}`,
|
|
637
|
+
from: previousAttempt.id,
|
|
638
|
+
to: attempt.id,
|
|
639
|
+
kind: kind === "approval" ? "approval" : "sequence",
|
|
640
|
+
sourceEventIds: [],
|
|
641
|
+
confidence: 0.7,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
else if (!previousAttempt && currentGroup?.anchorNodeId) {
|
|
645
|
+
edges.push({
|
|
646
|
+
id: `edge:${currentGroup.anchorNodeId}->${attempt.id}`,
|
|
647
|
+
from: currentGroup.anchorNodeId,
|
|
648
|
+
to: attempt.id,
|
|
649
|
+
kind: "contains",
|
|
650
|
+
sourceEventIds: [],
|
|
651
|
+
confidence: 0.8,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
previousAttempt = attempt;
|
|
655
|
+
if (projection.type === "step" && projection.status !== "started") {
|
|
656
|
+
activeAttemptsByKey.delete(attemptKey);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
for (const attempt of attempts) {
|
|
660
|
+
nodes.push({
|
|
661
|
+
id: attempt.id,
|
|
662
|
+
layer: "attempt",
|
|
663
|
+
kind: attempt.kind,
|
|
664
|
+
label: attempt.label,
|
|
665
|
+
status: attempt.status,
|
|
666
|
+
threadId,
|
|
667
|
+
runId,
|
|
668
|
+
agentId: attempt.agentId,
|
|
669
|
+
groupId: attempt.groupId,
|
|
670
|
+
sequenceStart: attempt.sequenceStart,
|
|
671
|
+
sequenceEnd: attempt.sequenceEnd,
|
|
672
|
+
startedAt: attempt.startedAt,
|
|
673
|
+
endedAt: attempt.endedAt,
|
|
674
|
+
sourceEventIds: [...new Set(attempt.sourceEventIds)],
|
|
675
|
+
detail: { ...attempt.detail, projectionKeys: attempt.projectionKeys },
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
groups.push(...attemptSetGroups.values());
|
|
679
|
+
return { nodes, edges, groups };
|
|
680
|
+
}
|
|
681
|
+
function resolveContext(input, runtimeTimeline, projections) {
|
|
682
|
+
const timelineHead = runtimeTimeline[0];
|
|
683
|
+
if (input.threadId && input.runId) {
|
|
684
|
+
return { threadId: input.threadId, runId: input.runId };
|
|
685
|
+
}
|
|
686
|
+
if (timelineHead) {
|
|
687
|
+
return {
|
|
688
|
+
threadId: input.threadId ?? timelineHead.threadId,
|
|
689
|
+
runId: input.runId ?? timelineHead.runId,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
const firstProjection = projections[0];
|
|
693
|
+
if (!input.threadId || !input.runId) {
|
|
694
|
+
throw new Error(`buildFlowGraph requires threadId and runId when runtime timeline data is absent${firstProjection ? "" : " or empty"}.`);
|
|
695
|
+
}
|
|
696
|
+
return { threadId: input.threadId, runId: input.runId };
|
|
697
|
+
}
|
|
698
|
+
export function buildFlowGraph(input) {
|
|
699
|
+
const runtimeTimeline = input.runtimeTimeline
|
|
700
|
+
?? (input.runtimeEvents ? projectRuntimeTimeline(input.runtimeEvents, {
|
|
701
|
+
...(input.threadId ? { threadId: input.threadId } : {}),
|
|
702
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
703
|
+
}) : []);
|
|
704
|
+
const initialAgentId = deriveInitialAgentId(input, runtimeTimeline);
|
|
705
|
+
const upstreamContext = input.upstreamEvents
|
|
706
|
+
? convertUpstreamEventsWithAgents(input.upstreamEvents, initialAgentId)
|
|
707
|
+
: { projections: [], delegationNodes: [] };
|
|
708
|
+
const upstreamProjections = input.upstreamProjections ?? upstreamContext.projections.map((record) => record.projection);
|
|
709
|
+
const { threadId, runId } = resolveContext(input, runtimeTimeline, upstreamProjections);
|
|
710
|
+
const { nodes: runtimeNodes, edges: runtimeEdges, groups: runtimeGroups } = buildRuntimeNodes(runtimeTimeline);
|
|
711
|
+
for (const node of runtimeNodes) {
|
|
712
|
+
node.agentId = typeof node.detail.agentId === "string" ? node.detail.agentId : initialAgentId;
|
|
713
|
+
}
|
|
714
|
+
const projectionRecords = input.upstreamProjections
|
|
715
|
+
? input.upstreamProjections.map((projection, index) => ({
|
|
716
|
+
projection,
|
|
717
|
+
agentId: initialAgentId,
|
|
718
|
+
sourceEventId: "key" in projection ? projection.key : `projection:${index + 1}`,
|
|
719
|
+
}))
|
|
720
|
+
: upstreamContext.projections;
|
|
721
|
+
const { nodes: attemptNodes, edges: attemptEdges, groups: attemptGroups } = buildAttempts(projectionRecords, upstreamContext.delegationNodes, runtimeGroups, threadId, runId);
|
|
722
|
+
return {
|
|
723
|
+
graphId: `${threadId}/${runId}`,
|
|
724
|
+
scope: input.scope ?? "run",
|
|
725
|
+
threadId,
|
|
726
|
+
runId,
|
|
727
|
+
nodes: [...runtimeNodes, ...attemptNodes],
|
|
728
|
+
edges: [...runtimeEdges, ...attemptEdges],
|
|
729
|
+
groups: [...runtimeGroups, ...attemptGroups],
|
|
730
|
+
metadata: {
|
|
731
|
+
runtimeTimelineCount: runtimeTimeline.length,
|
|
732
|
+
upstreamProjectionCount: upstreamProjections.length,
|
|
733
|
+
delegationCount: upstreamContext.delegationNodes.length,
|
|
734
|
+
...(input.metadata ?? {}),
|
|
735
|
+
},
|
|
736
|
+
};
|
|
737
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const PRODUCT_VIEW_KINDS = new Set(["agent", "llm", "tool", "skill"]);
|
|
2
|
+
function sanitizeMermaidId(value) {
|
|
3
|
+
return value.replace(/[^A-Za-z0-9_]/g, "_");
|
|
4
|
+
}
|
|
5
|
+
function escapeMermaidLabel(value) {
|
|
6
|
+
return value.replace(/"/g, '\\"');
|
|
7
|
+
}
|
|
8
|
+
function renderNode(node, mermaidId) {
|
|
9
|
+
const label = escapeMermaidLabel(`${node.label} [${node.status}]`);
|
|
10
|
+
switch (node.kind) {
|
|
11
|
+
case "approval":
|
|
12
|
+
return ` ${mermaidId}{"${label}"}`;
|
|
13
|
+
case "queue":
|
|
14
|
+
return ` ${mermaidId}[/"${label}"/]`;
|
|
15
|
+
case "recovery":
|
|
16
|
+
return ` ${mermaidId}{{"${label}"}}`;
|
|
17
|
+
case "artifact":
|
|
18
|
+
return ` ${mermaidId}[("${label}")]`;
|
|
19
|
+
case "skill":
|
|
20
|
+
return ` ${mermaidId}[["${label}"]]`;
|
|
21
|
+
case "memory":
|
|
22
|
+
return ` ${mermaidId}[("${label}")]`;
|
|
23
|
+
default:
|
|
24
|
+
return ` ${mermaidId}["${label}"]`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function renderEdge(edge, fromId, toId) {
|
|
28
|
+
const connector = edge.kind === "retry" || edge.kind === "fallback" ? "-.->" : edge.kind === "resume" ? "==>" : "-->";
|
|
29
|
+
const label = edge.label ?? edge.condition ?? (edge.kind !== "sequence" && edge.kind !== "contains" ? edge.kind : "");
|
|
30
|
+
return label
|
|
31
|
+
? ` ${fromId} ${connector}|"${escapeMermaidLabel(label)}"| ${toId}`
|
|
32
|
+
: ` ${fromId} ${connector} ${toId}`;
|
|
33
|
+
}
|
|
34
|
+
export function exportFlowGraphToMermaid(graph, options = {}) {
|
|
35
|
+
const direction = options.direction ?? "TD";
|
|
36
|
+
const view = options.view ?? "product";
|
|
37
|
+
const includedKinds = options.includeKinds
|
|
38
|
+
? new Set(options.includeKinds)
|
|
39
|
+
: view === "debug"
|
|
40
|
+
? null
|
|
41
|
+
: PRODUCT_VIEW_KINDS;
|
|
42
|
+
const includedEdgeKinds = options.includeEdgeKinds ? new Set(options.includeEdgeKinds) : null;
|
|
43
|
+
const includeGroups = options.includeGroups ?? true;
|
|
44
|
+
const includeDetails = options.includeDetails ?? false;
|
|
45
|
+
const nodes = graph.nodes.filter((node) => {
|
|
46
|
+
if (!includeDetails && node.layer === "detail") {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return includedKinds ? includedKinds.has(node.kind) : true;
|
|
50
|
+
});
|
|
51
|
+
const nodeIdSet = new Set(nodes.map((node) => node.id));
|
|
52
|
+
const edges = graph.edges.filter((edge) => {
|
|
53
|
+
if (!nodeIdSet.has(edge.from) || !nodeIdSet.has(edge.to)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return includedEdgeKinds ? includedEdgeKinds.has(edge.kind) : true;
|
|
57
|
+
});
|
|
58
|
+
const lines = [`flowchart ${direction}`];
|
|
59
|
+
const mermaidIdByNodeId = new Map();
|
|
60
|
+
for (const node of nodes) {
|
|
61
|
+
mermaidIdByNodeId.set(node.id, sanitizeMermaidId(node.id));
|
|
62
|
+
}
|
|
63
|
+
if (includeGroups) {
|
|
64
|
+
const groupedNodeIds = new Set();
|
|
65
|
+
for (const group of graph.groups) {
|
|
66
|
+
const groupNodes = group.nodeIds.filter((nodeId) => nodeIdSet.has(nodeId));
|
|
67
|
+
if (groupNodes.length === 0) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
lines.push(` subgraph ${sanitizeMermaidId(group.id)}["${escapeMermaidLabel(group.label)}"]`);
|
|
71
|
+
for (const nodeId of groupNodes) {
|
|
72
|
+
groupedNodeIds.add(nodeId);
|
|
73
|
+
const node = nodes.find((candidate) => candidate.id === nodeId);
|
|
74
|
+
if (!node) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
lines.push(renderNode(node, mermaidIdByNodeId.get(node.id)));
|
|
78
|
+
}
|
|
79
|
+
lines.push(" end");
|
|
80
|
+
}
|
|
81
|
+
for (const node of nodes) {
|
|
82
|
+
if (!groupedNodeIds.has(node.id)) {
|
|
83
|
+
lines.push(renderNode(node, mermaidIdByNodeId.get(node.id)));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
for (const node of nodes) {
|
|
89
|
+
lines.push(renderNode(node, mermaidIdByNodeId.get(node.id)));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const edge of edges) {
|
|
93
|
+
lines.push(renderEdge(edge, mermaidIdByNodeId.get(edge.from), mermaidIdByNodeId.get(edge.to)));
|
|
94
|
+
}
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const PRODUCT_VIEW_KINDS = new Set(["agent", "llm", "tool", "skill"]);
|
|
2
|
+
function sanitizeAlias(value) {
|
|
3
|
+
return value.replace(/[^A-Za-z0-9_]/g, "_");
|
|
4
|
+
}
|
|
5
|
+
function escapeLabel(value) {
|
|
6
|
+
return value.replace(/"/g, '\\"');
|
|
7
|
+
}
|
|
8
|
+
function selectNodes(graph, options) {
|
|
9
|
+
const includedKinds = options.includeKinds
|
|
10
|
+
? new Set(options.includeKinds)
|
|
11
|
+
: options.view === "debug"
|
|
12
|
+
? null
|
|
13
|
+
: PRODUCT_VIEW_KINDS;
|
|
14
|
+
return graph.nodes.filter((node) => {
|
|
15
|
+
if (node.layer === "detail") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (includedKinds) {
|
|
19
|
+
return includedKinds.has(node.kind);
|
|
20
|
+
}
|
|
21
|
+
return node.kind === "run"
|
|
22
|
+
|| node.kind === "agent"
|
|
23
|
+
|| node.kind === "approval"
|
|
24
|
+
|| node.kind === "recovery"
|
|
25
|
+
|| node.kind === "llm"
|
|
26
|
+
|| node.kind === "tool"
|
|
27
|
+
|| node.kind === "skill"
|
|
28
|
+
|| node.kind === "artifact";
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function getParticipantsForNode(node) {
|
|
32
|
+
const runtimeParticipant = { id: "runtime", alias: "Runtime", label: "Runtime" };
|
|
33
|
+
const agentParticipant = {
|
|
34
|
+
id: `agent:${node.agentId ?? "agent"}`,
|
|
35
|
+
alias: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`),
|
|
36
|
+
label: `Agent${node.agentId ? `:${node.agentId}` : ""}`,
|
|
37
|
+
};
|
|
38
|
+
if (node.kind === "run") {
|
|
39
|
+
return [
|
|
40
|
+
{ id: "user", alias: "User", label: "User" },
|
|
41
|
+
runtimeParticipant,
|
|
42
|
+
agentParticipant,
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
if (node.kind === "agent") {
|
|
46
|
+
const fromAgentId = typeof node.detail.fromAgentId === "string" ? node.detail.fromAgentId : "agent";
|
|
47
|
+
return [
|
|
48
|
+
{
|
|
49
|
+
id: `agent:${fromAgentId}`,
|
|
50
|
+
alias: sanitizeAlias(`Agent_${fromAgentId}`),
|
|
51
|
+
label: `Agent:${fromAgentId}`,
|
|
52
|
+
},
|
|
53
|
+
agentParticipant,
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
if (node.kind === "approval") {
|
|
57
|
+
return [
|
|
58
|
+
runtimeParticipant,
|
|
59
|
+
{ id: "operator", alias: "Operator", label: "Operator" },
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
if (node.kind === "llm") {
|
|
63
|
+
return [
|
|
64
|
+
agentParticipant,
|
|
65
|
+
{ id: "model", alias: "Model", label: "Model" },
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
if (node.kind === "tool") {
|
|
69
|
+
return [
|
|
70
|
+
agentParticipant,
|
|
71
|
+
{
|
|
72
|
+
id: `tool:${node.label}`,
|
|
73
|
+
alias: sanitizeAlias(`Tool_${node.label}`),
|
|
74
|
+
label: `Tool:${node.label.replace(/\s+\[[^\]]+\]$/, "")}`,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
if (node.kind === "skill") {
|
|
79
|
+
return [
|
|
80
|
+
agentParticipant,
|
|
81
|
+
{ id: "skill", alias: "Skill", label: "Skill" },
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
if (node.kind === "recovery") {
|
|
85
|
+
return [
|
|
86
|
+
runtimeParticipant,
|
|
87
|
+
agentParticipant,
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
if (node.kind === "artifact") {
|
|
91
|
+
return [
|
|
92
|
+
runtimeParticipant,
|
|
93
|
+
{ id: "artifact", alias: "ArtifactStore", label: "ArtifactStore" },
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
return [runtimeParticipant, agentParticipant];
|
|
97
|
+
}
|
|
98
|
+
function renderArrowForNode(node) {
|
|
99
|
+
if (node.kind === "run" && node.status === "started") {
|
|
100
|
+
return { from: "User", to: "Runtime", text: node.label };
|
|
101
|
+
}
|
|
102
|
+
if (node.kind === "run" && (node.status === "completed" || node.status === "failed")) {
|
|
103
|
+
return { from: "Runtime", to: "User", text: node.label };
|
|
104
|
+
}
|
|
105
|
+
if (node.kind === "agent") {
|
|
106
|
+
const fromAgentId = typeof node.detail.fromAgentId === "string" ? node.detail.fromAgentId : "agent";
|
|
107
|
+
return {
|
|
108
|
+
from: sanitizeAlias(`Agent_${fromAgentId}`),
|
|
109
|
+
to: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`),
|
|
110
|
+
text: node.label,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (node.kind === "approval" && node.status === "waiting") {
|
|
114
|
+
return { from: "Runtime", to: "Operator", text: node.label };
|
|
115
|
+
}
|
|
116
|
+
if (node.kind === "approval" && node.status === "resolved") {
|
|
117
|
+
return { from: "Operator", to: "Runtime", text: node.label };
|
|
118
|
+
}
|
|
119
|
+
if (node.kind === "recovery") {
|
|
120
|
+
return { from: "Runtime", to: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`), text: node.label };
|
|
121
|
+
}
|
|
122
|
+
if (node.kind === "llm") {
|
|
123
|
+
return {
|
|
124
|
+
from: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`),
|
|
125
|
+
to: "Model",
|
|
126
|
+
text: node.label,
|
|
127
|
+
dashed: node.status === "completed" || node.status === "failed",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (node.kind === "tool") {
|
|
131
|
+
return {
|
|
132
|
+
from: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`),
|
|
133
|
+
to: sanitizeAlias(`Tool_${node.label}`),
|
|
134
|
+
text: node.label,
|
|
135
|
+
dashed: node.status === "completed" || node.status === "failed",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (node.kind === "skill") {
|
|
139
|
+
return {
|
|
140
|
+
from: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`),
|
|
141
|
+
to: "Skill",
|
|
142
|
+
text: node.label,
|
|
143
|
+
dashed: node.status === "completed" || node.status === "failed",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (node.kind === "artifact") {
|
|
147
|
+
return { from: "Runtime", to: "ArtifactStore", text: node.label };
|
|
148
|
+
}
|
|
149
|
+
return { from: "Runtime", to: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`), text: node.label };
|
|
150
|
+
}
|
|
151
|
+
export function exportFlowGraphToSequenceMermaid(graph, options = {}) {
|
|
152
|
+
const nodes = selectNodes(graph, options);
|
|
153
|
+
const lines = ["sequenceDiagram"];
|
|
154
|
+
const participants = new Map();
|
|
155
|
+
for (const node of nodes) {
|
|
156
|
+
for (const participant of getParticipantsForNode(node)) {
|
|
157
|
+
participants.set(participant.id, participant);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const participant of participants.values()) {
|
|
161
|
+
lines.push(` participant ${participant.alias} as ${escapeLabel(participant.label)}`);
|
|
162
|
+
}
|
|
163
|
+
for (const node of nodes) {
|
|
164
|
+
const arrow = renderArrowForNode(node);
|
|
165
|
+
const connector = arrow.dashed ? "-->>" : "->>";
|
|
166
|
+
lines.push(` ${arrow.from}${connector}${arrow.to}: ${escapeLabel(node.label)}`);
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { buildFlowGraph } from "./build-flow-graph.js";
|
|
2
|
+
export { exportFlowGraphToMermaid } from "./export-mermaid.js";
|
|
3
|
+
export { exportFlowGraphToSequenceMermaid } from "./export-sequence-mermaid.js";
|
|
4
|
+
export type { BuildFlowGraphInput, FlowEdge, FlowEdgeKind, FlowGraph, FlowGraphMermaidOptions, FlowGraphSequenceMermaidOptions, FlowGroup, FlowGroupKind, FlowNode, FlowNodeKind, FlowNodeLayer, FlowNodeStatus, } from "./types.js";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { HarnessEvent, RuntimeTimelineItem } from "../contracts/types.js";
|
|
2
|
+
import type { UpstreamTimelineProjection } from "../upstream-events.js";
|
|
3
|
+
export type FlowNodeLayer = "runtime" | "execution" | "attempt" | "detail";
|
|
4
|
+
export type FlowNodeKind = "run" | "agent" | "queue" | "approval" | "recovery" | "artifact" | "llm" | "tool" | "skill" | "memory" | "chain" | "result" | "thinking" | "other";
|
|
5
|
+
export type FlowNodeStatus = "pending" | "started" | "completed" | "failed" | "waiting" | "resolved" | "skipped";
|
|
6
|
+
export type FlowEdgeKind = "sequence" | "contains" | "approval" | "resume" | "retry" | "fallback" | "result" | "spawn";
|
|
7
|
+
export type FlowGroupKind = "segment" | "approval-window" | "attempt-set" | "agent-scope";
|
|
8
|
+
export type FlowNode = {
|
|
9
|
+
id: string;
|
|
10
|
+
layer: FlowNodeLayer;
|
|
11
|
+
kind: FlowNodeKind;
|
|
12
|
+
label: string;
|
|
13
|
+
status: FlowNodeStatus;
|
|
14
|
+
threadId: string;
|
|
15
|
+
runId: string;
|
|
16
|
+
agentId?: string;
|
|
17
|
+
startedAt?: string;
|
|
18
|
+
endedAt?: string;
|
|
19
|
+
sequenceStart?: number;
|
|
20
|
+
sequenceEnd?: number;
|
|
21
|
+
parentNodeId?: string;
|
|
22
|
+
groupId?: string;
|
|
23
|
+
sourceEventIds: string[];
|
|
24
|
+
detail: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
export type FlowEdge = {
|
|
27
|
+
id: string;
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
kind: FlowEdgeKind;
|
|
31
|
+
label?: string;
|
|
32
|
+
condition?: string;
|
|
33
|
+
sourceEventIds: string[];
|
|
34
|
+
confidence?: number;
|
|
35
|
+
};
|
|
36
|
+
export type FlowGroup = {
|
|
37
|
+
id: string;
|
|
38
|
+
kind: FlowGroupKind;
|
|
39
|
+
label: string;
|
|
40
|
+
nodeIds: string[];
|
|
41
|
+
startedAt?: string;
|
|
42
|
+
endedAt?: string;
|
|
43
|
+
};
|
|
44
|
+
export type FlowGraph = {
|
|
45
|
+
graphId: string;
|
|
46
|
+
scope: "run" | "thread";
|
|
47
|
+
threadId: string;
|
|
48
|
+
runId: string;
|
|
49
|
+
nodes: FlowNode[];
|
|
50
|
+
edges: FlowEdge[];
|
|
51
|
+
groups: FlowGroup[];
|
|
52
|
+
metadata: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
export type BuildFlowGraphInput = {
|
|
55
|
+
threadId?: string;
|
|
56
|
+
runId?: string;
|
|
57
|
+
scope?: FlowGraph["scope"];
|
|
58
|
+
runtimeEvents?: readonly HarnessEvent[];
|
|
59
|
+
runtimeTimeline?: readonly RuntimeTimelineItem[];
|
|
60
|
+
upstreamEvents?: readonly unknown[];
|
|
61
|
+
upstreamProjections?: readonly UpstreamTimelineProjection[];
|
|
62
|
+
metadata?: Record<string, unknown>;
|
|
63
|
+
};
|
|
64
|
+
export type FlowGraphMermaidOptions = {
|
|
65
|
+
view?: "product" | "debug";
|
|
66
|
+
direction?: "TD" | "LR";
|
|
67
|
+
includeGroups?: boolean;
|
|
68
|
+
includeDetails?: boolean;
|
|
69
|
+
includeKinds?: readonly FlowNodeKind[];
|
|
70
|
+
includeEdgeKinds?: readonly FlowEdgeKind[];
|
|
71
|
+
};
|
|
72
|
+
export type FlowGraphSequenceMermaidOptions = {
|
|
73
|
+
view?: "product" | "debug";
|
|
74
|
+
includeKinds?: readonly FlowNodeKind[];
|
|
75
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRun, createAgentHarness, createAcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
|
|
1
|
+
export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
|
|
2
2
|
export type { AcpApproval, AcpArtifact, AcpEventNotification, AcpJsonRpcError, AcpJsonRpcRequest, AcpJsonRpcResponse, AcpJsonRpcSuccess, AcpRequestRecord, AcpRunRequestParams, AcpServerCapabilities, AcpSessionRecord, } from "./acp.js";
|
|
3
3
|
export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, NormalizeUserChatInputOptions, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, UpdateMemoryInput, UserChatInput, UserChatMessage, } from "./api.js";
|
|
4
|
+
export type { BuildFlowGraphInput, FlowEdge, FlowEdgeKind, FlowGraph, FlowGraphMermaidOptions, FlowGraphSequenceMermaidOptions, FlowGroup, FlowGroupKind, FlowNode, FlowNodeKind, FlowNodeLayer, FlowNodeStatus, } from "./flow/index.js";
|
|
4
5
|
export type { ToolMcpServerOptions } from "./mcp.js";
|
|
5
6
|
export { tool } from "./tools.js";
|
|
6
7
|
export type { UpstreamTimelineProjection, UpstreamTimelineReducer } from "./upstream-events.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRun, createAgentHarness, createAcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
|
|
1
|
+
export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
|
|
2
2
|
export { tool } from "./tools.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.157";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.157";
|
|
@@ -14,5 +14,5 @@ export declare function wrapResolvedToolWithModelFacingName<T>(resolvedTool: T,
|
|
|
14
14
|
export declare function hasCallableToolHandler(value: unknown): value is ResolvedToolLike;
|
|
15
15
|
export declare function normalizeResolvedToolSchema(resolvedTool: ResolvedToolLike): Record<string, unknown> | {
|
|
16
16
|
parse?: (input: unknown) => unknown;
|
|
17
|
-
} | z.ZodObject<{},
|
|
17
|
+
} | z.ZodObject<{}, z.core.$loose> | undefined;
|
|
18
18
|
export declare function asStructuredExecutableTool(resolvedTool: unknown, modelFacingName: string, description: string): unknown;
|
|
@@ -42,6 +42,12 @@ export async function dispatchRunListeners(stream, listeners, options) {
|
|
|
42
42
|
}
|
|
43
43
|
if (item.type === "upstream-event") {
|
|
44
44
|
await options.notifyListener(listeners.onUpstreamEvent, item.event);
|
|
45
|
+
await options.notifyListener(listeners.onUpstreamItem, {
|
|
46
|
+
threadId: item.threadId,
|
|
47
|
+
runId: item.runId,
|
|
48
|
+
agentId: item.agentId,
|
|
49
|
+
event: item.event,
|
|
50
|
+
});
|
|
45
51
|
continue;
|
|
46
52
|
}
|
|
47
53
|
if (item.type === "result") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botbotgo/agent-harness",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.158",
|
|
4
4
|
"description": "Workspace runtime for multi-agent applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"mem0ai": "^2.4.4",
|
|
60
60
|
"mustache": "^4.2.0",
|
|
61
61
|
"yaml": "^2.8.1",
|
|
62
|
-
"zod": "^3.
|
|
62
|
+
"zod": "^4.3.6"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
|
|
@@ -75,5 +75,9 @@
|
|
|
75
75
|
"@types/node": "^24.6.0",
|
|
76
76
|
"typescript": "^5.9.3",
|
|
77
77
|
"vitest": "^3.2.4"
|
|
78
|
+
},
|
|
79
|
+
"overrides": {
|
|
80
|
+
"@browserbasehq/stagehand": "3.2.0",
|
|
81
|
+
"openai": "6.33.0"
|
|
78
82
|
}
|
|
79
83
|
}
|