@botbotgo/agent-harness 0.0.247 → 0.0.249

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts CHANGED
@@ -32,6 +32,7 @@ export type RequestUpstreamEventItem = {
32
32
  sessionId: string;
33
33
  requestId: string;
34
34
  agentId: string;
35
+ agentName?: string;
35
36
  event: unknown;
36
37
  };
37
38
  export type PublicRunListeners = {
package/dist/api.js CHANGED
@@ -65,6 +65,7 @@ function toPublicRunListeners(listeners) {
65
65
  sessionId: item.threadId,
66
66
  requestId: item.runId,
67
67
  agentId: item.agentId,
68
+ agentName: item.agentName,
68
69
  event: item.event,
69
70
  })
70
71
  : undefined,
@@ -142,6 +143,7 @@ function toPublicRequestSummary(run) {
142
143
  requestId: run.runId,
143
144
  sessionId: run.threadId,
144
145
  agentId: run.agentId,
146
+ parentRunId: run.parentRunId,
145
147
  executionMode: run.executionMode,
146
148
  adapterKind: run.adapterKind,
147
149
  createdAt: run.createdAt,
@@ -98,6 +98,10 @@ export type RuntimeHistoryItem = {
98
98
  isError?: boolean;
99
99
  key: string;
100
100
  };
101
+ export type AgentReference = {
102
+ id: string;
103
+ name: string;
104
+ };
101
105
  export type RuntimeQueueDiagnostics = {
102
106
  status: HealthStatus;
103
107
  activeRunSlots: number;
@@ -414,6 +418,7 @@ export type UpstreamRuntimeEventItem = {
414
418
  threadId: string;
415
419
  runId: string;
416
420
  agentId: string;
421
+ agentName?: string;
417
422
  event: UpstreamRuntimeEvent;
418
423
  };
419
424
  export type RuntimeListeners = {
@@ -461,6 +466,7 @@ export type HarnessStreamItem = {
461
466
  threadId: string;
462
467
  runId: string;
463
468
  agentId: string;
469
+ agentName?: string;
464
470
  event: UpstreamRuntimeEvent;
465
471
  } | {
466
472
  type: "result";
@@ -476,6 +482,7 @@ export type ThreadRunRecord = {
476
482
  runId: string;
477
483
  threadId: string;
478
484
  agentId: string;
485
+ parentRunId?: string;
479
486
  executionMode: string;
480
487
  adapterKind?: string;
481
488
  createdAt: string;
@@ -1,5 +1,6 @@
1
1
  import { projectRuntimeTimeline } from "../runtime/harness/events/timeline.js";
2
2
  import { createUpstreamTimelineReducer } from "../upstream-events.js";
3
+ import { formatAgentName } from "../utils/agent-display.js";
3
4
  function asObject(value) {
4
5
  return typeof value === "object" && value !== null ? value : null;
5
6
  }
@@ -17,7 +18,8 @@ function extractUpstreamEventEnvelope(value) {
17
18
  ? nestedEvent
18
19
  : value;
19
20
  const agentId = readString(typed?.agentId);
20
- return { event, agentId: agentId ?? undefined };
21
+ const agentName = readString(typed?.agentName);
22
+ return { event, agentId: agentId ?? undefined, agentName: agentName ?? undefined };
21
23
  }
22
24
  function collectNestedAgentNames(value, names = new Set()) {
23
25
  if (Array.isArray(value)) {
@@ -352,6 +354,9 @@ function deriveInitialAgentId(input, runtimeTimeline) {
352
354
  }
353
355
  return "agent";
354
356
  }
357
+ function deriveAgentName(agentId, explicitName) {
358
+ return explicitName && explicitName.trim().length > 0 ? explicitName.trim() : formatAgentName(agentId);
359
+ }
355
360
  function extractDelegatedAgentId(event) {
356
361
  const typed = asObject(event);
357
362
  if (!typed) {
@@ -389,15 +394,21 @@ function convertUpstreamEventsWithAgents(upstreamEvents, initialAgentId) {
389
394
  const projections = [];
390
395
  const delegationNodes = [];
391
396
  let currentAgentId = initialAgentId;
397
+ let currentAgentName = deriveAgentName(initialAgentId);
392
398
  let ordinal = 0;
393
399
  upstreamEvents.forEach((event, index) => {
394
400
  const sourceEventId = `upstream:${index + 1}`;
395
401
  const envelope = extractUpstreamEventEnvelope(event);
396
402
  if (envelope.agentId && envelope.agentId !== currentAgentId) {
397
403
  currentAgentId = envelope.agentId;
404
+ currentAgentName = deriveAgentName(currentAgentId, envelope.agentName);
405
+ }
406
+ else if (envelope.agentName) {
407
+ currentAgentName = deriveAgentName(currentAgentId, envelope.agentName);
398
408
  }
399
409
  const nestedAgentId = extractAgentFromNestedMessages(envelope.event, currentAgentId);
400
410
  if (nestedAgentId && nestedAgentId !== currentAgentId) {
411
+ const nestedAgentName = deriveAgentName(nestedAgentId);
401
412
  ordinal += 1;
402
413
  delegationNodes.push({
403
414
  id: `delegate:${slugify(currentAgentId)}:${slugify(nestedAgentId)}:${ordinal}`,
@@ -410,19 +421,24 @@ function convertUpstreamEventsWithAgents(upstreamEvents, initialAgentId) {
410
421
  threadId: "",
411
422
  runId: "",
412
423
  agentId: nestedAgentId,
424
+ agentName: nestedAgentName,
413
425
  sourceEventIds: [sourceEventId],
414
426
  detail: {
415
427
  fromAgentId: currentAgentId,
428
+ fromAgentName: currentAgentName,
416
429
  toAgentId: nestedAgentId,
430
+ toAgentName: nestedAgentName,
417
431
  },
418
432
  });
419
433
  currentAgentId = nestedAgentId;
434
+ currentAgentName = nestedAgentName;
420
435
  }
421
436
  const emitted = reducer.consume(envelope.event);
422
437
  for (const projection of emitted) {
423
438
  projections.push({
424
439
  projection,
425
440
  agentId: currentAgentId,
441
+ agentName: currentAgentName,
426
442
  sourceEventId,
427
443
  });
428
444
  }
@@ -430,6 +446,7 @@ function convertUpstreamEventsWithAgents(upstreamEvents, initialAgentId) {
430
446
  if (!delegatedAgentId || delegatedAgentId === currentAgentId) {
431
447
  return;
432
448
  }
449
+ const delegatedAgentName = deriveAgentName(delegatedAgentId);
433
450
  ordinal += 1;
434
451
  delegationNodes.push({
435
452
  id: `delegate:${slugify(initialAgentId)}:${slugify(delegatedAgentId)}:${ordinal}`,
@@ -442,13 +459,17 @@ function convertUpstreamEventsWithAgents(upstreamEvents, initialAgentId) {
442
459
  threadId: "",
443
460
  runId: "",
444
461
  agentId: delegatedAgentId,
462
+ agentName: delegatedAgentName,
445
463
  sourceEventIds: [sourceEventId],
446
464
  detail: {
447
465
  fromAgentId: currentAgentId,
466
+ fromAgentName: currentAgentName,
448
467
  toAgentId: delegatedAgentId,
468
+ toAgentName: delegatedAgentName,
449
469
  },
450
470
  });
451
471
  currentAgentId = delegatedAgentId;
472
+ currentAgentName = delegatedAgentName;
452
473
  });
453
474
  return { projections, delegationNodes };
454
475
  }
@@ -479,7 +500,7 @@ function buildAttempts(projectionRecords, delegationNodes, runtimeGroups, thread
479
500
  currentGroup = segmentGroups[nextSegmentIndex] ?? currentGroup;
480
501
  }
481
502
  }
482
- function createAttempt(kind, label, projection, agentId) {
503
+ function createAttempt(kind, label, projection, agentId, agentName) {
483
504
  ordinal += 1;
484
505
  const attempt = {
485
506
  id: `attempt:${runId}:${slugify(kind)}:${slugify(label)}:${ordinal}`,
@@ -487,6 +508,7 @@ function buildAttempts(projectionRecords, delegationNodes, runtimeGroups, thread
487
508
  label,
488
509
  status: deriveStatusFromProjection(projection),
489
510
  agentId,
511
+ agentName,
490
512
  groupId: currentGroup?.id,
491
513
  sourceEventIds: [],
492
514
  attemptKey: buildAttemptKey(kind, label, currentGroup?.id),
@@ -589,7 +611,7 @@ function buildAttempts(projectionRecords, delegationNodes, runtimeGroups, thread
589
611
  const attemptKey = buildAttemptKey(kind, label, currentGroup?.id);
590
612
  let attempt;
591
613
  if (projection.type === "step" && projection.status === "started") {
592
- attempt = createAttempt(kind, label, projection, record.agentId);
614
+ attempt = createAttempt(kind, label, projection, record.agentId, record.agentName);
593
615
  activeAttemptsByKey.set(attemptKey, attempt);
594
616
  }
595
617
  else if (projection.type === "tool-result") {
@@ -597,16 +619,17 @@ function buildAttempts(projectionRecords, delegationNodes, runtimeGroups, thread
597
619
  const existing = toolAttemptsByName.get(toolName) ?? [];
598
620
  attempt = [...existing].reverse().find((candidate) => candidate.groupId === currentGroup?.id) ?? existing[existing.length - 1];
599
621
  if (!attempt) {
600
- attempt = createAttempt("tool", `Calling tool ${titleCase(toolName)}`, projection, record.agentId);
622
+ attempt = createAttempt("tool", `Calling tool ${titleCase(toolName)}`, projection, record.agentId, record.agentName);
601
623
  }
602
624
  }
603
625
  else {
604
626
  attempt = activeAttemptsByKey.get(attemptKey);
605
627
  if (!attempt) {
606
- attempt = createAttempt(kind, label, projection, record.agentId);
628
+ attempt = createAttempt(kind, label, projection, record.agentId, record.agentName);
607
629
  }
608
630
  }
609
631
  attempt.agentId = attempt.agentId ?? record.agentId;
632
+ attempt.agentName = attempt.agentName ?? record.agentName;
610
633
  attempt.projectionKeys.push(projection.key);
611
634
  attempt.sourceEventIds.push(record.sourceEventId);
612
635
  if (projection.type === "step") {
@@ -684,6 +707,7 @@ function buildAttempts(projectionRecords, delegationNodes, runtimeGroups, thread
684
707
  threadId,
685
708
  runId,
686
709
  agentId: attempt.agentId,
710
+ agentName: attempt.agentName,
687
711
  groupId: attempt.groupId,
688
712
  sequenceStart: attempt.sequenceStart,
689
713
  sequenceEnd: attempt.sequenceEnd,
@@ -730,11 +754,13 @@ export function buildFlowGraph(input) {
730
754
  const { nodes: runtimeNodes, edges: runtimeEdges, groups: runtimeGroups } = buildRuntimeNodes(runtimeTimeline);
731
755
  for (const node of runtimeNodes) {
732
756
  node.agentId = typeof node.detail.agentId === "string" ? node.detail.agentId : initialAgentId;
757
+ node.agentName = deriveAgentName(node.agentId);
733
758
  }
734
759
  const projectionRecords = input.upstreamProjections
735
760
  ? input.upstreamProjections.map((projection, index) => ({
736
761
  projection,
737
762
  agentId: initialAgentId,
763
+ agentName: deriveAgentName(initialAgentId),
738
764
  sourceEventId: "key" in projection ? projection.key : `projection:${index + 1}`,
739
765
  }))
740
766
  : upstreamContext.projections;
@@ -5,6 +5,13 @@ function sanitizeAlias(value) {
5
5
  function escapeLabel(value) {
6
6
  return value.replace(/"/g, '\\"');
7
7
  }
8
+ function formatAgentParticipantLabel(node) {
9
+ const agentId = node.agentId ?? "agent";
10
+ if (node.agentName && node.agentName !== agentId) {
11
+ return `Agent:${agentId} (${node.agentName})`;
12
+ }
13
+ return `Agent:${agentId}`;
14
+ }
8
15
  function selectNodes(graph, options) {
9
16
  const includedKinds = options.includeKinds
10
17
  ? new Set(options.includeKinds)
@@ -33,7 +40,7 @@ function getParticipantsForNode(node) {
33
40
  const agentParticipant = {
34
41
  id: `agent:${node.agentId ?? "agent"}`,
35
42
  alias: sanitizeAlias(`Agent_${node.agentId ?? "agent"}`),
36
- label: `Agent${node.agentId ? `:${node.agentId}` : ""}`,
43
+ label: formatAgentParticipantLabel(node),
37
44
  };
38
45
  if (node.kind === "run") {
39
46
  return [
@@ -48,7 +55,9 @@ function getParticipantsForNode(node) {
48
55
  {
49
56
  id: `agent:${fromAgentId}`,
50
57
  alias: sanitizeAlias(`Agent_${fromAgentId}`),
51
- label: `Agent:${fromAgentId}`,
58
+ label: typeof node.detail.fromAgentName === "string" && node.detail.fromAgentName !== fromAgentId
59
+ ? `Agent:${fromAgentId} (${node.detail.fromAgentName})`
60
+ : `Agent:${fromAgentId}`,
52
61
  },
53
62
  agentParticipant,
54
63
  ];
@@ -16,6 +16,7 @@ export type FlowNode = {
16
16
  threadId: string;
17
17
  runId: string;
18
18
  agentId?: string;
19
+ agentName?: string;
19
20
  startedAt?: string;
20
21
  endedAt?: string;
21
22
  sequenceStart?: number;
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.246";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.248";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.246";
1
+ export const AGENT_HARNESS_VERSION = "0.0.248";
@@ -67,10 +67,12 @@ export class FilePersistence {
67
67
  const runDir = this.runDir(input.threadId, input.runId);
68
68
  await ensureDir(path.join(runDir, "events"));
69
69
  await ensureDir(path.join(runDir, "approvals"));
70
+ const threadMeta = await this.getThreadMeta(input.threadId);
70
71
  const meta = {
71
72
  runId: input.runId,
72
73
  threadId: input.threadId,
73
74
  agentId: input.agentId,
75
+ parentRunId: threadMeta?.latestRunId ?? null,
74
76
  executionMode: input.executionMode,
75
77
  adapterKind: input.adapterKind ?? input.executionMode,
76
78
  createdAt: input.createdAt,
@@ -266,6 +268,7 @@ export class FilePersistence {
266
268
  runId: meta.runId,
267
269
  threadId: meta.threadId,
268
270
  agentId: meta.agentId,
271
+ ...(meta.parentRunId ? { parentRunId: meta.parentRunId } : {}),
269
272
  executionMode: meta.executionMode,
270
273
  adapterKind: meta.adapterKind ?? meta.executionMode,
271
274
  createdAt: meta.createdAt,
@@ -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 = 4;
7
+ const RUNTIME_SQLITE_SCHEMA_VERSION = 5;
8
8
  const RUNTIME_SQLITE_SCHEMA_FAMILY = "agent-harness-runtime";
9
9
  function asRow(value) {
10
10
  return value;
@@ -168,6 +168,7 @@ export class SqlitePersistence {
168
168
  run_id TEXT PRIMARY KEY,
169
169
  thread_id TEXT NOT NULL,
170
170
  agent_id TEXT NOT NULL,
171
+ parent_run_id TEXT,
171
172
  execution_mode TEXT NOT NULL,
172
173
  adapter_kind TEXT,
173
174
  created_at TEXT NOT NULL,
@@ -403,6 +404,7 @@ export class SqlitePersistence {
403
404
  runId: asString(row.run_id),
404
405
  threadId: asString(row.thread_id),
405
406
  agentId: asString(row.agent_id),
407
+ ...(asNullableString(row.parent_run_id) ? { parentRunId: asNullableString(row.parent_run_id) } : {}),
406
408
  executionMode: asString(row.execution_mode),
407
409
  adapterKind: asNullableString(row.adapter_kind) ?? undefined,
408
410
  createdAt: asString(row.created_at),
@@ -473,12 +475,19 @@ export class SqlitePersistence {
473
475
  SELECT run_id, thread_id, created_at, NULL, updated_at, agent_id, json_array(agent_id), NULL, '[]'
474
476
  FROM runs
475
477
  `);
478
+ await this.rawExecute("ALTER TABLE runs ADD COLUMN parent_run_id TEXT");
476
479
  await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
477
480
  return;
478
481
  }
479
482
  if (version === "3") {
480
483
  await this.rawExecute("ALTER TABLE run_inspection ADD COLUMN upstream_events_json TEXT NOT NULL DEFAULT '[]'");
481
484
  await this.rawExecute("UPDATE run_inspection SET upstream_events_json = '[]' WHERE upstream_events_json IS NULL");
485
+ await this.rawExecute("ALTER TABLE runs ADD COLUMN parent_run_id TEXT");
486
+ await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
487
+ return;
488
+ }
489
+ if (version === "4") {
490
+ await this.rawExecute("ALTER TABLE runs ADD COLUMN parent_run_id TEXT");
482
491
  await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
483
492
  return;
484
493
  }
@@ -493,6 +502,7 @@ export class SqlitePersistence {
493
502
  VALUES (?, ?, ?, ?, ?, ?, ?)`, [input.threadId, "default", input.agentId, input.status, input.runId, input.createdAt, input.createdAt]);
494
503
  }
495
504
  async bootstrapRun(input) {
505
+ const parentRunId = input.createThread ? null : (await this.getThreadMeta(input.threadId))?.latestRunId ?? null;
496
506
  await mkdir(this.threadDir(input.threadId), { recursive: true });
497
507
  await mkdir(path.join(this.runDir(input.threadId, input.runId), "events"), { recursive: true });
498
508
  const steps = [];
@@ -506,12 +516,13 @@ export class SqlitePersistence {
506
516
  }
507
517
  steps.push({
508
518
  sql: `INSERT OR REPLACE INTO runs
509
- (run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, previous_state, state_entered_at, last_transition_at, resumable, checkpoint_ref)
510
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
519
+ (run_id, thread_id, agent_id, parent_run_id, execution_mode, adapter_kind, created_at, updated_at, state, previous_state, state_entered_at, last_transition_at, resumable, checkpoint_ref)
520
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
511
521
  args: [
512
522
  input.runId,
513
523
  input.threadId,
514
524
  input.agentId,
525
+ parentRunId,
515
526
  input.executionMode,
516
527
  input.adapterKind ?? input.executionMode ?? null,
517
528
  input.createdAt,
@@ -562,13 +573,15 @@ export class SqlitePersistence {
562
573
  await this.executeTransaction(steps);
563
574
  }
564
575
  async createRun(input) {
576
+ const parentRunId = (await this.getThreadMeta(input.threadId))?.latestRunId ?? null;
565
577
  await mkdir(path.join(this.runDir(input.threadId, input.runId), "events"), { recursive: true });
566
578
  await this.execute(`INSERT OR REPLACE INTO runs
567
- (run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, previous_state, state_entered_at, last_transition_at, resumable, checkpoint_ref)
568
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
579
+ (run_id, thread_id, agent_id, parent_run_id, execution_mode, adapter_kind, created_at, updated_at, state, previous_state, state_entered_at, last_transition_at, resumable, checkpoint_ref)
580
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
569
581
  input.runId,
570
582
  input.threadId,
571
583
  input.agentId,
584
+ parentRunId,
572
585
  input.executionMode,
573
586
  input.adapterKind ?? input.executionMode ?? null,
574
587
  input.createdAt,
@@ -698,7 +711,7 @@ export class SqlitePersistence {
698
711
  ["runs.thread_id = ?", filter.threadId],
699
712
  ["runs.state = ?", filter.state],
700
713
  ]);
701
- const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
714
+ const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.parent_run_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
702
715
  run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
703
716
  FROM runs
704
717
  LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
@@ -707,7 +720,7 @@ export class SqlitePersistence {
707
720
  return rows.map((row) => this.mapRunSummary(row));
708
721
  }
709
722
  async getRun(runId) {
710
- const row = await this.selectOne(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
723
+ const row = await this.selectOne(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.parent_run_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
711
724
  run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
712
725
  FROM runs
713
726
  LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
@@ -737,7 +750,7 @@ export class SqlitePersistence {
737
750
  };
738
751
  }
739
752
  async listThreadRuns(threadId) {
740
- const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
753
+ const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.parent_run_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
741
754
  run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
742
755
  FROM runs
743
756
  LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
@@ -770,7 +783,7 @@ export class SqlitePersistence {
770
783
  return rows.map((row) => this.mapApproval(row));
771
784
  }
772
785
  async getRunMeta(threadId, runId) {
773
- const row = await this.selectOne(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at
786
+ const row = await this.selectOne(`SELECT run_id, thread_id, agent_id, parent_run_id, execution_mode, adapter_kind, created_at, updated_at
774
787
  FROM runs
775
788
  WHERE thread_id = ? AND run_id = ?`, [threadId, runId]);
776
789
  if (!row) {
@@ -780,6 +793,7 @@ export class SqlitePersistence {
780
793
  runId: asString(row.run_id),
781
794
  threadId: asString(row.thread_id),
782
795
  agentId: asString(row.agent_id),
796
+ parentRunId: asNullableString(row.parent_run_id),
783
797
  executionMode: asString(row.execution_mode),
784
798
  adapterKind: asNullableString(row.adapter_kind) ?? undefined,
785
799
  createdAt: asString(row.created_at),
@@ -12,6 +12,7 @@ export type PersistenceRunMeta = {
12
12
  runId: string;
13
13
  threadId: string;
14
14
  agentId: string;
15
+ parentRunId: string | null;
15
16
  executionMode: string;
16
17
  adapterKind?: string;
17
18
  createdAt: string;
@@ -46,6 +46,7 @@ export async function dispatchRunListeners(stream, listeners, options) {
46
46
  threadId: item.threadId,
47
47
  runId: item.runId,
48
48
  agentId: item.agentId,
49
+ agentName: item.agentName,
49
50
  event: item.event,
50
51
  });
51
52
  continue;
@@ -3,6 +3,7 @@ import { renderRuntimeFailure, renderToolFailure } from "../../support/harness-s
3
3
  import { getBindingPrimaryModel } from "../../support/compiled-binding.js";
4
4
  import { createContentBlocksItem, createToolResultKey, } from "../events/streaming.js";
5
5
  import { consumeRunInspectionUpstreamEvent } from "./inspection.js";
6
+ import { formatAgentName } from "../../../utils/agent-display.js";
6
7
  function normalizeStreamChunk(chunk) {
7
8
  if (typeof chunk === "string") {
8
9
  if (chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)) {
@@ -45,6 +46,7 @@ export async function* streamHarnessRun(options) {
45
46
  let streamActivityObserved = false;
46
47
  let nonUpstreamStreamActivityObserved = false;
47
48
  let currentAgentId = options.selectedAgentId;
49
+ let currentAgentName = formatAgentName(options.selectedAgentId);
48
50
  let delegationChain = [options.selectedAgentId];
49
51
  let syntheticFallback;
50
52
  try {
@@ -76,17 +78,23 @@ export async function* streamHarnessRun(options) {
76
78
  binding: options.binding,
77
79
  });
78
80
  currentAgentId = inspection.currentAgentId;
81
+ currentAgentName = formatAgentName(currentAgentId);
79
82
  delegationChain = inspection.delegationChain;
80
83
  await options.updateRunInspection(options.threadId, options.runId, {
81
84
  currentAgentId,
82
85
  delegationChain,
83
- appendUpstreamEvent: normalizedChunk.event,
86
+ appendUpstreamEvent: {
87
+ agentId: currentAgentId,
88
+ agentName: currentAgentName,
89
+ event: normalizedChunk.event,
90
+ },
84
91
  });
85
92
  yield {
86
93
  type: "upstream-event",
87
94
  threadId: options.threadId,
88
95
  runId: options.runId,
89
96
  agentId: currentAgentId,
97
+ agentName: currentAgentName,
90
98
  event: normalizedChunk.event,
91
99
  };
92
100
  continue;
@@ -1,6 +1,19 @@
1
1
  import { isTerminalRunState, toInspectableApprovalRecord } from "./helpers.js";
2
2
  import { projectRuntimeTimeline } from "../events/timeline.js";
3
3
  import { createUpstreamTimelineReducer } from "../../../upstream-events.js";
4
+ function unwrapPersistedUpstreamEvent(event) {
5
+ if (typeof event !== "object" || event === null || Array.isArray(event)) {
6
+ return event;
7
+ }
8
+ const typed = event;
9
+ if (Object.prototype.hasOwnProperty.call(typed, "event")
10
+ && typeof typed.event === "object"
11
+ && typed.event !== null
12
+ && !Array.isArray(typed.event)) {
13
+ return typed.event;
14
+ }
15
+ return event;
16
+ }
4
17
  function selectLatestPendingApproval(approvals) {
5
18
  return approvals
6
19
  .filter((approval) => approval.status === "pending")
@@ -8,7 +21,7 @@ function selectLatestPendingApproval(approvals) {
8
21
  }
9
22
  function buildRunInspectionProjection(upstreamEvents) {
10
23
  const reducer = createUpstreamTimelineReducer();
11
- const history = upstreamEvents.flatMap((event) => reducer.consume(event));
24
+ const history = upstreamEvents.flatMap((event) => reducer.consume(unwrapPersistedUpstreamEvent(event)));
12
25
  return {
13
26
  upstreamEvents,
14
27
  history,
@@ -22,6 +35,7 @@ export async function buildRequestInspectionRecord(persistence, request) {
22
35
  requestId: request.runId,
23
36
  sessionId: request.threadId,
24
37
  agentId: request.agentId,
38
+ parentRunId: request.parentRunId,
25
39
  executionMode: request.executionMode,
26
40
  adapterKind: request.adapterKind,
27
41
  createdAt: request.createdAt,
@@ -48,6 +62,7 @@ function toRunRecord(request) {
48
62
  runId: request.requestId,
49
63
  threadId: request.sessionId,
50
64
  agentId: request.agentId,
65
+ parentRunId: request.parentRunId,
51
66
  executionMode: request.executionMode,
52
67
  adapterKind: request.adapterKind,
53
68
  createdAt: request.createdAt,
@@ -385,6 +385,7 @@ export class AgentHarnessRuntime {
385
385
  requestId: request.runId,
386
386
  sessionId: request.threadId,
387
387
  agentId: request.agentId,
388
+ parentRunId: request.parentRunId,
388
389
  executionMode: request.executionMode,
389
390
  adapterKind: request.adapterKind,
390
391
  createdAt: request.createdAt,
@@ -410,6 +411,7 @@ export class AgentHarnessRuntime {
410
411
  runId: request.requestId,
411
412
  threadId: request.sessionId,
412
413
  agentId: request.agentId,
414
+ parentRunId: request.parentRunId,
413
415
  executionMode: request.executionMode,
414
416
  adapterKind: request.adapterKind,
415
417
  createdAt: request.createdAt,
@@ -1800,6 +1802,7 @@ function toRequestSummary(summary) {
1800
1802
  requestId: summary.runId,
1801
1803
  sessionId: summary.threadId,
1802
1804
  agentId: summary.agentId,
1805
+ parentRunId: summary.parentRunId,
1803
1806
  executionMode: summary.executionMode,
1804
1807
  adapterKind: summary.adapterKind,
1805
1808
  createdAt: summary.createdAt,
@@ -0,0 +1 @@
1
+ export declare function formatAgentName(agentId: string): string;
@@ -0,0 +1,18 @@
1
+ function normalizeSegment(value) {
2
+ return value
3
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
4
+ .replace(/[_-]+/g, " ")
5
+ .replace(/\s+/g, " ")
6
+ .trim();
7
+ }
8
+ export function formatAgentName(agentId) {
9
+ const normalized = normalizeSegment(agentId);
10
+ if (!normalized) {
11
+ return "Agent";
12
+ }
13
+ return normalized
14
+ .split(" ")
15
+ .filter(Boolean)
16
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
17
+ .join(" ");
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.247",
3
+ "version": "0.0.249",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",