@botbotgo/agent-harness 0.0.340 → 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.
Files changed (33) hide show
  1. package/dist/cli/chat-stream.js +48 -0
  2. package/dist/contracts/workspace.d.ts +10 -0
  3. package/dist/package-version.d.ts +2 -2
  4. package/dist/package-version.js +2 -2
  5. package/dist/runtime/adapter/flow/execution-context.js +3 -2
  6. package/dist/runtime/adapter/flow/stream-runtime.d.ts +6 -0
  7. package/dist/runtime/adapter/flow/stream-runtime.js +54 -15
  8. package/dist/runtime/adapter/invocation-result.js +111 -9
  9. package/dist/runtime/adapter/local-tool-invocation.js +21 -1
  10. package/dist/runtime/adapter/middleware/context-hygiene.d.ts +5 -0
  11. package/dist/runtime/adapter/middleware/context-hygiene.js +83 -0
  12. package/dist/runtime/adapter/middleware-assembly.d.ts +11 -0
  13. package/dist/runtime/adapter/middleware-assembly.js +154 -178
  14. package/dist/runtime/adapter/model/invocation-request.js +39 -1
  15. package/dist/runtime/adapter/runtime-adapter-support.js +33 -3
  16. package/dist/runtime/adapter/stream-event-projection.js +6 -5
  17. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +7 -0
  18. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +31 -24
  19. package/dist/runtime/agent-runtime-adapter.d.ts +3 -2
  20. package/dist/runtime/agent-runtime-adapter.js +128 -9
  21. package/dist/runtime/agent-runtime-assembly.d.ts +1 -0
  22. package/dist/runtime/agent-runtime-assembly.js +10 -2
  23. package/dist/runtime/harness/run/inspection.js +4 -5
  24. package/dist/runtime/harness/run/stream-run.js +232 -48
  25. package/dist/runtime/parsing/output-parsing.d.ts +1 -1
  26. package/dist/runtime/parsing/output-parsing.js +1 -1
  27. package/dist/runtime/parsing/output-recovery.d.ts +9 -0
  28. package/dist/runtime/parsing/output-recovery.js +46 -1
  29. package/dist/runtime/support/compiled-binding.d.ts +5 -0
  30. package/dist/runtime/support/compiled-binding.js +12 -0
  31. package/dist/workspace/agent-binding-compiler.js +8 -0
  32. package/dist/workspace/object-loader.js +6 -0
  33. package/package.json +1 -1
@@ -43,7 +43,7 @@ function readTerminalStructuredStatus(value) {
43
43
  return readTerminalStructuredStatus(JSON.parse(value));
44
44
  }
45
45
  catch {
46
- return null;
46
+ return /^\s*Status:\s*completed\b/im.test(value) ? "completed" : null;
47
47
  }
48
48
  }
49
49
  if (typeof value !== "object" || value === null) {
@@ -54,9 +54,20 @@ function readTerminalStructuredStatus(value) {
54
54
  return typed.status;
55
55
  }
56
56
  return (readTerminalStructuredStatus(typed.structuredResponse)
57
+ ?? readTerminalStructuredStatus(typed.content)
57
58
  ?? readTerminalStructuredStatus(typed.output)
58
59
  ?? readTerminalStructuredStatus(typed.data));
59
60
  }
