@botbotgo/agent-harness 0.0.327 → 0.0.328

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 (55) hide show
  1. package/dist/cli/main.js +30 -3
  2. package/dist/contracts/runtime-requests.d.ts +1 -2
  3. package/dist/contracts/runtime-scheduling.d.ts +1 -1
  4. package/dist/flow/flow-graph-upstream.js +3 -7
  5. package/dist/package-version.d.ts +1 -1
  6. package/dist/package-version.js +1 -1
  7. package/dist/projections/request-events.js +0 -1
  8. package/dist/resource/isolation.js +51 -10
  9. package/dist/resources/toolkit.mjs +183 -0
  10. package/dist/resources/tools/cancel_request.mjs +1 -1
  11. package/dist/resources/tools/fetch_url.mjs +1 -1
  12. package/dist/resources/tools/http_request.mjs +1 -1
  13. package/dist/resources/tools/inspect_approvals.mjs +1 -1
  14. package/dist/resources/tools/inspect_artifacts.mjs +1 -1
  15. package/dist/resources/tools/inspect_events.mjs +1 -1
  16. package/dist/resources/tools/inspect_requests.mjs +1 -1
  17. package/dist/resources/tools/inspect_sessions.mjs +1 -1
  18. package/dist/resources/tools/list_files.mjs +1 -1
  19. package/dist/resources/tools/read_artifact.mjs +1 -1
  20. package/dist/resources/tools/request_approval.mjs +1 -1
  21. package/dist/resources/tools/run_command.mjs +1 -1
  22. package/dist/resources/tools/schedule_task.mjs +1 -1
  23. package/dist/resources/tools/search_files.mjs +1 -1
  24. package/dist/resources/tools/send_message.mjs +1 -1
  25. package/dist/runtime/adapter/compat/deepagent-compat.d.ts +0 -9
  26. package/dist/runtime/adapter/compat/deepagent-compat.js +0 -22
  27. package/dist/runtime/adapter/flow/stream-runtime.d.ts +4 -0
  28. package/dist/runtime/adapter/flow/stream-runtime.js +239 -8
  29. package/dist/runtime/adapter/local-tool-invocation.js +53 -0
  30. package/dist/runtime/adapter/middleware-assembly.js +174 -29
  31. package/dist/runtime/adapter/runtime-adapter-support.js +1 -2
  32. package/dist/runtime/adapter/stream-event-projection.d.ts +17 -0
  33. package/dist/runtime/adapter/stream-event-projection.js +217 -4
  34. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +0 -3
  35. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +37 -17
  36. package/dist/runtime/adapter/tool/resolved-tool.js +29 -3
  37. package/dist/runtime/agent-runtime-adapter.d.ts +3 -3
  38. package/dist/runtime/agent-runtime-adapter.js +12 -33
  39. package/dist/runtime/agent-runtime-assembly.d.ts +3 -21
  40. package/dist/runtime/agent-runtime-assembly.js +4 -56
  41. package/dist/runtime/harness/run/inspection.js +21 -5
  42. package/dist/runtime/harness/run/run-operations.js +2 -1
  43. package/dist/runtime/harness/run/stream-run.d.ts +3 -1
  44. package/dist/runtime/harness/run/stream-run.js +205 -29
  45. package/dist/runtime/harness.js +3 -0
  46. package/dist/runtime/parsing/output-content.js +11 -4
  47. package/dist/runtime/parsing/output-recovery.d.ts +3 -0
  48. package/dist/runtime/parsing/output-recovery.js +57 -11
  49. package/dist/runtime/parsing/output-tool-args.d.ts +4 -0
  50. package/dist/runtime/parsing/output-tool-args.js +122 -0
  51. package/dist/runtime/parsing/stream-event-parsing.js +37 -3
  52. package/dist/runtime/support/harness-support.d.ts +1 -0
  53. package/dist/runtime/support/harness-support.js +44 -2
  54. package/dist/tools.js +34 -4
  55. package/package.json +8 -8
@@ -1,16 +1,13 @@
1
1
  import { MemorySaver } from "@langchain/langgraph";
2
- import { createFilesystemMiddleware, createPatchToolCallsMiddleware } from "deepagents";
3
- import { todoListMiddleware } from "langchain";
4
2
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "./adapter/upstream-configurable-keys.js";
5
3
  import { asStructuredExecutableTool } from "./adapter/tool/resolved-tool.js";
6
4
  import { compileInterruptOn } from "./adapter/tool/interrupt-policy.js";
