@poncho-ai/harness 0.52.2 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/harness@0.52.2 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
2
+ > @poncho-ai/harness@0.53.0 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
3
3
  > node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
4
4
 
5
5
  [embed-docs] Generated poncho-docs.ts with 4 topics
@@ -8,9 +8,9 @@
8
8
  CLI tsup v8.5.1
9
9
  CLI Target: es2022
10
10
  ESM Build start
11
- ESM dist/index.js 540.60 KB
12
11
  ESM dist/isolate-F2PPSUL6.js 53.82 KB
13
- ESM ⚡️ Build success in 204ms
12
+ ESM dist/index.js 541.42 KB
13
+ ESM ⚡️ Build success in 239ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 7242ms
16
- DTS dist/index.d.ts 93.60 KB
15
+ DTS ⚡️ Build success in 7708ms
16
+ DTS dist/index.d.ts 94.04 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @poncho-ai/harness
2
2
 
3
+ ## 0.53.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#145](https://github.com/cesr/poncho-ai/pull/145) [`bfa4976`](https://github.com/cesr/poncho-ai/commit/bfa4976ac8b05a300e22271e23c3bae4aadae2a8) Thanks [@cesr](https://github.com/cesr)! - events: add stable identity so streaming clients match instead of guess
8
+
9
+ Additive fields that let a streaming client reconstruct view-state by
10
+ identity rather than inferring structure from event order (the source of a
11
+ class of reconnect/subagent rendering bugs):
12
+ - `tool:started` / `tool:completed` / `tool:error` now carry `toolCallId`
13
+ (already in scope as `call.id` / `result.callId`). Clients match tool
14
+ pills by id instead of by tool name.
15
+ - `subagent:spawned|completed|error|stopped` now carry `parentToolCallId`
16
+ (the `spawn_subagent` tool call's id) and `task`; `completed`/`error`
17
+ also carry `resultText`. Clients attach subagent state to the spawning
18
+ tool's pill and render the result inline — no header-regex or
19
+ sequential-cursor pairing needed.
20
+ - `ToolContext` gains `toolCallId` so the `spawn_subagent` handler can
21
+ record which call produced the subagent (plumbed: tool-dispatcher →
22
+ spawn handler → `SubagentSpawnOptions.parentToolCallId` →
23
+ `subagentMeta.parentToolCallId` → the events above).
24
+ - `run:started` gains an optional `cause` field in the type
25
+ (`user|continuation|subagent_callback|approval_resume`); emission is
26
+ deferred to a later pass.
27
+
28
+ All fields are additive; older clients ignore them.
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [[`bfa4976`](https://github.com/cesr/poncho-ai/commit/bfa4976ac8b05a300e22271e23c3bae4aadae2a8)]:
33
+ - @poncho-ai/sdk@1.15.0
34
+
3
35
  ## 0.52.2
4
36
 
5
37
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -199,6 +199,9 @@ interface Conversation {
199
199
  * subagent's runs emit no telemetry (e.g. spawned from an incognito
200
200
  * turn). Read by the orchestrator's runSubagent / continuation. */
201
201
  suppressTelemetry?: boolean;
202
+ /** The parent's `spawn_subagent` tool call id — echoed onto subagent:*
203
+ * events so a client can attach subagent state to that tool's pill. */
204
+ parentToolCallId?: string;
202
205
  };
203
206
  channelMeta?: {
204
207
  platform: string;
@@ -1190,6 +1193,10 @@ interface SubagentManager {
1190
1193
  /** Inherit the parent run's telemetry choice — when true, the subagent
1191
1194
  * run (and its re-runs) emit no telemetry. */
1192
1195
  suppressTelemetry?: boolean;
1196
+ /** The id of the `spawn_subagent` tool call that produced this subagent,
1197
+ * so its events can carry `parentToolCallId` and a client can attach
1198
+ * subagent state to the spawning tool's pill. */
1199
+ parentToolCallId?: string;
1193
1200
  }): Promise<SubagentSpawnResult>;
1194
1201
  sendMessage(subagentId: string, message: string): Promise<SubagentSpawnResult>;
1195
1202
  stop(subagentId: string): Promise<void>;
package/dist/index.js CHANGED
@@ -8323,7 +8323,8 @@ var createSubagentTools = (manager) => [
8323
8323
  parentConversationId: conversationId,
8324
8324
  ownerId,
8325
8325
  tenantId: context.tenantId,
8326
- suppressTelemetry: context.suppressTelemetry
8326
+ suppressTelemetry: context.suppressTelemetry,
8327
+ parentToolCallId: context.toolCallId
8327
8328
  });
8328
8329
  return { subagentId, status: "running" };
8329
8330
  }