61
+ function isSubstantiveTerminalAssistantOutput(value) {
62
+ const normalized = sanitizeVisibleText(value).trim();
63
+ if (normalized.length < 80) {
64
+ return false;
65
+ }
66
+ if (/\b(?:delegated|waiting|wait for|initiated)\b/i.test(normalized) && !/\b(?:finding|summary|root cause|evidence|completed|result|issue)\b/i.test(normalized)) {
67
+ return false;
68
+ }
69
+ return true;
70
+ }
60
71
  function reconcilePlanStateToTerminalStatus(planState, status, updatedAt) {
61
72
  const items = planState.items.map((item) => ({
62
73
  ...item,
@@ -134,6 +145,57 @@ function buildPlanStateSignature(planState) {
134
145
  summary: planState.summary,
135
146
  });
136
147
  }
148
+ function recomputePlanSummary(items) {
149
+ return {
150
+ total: items.length,
151
+ pending: items.filter((item) => item.status === "pending").length,
152
+ inProgress: items.filter((item) => item.status === "in_progress").length,
153
+ completed: items.filter((item) => item.status === "completed").length,
154
+ failed: items.filter((item) => item.status === "failed").length,
155
+ cancelled: items.filter((item) => item.status === "cancelled").length,
156
+ };
157
+ }
158
+ function normalizePlanItemKey(item) {
159
+ return typeof item.id === "string" && item.id.trim().length > 0
160
+ ? `id:${item.id.trim()}`
161
+ : `content:${item.content.trim().toLowerCase()}`;
162
+ }
163
+ function mergePartialPlanState(currentPlanState, incomingPlanState) {
164
+ if (!currentPlanState || incomingPlanState.items.length >= currentPlanState.items.length) {
165
+ return incomingPlanState;
166
+ }
167
+ const currentByKey = new Map(currentPlanState.items.map((item, index) => [normalizePlanItemKey(item), { item, index }]));
168
+ const incomingByKey = new Map();
169
+ for (const incomingItem of incomingPlanState.items) {
170
+ const key = normalizePlanItemKey(incomingItem);
171
+ if (!currentByKey.has(key)) {
172
+ const currentStructuredIds = countStructuredTodoIds(currentPlanState.items);
173
+ const incomingStructuredIds = countStructuredTodoIds(incomingPlanState.items);
174
+ if (currentStructuredIds > 0 && incomingStructuredIds === 0) {
175
+ return currentPlanState;
176
+ }
177
+ const mergedItems = currentPlanState.items.map((item, index) => {
178
+ const incomingByIndex = incomingPlanState.items[index];
179
+ return incomingByIndex ? { ...item, status: incomingByIndex.status } : item;
180
+ });
181
+ return {
182
+ ...incomingPlanState,
183
+ items: mergedItems,
184
+ summary: recomputePlanSummary(mergedItems),
185
+ };
186
+ }
187
+ incomingByKey.set(key, incomingItem);
188
+ }
189
+ const mergedItems = currentPlanState.items.map((item) => {
190
+ const incomingItem = incomingByKey.get(normalizePlanItemKey(item));
191
+ return incomingItem ? { ...item, ...incomingItem } : item;
192
+ });
193
+ return {
194
+ ...incomingPlanState,
195
+ items: mergedItems,
196
+ summary: recomputePlanSummary(mergedItems),
197
+ };
198
+ }
137
199
  function countStructuredTodoIds(items) {
138
200
  return items.filter((item) => typeof item.id === "string" && item.id.length > 0).length;
139
201
  }
@@ -283,16 +345,49 @@ function summarizePlanState(planState) {
283
345
  if (planState.summary.total <= 0) {
284
346
  return null;
285
347
  }
286
- const counts = [
287
- planState.summary.inProgress > 0 ? `${planState.summary.inProgress} in progress` : "",
288
- planState.summary.pending > 0 ? `${planState.summary.pending} pending` : "",
289
- planState.summary.completed > 0 ? `${planState.summary.completed} completed` : "",
290
- planState.summary.failed > 0 ? `${planState.summary.failed} failed` : "",
291
- ].filter((value) => value.length > 0);
292
- if (counts.length === 0) {
293
- counts.push(`${planState.summary.total} total`);
294
- }
295
- return `Plan updated: ${counts.join(", ")}.`;
348
+ const statusMarker = (status) => {
349
+ switch (status) {
350
+ case "completed":
351
+ return "[x]";
352
+ case "in_progress":
353
+ return "[~]";
354
+ case "failed":
355
+ return "[!]";
356
+ case "cancelled":
357
+ return "[-]";
358
+ default:
359
+ return "[ ]";
360
+ }
361
+ };
362
+ const items = planState.items.slice(0, 6).map((item) => `${statusMarker(item.status)} ${item.content}`);
363
+ const suffix = planState.items.length > items.length ? ` | +${planState.items.length - items.length} more` : "";
364
+ return `TODO: ${items.join(" | ")}${suffix}`;
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
+ });
296
391
  }