7
- import { getBindingBackendConfig, getBindingDeepAgentSubagents, getBindingExecutionKind, getBindingExecutionParams, getBindingInterruptCompatibilityRules, getBindingMemorySources, getBindingMiddlewareConfigs, getBindingPrimaryTools, getBindingSkills, getBindingStoreConfig, } from "./support/compiled-binding.js";
5
+ import { getBindingBackendConfig, getBindingExecutionKind, getBindingExecutionParams, getBindingInterruptCompatibilityRules, getBindingMemorySources, getBindingMiddlewareConfigs, getBindingPrimaryTools, getBindingSkills, getBindingStoreConfig, } from "./support/compiled-binding.js";
8
6
  export const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
9
7
  const BUILTIN_MIDDLEWARE_ALIAS_TOOL_NAMES = new Set([
10
8
  "list_files",
11
9
  "search_files",
12
10
  "run_command",
13
- "delegate_task",
14
11
  ]);
15
12
  const MODEL_EXPOSED_BUILTIN_MIDDLEWARE_TOOL_NAMES = new Set([
16
13
  "fetch_url",
@@ -145,65 +142,16 @@ export function buildDeepAgentCreateParams(input) {
145
142
  ...(input.resolvedBackend !== undefined ? { backend: input.resolvedBackend } : {}),
146
143
  };
147
144
  }
148
- export function shouldUseMinimalDeepAgentAssembly(input) {
149
- const executionKind = getBindingExecutionKind(input.binding);
150
- if (executionKind !== "deepagent") {
151
- return false;
152
- }
153
- if (input.resolvedBackend === undefined) {
154
- return false;
155
- }
156
- if (input.resolvedSkills.length > 0) {
157
- return false;
158
- }
159
- if (input.resolvedSubagents.length > 0 || getBindingDeepAgentSubagents(input.binding).length > 0) {
160
- return false;
161
- }
162
- if (getBindingMemorySources(input.binding).length > 0) {
163
- return false;
164
- }
165
- if (input.resolvedMiddleware.length > 0 || (getBindingMiddlewareConfigs(input.binding)?.length ?? 0) > 0) {
166
- return false;
167
- }
168
- return input.resolvedInterruptOn === undefined || Object.keys(input.resolvedInterruptOn).length === 0;
169
- }
170
- export function buildMinimalDeepAgentCreateParams(input) {
171
- const executionKind = getBindingExecutionKind(input.binding);
172
- if (executionKind !== "deepagent" || !getBindingExecutionParams(input.binding)) {
173
- throw new Error(`Agent ${input.binding.agent.id} has no runnable params`);
174
- }
175
- const upstreamParams = buildUpstreamCreateBaseParams(input.binding, [
176
- "systemPrompt",
177
- "contextSchema",
178
- "responseFormat",
179
- "name",
180
- "description",
181
- "includeAgentName",
182
- "version",
183
- ]);
184
- return {
185
- ...upstreamParams,
186
- model: input.resolvedModel,
187
- tools: input.resolvedTools,
188
- middleware: [
189
- todoListMiddleware(),
190
- createFilesystemMiddleware({ backend: input.resolvedBackend }),
191
- createPatchToolCallsMiddleware(),
192
- ],
193
- ...(input.resolvedCheckpointer !== undefined ? { checkpointer: input.resolvedCheckpointer } : {}),
194
- ...(input.resolvedStore !== undefined ? { store: input.resolvedStore } : {}),
195
- };
196
- }
197
- export function shouldAttachMinimalDeepAgentCheckpointer(binding, resolvedInterruptOn) {
145
+ export function shouldAttachDeepAgentCheckpointer(binding, resolvedInterruptOn) {
198
146
  if (binding.harnessRuntime.checkpointer !== undefined) {
199
147
  return true;
200
148
  }
201
149
  return resolvedInterruptOn !== undefined && Object.keys(resolvedInterruptOn).length > 0;
202
150
  }
203
- export function shouldAttachMinimalDeepAgentStore(binding) {
151
+ export function shouldAttachDeepAgentStore(binding) {
204
152
  return getBindingStoreConfig(binding) !== undefined || getBindingMemorySources(binding).length > 0;
205
153
  }
206
- export function shouldAttachMinimalDeepAgentBackend(binding) {
154
+ export function shouldAttachDeepAgentBackend(binding) {
207
155
  return (getBindingBackendConfig(binding) !== undefined ||
208
156
  getBindingMemorySources(binding).length > 0 ||
209
157
  getBindingSkills(binding).length > 0 ||
@@ -26,6 +26,16 @@ function isTaskDelegationCompletionEvent(event) {
26
26
  return ((eventName === "on_tool_end" && name === "task")
27
27
  || (eventName === "on_chain_end" && runType === "tool" && name === "task"));
28
28
  }
29
+ function isTaskDelegationFailureEvent(event) {
30
+ const eventName = typeof event.event === "string" ? event.event : "";
31
+ const name = typeof event.name === "string" ? event.name : "";
32
+ const runType = typeof event.run_type === "string" ? event.run_type : "";
33
+ return ((eventName === "on_tool_error" && name === "task")
34
+ || (eventName === "on_chain_error" && runType === "tool" && name === "task"));
35
+ }
36
+ function isTaskDelegationTerminalEvent(event) {
37
+ return isTaskDelegationCompletionEvent(event) || isTaskDelegationFailureEvent(event);
38
+ }
29
39
  function readTracingConfig(binding) {
30
40
  const deepAgentTracing = asObject(binding.harnessRuntime?.deepagent?.passthrough)?.tracing;
31
41
  const langchainTracing = asObject(binding.harnessRuntime?.langchain?.passthrough)?.tracing;
@@ -152,9 +162,7 @@ function extractSubagentFromTaskToolEvent(event) {
152
162
  const input = asObject(data?.input);
153
163
  const subagentType = typeof input?.subagent_type === "string"
154
164
  ? input.subagent_type
155
- : typeof input?.subagentType === "string"
156
- ? input.subagentType
157
- : null;
165
+ : null;
158
166
  return subagentType && subagentType.trim().length > 0 ? subagentType.trim() : null;
159
167
  }
160
168
  function extractKnownSubagentFromEvent(event, knownSubagentIds) {
@@ -175,6 +183,14 @@ export function consumeRunInspectionUpstreamEvent(input) {
175
183
  }
176
184
  const knownSubagentIds = new Set(getBindingSubagents(input.binding).map((subagent) => subagent.name));
177
185
  const delegatedAgentId = extractSubagentFromTaskToolEvent(typed) ?? extractKnownSubagentFromEvent(typed, knownSubagentIds);
186
+ if (isTaskDelegationTerminalEvent(typed) && input.delegationChain.length > 1) {
187
+ const nextDelegationChain = input.delegationChain.slice(0, -1);
188
+ const nextAgentId = nextDelegationChain.at(-1) ?? input.currentAgentId;
189
+ return {
190
+ currentAgentId: nextAgentId,
191
+ delegationChain: nextDelegationChain,
192
+ };
193
+ }
178
194
  if (!delegatedAgentId) {
179
195
  return {
180
196
  currentAgentId: input.currentAgentId,
@@ -299,7 +315,7 @@ export function projectRuntimeSurfaceFromSingleUpstreamEvent(input) {
299
315
  },
300
316
  });
301
317
  }
302
- if (isTaskDelegationCompletionEvent(typed) && previousDelegationChain.length > 1) {
318
+ if (isTaskDelegationTerminalEvent(typed) && previousDelegationChain.length > 1) {
303
319
  const delegatedAgentId = previousDelegationChain.at(-1);
304
320
  const ownerAgentId = previousDelegationChain.at(-2);
305
321
  const delegatedAgentName = formatAgentName(delegatedAgentId);
@@ -308,7 +324,7 @@ export function projectRuntimeSurfaceFromSingleUpstreamEvent(input) {
308
324
  id: buildSurfaceId("agent", delegatedAgentId),
309
325
  name: delegatedAgentName,
310
326
  action: "handoff",
311
- status: "completed",
327
+ status: isTaskDelegationFailureEvent(typed) ? "failed" : "completed",
312
328
  agentId: delegatedAgentId,
313
329
  agentName: delegatedAgentName,
314
330
  ...(ownerAgentId ? { ownerAgentId, ownerAgentName: formatAgentName(ownerAgentId) } : {}),
@@ -1,3 +1,4 @@
1
+ import { describeRuntimeError } from "../../support/harness-support.js";
1
2
  import { isTerminalRequestState } from "./helpers.js";
2
3
  async function finalizeIfCancellationRequested(input) {
3
4
  const cancellation = await input.getRequestCancellation(input.requestId);
@@ -200,7 +201,7 @@ export async function executeQueuedRequestOperation(runtime, input) {
200
201
  await runtime.emitSyntheticFallback(sessionId, requestId, agentId, error);
201
202
  await runtime.setRequestStateAndEmit(sessionId, requestId, 104, "failed", {
202
203
  previousState: continuationState,
203
- error: error instanceof Error ? error.message : String(error),
204
+ error: describeRuntimeError(error),
204
205
  });
205
206
  return {
206
207
  sessionId: sessionId,
@@ -1,4 +1,4 @@
1
- import type { CompiledAgentBinding, HarnessEvent, MemoryRecord, MessageContent, RequestResult, RequestExecutionStep, RuntimeSnapshot, TranscriptMessage } from "../../../contracts/types.js";
1
+ import type { CompiledAgentBinding, HarnessEvent, MemoryRecord, MessageContent, RequestPlanState, RequestResult, RequestExecutionStep, RuntimeSnapshot, TranscriptMessage } from "../../../contracts/types.js";
2
2
  import { type InternalHarnessStreamItem } from "../events/streaming.js";
3
3
  type RuntimeStreamChunk = {
4
4
  kind: "commentary";
@@ -65,6 +65,8 @@ type StreamRunOptions = {
65
65
  runtimeSnapshot?: RuntimeSnapshot | null;
66
66
  }) => Promise<void>;
67
67
  appendRequestTraceItem: (sessionId: string, requestId: string, item: unknown) => Promise<void>;
68
+ loadRequestPlanState?: (sessionId: string, requestId: string) => Promise<RequestPlanState | null>;
69
+ saveRequestPlanState?: (sessionId: string, requestId: string, planState: RequestPlanState) => Promise<void>;
68
70
  emitSyntheticFallback: (sessionId: string, requestId: string, selectedAgentId: string, error: unknown) => Promise<void>;
69
71
  };
70
72
  export declare function streamHarnessRun(options: StreamRunOptions): AsyncGenerator<InternalHarnessStreamItem>;
@@ -1,6 +1,7 @@
1
1
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError } from "../../agent-runtime-adapter.js";
2
+ import { ExecutionReconciliationError } from "../../adapter/flow/stream-runtime.js";
2
3
  import { buildRequestPlanState, summarizeBuiltinWriteTodosArgs } from "../../adapter/runtime-adapter-support.js";
3
- import { renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
4
+ import { describeRuntimeError, renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
4
5
  import { getBindingPrimaryModel } from "../../support/compiled-binding.js";
5
6
  import { createContentBlocksItem, createToolResultKey, } from "../events/streaming.js";
6
7
  import { projectRuntimeSurfaceFromSingleUpstreamEvent } from "./inspection.js";
@@ -19,10 +20,64 @@ function createInitialPlanState(sessionId, requestId, updatedAt) {
19
20
  completed: 0,
20
21
  failed: 0,
21
22
  cancelled: 0,
22
- blocked: 0,
23
23
  },
24
24
  };
25
25
  }
26
+ function planStateHasUnfinishedItems(planState) {
27
+ if (!planState) {
28
+ return false;
29
+ }
30
+ return planState.summary.pending > 0 || planState.summary.inProgress > 0;
31
+ }
32
+ function planStateHasActiveItems(planState) {
33
+ if (!planState) {
34
+ return false;
35
+ }
36
+ return planState.summary.pending > 0 || planState.summary.inProgress > 0;
37
+ }
38
+ function readTerminalStructuredStatus(value) {
39
+ if (typeof value === "string") {
40
+ try {
41
+ return readTerminalStructuredStatus(JSON.parse(value));
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ if (typeof value !== "object" || value === null) {
48
+ return null;
49
+ }
50
+ const typed = value;
51
+ if (typed.status === "completed") {
52
+ return typed.status;
53
+ }
54
+ return (readTerminalStructuredStatus(typed.structuredResponse)
55
+ ?? readTerminalStructuredStatus(typed.output)
56
+ ?? readTerminalStructuredStatus(typed.data));
57
+ }
58
+ function reconcilePlanStateToTerminalStatus(planState, status, updatedAt) {
59
+ const items = planState.items.map((item) => ({
60
+ ...item,
61
+ status: item.status === "pending" || item.status === "in_progress"
62
+ ? status
63
+ : item.status,
64
+ }));
65
+ const summary = {
66
+ total: items.length,
67
+ pending: 0,
68
+ inProgress: 0,
69
+ completed: items.filter((item) => item.status === "completed").length,
70
+ failed: items.filter((item) => item.status === "failed").length,
71
+ cancelled: items.filter((item) => item.status === "cancelled").length,
72
+ };
73
+ return {
74
+ ...planState,
75
+ version: planState.version + 1,
76
+ updatedAt,
77
+ items,
78
+ summary,
79
+ };
80
+ }
26
81
  function getPlanStateFromToolResult(input) {
27
82
  if (typeof input.output !== "object" || input.output === null) {
28
83
  return null;
@@ -80,6 +135,26 @@ function buildPlanStateSignature(planState) {
80
135
  function countStructuredTodoIds(items) {
81
136
  return items.filter((item) => typeof item.id === "string" && item.id.length > 0).length;
82
137
  }
138
+ async function emitPlanStateUpdate(options, agentId, planState) {
139
+ await options.saveRequestPlanState?.(options.sessionId, options.requestId, planState);
140
+ return [{
141
+ type: "plan-state",
142
+ sessionId: options.sessionId,
143
+ requestId: options.requestId,
144
+ agentId,
145
+ planState,
146
+ }];
147
+ }
148
+ async function refreshPlanStateFromPersistence(options, currentPlanState) {
149
+ const persistedPlanState = await options.loadRequestPlanState?.(options.sessionId, options.requestId);
150
+ if (!persistedPlanState) {
151
+ return currentPlanState;
152
+ }
153
+ if (!currentPlanState || persistedPlanState.version >= currentPlanState.version) {
154
+ return persistedPlanState;
155
+ }
156
+ return currentPlanState;
157
+ }
83
158
  function shouldEmitPlanState(currentPlanState, nextPlanState) {
84
159
  if (!currentPlanState || currentPlanState.items.length === 0) {
85
160
  return true;
@@ -210,7 +285,6 @@ function summarizePlanState(planState) {
210
285
  planState.summary.inProgress > 0 ? `${planState.summary.inProgress} in progress` : "",
211
286
  planState.summary.pending > 0 ? `${planState.summary.pending} pending` : "",
212
287
  planState.summary.completed > 0 ? `${planState.summary.completed} completed` : "",
213
- planState.summary.blocked > 0 ? `${planState.summary.blocked} blocked` : "",
214
288
  planState.summary.failed > 0 ? `${planState.summary.failed} failed` : "",
215
289
  ].filter((value) => value.length > 0);
216
290
  if (counts.length === 0) {
@@ -228,6 +302,9 @@ function createSurfaceCommentary(surfaceItem) {
228
302
  return `Running tool ${name}.`;
229
303
  }
230
304
  if (surfaceItem.status === "completed") {
305
+ if (name.toLowerCase() === "task") {
306
+ return null;
307
+ }
231
308
  return `Tool ${name} completed.`;
232
309
  }
233
310
  if (surfaceItem.status === "failed") {
@@ -252,7 +329,7 @@ function createSurfaceCommentary(surfaceItem) {
252
329
  return `Delegating work to ${name}.`;
253
330
  }
254
331
  if (surfaceItem.status === "completed") {
255
- return `Delegation to ${name} completed.`;
332
+ return null;
256
333
  }
257
334
  if (surfaceItem.status === "failed") {
258
335
  return `Delegation to ${name} failed.`;
@@ -360,6 +437,7 @@ export async function* streamHarnessRun(options) {
360
437
  let streamActivityObserved = false;
361
438
  let nonUpstreamStreamActivityObserved = false;
362
439
  let assistantOutput = "";
440
+ let assistantOutputCameFromInvokeFallback = false;
363
441
  const bufferAssistantTextUntilCompletion = true;
364
442
  let currentAgentId = options.selectedAgentId;
365
443
  let currentAgentName = formatAgentName(options.selectedAgentId);
@@ -425,19 +503,32 @@ export async function* streamHarnessRun(options) {
425
503
  planStateVersion = upstreamPlanState.version;
426
504
  lastPlanStateSignature = signature;
427
505
  currentPlanState = upstreamPlanState;
428
- yield {
429
- type: "plan-state",
430
- sessionId: options.sessionId,
431
- requestId: options.requestId,
432
- agentId: currentAgentId,
433
- planState: upstreamPlanState,
434
- };
506
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, upstreamPlanState)) {
507
+ yield item;
508
+ }
435
509
  const commentary = summarizePlanState(upstreamPlanState);
436
510
  if (commentary) {
437
511
  yield* emitCommentary(commentary);
438
512
  }
439
513
  }
440
514
  }
515
+ const terminalStructuredStatus = readTerminalStructuredStatus(normalizedChunk.event);
516
+ if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
517
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
518
+ const signature = buildPlanStateSignature(reconciledPlanState);
519
+ if (signature !== lastPlanStateSignature) {
520
+ planStateVersion = reconciledPlanState.version;
521
+ lastPlanStateSignature = signature;
522
+ currentPlanState = reconciledPlanState;
523
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
524
+ yield item;
525
+ }
526
+ const commentary = summarizePlanState(reconciledPlanState);
527
+ if (commentary) {
528
+ yield* emitCommentary(commentary);
529
+ }
530
+ }
531
+ }
441
532
  upstreamEventOrdinal += 1;
442
533
  const projectionBinding = options.getBinding(currentAgentId) ?? options.binding;
443
534
  const surfaceProjection = projectRuntimeSurfaceFromSingleUpstreamEvent({
@@ -578,19 +669,32 @@ export async function* streamHarnessRun(options) {
578
669
  if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, planState)) {
579
670
  lastPlanStateSignature = signature;
580
671
  currentPlanState = planState;
581
- yield {
582
- type: "plan-state",
583
- sessionId: options.sessionId,
584
- requestId: options.requestId,
585
- agentId: currentAgentId,
586
- planState,
587
- };
672
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, planState)) {
673
+ yield item;
674
+ }
588
675
  const commentary = summarizePlanState(planState);
589
676
  if (commentary) {
590
677
  yield* emitCommentary(commentary);
591
678
  }
592
679
  }
593
680
  }
681
+ const terminalStructuredStatus = readTerminalStructuredStatus(normalizedChunk.output);
682
+ if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
683
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
684
+ const signature = buildPlanStateSignature(reconciledPlanState);
685
+ if (signature !== lastPlanStateSignature) {
686
+ planStateVersion = reconciledPlanState.version;
687
+ lastPlanStateSignature = signature;
688
+ currentPlanState = reconciledPlanState;
689
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
690
+ yield item;
691
+ }
692
+ const commentary = summarizePlanState(reconciledPlanState);
693
+ if (commentary) {
694
+ yield* emitCommentary(commentary);
695
+ }
696
+ }
697
+ }
594
698
  continue;
595
699
  }
596
700
  emitted = true;
@@ -614,6 +718,7 @@ export async function* streamHarnessRun(options) {
614
718
  assistantOutput = toolErrors.join("\n\n");
615
719
  emitted = true;
616
720
  }
721
+ currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
617
722
  if (!assistantOutput) {
618
723
  const actual = await options.invokeWithHistory(options.binding, options.input, options.sessionId, options.requestId);
619
724
  if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
@@ -622,6 +727,7 @@ export async function* streamHarnessRun(options) {
622
727
  if (actual.output) {
623
728
  assistantOutput = actual.output;
624
729
  emitted = true;
730
+ assistantOutputCameFromInvokeFallback = true;
625
731
  }
626
732
  const finalPlanState = getLatestPlanStateFromExecutedToolResults({
627
733
  sessionId: options.sessionId,
@@ -636,19 +742,42 @@ export async function* streamHarnessRun(options) {
636
742
  planStateVersion = finalPlanState.version;
637
743
  lastPlanStateSignature = signature;
638
744
  currentPlanState = finalPlanState;
639
- yield {
640
- type: "plan-state",
641
- sessionId: options.sessionId,
642
- requestId: options.requestId,
643
- agentId: currentAgentId,
644
- planState: finalPlanState,
645
- };
745
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, finalPlanState)) {
746
+ yield item;
747
+ }
646
748
  const commentary = summarizePlanState(finalPlanState);
647
749
  if (commentary) {
648
750
  yield* emitCommentary(commentary);
649
751
  }
650
752
  }
651
753
  }
754
+ currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
755
+ const terminalStructuredStatus = readTerminalStructuredStatus(actual.structuredResponse);
756
+ if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
757
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
758
+ const signature = buildPlanStateSignature(reconciledPlanState);
759
+ if (signature !== lastPlanStateSignature) {
760
+ planStateVersion = reconciledPlanState.version;
761
+ lastPlanStateSignature = signature;
762
+ currentPlanState = reconciledPlanState;
763
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
764
+ yield item;
765
+ }
766
+ const commentary = summarizePlanState(reconciledPlanState);
767
+ if (commentary) {
768
+ yield* emitCommentary(commentary);
769
+ }
770
+ }
771
+ }
772
+ }
773
+ currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
774
+ if (assistantOutputCameFromInvokeFallback
775
+ && nonUpstreamStreamActivityObserved
776
+ && planStateHasActiveItems(currentPlanState)) {
777
+ throw new ExecutionReconciliationError("Agent ended while the streamed plan state still had unfinished work.");
778
+ }
779
+ if (planStateHasActiveItems(currentPlanState)) {
780
+ throw new ExecutionReconciliationError("Agent ended while the streamed plan state still had unfinished work.");
652
781
  }
653
782
  if (assistantOutput && bufferAssistantTextUntilCompletion) {
654
783
  yield {
@@ -686,13 +815,15 @@ export async function* streamHarnessRun(options) {
686
815
  catch (error) {
687
816
  const shouldRetryAfterStreamingCompatibilityError = !assistantOutput &&
688
817
  isOpenAICompatibleStreamingCompatibilityError(options.binding, error);
689
- if ((emitted || streamActivityObserved) && !shouldRetryAfterStreamingCompatibilityError) {
818
+ if ((emitted || streamActivityObserved)
819
+ && !shouldRetryAfterStreamingCompatibilityError) {
690
820
  const runtimeFailure = renderRuntimeFailure(error);
821
+ const detailedError = describeRuntimeError(error);
691
822
  yield {
692
823
  type: "event",
693
824
  event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
694
825
  previousState: "running",
695
- error: error instanceof Error ? error.message : String(error),
826
+ error: detailedError,
696
827
  }),
697
828
  };
698
829
  yield {
@@ -717,11 +848,12 @@ export async function* streamHarnessRun(options) {
717
848
  }
718
849
  if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
719
850
  const runtimeFailure = renderRuntimeFailure(error);
851
+ const detailedError = describeRuntimeError(error);
720
852
  yield {
721
853
  type: "event",
722
854
  event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
723
855
  previousState: "running",
724
- error: error.message,
856
+ error: detailedError,
725
857
  }),
726
858
  };
727
859
  yield {
@@ -744,6 +876,36 @@ export async function* streamHarnessRun(options) {
744
876
  };
745
877
  return;
746
878
  }
879
+ if (error instanceof ExecutionReconciliationError) {
880
+ const runtimeFailure = renderRuntimeFailure(error);
881
+ const detailedError = describeRuntimeError(error);
882
+ yield {
883
+ type: "event",
884
+ event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
885
+ previousState: "running",
886
+ error: detailedError,
887
+ }),
888
+ };
889
+ yield {
890
+ type: "content",
891
+ sessionId: options.sessionId,
892
+ requestId: options.requestId,
893
+ agentId: options.selectedAgentId,
894
+ content: runtimeFailure,
895
+ };
896
+ yield {
897
+ type: "result",
898
+ result: {
899
+ sessionId: options.sessionId,
900
+ requestId: options.requestId,
901
+ agentId: currentAgentId,
902
+ state: "failed",
903
+ output: runtimeFailure,
904
+ finalMessageText: runtimeFailure,
905
+ },
906
+ };
907
+ return;
908
+ }
747
909
  try {
748
910
  syntheticFallback = {
749
911
  strategy: "stream-to-invoke",
@@ -757,6 +919,19 @@ export async function* streamHarnessRun(options) {
757
919
  if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
758
920
  yield createContentBlocksItem(options.sessionId, options.requestId, options.selectedAgentId, actual.contentBlocks);
759
921
  }
922
+ if (actual.output) {
923
+ yield {
924
+ type: "event",
925
+ event: await options.emit(options.sessionId, options.requestId, 3, "output.delta", { content: actual.output }),
926
+ };
927
+ yield {
928
+ type: "content",
929
+ sessionId: options.sessionId,
930
+ requestId: options.requestId,
931
+ agentId: currentAgentId,
932
+ content: actual.output,
933
+ };
934
+ }
760
935
  const terminalEvent = await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, actual.state, {
761
936
  previousState: "running",
762
937
  });
@@ -798,11 +973,12 @@ export async function* streamHarnessRun(options) {
798
973
  };
799
974
  await options.emitSyntheticFallback(options.sessionId, options.requestId, options.selectedAgentId, syntheticFallback);
800
975
  const runtimeFailure = renderRuntimeFailure(invokeError);
976
+ const detailedError = describeRuntimeError(invokeError);
801
977
  yield {
802
978
  type: "event",
803
979
  event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
804
980
  previousState: "running",
805
- error: invokeError instanceof Error ? invokeError.message : String(invokeError),
981
+ error: detailedError,
806
982
  }),
807
983
  };
808
984
  yield {
@@ -215,6 +215,7 @@ export class AgentHarnessRuntime {
215
215
  runtimeAdapterOptions: {
216
216
  ...runtimeAdapterOptions,
217
217
  scheduleManager: runtimeAdapterOptions.scheduleManager ?? this.scheduleManager,
218
+ bindingResolver: runtimeAdapterOptions.bindingResolver ?? ((agentId) => this.workspace.bindings.get(agentId)),
218
219
  functionToolContextResolver: runtimeAdapterOptions.functionToolContextResolver ?? ((input) => this.buildFunctionToolContext(input)),
219
220
  },
220
221
  checkpointers: this.checkpointers,
@@ -1303,6 +1304,8 @@ export class AgentHarnessRuntime {
1303
1304
  clearRequestInput: (sessionId, requestId) => this.persistence.clearRequestInput(sessionId, requestId),
1304
1305
  updateRequestInspection: (sessionId, requestId, patch) => this.persistence.updateRequestInspection(sessionId, requestId, patch),
1305
1306
  appendRequestTraceItem: (sessionId, requestId, item) => this.persistence.appendRequestTraceItem(sessionId, requestId, item),
1307
+ loadRequestPlanState: (sessionId, requestId) => this.persistence.getRequestPlanState(sessionId, requestId),
1308
+ saveRequestPlanState: (sessionId, requestId, planState) => this.persistence.saveRequestPlanState(sessionId, requestId, planState),
1306
1309
  emitSyntheticFallback: (sessionId, requestId, selectedAgentId, error) => this.runtimeEventOperations.emitSyntheticFallback(sessionId, requestId, selectedAgentId, error),
1307
1310
  });
1308
1311
  for await (const item of stream) {
@@ -1,5 +1,5 @@
1
1
  import { AIMessage } from "langchain";
2
- import { salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
2
+ import { salvageFunctionLikeToolCall, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
3
3
  export function sanitizeVisibleText(value) {
4
4
  return value
5
5
  .replace(/[A-Za-z0-9_]*Middleware\.after_model/g, "")
@@ -403,19 +403,26 @@ function normalizeAgentMessage(value) {
403
403
  })
404
404
  .filter((toolCall) => toolCall !== null)
405
405
  : [];
406
- const recoveredToolCalls = normalizedInvalidToolCalls.filter((toolCall) => typeof toolCall.args === "object" && !!toolCall.args && !Array.isArray(toolCall.args));
407
406
  const normalizedContent = typeof typed.content === "string" || Array.isArray(typed.content)
408
407
  ? typed.content
409
408
  : typeof typed.content === "object" && typed.content
410
409
  ? readTextContent(typed.content)
411
410
  : "";
411
+ const recoveredToolCalls = normalizedInvalidToolCalls.filter((toolCall) => typeof toolCall.args === "object" && !!toolCall.args && !Array.isArray(toolCall.args));
412
+ const functionLikeToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && typeof normalizedContent === "string"
413
+ ? salvageFunctionLikeToolCall(normalizedContent)
414
+ : null;
412
415
  return new AIMessage({
413
- content: normalizedContent,
416
+ content: functionLikeToolCall ? "" : normalizedContent,
414
417
  name: typeof typed.name === "string" ? typed.name : undefined,
415
418
  additional_kwargs: typeof typed.additional_kwargs === "object" && typed.additional_kwargs ? typed.additional_kwargs : {},
416
419
  response_metadata: typeof typed.response_metadata === "object" && typed.response_metadata ? typed.response_metadata : {},
417
420
  id: typeof typed.id === "string" ? typed.id : undefined,
418
- tool_calls: [...normalizedToolCalls, ...recoveredToolCalls],
421
+ tool_calls: [
422
+ ...normalizedToolCalls,
423
+ ...recoveredToolCalls,
424
+ ...(functionLikeToolCall ? [{ name: functionLikeToolCall.name, args: functionLikeToolCall.args }] : []),
425
+ ],
419
426
  invalid_tool_calls: normalizedInvalidToolCalls.filter((toolCall) => toolCall.type !== "tool_call"),
420
427
  usage_metadata: typeof typed.usage_metadata === "object" && typed.usage_metadata ? typed.usage_metadata : undefined,
421
428
  });
@@ -11,6 +11,9 @@ export declare function resolveExecutionWithoutToolEvidenceInstruction(request:
11
11
  export declare function resolveExecutionWithoutToolEvidenceTextInstruction(request: unknown, assistantText: string, toolCallEvidence?: boolean, resultEvidence?: {
12
12
  hasWriteTodosEvidence?: boolean;
13
13
  hasToolResultEvidence?: boolean;
14
+ hasIncompletePlanState?: boolean;
15
+ hasOpenTaskDelegation?: boolean;
16
+ hasMissingDelegatedExecutionEvidence?: boolean;
14
17
  }): string | null;
15
18
  export declare function resolveToolCallRecoveryInstruction(error: unknown): string | null;
16
19
  export declare function appendToolRecoveryInstruction(input: unknown, instruction: string): unknown;