@botbotgo/agent-harness 0.0.341 → 0.0.342

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.
@@ -22,6 +22,8 @@ export async function streamChatMessage(input) {
22
22
  let liveRequestAnnotations = [];
23
23
  let persistedRequestTreeEventCount = 0;
24
24
  let persistedRequestTreeTodoSignature = "";
25
+ let previousTodoStatuses = new Map();
26
+ const renderedTodoTransitionLines = new Set();
25
27
  const requestTreeRenderThrottleMs = 75;
26
28
  let suppressRequestTreeRendering = false;
27
29
  let lastStableRequestTreeKey;
@@ -193,6 +195,46 @@ export async function streamChatMessage(input) {
193
195
  return true;
194
196
  };
195
197
  const formatAgentProgressLabel = (agentId) => agentId && agentId.trim().length > 0 ? ` agent:${agentId}` : "";
198
+ const buildTodoTransitionKey = (todo) => todo.key || todo.id || `content:${todo.content.trim().toLowerCase()}`;
199
+ const readTerminalTodoTransitionLabel = (status) => {
200
+ if (status === "completed") {
201
+ return "completed";
202
+ }
203
+ if (status === "failed") {
204
+ return "failed";
205
+ }
206
+ if (status === "cancelled") {
207
+ return "cancelled";
208
+ }
209
+ return null;
210
+ };
211
+ const writeTodoTransitionProgress = (snapshot) => {
212
+ const nextStatuses = new Map();
213
+ const lines = [];
214
+ for (const todo of snapshot.plan.items) {
215
+ const key = buildTodoTransitionKey(todo);
216
+ nextStatuses.set(key, todo.status);
217
+ const label = readTerminalTodoTransitionLabel(todo.status);
218
+ if (!label) {
219
+ continue;
220
+ }
221
+ const previousStatus = previousTodoStatuses.get(key);
222
+ if (!previousStatus || previousStatus === todo.status) {
223
+ continue;
224
+ }
225
+ const text = `TODO ${label}: ${todo.content}.`;
226
+ if (renderedTodoTransitionLines.has(text)) {
227
+ continue;
228
+ }
229
+ renderedTodoTransitionLines.add(text);
230
+ lines.push(`[${formatPerfClock(Date.now())} +${formatElapsed(Date.now())}]${formatAgentProgressLabel(snapshot.agentId)} ${text}\n`);
231
+ }
232
+ previousTodoStatuses = nextStatuses;
233
+ if (lines.length === 0) {
234
+ return;
235
+ }
236
+ writeChatStderr(lines.join(""));
237
+ };
196
238
  const renderContentBlocks = (contentBlocks, agentId) => {
197
239
  latestAgentId = agentId || latestAgentId;
198
240
  const rendered = contentBlocks
@@ -221,6 +263,9 @@ export async function streamChatMessage(input) {
221
263
  latestAgentId = snapshot.agentId || latestAgentId;
222
264
  latestSnapshot = snapshot;
223
265
  firstSnapshotAt ??= Date.now();
266
+ if (!input.requestEvents) {
267
+ writeTodoTransitionProgress(snapshot);
268
+ }
224
269
  if (input.requestEvents && !suppressRequestTreeRendering) {
225
270
  const now = Date.now();
226
271
  const nextTreeKey = buildRequestSnapshotRenderKey(snapshot);
@@ -283,6 +328,9 @@ export async function streamChatMessage(input) {
283
328
  if (wroteContent || wroteRenderableBlocks) {
284
329
  return;
285
330
  }
331
+ if (renderedTodoTransitionLines.has(delta.text)) {
332
+ return;
333
+ }
286
334
  const progressLine = `[${formatPerfClock(Date.now())} +${formatElapsed(Date.now())}]${formatAgentProgressLabel(delta.agentId)} ${delta.text}\n`;
287
335
  if (input.requestEvents && input.liveRequestTree && !suppressRequestTreeRendering && lastRenderedRequestTree) {
288
336
  liveRequestAnnotations.push(progressLine);
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.341";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.342";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.341";
1
+ export const AGENT_HARNESS_VERSION = "0.0.342";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -363,6 +363,32 @@ function summarizePlanState(planState) {
363
363
  const suffix = planState.items.length > items.length ? ` | +${planState.items.length - items.length} more` : "";
364
364
  return `TODO: ${items.join(" | ")}${suffix}`;
365
365
  }
366
+ function summarizePlanStateTerminalTransitions(previousPlanState, nextPlanState) {
367
+ const previousByKey = new Map((previousPlanState?.items ?? []).map((item) => [normalizePlanItemKey(item), item]));
368
+ const terminalLabel = (status) => {
369
+ switch (status) {
370
+ case "completed":
371
+ return "completed";
372
+ case "failed":
373
+ return "failed";
374
+ case "cancelled":
375
+ return "cancelled";
376
+ default:
377
+ return null;
378
+ }
379
+ };
380
+ return nextPlanState.items.flatMap((item) => {
381
+ const label = terminalLabel(item.status);
382
+ if (!label) {
383
+ return [];
384
+ }
385
+ const previousStatus = previousByKey.get(normalizePlanItemKey(item))?.status;
386
+ if (previousStatus === item.status) {
387
+ return [];
388
+ }
389
+ return [`TODO ${label}: ${item.content}.`];
390
+ });
391
+ }
366
392
  function createSurfaceCommentary(surfaceItem) {
367
393
  const name = normalizeCommentaryText(surfaceItem.name);
368
394
  if (!name) {
@@ -574,12 +600,16 @@ export async function* streamHarnessRun(options) {
574
600
  const mergedPlanState = mergePartialPlanState(currentPlanState, upstreamPlanState);
575
601
  const signature = buildPlanStateSignature(mergedPlanState);
576
602
  if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, mergedPlanState)) {
603
+ const previousPlanState = currentPlanState;
577
604
  planStateVersion = mergedPlanState.version;
578
605
  lastPlanStateSignature = signature;
579
606
  currentPlanState = mergedPlanState;
580
607
  for (const item of await emitPlanStateUpdate(options, currentAgentId, mergedPlanState)) {
581
608
  yield item;
582
609
  }
610
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, mergedPlanState)) {
611
+ yield* emitCommentary(commentary);
612
+ }
583
613
  const commentary = summarizePlanState(mergedPlanState);
584
614
  if (commentary) {
585
615
  yield* emitCommentary(commentary);
@@ -733,11 +763,15 @@ export async function* streamHarnessRun(options) {
733
763
  const mergedPlanState = mergePartialPlanState(currentPlanState, planState);
734
764
  const signature = buildPlanStateSignature(mergedPlanState);
735
765
  if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, mergedPlanState)) {
766
+ const previousPlanState = currentPlanState;
736
767
  lastPlanStateSignature = signature;
737
768
  currentPlanState = mergedPlanState;
738
769
  for (const item of await emitPlanStateUpdate(options, currentAgentId, mergedPlanState)) {
739
770
  yield item;
740
771
  }
772
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, mergedPlanState)) {
773
+ yield* emitCommentary(commentary);
774
+ }
741
775
  const commentary = summarizePlanState(mergedPlanState);
742
776
  if (commentary) {
743
777
  yield* emitCommentary(commentary);
@@ -751,12 +785,16 @@ export async function* streamHarnessRun(options) {
751
785
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
752
786
  const signature = buildPlanStateSignature(reconciledPlanState);
753
787
  if (signature !== lastPlanStateSignature) {
788
+ const previousPlanState = currentPlanState;
754
789
  planStateVersion = reconciledPlanState.version;
755
790
  lastPlanStateSignature = signature;
756
791
  currentPlanState = reconciledPlanState;
757
792
  for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
758
793
  yield item;
759
794
  }
795
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
796
+ yield* emitCommentary(commentary);
797
+ }
760
798
  const commentary = summarizePlanState(reconciledPlanState);
761
799
  if (commentary) {
762
800
  yield* emitCommentary(commentary);
@@ -816,12 +854,16 @@ export async function* streamHarnessRun(options) {
816
854
  const mergedPlanState = mergePartialPlanState(currentPlanState, finalPlanState);
817
855
  const signature = buildPlanStateSignature(mergedPlanState);
818
856
  if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, mergedPlanState)) {
857
+ const previousPlanState = currentPlanState;
819
858
  planStateVersion = mergedPlanState.version;
820
859
  lastPlanStateSignature = signature;
821
860
  currentPlanState = mergedPlanState;
822
861
  for (const item of await emitPlanStateUpdate(options, currentAgentId, mergedPlanState)) {
823
862
  yield item;
824
863
  }
864
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, mergedPlanState)) {
865
+ yield* emitCommentary(commentary);
866
+ }
825
867
  const commentary = summarizePlanState(mergedPlanState);
826
868
  if (commentary) {
827
869
  yield* emitCommentary(commentary);
@@ -834,12 +876,16 @@ export async function* streamHarnessRun(options) {
834
876
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
835
877
  const signature = buildPlanStateSignature(reconciledPlanState);
836
878
  if (signature !== lastPlanStateSignature) {
879
+ const previousPlanState = currentPlanState;
837
880
  planStateVersion = reconciledPlanState.version;
838
881
  lastPlanStateSignature = signature;
839
882
  currentPlanState = reconciledPlanState;
840
883
  for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
841
884
  yield item;
842
885
  }
886
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
887
+ yield* emitCommentary(commentary);
888
+ }
843
889
  const commentary = summarizePlanState(reconciledPlanState);
844
890
  if (commentary) {
845
891
  yield* emitCommentary(commentary);
@@ -852,12 +898,16 @@ export async function* streamHarnessRun(options) {
852
898
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, "completed", new Date().toISOString());
853
899
  const signature = buildPlanStateSignature(reconciledPlanState);
854
900
  if (signature !== lastPlanStateSignature) {
901
+ const previousPlanState = currentPlanState;
855
902
  planStateVersion = reconciledPlanState.version;
856
903
  lastPlanStateSignature = signature;
857
904
  currentPlanState = reconciledPlanState;
858
905
  for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
859
906
  yield item;
860
907
  }
908
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
909
+ yield* emitCommentary(commentary);
910
+ }
861
911
  const commentary = summarizePlanState(reconciledPlanState);
862
912
  if (commentary) {
863
913
  yield* emitCommentary(commentary);
@@ -916,12 +966,16 @@ export async function* streamHarnessRun(options) {
916
966
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
917
967
  const signature = buildPlanStateSignature(reconciledPlanState);
918
968
  if (signature !== lastPlanStateSignature) {
969
+ const previousPlanState = currentPlanState;
919
970
  planStateVersion = reconciledPlanState.version;
920
971
  lastPlanStateSignature = signature;
921
972
  currentPlanState = reconciledPlanState;
922
973
  for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
923
974
  yield item;
924
975
  }
976
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
977
+ yield* emitCommentary(commentary);
978
+ }
925
979
  const commentary = summarizePlanState(reconciledPlanState);
926
980
  if (commentary) {
927
981
  yield* emitCommentary(commentary);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.341",
3
+ "version": "0.0.342",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",