297
392
  function createSurfaceCommentary(surfaceItem) {
298
393
  const name = normalizeCommentaryText(surfaceItem.name);
@@ -446,6 +541,10 @@ export async function* streamHarnessRun(options) {
446
541
  let delegationChain = [options.selectedAgentId];
447
542
  let upstreamEventOrdinal = 0;
448
543
  let syntheticFallback;
544
+ const toolErrors = [];
545
+ let sawSuccessfulToolResult = false;
546
+ let lastToolResultKey = null;
547
+ const executedToolResults = [];
449
548
  const emittedCommentary = new Set();
450
549
  const emitCommentary = function* (content) {
451
550
  const normalized = normalizeCommentaryText(content);
@@ -467,10 +566,6 @@ export async function* streamHarnessRun(options) {
467
566
  options.releaseRequestSlotPromise,
468
567
  ]).then(([loadedPriorHistory, resolvedReleaseRunSlot]) => [loadedPriorHistory, resolvedReleaseRunSlot]);
469
568
  releaseRunSlot = acquiredReleaseRunSlot;
470
- const toolErrors = [];
471
- let sawSuccessfulToolResult = false;
472
- let lastToolResultKey = null;
473
- const executedToolResults = [];
474
569
  const recalledMemories = options.invocation.memoryRecall?.items ?? [];
475
570
  for (const item of createRuntimeMemoryRecallSteps(options.sessionId, options.requestId, recalledMemories)) {
476
571
  yield item;
@@ -502,32 +597,20 @@ export async function* streamHarnessRun(options) {
502
597
  updatedAt: new Date().toISOString(),
503
598
  });
504
599
  if (upstreamPlanState) {
505
- const signature = buildPlanStateSignature(upstreamPlanState);
506
- if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, upstreamPlanState)) {
507
- planStateVersion = upstreamPlanState.version;
600
+ const mergedPlanState = mergePartialPlanState(currentPlanState, upstreamPlanState);
601
+ const signature = buildPlanStateSignature(mergedPlanState);
602
+ if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, mergedPlanState)) {
603
+ const previousPlanState = currentPlanState;
604
+ planStateVersion = mergedPlanState.version;
508
605
  lastPlanStateSignature = signature;
509
- currentPlanState = upstreamPlanState;
510
- for (const item of await emitPlanStateUpdate(options, currentAgentId, upstreamPlanState)) {
606
+ currentPlanState = mergedPlanState;
607
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, mergedPlanState)) {
511
608
  yield item;
512
609
  }
513
- const commentary = summarizePlanState(upstreamPlanState);
514
- if (commentary) {
610
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, mergedPlanState)) {
515
611
  yield* emitCommentary(commentary);
516
612
  }
517
- }
518
- }
519
- const terminalStructuredStatus = readTerminalStructuredStatus(normalizedChunk.event);
520
- if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
521
- const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
522
- const signature = buildPlanStateSignature(reconciledPlanState);
523
- if (signature !== lastPlanStateSignature) {
524
- planStateVersion = reconciledPlanState.version;
525
- lastPlanStateSignature = signature;
526
- currentPlanState = reconciledPlanState;
527
- for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
528
- yield item;
529
- }
530
- const commentary = summarizePlanState(reconciledPlanState);
613
+ const commentary = summarizePlanState(mergedPlanState);
531
614
  if (commentary) {
532
615
  yield* emitCommentary(commentary);
533
616
  }
@@ -677,30 +760,41 @@ export async function* streamHarnessRun(options) {
677
760
  updatedAt: new Date().toISOString(),
678
761
  });
679
762
  if (planState) {
680
- const signature = buildPlanStateSignature(planState);
681
- if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, planState)) {
763
+ const mergedPlanState = mergePartialPlanState(currentPlanState, planState);
764
+ const signature = buildPlanStateSignature(mergedPlanState);
765
+ if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, mergedPlanState)) {
766
+ const previousPlanState = currentPlanState;
682
767
  lastPlanStateSignature = signature;
683
- currentPlanState = planState;
684
- for (const item of await emitPlanStateUpdate(options, currentAgentId, planState)) {
768
+ currentPlanState = mergedPlanState;
769
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, mergedPlanState)) {
685
770
  yield item;
686
771
  }
687
- const commentary = summarizePlanState(planState);
772
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, mergedPlanState)) {
773
+ yield* emitCommentary(commentary);
774
+ }
775
+ const commentary = summarizePlanState(mergedPlanState);
688
776
  if (commentary) {
689
777
  yield* emitCommentary(commentary);
690
778
  }
