@botbotgo/agent-harness 0.0.354 → 0.0.355

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.354";
2
- export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.355";
2
+ export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.354";
2
- export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
1
+ export const AGENT_HARNESS_VERSION = "0.0.355";
2
+ export const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -1 +1,10 @@
1
- Do not stop at a plan or ask the user to choose the next obvious diagnostic step when the request is for deep investigation, root-cause analysis, or step-by-step execution. Do not ask for more background, scope, logs, or environment details until you have first exhausted the context and tools already available in this runtime. Start from the current workspace, shell, and attached runtime/tool context by default, then continue the investigation yourself with the next concrete tool call. If the user explicitly asked for a plan, or if the task is clearly non-trivial and multi-step, call write_todos first with concrete investigation/execution steps before any other tool call or final answer. If the todo board already exists, do not restart planning and do not repeat the same clarification request; your next response must continue execution with a concrete diagnostic tool call. When prior tool results or triage evidence already exist, your next response must contain tool calls only and must advance the investigation from that evidence. Then keep executing the next diagnostic steps until you can explain the likely causes, impact, and recommended next actions. Ask a blocking clarification question only after the available evidence is genuinely insufficient to continue.
1
+ Do not stop at a plan or ask the user to choose the next obvious diagnostic step when the request is for deep investigation, root-cause analysis, or step-by-step execution. Do not ask for more background, scope, logs, or environment details until you have first exhausted the context and tools already available in this runtime.
2
+
3
+ If no concrete execution has happened yet, start from the current workspace, shell, and attached runtime/tool context by default, then continue the investigation yourself with the next concrete tool call. If the user explicitly asked for a plan, or if the task is clearly non-trivial and multi-step, call write_todos first with concrete investigation/execution steps before any other tool call or final answer.
4
+
5
+ If a todo board already exists, do not restart planning and do not repeat the same clarification request. Use the current todo board and prior tool results to choose one of these terminally useful actions:
6
+ - If more evidence is genuinely needed and an available tool can get it, make the next concrete tool call.
7
+ - If the existing evidence is enough to answer, update the todo board to completed or blocked as appropriate, then provide the final answer grounded in the tool results.
8
+ - If the available tools cannot resolve the remaining work, update the todo board to blocked or failed and provide a blocker report with the evidence.
9
+
10
+ Never print a tool-call JSON object, function call, or tool name as prose when you intend to use a tool. Actually call the tool. Ask a blocking clarification question only after the available evidence is genuinely insufficient to continue.
@@ -11,3 +11,9 @@ export declare function resolveDeepAgentSkillSourcePaths(options: {
11
11
  ownerId: string;
12
12
  skillPaths?: string[];
13
13
  }): string[] | undefined;
14
+ export declare function resolveDeepAgentSkillSourceRootPaths(options: {
15
+ workspaceRoot?: string;
16
+ runtimeRoot?: string;
17
+ ownerId: string;
18
+ skillPaths?: string[];
19
+ }): string[] | undefined;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { existsSync } from "node:fs";
2
3
  export function relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) {
3
4
  if (!workspaceRoot || !skillPaths) {
4
5
  return skillPaths;
@@ -24,3 +25,18 @@ export function resolveDeepAgentSkillSourcePaths(options) {
24
25
  }
25
26
  return relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) ?? skillPaths;
26
27
  }
28
+ export function resolveDeepAgentSkillSourceRootPaths(options) {
29
+ const { workspaceRoot, skillPaths } = options;
30
+ if (!skillPaths) {
31
+ return skillPaths;
32
+ }
33
+ const sourceRoots = Array.from(new Set(skillPaths.map((skillPath) => {
34
+ const absolutePath = path.isAbsolute(skillPath) || !workspaceRoot
35
+ ? skillPath
36
+ : path.resolve(workspaceRoot, skillPath);
37
+ return existsSync(path.join(absolutePath, "SKILL.md"))
38
+ ? path.dirname(absolutePath)
39
+ : absolutePath;
40
+ })));
41
+ return relativizeDeepAgentSkillSourcePaths(workspaceRoot, sourceRoots) ?? sourceRoots;
42
+ }
@@ -4,9 +4,10 @@ import { finalizeRequestResult } from "../invocation-result.js";
4
4
  import { invokeRuntimeWithLocalTools } from "./invoke-runtime.js";
