@botbotgo/agent-harness 0.0.250 → 0.0.252

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.
Files changed (48) hide show
  1. package/README.md +13 -14
  2. package/README.zh.md +11 -12
  3. package/dist/api.d.ts +13 -6
  4. package/dist/api.js +70 -6
  5. package/dist/config/agents/direct.yaml +3 -3
  6. package/dist/config/agents/orchestra.yaml +3 -3
  7. package/dist/config/catalogs/stores.yaml +3 -9
  8. package/dist/config/runtime/workspace.yaml +1 -2
  9. package/dist/contracts/runtime.d.ts +9 -14
  10. package/dist/flow/build-flow-graph.js +198 -67
  11. package/dist/flow/export-mermaid.js +314 -4
  12. package/dist/flow/export-sequence-mermaid.js +149 -2
  13. package/dist/flow/types.d.ts +11 -1
  14. package/dist/index.d.ts +2 -3
  15. package/dist/index.js +1 -1
  16. package/dist/package-version.d.ts +1 -1
  17. package/dist/package-version.js +1 -1
  18. package/dist/persistence/file-store.d.ts +3 -2
  19. package/dist/persistence/file-store.js +34 -8
  20. package/dist/persistence/sqlite-store.d.ts +2 -2
  21. package/dist/persistence/sqlite-store.js +64 -11
  22. package/dist/persistence/types.d.ts +3 -3
  23. package/dist/protocol/a2a/http.js +2 -4
  24. package/dist/resource/isolation.js +30 -2
  25. package/dist/resource/resource-impl.js +1 -4
  26. package/dist/runtime/harness/events/streaming.js +8 -8
  27. package/dist/runtime/harness/run/inspection.d.ts +2 -0
  28. package/dist/runtime/harness/run/inspection.js +91 -46
  29. package/dist/runtime/harness/run/stream-run.d.ts +2 -2
  30. package/dist/runtime/harness/run/stream-run.js +34 -23
  31. package/dist/runtime/harness/run/surface-semantics.d.ts +14 -0
  32. package/dist/runtime/harness/run/surface-semantics.js +106 -0
  33. package/dist/runtime/harness/run/thread-records.js +2 -34
  34. package/dist/runtime/harness/system/store.d.ts +6 -4
  35. package/dist/runtime/harness/system/store.js +76 -42
  36. package/dist/runtime/harness.js +7 -7
  37. package/dist/runtime/maintenance/checkpoint-maintenance.js +4 -119
  38. package/dist/runtime/maintenance/index.d.ts +0 -1
  39. package/dist/runtime/maintenance/index.js +0 -1
  40. package/dist/runtime/support/runtime-env.d.ts +1 -0
  41. package/dist/runtime/support/runtime-env.js +5 -0
  42. package/dist/runtime/support/runtime-factories.js +2 -42
  43. package/dist/upstream-events.js +14 -0
  44. package/package.json +1 -3
  45. package/dist/runtime/maintenance/sqlite-maintained-checkpoint-saver.d.ts +0 -9
  46. package/dist/runtime/maintenance/sqlite-maintained-checkpoint-saver.js +0 -39
  47. package/dist/runtime/support/sqlite-drivers.d.ts +0 -12
  48. package/dist/runtime/support/sqlite-drivers.js +0 -24
@@ -1,6 +1,5 @@
1
1
  import path from "node:path";
2
- import { rm } from "node:fs/promises";
3
- import { readdir } from "node:fs/promises";
2
+ import { appendFile, readFile, readdir, rm, writeFile } from "node:fs/promises";
4
3
  import { ensureDir, fileExists, readJson, writeJson } from "../utils/fs.js";