691
779
  }
692
780
  }
693
- const terminalStructuredStatus = readTerminalStructuredStatus(normalizedChunk.output);
781
+ const terminalStructuredStatus = normalizedChunk.toolName === "task"
782
+ ? readTerminalStructuredStatus(normalizedChunk.output)
783
+ : null;
694
784
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
695
785
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
696
786
  const signature = buildPlanStateSignature(reconciledPlanState);
697
787
  if (signature !== lastPlanStateSignature) {
788
+ const previousPlanState = currentPlanState;
698
789
  planStateVersion = reconciledPlanState.version;
699
790
  lastPlanStateSignature = signature;
700
791
  currentPlanState = reconciledPlanState;
701
792
  for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
702
793
  yield item;
703
794
  }
795
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
796
+ yield* emitCommentary(commentary);
797
+ }
704
798
  const commentary = summarizePlanState(reconciledPlanState);
705
799
  if (commentary) {
706
800
  yield* emitCommentary(commentary);
@@ -757,15 +851,20 @@ export async function* streamHarnessRun(options) {
757
851
  updatedAt: new Date().toISOString(),
758
852
  });
759
853
  if (finalPlanState) {
760
- const signature = buildPlanStateSignature(finalPlanState);
761
- if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, finalPlanState)) {
762
- planStateVersion = finalPlanState.version;
854
+ const mergedPlanState = mergePartialPlanState(currentPlanState, finalPlanState);
855
+ const signature = buildPlanStateSignature(mergedPlanState);
856
+ if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, mergedPlanState)) {
857
+ const previousPlanState = currentPlanState;
858
+ planStateVersion = mergedPlanState.version;
763
859
  lastPlanStateSignature = signature;
764
- currentPlanState = finalPlanState;
765
- for (const item of await emitPlanStateUpdate(options, currentAgentId, finalPlanState)) {
860
+ currentPlanState = mergedPlanState;
861
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, mergedPlanState)) {
766
862
  yield item;
767
863
  }
768
- const commentary = summarizePlanState(finalPlanState);
864
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, mergedPlanState)) {
865
+ yield* emitCommentary(commentary);
866
+ }
867
+ const commentary = summarizePlanState(mergedPlanState);
769
868
  if (commentary) {
770
869
  yield* emitCommentary(commentary);
771
870
  }
@@ -777,12 +876,16 @@ export async function* streamHarnessRun(options) {
777
876
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
778
877
  const signature = buildPlanStateSignature(reconciledPlanState);
779
878
  if (signature !== lastPlanStateSignature) {
879
+ const previousPlanState = currentPlanState;
780
880
  planStateVersion = reconciledPlanState.version;
781
881
  lastPlanStateSignature = signature;
782
882
  currentPlanState = reconciledPlanState;
783
883
  for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
784
884
  yield item;
785
885
  }
886
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
887
+ yield* emitCommentary(commentary);
888
+ }
786
889
  const commentary = summarizePlanState(reconciledPlanState);
787
890
  if (commentary) {
788
891
  yield* emitCommentary(commentary);
@@ -791,6 +894,26 @@ export async function* streamHarnessRun(options) {
791
894
  }
792
895
  }
793
896
  currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
897
+ if (isSubstantiveTerminalAssistantOutput(assistantOutput) && planStateHasActiveItems(currentPlanState)) {
898
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, "completed", new Date().toISOString());
899
+ const signature = buildPlanStateSignature(reconciledPlanState);
900
+ if (signature !== lastPlanStateSignature) {
901
+ const previousPlanState = currentPlanState;
902
+ planStateVersion = reconciledPlanState.version;
903
+ lastPlanStateSignature = signature;
904
+ currentPlanState = reconciledPlanState;
905
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
906
+ yield item;
907
+ }
908
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
909
+ yield* emitCommentary(commentary);
910
+ }
911
+ const commentary = summarizePlanState(reconciledPlanState);
912
+ if (commentary) {
913
+ yield* emitCommentary(commentary);
914
+ }
915
+ }
916
+ }
794
917
  if (assistantOutputCameFromInvokeFallback
795
918
  && nonUpstreamStreamActivityObserved
796
919
  && planStateHasActiveItems(currentPlanState)) {
@@ -833,6 +956,67 @@ export async function* streamHarnessRun(options) {
833
956
  };
834
957
  }
