@botbotgo/agent-harness 0.0.392 → 0.0.394

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,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.392";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.394";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.392";
1
+ export const AGENT_HARNESS_VERSION = "0.0.394";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
@@ -5,6 +5,7 @@ import { buildRawModelMessages } from "../model/message-assembly.js";
5
5
  import { projectRuntimeStreamEvent, createStreamEventProjectionState } from "../stream-event-projection.js";
6
6
  import { projectTextStreamChunks } from "../stream-text-consumption.js";
7
7
  import { computeRemainingTimeoutMs } from "../resilience.js";
8
+ import { stringifyToolOutput } from "../tool/tool-arguments.js";
8
9
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "../upstream-configurable-keys.js";
9
10
  export class ExecutionReconciliationError extends Error {
10
11
  constructor(message) {
@@ -61,12 +62,48 @@ function hasIncompletePlanStateInExecutedToolResults(executedToolResults) {
61
62
  }
62
63
  return false;
63
64
  }
65
+ function hasIncompleteTodosArray(value) {
66
+ if (!Array.isArray(value)) {
67
+ return null;
68
+ }
69
+ return value.some((todo) => typeof todo === "object"
70
+ && todo !== null
71
+ && typeof todo.status === "string"
72
+ && ["pending", "in_progress"].includes(todo.status.trim().toLowerCase()));
73
+ }
74
+ function hasIncompletePlanOutput(value) {
75
+ if (typeof value !== "object" || value === null) {
76
+ return null;
77
+ }
78
+ const typed = value;
79
+ if (typeof typed.pending === "number" || typeof typed.inProgress === "number") {
80
+ return (typeof typed.pending === "number" ? typed.pending : 0) > 0
81
+ || (typeof typed.inProgress === "number" ? typed.inProgress : 0) > 0;
82
+ }
83
+ const directTodos = hasIncompleteTodosArray(typed.todos);
84
+ if (directTodos !== null) {
85
+ return directTodos;
86
+ }
87
+ for (const nested of [typed.summary, typed.update, typed.data, typed.output]) {
88
+ const nestedCompleteness = hasIncompletePlanOutput(nested);
89
+ if (nestedCompleteness !== null) {
90
+ return nestedCompleteness;
91
+ }
92
+ }
93
+ return null;
94
+ }
64
95
  function isPlanToolName(toolName) {
65
96
  return toolName === "write_todos"
66
97
  || toolName === "read_todos"
67
98
  || toolName === "tool_call_write_todos"
68
99
  || toolName === "tool_call_read_todos";
69
100
  }
101
+ function isCompletedPlanToolResultChunk(chunk) {
102
+ if (chunk.kind !== "tool-result" || !isPlanToolName(chunk.toolName)) {
103
+ return false;
104
+ }
105
+ return hasIncompletePlanOutput(chunk.output) === false;
106
+ }
70
107
  function hasNonTodoToolEvidence(executedToolResults) {
71
108
  return executedToolResults.some((item) => !isPlanToolName(item.toolName));
72
109
  }
@@ -128,6 +165,26 @@ function hasCompletedPlanWithEvidence(evidence) {
128
165
  && !hasMissingDelegatedExecutionEvidence(evidence)
129
166
  && !hasMissingDelegatedFindings(evidence);
130
167
  }
168
+ function buildDeterministicFinalFromStreamToolEvidence(executedToolResults) {
169
+ const evidence = executedToolResults
170
+ .filter((item) => item.isError !== true && !isPlanToolName(item.toolName))
171
+ .map((item) => {
172
+ const visible = toVisibleContent(item.output);
173
+ const raw = visible || stringifyToolOutput(item.output).trim();
174
+ const clipped = raw.length > 4000 ? `${raw.slice(0, 4000)}\n... [truncated]` : raw;
175
+ return `## ${item.toolName}\n${clipped}`;
176
+ });
177
+ return [
178
+ "Status: completed",
179
+ "",
180
+ "Summary:",
181
+ "- Completed the required TODO burn down after collecting tool evidence.",
182
+ "- Returning an evidence-grounded deterministic summary because the streamed model did not produce a clean final synthesis before the required plan completed.",
183
+ "",
184
+ "Evidence:",
185
+ evidence.length > 0 ? evidence.join("\n\n") : "(no non-planning tool evidence captured)",
186
+ ].join("\n");
187
+ }
131
188
  function readBindingExecutionParams(binding) {
132
189
  const params = binding.execution?.params ?? binding.deepAgentParams ?? binding.langchainAgentParams;
133
190
  return {
@@ -460,6 +517,9 @@ export async function* streamRuntimeExecution(options) {
460
517
  if (shouldProfile)
461
518
  yield streamEventsConsume.chunk;
462
519
  try {
520
+ let sawCompletedPlanToolResult = false;
521
+ let sawSuccessfulNonTodoToolResult = false;
522
+ const streamedToolResults = [];
463
523
  for await (const event of options.iterateWithTimeout(events, options.streamIdleTimeoutMs, "agent streamEvents", options.streamDeadlineAt, options.invokeTimeoutMs)) {
464
524
  const projectedChunks = projectRuntimeStreamEvent({
465
525
  event,
@@ -484,18 +544,46 @@ export async function* streamRuntimeExecution(options) {
484
544
  if (chunk.kind === "tool-result" && chunk.isError === true && isRetrySafeInvalidToolSelectionError(chunk.output)) {
485
545
  sawRetrySafeInvalidToolSelectionError = true;
486
546
  }
547
+ if (chunk.kind === "tool-result") {
548
+ streamedToolResults.push({
549
+ toolName: chunk.toolName,
550
+ output: chunk.output,
551
+ isError: chunk.isError,
552
+ });
553
+ }
554
+ if (chunk.kind === "tool-result" && !isPlanToolName(chunk.toolName) && chunk.isError !== true) {
555
+ sawSuccessfulNonTodoToolResult = true;
556
+ }
557
+ if (isCompletedPlanToolResultChunk(chunk)) {
558
+ sawCompletedPlanToolResult = true;
559
+ }
487
560
  if ((eventContainsNonTodoToolResult || eventContainsNonRetrySafeChunk) && deferredStreamContent.length > 0) {
488
561
  yield* flushDeferredStreamContent();
489
562
  }
490
563
  if (eventContainsNonTodoToolResult || eventContainsNonRetrySafeChunk) {
491
564
  emittedUnsafeStreamSideEffects = true;
492
565
  }
493
- if (chunk.kind === "content" && (shouldDeferStreamContent() || projectionState.hasFailedTaskDelegation)) {
566
+ const shouldDeferRequiredPlanContent = requiresPlanEvidence(options.binding)
567
+ && projectionState.sawPlanState
568
+ && !hasCompletedPlanWithEvidence(buildExecutionRecoveryEvidence({ projectionState }));
569
+ if (chunk.kind === "content"
570
+ && (shouldDeferStreamContent() || projectionState.hasFailedTaskDelegation || shouldDeferRequiredPlanContent)) {
494
571
  deferredStreamContent.push(chunk);
495
572
  continue;
496
573
  }
497
574
  yield chunk;
498
575
  }
576
+ if (requiresPlanEvidence(options.binding) && sawCompletedPlanToolResult && sawSuccessfulNonTodoToolResult) {
577
+ deferredStreamContent.length = 0;
578
+ yield { kind: "content", content: buildDeterministicFinalFromStreamToolEvidence(streamedToolResults) };
579
+ return;
580
+ }
581
+ const eventExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
582
+ if (requiresPlanEvidence(options.binding) && hasCompletedPlanWithEvidence(eventExecutionEvidence)) {
583
+ deferredStreamContent.length = 0;
584
+ yield { kind: "content", content: buildDeterministicFinalFromStreamToolEvidence(streamedToolResults) };
585
+ return;
586
+ }
499
587
  const terminalVisibleOutput = readTerminalEventVisibleOutput(event);
500
588
  if (terminalVisibleOutput) {
501
589
  const terminalExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.392",
3
+ "version": "0.0.394",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",