5
4
  function nowIso() {
6
5
  return new Date(Date.now()).toISOString();
@@ -25,6 +24,9 @@ export class FilePersistence {
25
24
  runControlPath(runId) {
26
25
  return path.join(this.runRoot, "indexes", "run-control", `${runId}.json`);
27
26
  }
27
+ traceItemsPath(threadId, runId) {
28
+ return path.join(this.runDir(threadId, runId), "trace-items.ndjson");
29
+ }
28
30
  async initialize() {
29
31
  await Promise.all([
30
32
  "indexes/threads",
@@ -93,12 +95,13 @@ export class FilePersistence {
93
95
  currentAgentId: input.currentAgentId ?? input.agentId,
94
96
  delegationChain: input.delegationChain ?? [input.currentAgentId ?? input.agentId],
95
97
  runtimeSnapshot: input.runtimeSnapshot ?? null,
96
- upstreamEvents: [],
98
+ traceItems: [],
97
99
  };
98
100
  await Promise.all([
99
101
  writeJson(path.join(runDir, "meta.json"), meta),
100
102
  writeJson(path.join(runDir, "lifecycle.json"), lifecycle),
101
103
  writeJson(path.join(runDir, "inspection.json"), inspection),
104
+ writeFile(this.traceItemsPath(input.threadId, input.runId), "", "utf8"),
102
105
  writeJson(path.join(runDir, "checkpoint-ref.json"), {
103
106
  threadId: input.threadId,
104
107
  runId: input.runId,
@@ -401,14 +404,17 @@ export class FilePersistence {
401
404
  const inspection = await readJson(path.join(this.runDir(threadId, runId), "inspection.json"));
402
405
  return {
403
406
  ...inspection,
404
- upstreamEvents: Array.isArray(inspection.upstreamEvents) ? inspection.upstreamEvents : [],
407
+ traceItems: (await this.listRunTraceItems(threadId, runId))
408
+ ?? (Array.isArray(inspection.traceItems)
409
+ ? inspection.traceItems
410
+ : Array.isArray(inspection.upstreamEvents)
411
+ ? inspection.upstreamEvents
412
+ : []),
405
413
  };
406
414
  }
407
415
  async updateRunInspection(threadId, runId, patch) {
408
416
  const inspectionPath = path.join(this.runDir(threadId, runId), "inspection.json");
409
- const current = await this.getRunInspection(threadId, runId);
410
- const nextUpstreamEvents = patch.upstreamEvents
411
- ?? (patch.appendUpstreamEvent === undefined ? current.upstreamEvents : [...current.upstreamEvents, patch.appendUpstreamEvent]);
417
+ const current = await readJson(inspectionPath);
412
418
  await writeJson(inspectionPath, {
413
419
  ...current,
414
420
  ...(patch.endedAt !== undefined ? { endedAt: patch.endedAt } : {}),
@@ -416,9 +422,29 @@ export class FilePersistence {
416
422
  ...(patch.currentAgentId !== undefined ? { currentAgentId: patch.currentAgentId } : {}),
417
423
  ...(patch.delegationChain ? { delegationChain: patch.delegationChain } : {}),
418
424
  ...(patch.runtimeSnapshot !== undefined ? { runtimeSnapshot: patch.runtimeSnapshot } : {}),
419
- upstreamEvents: nextUpstreamEvents,
425
+ traceItems: Array.isArray(current.traceItems) ? current.traceItems : [],
420
426
  });
421
427
  }
428
+ async appendRunTraceItem(threadId, runId, item) {
429
+ await appendFile(this.traceItemsPath(threadId, runId), `${JSON.stringify(item)}\n`, "utf8");
430
+ }
431
+ async listRunTraceItems(threadId, runId) {
432
+ const traceItemsPath = this.traceItemsPath(threadId, runId);
433
+ if (await fileExists(traceItemsPath)) {
434
+ const contents = await readFile(traceItemsPath, "utf8");
435
+ return contents
436
+ .split("\n")
437
+ .map((line) => line.trim())
438
+ .filter((line) => line.length > 0)
439
+ .map((line) => JSON.parse(line));
440
+ }
441
+ const inspection = await readJson(path.join(this.runDir(threadId, runId), "inspection.json"));
442
+ return Array.isArray(inspection.traceItems)
443
+ ? inspection.traceItems
444
+ : Array.isArray(inspection.upstreamEvents)
445
+ ? inspection.upstreamEvents
446
+ : [];
447
+ }
422
448
  async deleteThread(threadId) {
423
449
  const threadDir = this.threadDir(threadId);
424
450
  const threadIndexPath = this.threadIndexPath(threadId);
@@ -82,9 +82,9 @@ export declare class SqlitePersistence implements RuntimePersistence {
82
82
  currentAgentId?: string | null;
83
83
  delegationChain?: string[];
84
84
  runtimeSnapshot?: RunSummary["runtimeSnapshot"] | null;
85
- upstreamEvents?: unknown[];
86
- appendUpstreamEvent?: unknown;
87
85
  }): Promise<void>;
86
+ appendRunTraceItem(threadId: string, runId: string, item: unknown): Promise<void>;
87
+ listRunTraceItems(threadId: string, runId: string): Promise<unknown[]>;
88
88
  deleteThread(threadId: string): Promise<boolean>;
89
89
  saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
90
90
  getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
@@ -4,7 +4,7 @@ import { createClient } from "@libsql/client";
4
4
  import { fileExists, readJson, writeJson } from "../utils/fs.js";
5
5
  import { SqliteRunContextStore } from "./sqlite-run-context-store.js";
6
6
  import { SqliteRunQueueStore } from "./sqlite-run-queue-store.js";
7
- const RUNTIME_SQLITE_SCHEMA_VERSION = 5;
7
+ const RUNTIME_SQLITE_SCHEMA_VERSION = 6;
8
8
  const RUNTIME_SQLITE_SCHEMA_FAMILY = "agent-harness-runtime";
9
9
  function asRow(value) {
10
10
  return value;
@@ -196,11 +196,24 @@ export class SqlitePersistence {
196
196
  FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
197
197
  FOREIGN KEY (run_id) REFERENCES runs(run_id)
198
198
  )
199
+ `);
200
+ await this.rawExecute(`
201
+ CREATE TABLE IF NOT EXISTS run_trace_items (
202
+ trace_id INTEGER PRIMARY KEY AUTOINCREMENT,
203
+ run_id TEXT NOT NULL,
204
+ thread_id TEXT NOT NULL,
205
+ item_json TEXT NOT NULL,
206
+ created_at TEXT NOT NULL,
207
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
208
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
209
+ )
199
210
  `);
200
211
  await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_thread_updated_idx ON runs(thread_id, updated_at DESC)");
212
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_thread_created_idx ON runs(thread_id, created_at DESC)");
201
213
  await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_state_updated_idx ON runs(state, updated_at DESC)");
202
214
  await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_agent_updated_idx ON runs(agent_id, updated_at DESC)");
203
215
  await this.rawExecute("CREATE INDEX IF NOT EXISTS run_inspection_thread_activity_idx ON run_inspection(thread_id, last_activity_at DESC)");
216
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS run_trace_items_thread_run_trace_idx ON run_trace_items(thread_id, run_id, trace_id)");
204
217
  await this.rawExecute(`
205
218
  CREATE TABLE IF NOT EXISTS thread_messages (
206
219
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -491,6 +504,23 @@ export class SqlitePersistence {
491
504
  await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
492
505
  return;
493
506
  }
507
+ if (version === "5") {
508
+ await this.rawExecute(`
509
+ CREATE TABLE IF NOT EXISTS run_trace_items (
510
+ trace_id INTEGER PRIMARY KEY AUTOINCREMENT,
511
+ run_id TEXT NOT NULL,
512
+ thread_id TEXT NOT NULL,
513
+ item_json TEXT NOT NULL,
514
+ created_at TEXT NOT NULL,
515
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
516
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
517
+ )
518
+ `);
519
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_thread_created_idx ON runs(thread_id, created_at DESC)");
520
+ await this.rawExecute("CREATE INDEX IF NOT EXISTS run_trace_items_thread_run_trace_idx ON run_trace_items(thread_id, run_id, trace_id)");
521
+ await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
522
+ return;
523
+ }
494
524
  if (version !== String(RUNTIME_SQLITE_SCHEMA_VERSION)) {
495
525
  throw new Error(`Unsupported runtime sqlite schema version ${JSON.stringify(version)} in ${this.dbPath}. Expected ${RUNTIME_SQLITE_SCHEMA_VERSION}.`);
496
526
  }
@@ -830,26 +860,48 @@ export class SqlitePersistence {
830
860
  currentAgentId: asNullableString(row.current_agent_id),
831
861
  delegationChain: parseJson(row.delegation_chain_json),
832
862
  runtimeSnapshot: row.runtime_snapshot_json ? parseJson(row.runtime_snapshot_json) : null,
833
- upstreamEvents: row.upstream_events_json ? parseJson(row.upstream_events_json) : [],
863
+ traceItems: await this.listRunTraceItems(threadId, runId),
834
864
  };
835
865
  }
836
866
  async updateRunInspection(threadId, runId, patch) {
837
- const current = await this.getRunInspection(threadId, runId);
838
- const nextUpstreamEvents = patch.upstreamEvents
839
- ?? (patch.appendUpstreamEvent === undefined ? current.upstreamEvents : [...current.upstreamEvents, patch.appendUpstreamEvent]);
867
+ const row = await this.selectOne(`SELECT ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json
868
+ FROM run_inspection
869
+ WHERE run_id = ? AND thread_id = ?`, [runId, threadId]);
870
+ if (!row) {
871
+ throw new Error(`Missing run inspection ${runId} for thread ${threadId}`);
872
+ }
840
873
  await this.execute(`UPDATE run_inspection
841
874
  SET ended_at = ?, last_activity_at = ?, current_agent_id = ?, delegation_chain_json = ?, runtime_snapshot_json = ?, upstream_events_json = ?
842
875
  WHERE run_id = ? AND thread_id = ?`, [
843
- patch.endedAt === undefined ? current.endedAt : patch.endedAt,
844
- patch.lastActivityAt ?? current.lastActivityAt,
845
- patch.currentAgentId === undefined ? current.currentAgentId : patch.currentAgentId,
846
- JSON.stringify(patch.delegationChain ?? current.delegationChain),
847
- JSON.stringify(patch.runtimeSnapshot === undefined ? current.runtimeSnapshot : patch.runtimeSnapshot),
848
- JSON.stringify(nextUpstreamEvents),
876
+ patch.endedAt === undefined ? asNullableString(row.ended_at) : patch.endedAt,
877
+ patch.lastActivityAt ?? asString(row.last_activity_at),
878
+ patch.currentAgentId === undefined ? asNullableString(row.current_agent_id) : patch.currentAgentId,
879
+ JSON.stringify(patch.delegationChain ?? parseJson(row.delegation_chain_json)),
880
+ JSON.stringify(patch.runtimeSnapshot === undefined
881
+ ? (row.runtime_snapshot_json ? parseJson(row.runtime_snapshot_json) : null)
882
+ : patch.runtimeSnapshot),
883
+ "[]",
849
884
  runId,
850
885
  threadId,
851
886
  ]);
852
887
  }
888
+ async appendRunTraceItem(threadId, runId, item) {
889
+ await this.execute(`INSERT INTO run_trace_items (run_id, thread_id, item_json, created_at)
890
+ VALUES (?, ?, ?, ?)`, [runId, threadId, JSON.stringify(item), nowIso()]);
891
+ }
892
+ async listRunTraceItems(threadId, runId) {
893
+ const rows = await this.selectAll(`SELECT item_json
894
+ FROM run_trace_items
895
+ WHERE thread_id = ? AND run_id = ?
896
+ ORDER BY trace_id ASC`, [threadId, runId]);
897
+ if (rows.length > 0) {
898
+ return rows.map((row) => parseJson(row.item_json));
899
+ }
900
+ const row = await this.selectOne(`SELECT upstream_events_json
901
+ FROM run_inspection
902
+ WHERE thread_id = ? AND run_id = ?`, [threadId, runId]);
903
+ return row?.upstream_events_json ? parseJson(row.upstream_events_json) : [];
904
+ }
853
905
  async deleteThread(threadId) {
854
906
  const exists = await this.getSession(threadId);
855
907
  if (!exists) {
@@ -858,6 +910,7 @@ export class SqlitePersistence {
858
910
  await this.execute("DELETE FROM artifacts WHERE thread_id = ?", [threadId]);
859
911
  await this.execute("DELETE FROM approvals WHERE thread_id = ?", [threadId]);
860
912
  await this.execute("DELETE FROM events WHERE thread_id = ?", [threadId]);
913
+ await this.execute("DELETE FROM run_trace_items WHERE thread_id = ?", [threadId]);
861
914
  await this.execute("DELETE FROM run_inspection WHERE thread_id = ?", [threadId]);
862
915
  await this.execute("DELETE FROM run_queue WHERE thread_id = ?", [threadId]);
863
916
  await this.execute("DELETE FROM run_requests WHERE thread_id = ?", [threadId]);
@@ -33,7 +33,7 @@ export type PersistedRunInspection = {
33
33
  currentAgentId: string | null;
34
34
  delegationChain: string[];
35
35
  runtimeSnapshot: RuntimeSnapshot | null;
36
- upstreamEvents: unknown[];
36
+ traceItems: unknown[];
37
37
  };
38
38
  export type PersistedRunRequest = {
39
39
  input: MessageContent;
@@ -142,15 +142,15 @@ export interface RuntimePersistence {
142
142
  getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
143
143
  getRunLifecycle(threadId: string, runId: string): Promise<PersistenceLifecycle>;
144
144
  getRunInspection(threadId: string, runId: string): Promise<PersistedRunInspection>;
145
+ listRunTraceItems(threadId: string, runId: string): Promise<unknown[]>;
145
146
  updateRunInspection(threadId: string, runId: string, patch: {
146
147
  endedAt?: string | null;
147
148
  lastActivityAt?: string;
148
149
  currentAgentId?: string | null;
149
150
  delegationChain?: string[];
150
151
  runtimeSnapshot?: RuntimeSnapshot | null;
151
- upstreamEvents?: unknown[];
152
- appendUpstreamEvent?: unknown;
153
152
  }): Promise<void>;
153
+ appendRunTraceItem(threadId: string, runId: string, item: unknown): Promise<void>;
154
154
  deleteThread(threadId: string): Promise<boolean>;
155
155
  saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
156
156
  getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
@@ -382,8 +382,7 @@ function toSessionRecord(session) {
382
382
  currentAgentId: run.currentAgentId,
383
383
  delegationChain: run.delegationChain,
384
384
  runtimeSnapshot: run.runtimeSnapshot,
385
- upstreamEvents: run.upstreamEvents,
386
- history: run.history,
385
+ traceItems: run.traceItems,
387
386
  runtimeTimeline: run.runtimeTimeline,
388
387
  })),
389
388
  pendingDecision: session.pendingDecision,
@@ -410,8 +409,7 @@ function toRequestRecord(request) {
410
409
  currentAgentId: request.currentAgentId,
411
410
  delegationChain: request.delegationChain,
412
411
  runtimeSnapshot: request.runtimeSnapshot,
413
- upstreamEvents: request.upstreamEvents,
414
- history: request.history,
412
+ traceItems: request.traceItems,
415
413
  runtimeTimeline: request.runtimeTimeline,
416
414
  };
417
415
  }
@@ -29,11 +29,39 @@ function dependencyLinkPath(isolatedRoot, dependencyName) {
29
29
  function dependencyPackageRoot(packageJsonPath) {
30
30
  return path.dirname(packageJsonPath);
31
31
  }
32
- function resolveDeclaredDependency(packageRoot, dependencyName) {
32
+ function declaredDependencySpec(manifest, dependencyName) {
33
+ return manifest.dependencies?.[dependencyName]
34
+ ?? manifest.optionalDependencies?.[dependencyName]
35
+ ?? manifest.peerDependencies?.[dependencyName]
36
+ ?? null;
37
+ }
38
+ function resolveDeclaredFileDependency(packageRoot, spec) {
39
+ if (!spec.startsWith("file:")) {
40
+ return null;
41
+ }
42
+ const relativePath = spec.slice("file:".length).trim();
43
+ if (!relativePath) {
44
+ return null;
45
+ }
46
+ const resolvedPath = path.resolve(packageRoot, relativePath);
47
+ const packageJsonPath = resolvedPath.endsWith("package.json")
48
+ ? resolvedPath
49
+ : path.join(resolvedPath, "package.json");
50
+ if (!existsSync(packageJsonPath)) {
51
+ return null;
52
+ }
53
+ return dependencyPackageRoot(packageJsonPath);
54
+ }
55
+ function resolveDeclaredDependency(packageRoot, manifest, dependencyName) {
33
56
  const localPath = path.join(packageRoot, "node_modules", ...dependencyName.split("/"), "package.json");
34
57
  if (existsSync(localPath)) {
35
58
  return dependencyPackageRoot(localPath);
36
59
  }
60
+ const declaredSpec = declaredDependencySpec(manifest, dependencyName);
61
+ const localFileDependency = declaredSpec ? resolveDeclaredFileDependency(packageRoot, declaredSpec) : null;
62
+ if (localFileDependency) {
63
+ return localFileDependency;
64
+ }
37
65
  try {
38
66
  const requireFromPackage = createRequire(path.join(packageRoot, "package.json"));
39
67
  return dependencyPackageRoot(requireFromPackage.resolve(`${dependencyName}/package.json`));
@@ -44,7 +72,7 @@ function resolveDeclaredDependency(packageRoot, dependencyName) {
44
72
  }
45
73
  async function linkDeclaredDependencies(isolatedRoot, packageRoot, manifest) {
46
74
  for (const dependencyName of declaredDependencyNames(manifest)) {
47
- const resolved = resolveDeclaredDependency(packageRoot, dependencyName);
75
+ const resolved = resolveDeclaredDependency(packageRoot, manifest, dependencyName);
48
76
  if (!resolved) {
49
77
  continue;
50
78
  }
@@ -418,10 +418,7 @@ async function runFunctionToolInSubprocess(config, input, context) {
418
418
  return await new Promise((resolve, reject) => {
419
419
  const child = spawn(config.command, config.args, {
420
420
  cwd: config.cwd,
421
- env: {
422
- ...process.env,
423
- ...(config.env ?? {}),
424
- },
421
+ env: createRuntimeEnv(config.env, process.env),
425
422
  stdio: ["pipe", "pipe", "pipe"],
426
423
  });
427
424
  let stdout = "";
@@ -42,14 +42,14 @@ 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
- agentName: item.agentName,
50
- surfaceItems: item.surfaceItems,
51
- event: item.event,
52
- });
45
+ if (item.surfaceItem) {
46
+ await options.notifyListener(listeners.onTraceItem, {
47
+ threadId: item.threadId,
48
+ runId: item.runId,
49
+ surfaceItem: item.surfaceItem,
50
+ event: item.event,
51
+ });
52
+ }
53
53
  continue;
54
54
  }
55
55
  if (item.type === "result") {
@@ -21,9 +21,11 @@ export declare function projectRuntimeSurfaceFromSingleUpstreamEvent(input: {
21
21
  binding?: CompiledAgentBinding;
22
22
  currentAgentId: string;
23
23
  currentAgentName?: string;
24
+ delegationChain?: string[];
24
25
  sourceEventId: string;
25
26
  }): {
26
27
  currentAgentId: string;
27
28
  currentAgentName: string;
29
+ delegationChain: string[];
28
30
  items: RuntimeSurfaceItem[];
29
31
  };
@@ -3,37 +3,19 @@ import { createUpstreamTimelineReducer } from "../../../upstream-events.js";
3
3
  import { formatAgentName } from "../../../utils/agent-display.js";
4
4
  import { getBindingMemorySources, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSkills, getBindingSubagents, } from "../../support/compiled-binding.js";
5
5
  import { buildRuntimeGovernanceBundles } from "./governance.js";
6
+ import { buildSurfaceId, resolveSurfaceAction, resolveSurfaceDisplayName, } from "./surface-semantics.js";
6
7
  function asObject(value) {
7
8
  return typeof value === "object" && value !== null ? value : null;
8
9
  }
9
10
  function readStringArray(value) {
10
11
  return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
11
12
  }
12
- function normalizeLabel(value) {
13
- return value.replace(/\s+/g, " ").trim();
14
- }
15
- function slugify(value) {
16
- return value
17
- .toLowerCase()
18
- .replace(/[^a-z0-9]+/g, "-")
19
- .replace(/^-+|-+$/g, "")
20
- .slice(0, 80) || "item";
21
- }
22
- function stripStepPrefix(label) {
23
- return normalizeLabel(label)
24
- .replace(/^Calling LLM\s+/i, "")
25
- .replace(/^Completed LLM\s+/i, "")
26
- .replace(/^Calling tool\s+/i, "")
27
- .replace(/^Completed tool\s+/i, "")
28
- .replace(/^Tool\s+/i, "")
29
- .replace(/\s+failed$/i, "")
30
- .replace(/^Calling skill\s+/i, "")
31
- .replace(/^Completed skill\s+/i, "")
32
- .replace(/^Accessing memory\s+/i, "")
33
- .replace(/^Completed memory\s+/i, "");
34
- }
35
- function buildSurfaceId(kind, value) {
36
- return `${kind}:${slugify(value)}`;
13
+ function isTaskDelegationCompletionEvent(event) {
14
+ const eventName = typeof event.event === "string" ? event.event : "";
15
+ const name = typeof event.name === "string" ? event.name : "";
16
+ const runType = typeof event.run_type === "string" ? event.run_type : "";
17
+ return ((eventName === "on_tool_end" && name === "task")
18
+ || (eventName === "on_chain_end" && runType === "tool" && name === "task"));
37
19
  }
38
20
  function readTracingConfig(binding) {
39
21
  const deepAgentTracing = asObject(binding.harnessRuntime?.deepagent?.passthrough)?.tracing;
@@ -216,16 +198,19 @@ export function projectRuntimeSurfaceFromUpstreamEvents(input) {
216
198
  const items = [];
217
199
  let currentAgentId = input.initialAgentId;
218
200
  let currentAgentName = formatAgentName(currentAgentId);
201
+ let delegationChain = [input.initialAgentId];
219
202
  for (const [index, rawEvent] of input.upstreamEvents.entries()) {
220
203
  const projected = projectRuntimeSurfaceFromSingleUpstreamEvent({
221
204
  event: rawEvent,
222
205
  binding: input.binding,
223
206
  currentAgentId,
224
207
  currentAgentName,
208
+ delegationChain,
225
209
  sourceEventId: `upstream:${index + 1}`,
226
210
  });
227
211
  currentAgentId = projected.currentAgentId;
228
212
  currentAgentName = projected.currentAgentName;
213
+ delegationChain = projected.delegationChain;
229
214
  items.push(...projected.items);
230
215
  }
231
216
  return items;
@@ -235,49 +220,93 @@ export function projectRuntimeSurfaceFromSingleUpstreamEvent(input) {
235
220
  const items = [];
236
221
  let currentAgentId = input.currentAgentId;
237
222
  let currentAgentName = input.currentAgentName ?? formatAgentName(currentAgentId);
223
+ let delegationChain = input.delegationChain ?? [input.currentAgentId];
238
224
  const unwrapped = unwrapUpstreamEvent(input.event);
239
225
  if (unwrapped.agentId) {
240
- if (unwrapped.agentId !== currentAgentId) {
241
- const nextAgentName = unwrapped.agentName ?? formatAgentName(unwrapped.agentId);
226
+ currentAgentId = unwrapped.agentId;
227
+ currentAgentName = unwrapped.agentName ?? formatAgentName(currentAgentId);
228
+ if (unwrapped.agentId !== input.currentAgentId) {
242
229
  items.push({
243
230
  kind: "agent",
244
231
  id: buildSurfaceId("agent", unwrapped.agentId),
245
- name: nextAgentName,
246
- label: `Delegate to ${nextAgentName}`,
247
- status: "completed",
248
- ownerAgentId: currentAgentId,
249
- ownerAgentName: currentAgentName,
232
+ name: currentAgentName,
233
+ action: "handoff",
234
+ status: "started",
235
+ agentId: unwrapped.agentId,
236
+ agentName: currentAgentName,
237
+ ownerAgentId: input.currentAgentId,
238
+ ownerAgentName: input.currentAgentName ?? formatAgentName(input.currentAgentId),
250
239
  sourceEventId: input.sourceEventId,
240
+ detail: {
241
+ fromAgentId: input.currentAgentId,
242
+ fromAgentName: input.currentAgentName ?? formatAgentName(input.currentAgentId),
243
+ toAgentId: unwrapped.agentId,
244
+ toAgentName: currentAgentName,
245
+ },
251
246
  });
252
247
  }
253
- currentAgentId = unwrapped.agentId;
254
- currentAgentName = unwrapped.agentName ?? formatAgentName(currentAgentId);
255
248
  }
256
249
  else if (unwrapped.agentName) {
257
250
  currentAgentName = unwrapped.agentName;
258
251
  }
259
252
  const typed = asObject(unwrapped.event);
260
253
  if (typed && input.binding) {
254
+ const previousAgentId = currentAgentId;
255
+ const previousDelegationChain = delegationChain;
261
256
  const next = consumeRunInspectionUpstreamEvent({
262
257
  event: typed,
263
258
  currentAgentId,
264
- delegationChain: [currentAgentId],
259
+ delegationChain,
265
260
  binding: input.binding,
266
261
  });
267
- if (next.currentAgentId !== currentAgentId) {
268
- currentAgentId = next.currentAgentId;
269
- currentAgentName = formatAgentName(currentAgentId);
262
+ if (next.delegationChain.length > previousDelegationChain.length) {
263
+ const delegatedAgentName = formatAgentName(next.currentAgentId);
264
+ const ownerAgentId = previousDelegationChain.at(-1) ?? previousAgentId;
270
265
  items.push({
271
266
  kind: "agent",
272
- id: buildSurfaceId("agent", currentAgentId),
273
- name: currentAgentName,
274
- label: `Delegate to ${currentAgentName}`,
267
+ id: buildSurfaceId("agent", next.currentAgentId),
268
+ name: delegatedAgentName,
269
+ action: "handoff",
270
+ status: "started",
271
+ agentId: next.currentAgentId,
272
+ agentName: delegatedAgentName,
273
+ ownerAgentId,
274
+ ownerAgentName: formatAgentName(ownerAgentId),
275
+ sourceEventId: input.sourceEventId,
276
+ detail: {
277
+ fromAgentId: ownerAgentId,
278
+ fromAgentName: formatAgentName(ownerAgentId),
279
+ toAgentId: next.currentAgentId,
280
+ toAgentName: delegatedAgentName,
281
+ },
282
+ });
283
+ }
284
+ if (isTaskDelegationCompletionEvent(typed) && previousDelegationChain.length > 1) {
285
+ const delegatedAgentId = previousDelegationChain.at(-1);
286
+ const ownerAgentId = previousDelegationChain.at(-2);
287
+ const delegatedAgentName = formatAgentName(delegatedAgentId);
288
+ items.push({
289
+ kind: "agent",
290
+ id: buildSurfaceId("agent", delegatedAgentId),
291
+ name: delegatedAgentName,
292
+ action: "handoff",
275
293
  status: "completed",
276
- ownerAgentId: next.delegationChain.at(-2),
277
- ownerAgentName: next.delegationChain.at(-2) ? formatAgentName(next.delegationChain.at(-2)) : undefined,
294
+ agentId: delegatedAgentId,
295
+ agentName: delegatedAgentName,
296
+ ...(ownerAgentId ? { ownerAgentId, ownerAgentName: formatAgentName(ownerAgentId) } : {}),
278
297
  sourceEventId: input.sourceEventId,
298
+ detail: {
299
+ ...(ownerAgentId
300
+ ? { fromAgentId: ownerAgentId, fromAgentName: formatAgentName(ownerAgentId) }
301
+ : {}),
302
+ toAgentId: delegatedAgentId,
303
+ toAgentName: delegatedAgentName,
304
+ },
279
305
  });
280
306
  }
307
+ currentAgentId = next.currentAgentId;
308
+ currentAgentName = formatAgentName(currentAgentId);
309
+ delegationChain = next.delegationChain;
281
310
  }
282
311
  for (const projection of reducer.consume(unwrapped.event)) {
283
312
  if (projection.type !== "step") {
@@ -287,20 +316,36 @@ export function projectRuntimeSurfaceFromSingleUpstreamEvent(input) {
287
316
  if (!kind) {
288
317
  continue;
289
318
  }
319
+ const displayName = resolveSurfaceDisplayName({
320
+ kind,
321
+ step: projection.step,
322
+ binding: input.binding,
323
+ });
290
324
  items.push({
291
325
  kind,
292
- id: buildSurfaceId(kind, stripStepPrefix(projection.step) || projection.step),
293
- name: stripStepPrefix(projection.step) || formatAgentName(kind),
294
- label: projection.step,
326
+ id: buildSurfaceId(kind, displayName || projection.step),
327
+ name: displayName,
328
+ action: resolveSurfaceAction({
329
+ kind,
330
+ step: projection.step,
331
+ event: unwrapped.event,
332
+ }),
295
333
  status: projection.status,
334
+ agentId: currentAgentId,
335
+ agentName: currentAgentName,
296
336
  ownerAgentId: currentAgentId,
297
337
  ownerAgentName: currentAgentName,
298
338
  sourceEventId: input.sourceEventId,
339
+ detail: {
340
+ step: projection.step,
341
+ category: projection.category,
342
+ },
299
343
  });
300
344
  }
301
345
  return {
302
346
  currentAgentId,
303
347
  currentAgentName,
348
+ delegationChain,
304
349
  items,
305
350
  };
306
351
  }
@@ -10,6 +10,7 @@ type RuntimeStreamChunk = string | {
10
10
  };
11
11
  type StreamRunOptions = {
12
12
  binding: CompiledAgentBinding;
13
+ getBinding: (agentId: string) => CompiledAgentBinding | undefined;
13
14
  input: MessageContent;
14
15
  invocation: {
15
16
  context?: Record<string, unknown>;
@@ -53,9 +54,8 @@ type StreamRunOptions = {
53
54
  currentAgentId?: string | null;
54
55
  delegationChain?: string[];
55
56
  runtimeSnapshot?: RuntimeSnapshot | null;
56
- upstreamEvents?: unknown[];
57
- appendUpstreamEvent?: unknown;
58
57
  }) => Promise<void>;
58
+ appendRunTraceItem: (threadId: string, runId: string, item: unknown) => Promise<void>;
59
59
  emitSyntheticFallback: (threadId: string, runId: string, selectedAgentId: string, error: unknown) => Promise<void>;
60
60
  };
61
61
  export declare function streamHarnessRun(options: StreamRunOptions): AsyncGenerator<InternalHarnessStreamItem>;