835
958
  catch (error) {
959
+ const deterministicToolEvidenceOutput = resolveDeterministicFinalOutput({
960
+ visibleOutput: assistantOutput,
961
+ executedToolResults,
962
+ });
963
+ if (!assistantOutput && sawSuccessfulToolResult && deterministicToolEvidenceOutput) {
964
+ const terminalStructuredStatus = readTerminalStructuredStatus(deterministicToolEvidenceOutput);
965
+ if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
966
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
967
+ const signature = buildPlanStateSignature(reconciledPlanState);
968
+ if (signature !== lastPlanStateSignature) {
969
+ const previousPlanState = currentPlanState;
970
+ planStateVersion = reconciledPlanState.version;
971
+ lastPlanStateSignature = signature;
972
+ currentPlanState = reconciledPlanState;
973
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
974
+ yield item;
975
+ }
976
+ for (const commentary of summarizePlanStateTerminalTransitions(previousPlanState, reconciledPlanState)) {
977
+ yield* emitCommentary(commentary);
978
+ }
979
+ const commentary = summarizePlanState(reconciledPlanState);
980
+ if (commentary) {
981
+ yield* emitCommentary(commentary);
982
+ }
983
+ }
984
+ }
985
+ await options.appendAssistantMessage(options.sessionId, options.requestId, deterministicToolEvidenceOutput);
986
+ yield {
987
+ type: "event",
988
+ event: await options.emit(options.sessionId, options.requestId, 3, "output.delta", { content: deterministicToolEvidenceOutput }),
989
+ };
990
+ yield {
991
+ type: "content",
992
+ sessionId: options.sessionId,
993
+ requestId: options.requestId,
994
+ agentId: currentAgentId,
995
+ content: deterministicToolEvidenceOutput,
996
+ };
997
+ const completedEvent = await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "completed", {
998
+ previousState: "running",
999
+ });
1000
+ yield {
1001
+ type: "event",
1002
+ event: completedEvent,
1003
+ };
1004
+ yield {
1005
+ type: "result",
1006
+ result: {
1007
+ sessionId: options.sessionId,
1008
+ requestId: options.requestId,
1009
+ agentId: currentAgentId,
1010
+ state: "completed",
1011
+ output: deterministicToolEvidenceOutput,
1012
+ finalMessageText: deterministicToolEvidenceOutput,
1013
+ metadata: {
1014
+ executedToolResults,
1015
+ },
1016
+ },
1017
+ };
1018
+ return;
1019
+ }
836
1020
  const shouldRetryAfterStreamingCompatibilityError = !assistantOutput &&
837
1021
  isOpenAICompatibleStreamingCompatibilityError(options.binding, error);