@@ -8585,7 +8586,7 @@ var ToolDispatcher = class {
8585
8586
  };
8586
8587
  }
8587
8588
  try {
8588
- const output = await definition.handler(call.input, context);
8589
+ const output = await definition.handler(call.input, { ...context, toolCallId: call.id });
8589
8590
  if (context.abortSignal?.aborted) {
8590
8591
  return {
8591
8592
  callId: call.id,
@@ -11170,7 +11171,7 @@ ${textContent}` };
11170
11171
  return;
11171
11172
  }
11172
11173
  const runtimeToolName = exposedToolNames.get(call.name) ?? call.name;
11173
- yield pushEvent({ type: "tool:started", tool: runtimeToolName, input: call.input });
11174
+ yield pushEvent({ type: "tool:started", tool: runtimeToolName, toolCallId: call.id, input: call.input });
11174
11175
  if (this.requiresApprovalForToolCall(runtimeToolName, call.input)) {
11175
11176
  approvalNeeded.push({
11176
11177
  approvalId: `approval_${randomUUID5()}`,
@@ -11363,6 +11364,7 @@ ${textContent}` };
11363
11364
  yield pushEvent({
11364
11365
  type: "tool:error",
11365
11366
  tool: result2.tool,
11367
+ toolCallId: result2.callId,
11366
11368
  error: result2.error,
11367
11369
  recoverable: true
11368
11370
  });
@@ -11404,6 +11406,7 @@ ${textContent}` };
11404
11406
  yield pushEvent({
11405
11407
  type: "tool:completed",
11406
11408
  tool: result2.tool,
11409
+ toolCallId: result2.callId,
11407
11410
  input: callInputMap.get(result2.callId),
11408
11411
  output: result2.output,
11409
11412
  duration: now() - batchStart,
@@ -12826,11 +12829,14 @@ var AgentOrchestrator = class {
12826
12829
  result: { status: "completed", response: responseText, steps: 0, tokens: { input: 0, output: 0, cached: 0 }, duration: 0 },
12827
12830
  timestamp: Date.now()
12828
12831
  };
12829
- await this.conversationStore.appendSubagentResult(conv.parentConversationId, pendingResult);
12832
+ await this.appendSubagentResultReliable(conv.parentConversationId, pendingResult);
12830
12833
  await this.eventSink(conv.parentConversationId, {
12831
12834
  type: "subagent:completed",
12832
12835
  subagentId,
12833
- conversationId: subagentId
12836
+ conversationId: subagentId,
12837
+ task: conv.subagentMeta?.task ?? conv.title,
12838
+ parentToolCallId: conv.subagentMeta?.parentToolCallId,
12839
+ resultText: responseText
12834
12840
  });
12835
12841
  await this.triggerParentCallback(conv.parentConversationId);
12836
12842
  }
@@ -12906,9 +12912,11 @@ var AgentOrchestrator = class {
12906
12912
  let latestRunId = "";
12907
12913
  let runResult;
12908
12914
  let runError;
12915
+ let parentToolCallId;
12909
12916
  try {
12910
12917
  const conversation = await this.conversationStore.getWithArchive(childConversationId);
12911
12918
  if (!conversation) throw new Error("Subagent conversation not found");
12919
+ parentToolCallId = conversation.subagentMeta?.parentToolCallId;
12912
12920
  if (conversation.subagentMeta?.status === "stopped") return;
12913
12921
  conversation.lastActivityAt = Date.now();
12914
12922
  await this.conversationStore.update(conversation);
@@ -13126,7 +13134,10 @@ var AgentOrchestrator = class {
13126
13134
  await this.eventSink(parentConversationId, {
13127
13135
  type: "subagent:completed",
13128
13136
  subagentId: childConversationId,
13129
- conversationId: childConversationId
13137
+ conversationId: childConversationId,
13138
+ task,
13139
+ parentToolCallId,
13140
+ resultText: subagentResponse
13130
13141
  });
13131
13142
  this.triggerParentCallback(parentConversationId).catch(
13132
13143
  (err) => console.error(`[poncho][subagent] Parent callback failed:`, err instanceof Error ? err.message : err)
@@ -13157,7 +13168,9 @@ var AgentOrchestrator = class {
13157
13168
  type: "subagent:error",
13158
13169
  subagentId: childConversationId,
13159
13170
  conversationId: childConversationId,
13160
- error: errMsg
13171
+ error: errMsg,
13172
+ task,
13173
+ parentToolCallId
13161
13174
  });
13162
13175
  this.triggerParentCallback(parentConversationId).catch(
13163
13176
  (err2) => console.error(`[poncho][subagent] Parent callback failed:`, err2 instanceof Error ? err2.message : err2)
@@ -13486,7 +13499,10 @@ ${resultBody}`,
13486
13499
  await this.eventSink(parentConversationId, {
13487
13500
  type: "subagent:completed",
13488
13501
  subagentId: conversationId,
13489
- conversationId
13502
+ conversationId,
13503
+ task,
13504
+ parentToolCallId: conversation.subagentMeta?.parentToolCallId,
13505
+ resultText: subagentResponse
13490
13506
  });
13491
13507
  if (parentConv) {
13492
13508
  if (this.isServerless) {
@@ -13530,7 +13546,9 @@ ${resultBody}`,
13530
13546
  await this.eventSink(conversation.parentConversationId, {
13531
13547
  type: "subagent:completed",
13532
13548
  subagentId: conversationId,
13533
- conversationId
13549
+ conversationId,
13550
+ task,
13551
+ parentToolCallId: conversation.subagentMeta?.parentToolCallId
13534
13552
  });
13535
13553
  if (parentConv) {
13536
13554
  if (this.isServerless) {
@@ -13560,7 +13578,7 @@ ${resultBody}`,
13560
13578
  opts.tenantId ?? null,
13561
13579
  {
13562
13580
  parentConversationId: opts.parentConversationId,
13563
- subagentMeta: { task: opts.task, status: "running", suppressTelemetry: opts.suppressTelemetry },
13581
+ subagentMeta: { task: opts.task, status: "running", suppressTelemetry: opts.suppressTelemetry, parentToolCallId: opts.parentToolCallId },
13564
13582
  messages: [{ role: "user", content: opts.task }]
13565
13583
  }
13566
13584
  );
@@ -13572,7 +13590,8 @@ ${resultBody}`,
13572
13590
  type: "subagent:spawned",
13573
13591
  subagentId: conversation.conversationId,
13574
13592
  conversationId: conversation.conversationId,
13575
- task: opts.task
13593
+ task: opts.task,
13594
+ parentToolCallId: opts.parentToolCallId
13576
13595
  });
13577
13596
  if (this.isServerless) {
13578
13597
  this.hooks.dispatchBackground("subagent-run", conversation.conversationId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.52.2",
3
+ "version": "0.53.0",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,7 +34,7 @@
34
34
  "mustache": "^4.2.0",
35
35
  "yaml": "^2.4.0",
36
36
  "zod": "^3.22.0",
37
- "@poncho-ai/sdk": "1.14.0"
37
+ "@poncho-ai/sdk": "1.15.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "esbuild": ">=0.17.0",
package/src/harness.ts CHANGED
@@ -3344,7 +3344,7 @@ Code is wrapped in an async IIFE — use \`return\` to return a value to the too
3344
3344
  return;
3345
3345
  }
3346
3346
  const runtimeToolName = exposedToolNames.get(call.name) ?? call.name;
3347
- yield pushEvent({ type: "tool:started", tool: runtimeToolName, input: call.input });
3347
+ yield pushEvent({ type: "tool:started", tool: runtimeToolName, toolCallId: call.id, input: call.input });
3348
3348
  if (this.requiresApprovalForToolCall(runtimeToolName, call.input)) {
3349
3349
  approvalNeeded.push({
3350
3350
  approvalId: `approval_${randomUUID()}`,
@@ -3563,6 +3563,7 @@ Code is wrapped in an async IIFE — use \`return\` to return a value to the too
3563
3563
  yield pushEvent({
3564
3564
  type: "tool:error",
3565
3565
  tool: result.tool,
3566
+ toolCallId: result.callId,
3566
3567
  error: result.error,
3567
3568
  recoverable: true,
3568
3569
  });
@@ -3604,6 +3605,7 @@ Code is wrapped in an async IIFE — use \`return\` to return a value to the too
3604
3605
  yield pushEvent({
3605
3606
  type: "tool:completed",
3606
3607
  tool: result.tool,
3608
+ toolCallId: result.callId,
3607
3609
  input: callInputMap.get(result.callId),
3608
3610
  output: result.output,
3609
3611
  duration: now() - batchStart,
@@ -693,12 +693,15 @@ export class AgentOrchestrator {
693
693
  result: { status: "completed", response: responseText, steps: 0, tokens: { input: 0, output: 0, cached: 0 }, duration: 0 },
694
694
  timestamp: Date.now(),
695
695
  };
696
- await this.conversationStore.appendSubagentResult(conv.parentConversationId, pendingResult);
696
+ await this.appendSubagentResultReliable(conv.parentConversationId, pendingResult);
697
697
 
698
698
  await this.eventSink(conv.parentConversationId, {
699
699
  type: "subagent:completed",
700
700
  subagentId,
701
701
  conversationId: subagentId,
702
+ task: conv.subagentMeta?.task ?? conv.title,
703
+ parentToolCallId: conv.subagentMeta?.parentToolCallId,
704
+ resultText: responseText,
702
705
  });
703
706
 
704
707
  await this.triggerParentCallback(conv.parentConversationId);
@@ -796,10 +799,14 @@ export class AgentOrchestrator {
796
799
  let latestRunId = "";
797
800
  let runResult: { status: "completed" | "error" | "cancelled"; response?: string; steps: number; duration: number; continuation?: boolean; continuationMessages?: Message[] } | undefined;
798
801
  let runError: { code?: string; message?: string } | undefined;
802
+ // The spawning tool call's id — echoed onto subagent:* events so the
803
+ // client can attach subagent state to that tool's pill.
804
+ let parentToolCallId: string | undefined;
799
805
 
800
806
  try {
801
807
  const conversation = await this.conversationStore.getWithArchive(childConversationId);
802
808
  if (!conversation) throw new Error("Subagent conversation not found");
809
+ parentToolCallId = conversation.subagentMeta?.parentToolCallId;
803
810
 
804
811
  if (conversation.subagentMeta?.status === "stopped") return;
805
812
 
@@ -1054,6 +1061,9 @@ export class AgentOrchestrator {
1054
1061
  type: "subagent:completed",
1055
1062
  subagentId: childConversationId,
1056
1063
  conversationId: childConversationId,
1064
+ task,
1065
+ parentToolCallId,
1066
+ resultText: subagentResponse,
1057
1067
  });
1058
1068
 
1059
1069
  this.triggerParentCallback(parentConversationId).catch(err =>
@@ -1090,6 +1100,8 @@ export class AgentOrchestrator {
1090
1100
  subagentId: childConversationId,
1091
1101
  conversationId: childConversationId,
1092
1102
  error: errMsg,
1103
+ task,
1104
+ parentToolCallId,
1093
1105
  });
1094
1106
 
1095
1107
  this.triggerParentCallback(parentConversationId).catch(err2 =>
@@ -1476,6 +1488,9 @@ export class AgentOrchestrator {
1476
1488
  type: "subagent:completed",
1477
1489
  subagentId: conversationId,
1478
1490
  conversationId,
1491
+ task,
1492
+ parentToolCallId: conversation.subagentMeta?.parentToolCallId,
1493
+ resultText: subagentResponse,
1479
1494
  });
1480
1495
 
1481
1496
  if (parentConv) {
@@ -1520,6 +1535,8 @@ export class AgentOrchestrator {
1520
1535
  type: "subagent:completed",
1521
1536
  subagentId: conversationId,
1522
1537
  conversationId,
1538
+ task,
1539
+ parentToolCallId: conversation.subagentMeta?.parentToolCallId,
1523
1540
  });
1524
1541
 
1525
1542
  if (parentConv) {
@@ -1553,7 +1570,7 @@ export class AgentOrchestrator {
1553
1570
  opts.tenantId ?? null,
1554
1571
  {
1555
1572
  parentConversationId: opts.parentConversationId,
1556
- subagentMeta: { task: opts.task, status: "running", suppressTelemetry: opts.suppressTelemetry },
1573
+ subagentMeta: { task: opts.task, status: "running", suppressTelemetry: opts.suppressTelemetry, parentToolCallId: opts.parentToolCallId },
1557
1574
  messages: [{ role: "user", content: opts.task }],
1558
1575
  },
1559
1576
  );
@@ -1568,6 +1585,7 @@ export class AgentOrchestrator {
1568
1585
  subagentId: conversation.conversationId,
1569
1586
  conversationId: conversation.conversationId,
1570
1587
  task: opts.task,
1588
+ parentToolCallId: opts.parentToolCallId,
1571
1589
  });
1572
1590
 
1573
1591
  if (this.isServerless) {
package/src/state.ts CHANGED
@@ -79,6 +79,9 @@ export interface Conversation {
79
79
  * subagent's runs emit no telemetry (e.g. spawned from an incognito
80
80
  * turn). Read by the orchestrator's runSubagent / continuation. */
81
81
  suppressTelemetry?: boolean;
82
+ /** The parent's `spawn_subagent` tool call id — echoed onto subagent:*
83
+ * events so a client can attach subagent state to that tool's pill. */
84
+ parentToolCallId?: string;
82
85
  };
83
86
  channelMeta?: {
84
87
  platform: string;
@@ -40,6 +40,10 @@ export interface SubagentManager {
40
40
  /** Inherit the parent run's telemetry choice — when true, the subagent
41
41
  * run (and its re-runs) emit no telemetry. */
42
42
  suppressTelemetry?: boolean;
43
+ /** The id of the `spawn_subagent` tool call that produced this subagent,
44
+ * so its events can carry `parentToolCallId` and a client can attach
45
+ * subagent state to the spawning tool's pill. */
46
+ parentToolCallId?: string;
43
47
  }): Promise<SubagentSpawnResult>;
44
48
 
45
49
  sendMessage(subagentId: string, message: string): Promise<SubagentSpawnResult>;
@@ -46,6 +46,7 @@ export const createSubagentTools = (
46
46
  ownerId,
47
47
  tenantId: context.tenantId,
48
48
  suppressTelemetry: context.suppressTelemetry,
49
+ parentToolCallId: context.toolCallId,
49
50
  });
50
51
  return { subagentId, status: "running" };
51
52
  },
@@ -62,7 +62,10 @@ export class ToolDispatcher {
62
62
  }
63
63
 
64
64
  try {
65
- const output = await definition.handler(call.input, context);
65
+ // Per-call context: stamp the current tool call's id so handlers that
66
+ // spawn further work (spawn_subagent) can record `parentToolCallId`.
67
+ // A fresh object — `context` is shared across a batch, don't mutate it.
68
+ const output = await definition.handler(call.input, { ...context, toolCallId: call.id });
66
69
  if (context.abortSignal?.aborted) {
67
70
  return {
68
71
  callId: call.id,