5
5
  import { buildInvocationRequest } from "../model/invocation-request.js";
6
6
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "../upstream-configurable-keys.js";
7
- import { extractVisibleOutput, tryParseJson } from "../../parsing/output-parsing.js";
7
+ import { appendToolRecoveryInstruction, extractVisibleOutput, tryParseJson } from "../../parsing/output-parsing.js";
8
8
  import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
9
9
  import { isEmptyFinalAiMessageError } from "../resilience.js";
10
+ import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../../prompts/runtime-prompts.js";
10
11
  function readBindingExecutionParams(binding) {
11
12
  const params = binding.execution?.params ?? binding.deepAgentParams ?? binding.langchainAgentParams;
12
13
  return {
@@ -36,6 +37,29 @@ function isDelegationOnlyBinding(binding) {
36
37
  function hasTaskDelegationEvidence(executedToolResults) {
37
38
  return executedToolResults.some((item) => item.toolName === "task");
38
39
  }
40
+ function hasIncompleteTodos(value) {
41
+ if (!Array.isArray(value)) {
42
+ return false;
43
+ }
44
+ return value.some((todo) => {
45
+ if (typeof todo !== "object" || todo === null) {
46
+ return false;
47
+ }
48
+ const status = typeof todo.status === "string"
49
+ ? todo.status.trim().toLowerCase()
50
+ : "";
51
+ return status === "pending" || status === "in_progress";
52
+ });
53
+ }
54
+ function hasIncompleteUpstreamPlan(value) {
55
+ if (typeof value !== "object" || value === null) {
56
+ return false;
57
+ }
58
+ const typed = value;
59
+ return hasIncompleteTodos(typed.todos)
60
+ || hasIncompleteTodos(typed.stateSnapshot?.todos)
61
+ || hasIncompleteTodos(typed.metadata?.stateSnapshot?.todos);
62
+ }
39
63
  function hasNativeTaskDelegationIntent(value) {
40
64
  if (typeof value !== "object" || value === null) {
41
65
  return false;
@@ -285,11 +309,25 @@ export async function executeRequestInvocation(options) {
285
309
  toolRuntimeContext: invokeOptions.toolRuntimeContext,
286
310
  });
287
311
  }
288
- const result = localOrUpstreamInvocation.result;
312
+ let result = localOrUpstreamInvocation.result;
289
313
  const executedToolResults = [...localOrUpstreamInvocation.executedToolResults];
290
314
  if (!result) {
291
315
  throw new Error("Agent invocation returned no result");
292
316
  }
317
+ if (options.resumePayload === undefined
318
+ && options.binding.harnessRuntime.executionContract?.requiresPlan === true
319
+ && hasIncompleteUpstreamPlan(result)
320
+ && !extractVisibleOutput(result).trim()) {
321
+ const messages = Array.isArray(result.messages)
322
+ ? result.messages
323
+ : undefined;
324
+ const recoveryBase = messages ? { messages } : request;
325
+ const recoveredRequest = appendToolRecoveryInstruction(recoveryBase, AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION);
326
+ const recoveredInvocation = await invokeOnce(recoveredRequest);
327
+ localOrUpstreamInvocation = recoveredInvocation;
328
+ result = recoveredInvocation.result;
329
+ executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
330
+ }
293
331
  try {
294
332
  return finalizeRequestResult({
295
333
  bindingAgentId: options.binding.agent.id,
@@ -57,6 +57,17 @@ function looksLikeToolBlocker(value) {
57
57
  || /invalid tool call/iu.test(normalized)
58
58
  || /tool call.*schema/iu.test(normalized);
59
59
  }
60
+ function isSubstantiveFinalOutput(value) {
61
+ const normalized = sanitizeVisibleText(value).trim();
62
+ if (normalized.length < 80) {
63
+ return false;
64
+ }
65
+ return !looksLikeLeakedToolCallText(normalized)
66
+ && !looksLikeToolBlocker(normalized)
67
+ && !looksLikeClarificationQuestion(normalized)
68
+ && !looksLikeNonEvidenceApology(normalized)
69
+ && !isLowSignalStructuredCompletion(normalized);
70
+ }
60
71
  function normalizeToolOutputText(output) {
61
72
  const directText = typeof output === "string"
62
73
  ? sanitizeVisibleText(output).trim()
@@ -343,18 +354,30 @@ export function finalizeRequestResult(params) {
343
354
  && !hasFinalMessageToolCalls(result)) {
344
355
  throw new Error("empty_final_output");
345
356
  }
357
+ const stateSnapshot = buildStateSnapshot(result);
358
+ const hasIncompleteRequiredPlan = binding?.harnessRuntime?.executionContract?.requiresPlan === true
359
+ && hasIncompleteStateSnapshotPlan(stateSnapshot);
346
360
  const serializedResult = JSON.stringify(result, null, 2);
347
- const output = resolveDeterministicFinalOutput({
361
+ let output = resolveDeterministicFinalOutput({
348
362
  visibleOutput,
349
363
  toolFallback,
350
364
  executedToolResults: allExecutedToolResults,
351
365
  })
352
366
  || (containsLikelySkillDocument(result) ? "" : serializedResult);
367
+ const preliminaryTerminalStatus = readTerminalExecutionStatus(output);
368
+ const hasMissingRequiredFinalAnswer = binding?.harnessRuntime?.executionContract?.requiresPlan === true
369
+ && !visibleOutput
370
+ && !preliminaryTerminalStatus
371
+ && allExecutedToolResults.some((toolResult) => toolResult.isError !== true && toolResult.toolName !== "write_todos" && toolResult.toolName !== "read_todos");
372
+ if (hasIncompleteRequiredPlan && !visibleOutput) {
373
+ output = "runtime_error=Agent ended while required plan still had unfinished work.";
374
+ }
375
+ else if (hasMissingRequiredFinalAnswer) {
376
+ output = "runtime_error=Agent ended after tool execution without producing a final answer.";
377
+ }
353
378
  const finalMessageText = sanitizeVisibleText(output);
354
379
  const terminalStatus = structuredTerminalStatus ?? readTerminalExecutionStatus(finalMessageText);
355
- const stateSnapshot = buildStateSnapshot(result);
356
- const hasIncompleteRequiredPlan = binding?.harnessRuntime?.executionContract?.requiresPlan === true
357
- && hasIncompleteStateSnapshotPlan(stateSnapshot);
380
+ const hasSubstantiveFinalOutput = Boolean(visibleOutput) && isSubstantiveFinalOutput(finalMessageText);
358
381
  const hasTerminalToolBlocker = looksLikeToolBlocker(finalMessageText);
359
382
  const memoryCandidates = allExecutedToolResults.flatMap((toolResult) => toolResult.memoryCandidates ?? []);
360
383
  return {
@@ -363,7 +386,7 @@ export function finalizeRequestResult(params) {
363
386
  agentId: bindingAgentId,
364
387
  state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
365
388
  ? "waiting_for_approval"
366
- : hasIncompleteRequiredPlan
389
+ : (hasIncompleteRequiredPlan && !hasSubstantiveFinalOutput) || hasMissingRequiredFinalAnswer
367
390
  ? "failed"
368
391
  : hasTerminalToolBlocker
369
392
  ? "failed"
@@ -2,7 +2,7 @@ import type { CompiledAgentBinding, MessageContent, RequestResult, RuntimeAdapte
2
2
  import { type RuntimeStreamChunk } from "./parsing/stream-event-parsing.js";
3
3
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn } from "./agent-runtime-assembly.js";
4
4
  import { RuntimeOperationTimeoutError } from "./adapter/runtime-shell.js";
5
- export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
5
+ export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
6
6
  export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
7
7
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
8
8
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
@@ -1,10 +1,10 @@
1
1
  import path from "node:path";
2
2
  import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
3
3
  import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
4
- import { tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
4
+ import { sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
5
5
  import { extractMessageText } from "../utils/message-content.js";
6
6
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityHierarchy, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
7
- import { resolveDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
7
+ import { resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, } from "./adapter/compat/deepagent-compat.js";
8
8
  import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtime-prompts.js";
9
9
  import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
10
10
  import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
@@ -18,7 +18,7 @@ import { resolveAdapterTools } from "./adapter/tool-resolution.js";
18
18
  import { resolveRuntimeStreamExecutionContext, } from "./adapter/flow/execution-context.js";
19
19
  import { isRetryableProviderError } from "./adapter/resilience.js";
20
20
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "./adapter/upstream-configurable-keys.js";
21
- export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
21
+ export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
22
22
  export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
23
23
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
24
24
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
@@ -509,13 +509,17 @@ export class AgentRuntimeAdapter {
509
509
  ? resolveRunnableCheckpointer(this.options, binding)
510
510
  : undefined;
511
511
  const resolvedStore = shouldAttachDeepAgentStore(binding) ? this.options.storeResolver?.(binding) : undefined;
512
- const resolvedBackend = shouldAttachDeepAgentBackend(binding) ? this.options.backendResolver?.(binding) : undefined;
513
512
  const resolvedSkills = resolveDeepAgentSkillSourcePaths({
514
513
  workspaceRoot: binding.harnessRuntime.workspaceRoot,
515
514
  runtimeRoot: binding.harnessRuntime.runtimeRoot,
516
515
  ownerId: binding.agent.id,
517
516
  skillPaths: getBindingSkills(binding),
518
517
  }) ?? [];
518
+ const resolvedBackend = shouldAttachDeepAgentBackend(binding)
519
+ ? (this.options.backendResolver?.(binding) ?? (resolvedSkills.length > 0 ? this.resolveFilesystemBackend(binding, {
520
+ sessionId: options.sessionId ?? options.legacySessionId,
521
+ }) : undefined))
522
+ : undefined;
519
523
  if (shouldUseConfigurableDeepAgentAssembly(binding)) {
520
524
  return this.createConfigurableDeepAgentRunnable(binding, {
521
525
  resolvedModel,
@@ -539,7 +543,15 @@ export class AgentRuntimeAdapter {
539
543
  const subagents = inlineSubagents;
540
544
  const middleware = [
541
545
  ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
542
- ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({ backend, sources: input.resolvedSkills })] : []),
546
+ ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({
547
+ backend,
548
+ sources: resolveDeepAgentSkillSourceRootPaths({
549
+ workspaceRoot: binding.harnessRuntime.workspaceRoot,
550
+ runtimeRoot: binding.harnessRuntime.runtimeRoot,
551
+ ownerId: binding.agent.id,
552
+ skillPaths: input.resolvedSkills,
553
+ }) ?? input.resolvedSkills,
554
+ })] : []),
543
555
  ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend })]),
544
556
  ...(subagents.length > 0
545
557
  ? [createSubAgentMiddleware({
@@ -645,9 +657,9 @@ export class AgentRuntimeAdapter {
645
657
  sessionId,
646
658
  requestId,
647
659
  agentId: binding.agent.id,
648
- state: "completed",
649
- output,
650
- finalMessageText: output,
660
+ state: compactDelegation.delegatedResult?.state ?? "completed",
661
+ output: sanitizeVisibleText(output),
662
+ finalMessageText: sanitizeVisibleText(output),
651
663
  metadata: {
652
664
  executedToolResults: [
653
665
  {
@@ -750,7 +762,7 @@ export class AgentRuntimeAdapter {
750
762
  if (typeof model.invoke !== "function") {
751
763
  return null;
752
764
  }
753
- const raw = await this.withTimeout(() => model.invoke(prompt, resolveLangChainInvocationConfig(binding, {
765
+ const raw = await this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(prompt, resolveLangChainInvocationConfig(binding, {
754
766
  sessionId,
755
767
  requestId,
756
768
  context: options.context,
@@ -759,7 +771,7 @@ export class AgentRuntimeAdapter {
759
771
  sessionId,
760
772
  requestId,
761
773
  }),
762
- })), resolveBindingTimeout(binding), "delegation router invoke", "invoke");
774
+ })), resolveBindingTimeout(binding), "delegation router invoke", "invoke"));
763
775
  const parsed = parseFirstJsonObject(readModelText(raw));
764
776
  if (typeof parsed !== "object" || parsed === null) {
765
777
  return null;
@@ -8,6 +8,7 @@ export declare function materializeModelExposedBuiltinMiddlewareTools(input: {
8
8
  explicitToolNames?: string[];
9
9
  modelExposed?: boolean | string[];
10
10
  }): unknown[];
11
+ export declare function buildRuntimeTemporalContext(now?: Date): string;
11
12
  export declare function buildDeepAgentSystemPromptWithCapabilityHierarchy(input: {
12
13
  systemPrompt?: unknown;
13
14
  subagents: Array<Pick<UpstreamSubagentConfig, "name" | "description"> | Pick<CompiledAsyncSubAgent, "name" | "description">>;
@@ -53,6 +53,21 @@ function buildSkillCatalog(skillPaths) {
53
53
  };
54
54
  });
55
55
  }
56
+ export function buildRuntimeTemporalContext(now = new Date()) {
57
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "system local time";
58
+ return [
59
+ "Runtime temporal context:",
60
+ `- Current date/time: ${now.toISOString()}`,
61
+ `- Local time zone: ${timeZone}`,
62
+ "Use the runtime date/time above as authoritative for interpreting current, latest, recent, today, tomorrow, yesterday, and tool-returned dates.",
63
+ "Do not reject tool evidence as future-dated solely because it is newer than the model's training data.",
64
+ ].join("\n");
65
+ }
66
+ function appendRuntimeTemporalContext(systemPrompt) {
67
+ return [typeof systemPrompt === "string" ? systemPrompt : undefined, buildRuntimeTemporalContext()]
68
+ .filter((part) => typeof part === "string" && part.length > 0)
69
+ .join("\n\n");
70
+ }
56
71
  export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
57
72
  const basePrompt = typeof input.systemPrompt === "string" ? input.systemPrompt : undefined;
58
73
  const skills = buildSkillCatalog(input.skills ?? []);
@@ -122,7 +137,7 @@ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
122
137
  : []),
123
138
  "",
124
139
  ].join("\n");
125
- return [basePrompt, catalogPrompt].filter((part) => typeof part === "string" && part.length > 0).join("\n\n");
140
+ return appendRuntimeTemporalContext([basePrompt, catalogPrompt].filter((part) => typeof part === "string" && part.length > 0).join("\n\n"));
126
141
  }
127
142
  export const buildDeepAgentSystemPromptWithSubagentCatalog = buildDeepAgentSystemPromptWithCapabilityHierarchy;
128
143
  export function resolveRunnableCheckpointer(options, binding) {
@@ -175,7 +190,7 @@ export function buildLangChainCreateParams(input) {
175
190
  ...(legacyPassthrough ?? {}),
176
191
  ...(langchainPassthrough ?? {}),
177
192
  ...(input.passthroughOverride ?? {}),
178
- systemPrompt: input.systemPromptOverride ?? executionParams.systemPrompt,
193
+ systemPrompt: appendRuntimeTemporalContext(input.systemPromptOverride ?? executionParams.systemPrompt),
179
194
  model: input.resolvedModel,
180
195
  tools: input.resolvedTools,
181
196
  middleware: input.resolvedMiddleware,
@@ -1092,8 +1092,9 @@ export async function* streamHarnessRun(options) {
1092
1092
  visibleOutput: assistantOutput,
1093
1093
  executedToolResults,
1094
1094
  });
1095
- if (!assistantOutput && sawSuccessfulToolResult && deterministicToolEvidenceOutput) {
1096
- const terminalStructuredStatus = readTerminalExecutionStatus(deterministicToolEvidenceOutput);
1095
+ const terminalStructuredStatus = readTerminalExecutionStatus(deterministicToolEvidenceOutput);
1096
+ const canUseDeterministicToolEvidenceOutput = !currentPlanState || !planStateHasActiveItems(currentPlanState) || Boolean(terminalStructuredStatus);
1097
+ if (!assistantOutput && sawSuccessfulToolResult && deterministicToolEvidenceOutput && canUseDeterministicToolEvidenceOutput) {
1097
1098
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
1098
1099
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToPlanItemStatus(terminalStructuredStatus), new Date().toISOString());
1099
1100
  const signature = buildPlanStateSignature(reconciledPlanState);
@@ -59,6 +59,9 @@ function consumeLeadingFunctionLikeToolCall(value) {
59
59
  function stripVisibleFunctionLikeToolCallText(value) {
60
60
  let remaining = value.trim();
61
61
  let removedLeadingCall = false;
62
+ if (salvageJsonToolCalls(remaining).length > 0) {
63
+ return "";
64
+ }
62
65
  while (remaining.length > 0) {
63
66
  const consumed = consumeLeadingFunctionLikeToolCall(remaining);
64
67
  if (!consumed) {
@@ -84,6 +87,7 @@ function stripVisibleFunctionLikeToolCallText(value) {
84
87
  }
85
88
  export function sanitizeVisibleText(value) {
86
89
  return stripVisibleFunctionLikeToolCallText(value
90
+ .replace(/<agent_memory>[\s\S]*?<\/agent_memory>/giu, "")
87
91
  .replace(/[A-Za-z0-9_]*Middleware\.after_model/g, "")
88
92
  .replace(/todoListMiddleware\.after_model/g, "")
89
93
  .replace(/__end__+/g, "")
@@ -205,6 +205,43 @@ function closeJsonContainerSuffix(value) {
205
205
  }
206
206
  return `${trimmed}${stack.reverse().join("")}`;
207
207
  }
208
+ function normalizePythonLikeJson(value) {
209
+ const trimmed = value.trim();
210
+ if (!trimmed.includes("'")) {
211
+ return null;
212
+ }
213
+ let output = "";
214
+ let inSingle = false;
215
+ let inDouble = false;
216
+ let escaping = false;
217
+ for (const char of trimmed) {
218
+ if (escaping) {
219
+ output += char;
220
+ escaping = false;
221
+ continue;
222
+ }
223
+ if (char === "\\") {
224
+ output += char;
225
+ escaping = true;
226
+ continue;
227
+ }
228
+ if (char === "\"" && !inSingle) {
229
+ inDouble = !inDouble;
230
+ output += char;
231
+ continue;
232
+ }
233
+ if (char === "'" && !inDouble) {
234
+ inSingle = !inSingle;
235
+ output += "\"";
236
+ continue;
237
+ }
238
+ output += char;
239
+ }
240
+ if (inSingle || inDouble) {
241
+ return null;
242
+ }
243
+ return output;
244
+ }
208
245
  export function salvageToolArgs(value) {
209
246
  if (typeof value === "object" && value && !Array.isArray(value)) {
210
247
  return value;
@@ -255,6 +292,13 @@ export function salvageJsonToolCalls(value) {
255
292
  if (direct) {
256
293
  return direct;
257
294
  }
295
+ const pythonLike = normalizePythonLikeJson(trimmed);
296
+ if (pythonLike) {
297
+ const parsed = tryParseJson(pythonLike);
298
+ if (parsed) {
299
+ return parsed;
300
+ }
301
+ }
258
302
  const closed = closeJsonContainerSuffix(trimmed);
259
303
  if (closed) {
260
304
  const parsed = tryParseJson(closed);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.354",
3
+ "version": "0.0.355",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",