838
1022
  if ((emitted || streamActivityObserved)
@@ -1,4 +1,4 @@
1
- export { appendToolRecoveryInstruction, isRepairableWriteTodosContentFailure, isRepairableWriteTodosEmptyFailure, isRepairableWriteTodosPlaceholderFailure, isRetrySafeInvalidToolSelectionError, isToolCallRecoveryFailure, isToolCallValidationFailure, isWorkspacePathScopeFailure, resolveExecutionWithoutToolEvidenceInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, resolveToolCallRecoveryInstruction, shouldValidateExecutionWithoutToolEvidence, wrapResolvedModel, } from "./output-recovery.js";
1
+ export { appendToolRecoveryInstruction, isRepairableWriteTodosContentFailure, isRepairableWriteTodosEmptyFailure, isRepairableWriteTodosPlaceholderFailure, isRetrySafeInvalidToolSelectionError, resolveMissingPlanRecoveryInstruction, isToolCallRecoveryFailure, isToolCallValidationFailure, isWorkspacePathScopeFailure, resolveExecutionWithoutToolEvidenceInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, resolveToolCallRecoveryInstruction, shouldValidateExecutionWithoutToolEvidence, wrapResolvedModel, } from "./output-recovery.js";
2
2
  export { BROWSER_CAPABILITY_DISCLAIMER_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, } from "../prompts/runtime-prompts.js";
3
3
  export { containsLikelySkillDocument, extractContentBlocks, extractEmptyAssistantMessageFailure, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, hasToolCalls, readTextContent, sanitizeVisibleText, } from "./output-content.js";
4
4
  export { isLikelyToolArgsObject, salvageToolArgs, tryParseJson, } from "./output-tool-args.js";
@@ -1,4 +1,4 @@
1
- export { appendToolRecoveryInstruction, isRepairableWriteTodosContentFailure, isRepairableWriteTodosEmptyFailure, isRepairableWriteTodosPlaceholderFailure, isRetrySafeInvalidToolSelectionError, isToolCallRecoveryFailure, isToolCallValidationFailure, isWorkspacePathScopeFailure, resolveExecutionWithoutToolEvidenceInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, resolveToolCallRecoveryInstruction, shouldValidateExecutionWithoutToolEvidence, wrapResolvedModel, } from "./output-recovery.js";
1
+ export { appendToolRecoveryInstruction, isRepairableWriteTodosContentFailure, isRepairableWriteTodosEmptyFailure, isRepairableWriteTodosPlaceholderFailure, isRetrySafeInvalidToolSelectionError, resolveMissingPlanRecoveryInstruction, isToolCallRecoveryFailure, isToolCallValidationFailure, isWorkspacePathScopeFailure, resolveExecutionWithoutToolEvidenceInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, resolveToolCallRecoveryInstruction, shouldValidateExecutionWithoutToolEvidence, wrapResolvedModel, } from "./output-recovery.js";
2
2
  export { BROWSER_CAPABILITY_DISCLAIMER_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, } from "../prompts/runtime-prompts.js";
3
3
  export { containsLikelySkillDocument, extractContentBlocks, extractEmptyAssistantMessageFailure, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, hasToolCalls, readTextContent, sanitizeVisibleText, } from "./output-content.js";
4
4
  export { isLikelyToolArgsObject, salvageToolArgs, tryParseJson, } from "./output-tool-args.js";
@@ -7,11 +7,20 @@ export declare function isRepairableWriteTodosEmptyFailure(error: unknown): bool
7
7
  export declare function isToolCallRecoveryFailure(error: unknown): boolean;
8
8
  export declare function isRetrySafeInvalidToolSelectionError(value: unknown): boolean;
9
9
  export declare function shouldValidateExecutionWithoutToolEvidence(request: unknown): boolean;
10
+ export declare function shouldRequireVisibleTodoPlan(request: unknown): boolean;
11
+ export declare function resolveMissingPlanRecoveryInstruction(params: {
12
+ request: unknown;
13
+ assistantText?: string;
14
+ hasPlanStateEvidence?: boolean;
15
+ hasWriteTodosEvidence?: boolean;
16
+ hasToolResultEvidence?: boolean;
17
+ }): string | null;
10
18
  export declare function resolveExecutionWithoutToolEvidenceInstruction(request: unknown, result: unknown): string | null;
11
19
  export declare function resolveExecutionWithoutToolEvidenceTextInstruction(request: unknown, assistantText: string, toolCallEvidence?: boolean, resultEvidence?: {
12
20
  hasWriteTodosEvidence?: boolean;
13
21
  hasToolResultEvidence?: boolean;
14
22
  hasIncompletePlanState?: boolean;
23
+ hasPlanStateEvidence?: boolean;
15
24
  hasOpenTaskDelegation?: boolean;
16
25
  hasMissingDelegatedExecutionEvidence?: boolean;
17
26
  }): string | null;
@@ -1,4 +1,4 @@
1
- import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, } from "../prompts/runtime-prompts.js";
1
+ import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION, } from "../prompts/runtime-prompts.js";
2
2
  import { wrapNormalizedMessage, readTextContent } from "./output-content.js";
3
3
  function collectRequestMessages(request) {
4
4
  if (typeof request !== "object" || !request || Array.isArray(request)) {
@@ -111,6 +111,41 @@ export function shouldValidateExecutionWithoutToolEvidence(request) {
111
111
  }
112
112
  return readSystemInstructionText(request).length > 0;
113
113
  }
114
+ export function shouldRequireVisibleTodoPlan(request) {
115
+ const userText = readLatestUserRequestText(request).toLowerCase();
116
+ if (!userText) {
117
+ return false;
118
+ }
119
+ return [
120
+ "investigate",
121
+ "investigation",
122
+ "issue",
123
+ "issues",
124
+ "rca",
125
+ "root cause",
126
+ "go deeper",
127
+ "deep research",
128
+ "debug",
129
+ "排查",
130
+ "调查",
131
+ "问题",
132
+ "根因",
133
+ "故障",
134
+ "集群",
135
+ "cluster",
136
+ ].some((keyword) => userText.includes(keyword));
137
+ }
138
+ export function resolveMissingPlanRecoveryInstruction(params) {
139
+ const hasPlanEvidence = params.hasWriteTodosEvidence === true
140
+ || params.hasPlanStateEvidence === true;
141
+ if (!shouldRequireVisibleTodoPlan(params.request) || hasPlanEvidence) {
142
+ return null;
143
+ }
144
+ if (params.hasToolResultEvidence === true) {
145
+ return WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION;
146
+ }
147
+ return null;
148
+ }
114
149
  export function resolveExecutionWithoutToolEvidenceInstruction(request, result) {
115
150
  const assistantText = readTextContent(result).trim();
116
151
  return resolveExecutionWithoutToolEvidenceTextInstruction(request, assistantText, false, {});
@@ -120,6 +155,16 @@ export function resolveExecutionWithoutToolEvidenceTextInstruction(request, assi
120
155
  return null;
121
156
  }
122
157
  const normalizedText = assistantText.trim();
158
+ const missingPlanRecoveryInstruction = resolveMissingPlanRecoveryInstruction({
159
+ request,
160
+ assistantText: normalizedText,
161
+ hasWriteTodosEvidence: resultEvidence.hasWriteTodosEvidence,
162
+ hasPlanStateEvidence: resultEvidence.hasIncompletePlanState === true || resultEvidence.hasPlanStateEvidence === true,
163
+ hasToolResultEvidence: resultEvidence.hasToolResultEvidence,
164
+ });
165
+ if (missingPlanRecoveryInstruction) {
166
+ return missingPlanRecoveryInstruction;
167
+ }
123
168
  const hasUnfinishedExecution = resultEvidence.hasIncompletePlanState === true
124
169
  || resultEvidence.hasOpenTaskDelegation === true
125
170
  || resultEvidence.hasMissingDelegatedExecutionEvidence === true;
@@ -31,6 +31,11 @@ export declare function getBindingDeepAgentSubagents(binding: CompiledAgentBindi
31
31
  export declare function getBindingGeneralPurposeAgent(binding: CompiledAgentBinding): boolean | undefined;
32
32
  export declare function getBindingTaskDescription(binding: CompiledAgentBinding): string | undefined;
33
33
  export declare function getBindingBackendConfig(binding: CompiledAgentBinding): Record<string, unknown> | undefined;
34
+ export declare function getBindingInteractionMode(binding: CompiledAgentBinding): "stream" | "invoke" | undefined;
35
+ export declare function getBindingBuiltinToolsConfig(binding: CompiledAgentBinding): {
36
+ filesystem?: boolean;
37
+ todos?: boolean;
38
+ } | undefined;
34
39
  export declare function getBindingFilesystemConfig(binding: CompiledAgentBinding): Record<string, unknown> | undefined;
35
40
  export declare function isLangChainBinding(binding: CompiledAgentBinding): boolean;
36
41
  export declare function isDeepAgentBinding(binding: CompiledAgentBinding): boolean;
@@ -229,6 +229,18 @@ export function getBindingBackendConfig(binding) {
229
229
  const backend = execution?.backend;
230
230
  return typeof backend === "object" && backend ? backend : undefined;
231
231
  }
232
+ export function getBindingInteractionMode(binding) {
233
+ const execution = getBindingExecutionParams(binding);
234
+ const mode = execution?.interactionMode;
235
+ return mode === "invoke" || mode === "stream" ? mode : undefined;
236
+ }
237
+ export function getBindingBuiltinToolsConfig(binding) {
238
+ const execution = getBindingExecutionParams(binding);
239
+ const builtinTools = execution?.builtinTools;
240
+ return typeof builtinTools === "object" && builtinTools
241
+ ? builtinTools
242
+ : undefined;
243
+ }
232
244
  export function getBindingFilesystemConfig(binding) {
233
245
  const langchainFilesystem = binding.harnessRuntime?.langchain?.filesystem;
234
246
  if (typeof langchainFilesystem === "object" && langchainFilesystem) {
@@ -169,6 +169,7 @@ export function requireTools(tools, bindings, ownerId) {
169
169
  function buildSubagent(agent, workspaceRoot, models, tools, parentSkills, parentModel) {
170
170
  const execution = compileExecutionCore(agent, workspaceRoot, models, tools);
171
171
  return {
172
+ agentId: agent.id,
172
173
  name: resolveAgentRuntimeName(agent),
173
174
  description: agent.description,
174
175
  systemPrompt: execution.systemPrompt ?? WORKSPACE_BOUNDARY_GUIDANCE,
@@ -178,6 +179,7 @@ function buildSubagent(agent, workspaceRoot, models, tools, parentSkills, parent
178
179
  skills: compileAgentSkills(workspaceRoot, agent, parentSkills),
179
180
  responseFormat: execution.responseFormat,
180
181
  middleware: execution.middleware,
182
+ builtinTools: getAgentExecutionObject(agent, "builtinTools"),
181
183
  };
182
184
  }
183
185
  function resolveSystemPrompt(agent, workspaceRoot) {
@@ -484,6 +486,12 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
484
486
  name: resolveAgentRuntimeName(agent),
485
487
  memory: compiledAgentMemory,
486
488
  skills: compiledAgentSkills,
489
+ interactionMode: getAgentExecutionConfigValue(agent, "interactionMode", { executionMode: "deepagent" }) === "invoke"
490
+ ? "invoke"
491
+ : getAgentExecutionConfigValue(agent, "interactionMode", { executionMode: "deepagent" }) === "stream"
492
+ ? "stream"
493
+ : undefined,
494
+ builtinTools: getAgentExecutionObject(agent, "builtinTools"),
487
495
  },
488
496
  };
489
497
  return attachLegacyExecutionAliases({
@@ -27,6 +27,8 @@ const CONSUMED_AGENT_CONFIG_KEYS = [
27
27
  "taskDescription",
28
28
  "generalPurposeAgent",
29
29
  "filesystem",
30
+ "builtinTools",
31
+ "interactionMode",
30
32
  ];
31
33
  const NON_AGENT_CONFIG_ITEM_KEYS = [
32
34
  "id",
@@ -61,6 +63,8 @@ const MIGRATED_AGENT_CONFIG_KEYS = [
61
63
  "taskDescription",
62
64
  "generalPurposeAgent",
63
65
  "filesystem",
66
+ "builtinTools",
67
+ "interactionMode",
64
68
  ];
65
69
  function normalizeAgentItemForMerge(item) {
66
70
  const normalized = { ...item };
@@ -223,6 +227,7 @@ function resolveExecutionBackend(item, current) {
223
227
  function readSharedAgentConfigFields(config, options = {}) {
224
228
  return {
225
229
  ...(typeof config.store === "object" && config.store ? { store: config.store } : {}),
230
+ ...(typeof config.builtinTools === "object" && config.builtinTools ? { builtinTools: cloneConfigValue(config.builtinTools) } : {}),
226
231
  ...(options.includeDelegationControls && typeof config.taskDescription === "string" && config.taskDescription.trim()
227
232
  ? { taskDescription: config.taskDescription }
228
233
  : {}),
@@ -258,6 +263,7 @@ function readSharedAgentConfig(config) {
258
263
  ...(config.stateSchema !== undefined ? { stateSchema: config.stateSchema } : {}),
259
264
  ...(config.responseFormat !== undefined ? { responseFormat: config.responseFormat } : {}),
260
265
  ...(config.contextSchema !== undefined ? { contextSchema: config.contextSchema } : {}),
266
+ ...(config.interactionMode === "stream" || config.interactionMode === "invoke" ? { interactionMode: config.interactionMode } : {}),
261
267
  ...(config.includeAgentName === "inline" ? { includeAgentName: "inline" } : {}),
262
268
  ...(config.version === "v1" || config.version === "v2" ? { version: config.version } : {}),
263
269
  ...(typeof config.filesystem === "object" && config.filesystem ? { filesystem: config.filesystem } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.340",
3
+ "version": "0.0.342",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",