@botbotgo/agent-harness 0.0.338 → 0.0.341

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 (32) hide show
  1. package/dist/contracts/workspace.d.ts +10 -0
  2. package/dist/package-version.d.ts +2 -2
  3. package/dist/package-version.js +2 -2
  4. package/dist/runtime/adapter/flow/execution-context.js +3 -2
  5. package/dist/runtime/adapter/flow/stream-runtime.d.ts +6 -0
  6. package/dist/runtime/adapter/flow/stream-runtime.js +54 -15
  7. package/dist/runtime/adapter/invocation-result.js +111 -9
  8. package/dist/runtime/adapter/local-tool-invocation.js +21 -1
  9. package/dist/runtime/adapter/middleware/context-hygiene.d.ts +5 -0
  10. package/dist/runtime/adapter/middleware/context-hygiene.js +83 -0
  11. package/dist/runtime/adapter/middleware-assembly.d.ts +11 -0
  12. package/dist/runtime/adapter/middleware-assembly.js +154 -178
  13. package/dist/runtime/adapter/model/invocation-request.js +39 -1
  14. package/dist/runtime/adapter/runtime-adapter-support.js +33 -3
  15. package/dist/runtime/adapter/stream-event-projection.js +6 -5
  16. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +7 -0
  17. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +31 -24
  18. package/dist/runtime/agent-runtime-adapter.d.ts +3 -2
  19. package/dist/runtime/agent-runtime-adapter.js +128 -9
  20. package/dist/runtime/agent-runtime-assembly.d.ts +1 -0
  21. package/dist/runtime/agent-runtime-assembly.js +10 -2
  22. package/dist/runtime/harness/run/inspection.js +4 -5
  23. package/dist/runtime/harness/run/stream-run.js +180 -50
  24. package/dist/runtime/parsing/output-parsing.d.ts +1 -1
  25. package/dist/runtime/parsing/output-parsing.js +1 -1
  26. package/dist/runtime/parsing/output-recovery.d.ts +9 -0
  27. package/dist/runtime/parsing/output-recovery.js +46 -1
  28. package/dist/runtime/support/compiled-binding.d.ts +5 -0
  29. package/dist/runtime/support/compiled-binding.js +12 -0
  30. package/dist/workspace/agent-binding-compiler.js +8 -0
  31. package/dist/workspace/object-loader.js +6 -0
  32. package/package.json +1 -1
@@ -1,102 +1,118 @@
1
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
2
- import { createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, StateBackend, } from "deepagents";
3
- import { createAgent, humanInTheLoopMiddleware } from "langchain";
1
+ import { AIMessage, HumanMessage } from "@langchain/core/messages";
2
+ import { createMemoryMiddleware, createFilesystemMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, StateBackend, } from "deepagents";
3
+ import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
4
4
  import { createBuiltinMiddlewareTools } from "./tool/builtin-middleware-tools.js";
5
5
  import { compileInterruptOn } from "./tool/interrupt-policy.js";
6
6
  import { extractToolFallbackContext, extractVisibleOutput } from "../parsing/output-parsing.js";
7
7
  import { isRecord } from "../../utils/object.js";
8
8
  import { resolveDeclaredMiddleware } from "./tool/declared-middleware.js";
9
9
  import { UPSTREAM_SESSION_CONFIG_KEY } from "./upstream-configurable-keys.js";
10
- import { bindingHasLangChainSubagentSupport, bindingHasMiddlewareKind, getBindingExecutionKind, getBindingGeneralPurposeAgent, getBindingDeepAgentSubagents, getBindingInterruptCompatibilityRules, getBindingMiddlewareConfigs, getBindingMemorySources, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSkills, getBindingSubagents, getBindingTaskDescription, isDeepAgentBinding, isLangChainBinding, } from "../support/compiled-binding.js";
10
+ import { bindingHasLangChainSubagentSupport, bindingHasMiddlewareKind, getBindingBuiltinToolsConfig, getBindingExecutionKind, getBindingGeneralPurposeAgent, getBindingDeepAgentSubagents, getBindingInterruptCompatibilityRules, getBindingMiddlewareConfigs, getBindingMemorySources, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSkills, getBindingSubagents, getBindingTaskDescription, isDeepAgentBinding, isLangChainBinding, } from "../support/compiled-binding.js";
11
11
  import { materializeDeepAgentSkillSourcePaths } from "./compat/deepagent-compat.js";
12
12
  import { DEFAULT_SUBAGENT_PROMPT } from "../prompts/runtime-prompts.js";
13
- import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../prompts/runtime-prompts.js";
14
- import { createStreamEventProjectionState, projectRuntimeStreamEvent } from "./stream-event-projection.js";
15
- import { resolveDeterministicFinalOutput } from "./invocation-result.js";
16
- const EMPTY_TOOL_NAME_MAPPING = {
17
- originalToModelFacing: new Map(),
18
- modelFacingToOriginal: new Map(),
19
- };
20
- function readPlanStateSummaryCounts(summary) {
21
- if (typeof summary !== "object" || summary === null) {
22
- return null;
13
+ import { createContextHygieneMiddleware } from "./middleware/context-hygiene.js";
14
+ const INVALID_TOOL_MESSAGE_BLOCK_TYPES = new Set(["tool_use", "thinking", "redacted_thinking"]);
15
+ function extractDeepAgentTaskContent(result) {
16
+ if (typeof result !== "object" || result === null) {
17
+ return undefined;
23
18
  }
24
- const typed = summary;
25
- const hasAny = typeof typed.pending === "number"
26
- || typeof typed.inProgress === "number";
27
- if (!hasAny) {
28
- return null;
19
+ const messages = result.messages;
20
+ if (!Array.isArray(messages) || messages.length === 0) {
21
+ return undefined;
29
22
  }
30
- return {
31
- pending: typeof typed.pending === "number" ? typed.pending : 0,
32
- inProgress: typeof typed.inProgress === "number" ? typed.inProgress : 0,
33
- };
34
- }
35
- function hasIncompletePlanTodos(value) {
36
- if (!Array.isArray(value)) {
37
- return null;
23
+ const lastMessage = messages[messages.length - 1];
24
+ if (typeof lastMessage !== "object" || lastMessage === null) {
25
+ return undefined;
38
26
  }
39
- return value.some((item) => {
40
- if (typeof item !== "object" || item === null) {
41
- return false;
42
- }
43
- const status = item.status;
44
- return status === "pending" || status === "in_progress";
45
- });
27
+ if ("content" in lastMessage) {
28
+ return lastMessage.content;
29
+ }
30
+ if ("kwargs" in lastMessage) {
31
+ return (lastMessage.kwargs)?.content;
32
+ }
33
+ return undefined;
46
34
  }
47
- function hasIncompletePlanStateInValue(value) {
48
- if (Array.isArray(value)) {
49
- return value.some((item) => hasIncompletePlanStateInValue(item));
35
+ export function extractSubagentRequestText(state) {
36
+ if (!isRecord(state)) {
37
+ return "";
50
38
  }
51
- if (typeof value !== "object" || value === null) {
52
- return false;
39
+ const messages = Array.isArray(state.messages) ? state.messages : [];
40
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
41
+ if (!isRecord(lastMessage)) {
42
+ return "";
53
43
  }
54
- const typed = value;
55
- const summaryCounts = readPlanStateSummaryCounts(typed.summary);
56
- if (summaryCounts) {
57
- return summaryCounts.pending > 0 || summaryCounts.inProgress > 0;
44
+ const content = lastMessage.content;
45
+ if (typeof content === "string") {
46
+ return content;
58
47
  }
59
- const todoCompleteness = hasIncompletePlanTodos(typed.todos);
60
- if (todoCompleteness !== null) {
61
- return todoCompleteness;
48
+ if (Array.isArray(content)) {
49
+ return content
50
+ .map((block) => {
51
+ if (typeof block === "string") {
52
+ return block;
53
+ }
54
+ if (isRecord(block) && typeof block.text === "string") {
55
+ return block.text;
56
+ }
57
+ return "";
58
+ })
59
+ .filter((part) => part.length > 0)
60
+ .join("\n");
62
61
  }
63
- const nestedCandidates = [
64
- typed.summary,
65
- typed.output,
66
- typed.content,
67
- typed.update,
68
- typed.data,
69
- typed.messages,
70
- ];
71
- return nestedCandidates.some((candidate) => hasIncompletePlanStateInValue(candidate));
62
+ return "";
72
63
  }
73
- function hasUnresolvedDelegatedExecution(state) {
74
- return state.hasIncompletePlanState || state.openTaskDelegations > 0;
64
+ export function wrapRequestResultAsSubagentResponse(result) {
65
+ return {
66
+ messages: [new AIMessage({ content: result.output || "Task completed" })],
67
+ ...(result.structuredResponse !== undefined ? { structuredResponse: result.structuredResponse } : {}),
68
+ };
75
69
  }
76
- function formatDelegatedExecutionBlocker(state) {
77
- const summary = state.emittedOutput.trim();
78
- if (summary) {
79
- return summary;
80
- }
81
- if (state.emittedToolError) {
82
- return "Delegated investigation encountered a tool failure before the plan completed.";
83
- }
84
- if (state.openTaskDelegations > 0) {
85
- return "Delegated investigation did not finish before control returned to the parent agent.";
86
- }
87
- return "Delegated investigation ended before the plan was completed.";
70
+ function shouldUseControlledBuiltinSubagent(subagent) {
71
+ return subagent.builtinTools?.filesystem === false || subagent.builtinTools?.todos === false;
72
+ }
73
+ function createFallbackDeepAgentBackend() {
74
+ return new StateBackend({});
88
75
  }
89
- function requiresDelegatedExecutionRecovery(state) {
90
- return hasUnresolvedDelegatedExecution(state);
76
+ async function buildControlledBuiltinSubagent(input) {
77
+ const resolvedModel = input.subagent.model ? await input.resolveModel(input.subagent.model) : undefined;
78
+ const resolvedTools = input.subagent.tools ? input.resolveTools(input.subagent.tools, input.binding) : undefined;
79
+ const resolvedSkillPaths = (await materializeDeepAgentSkillSourcePaths({
80
+ workspaceRoot: input.binding?.harnessRuntime.workspaceRoot,
81
+ runtimeRoot: input.binding?.harnessRuntime.runtimeRoot,
82
+ ownerId: `${input.binding?.agent.id ?? "agent"}-${input.subagent.name}`,
83
+ skillPaths: input.subagent.skills,
84
+ })) ?? [];
85
+ const resolvedCustomMiddleware = await resolveDeclaredMiddleware(input.subagent.middleware, input.createDeclaredMiddlewareResolverOptions(input.binding));
86
+ const interruptOn = compileInterruptOn(input.subagent.tools ?? [], input.subagent.interruptOn);
87
+ const backend = input.resolveBackend?.(input.binding) ?? createFallbackDeepAgentBackend();
88
+ const builtinTools = input.subagent.builtinTools ?? {};
89
+ const middleware = [
90
+ ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
91
+ ...(resolvedSkillPaths.length > 0 ? [createSkillsMiddleware({ backend: backend, sources: resolvedSkillPaths })] : []),
92
+ ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend: backend })]),
93
+ createSummarizationMiddleware({
94
+ model: resolvedModel,
95
+ backend: backend,
96
+ }),
97
+ createPatchToolCallsMiddleware(),
98
+ ...resolvedCustomMiddleware,
99
+ ...(interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []),
100
+ ];
101
+ const runnable = createAgent({
102
+ model: resolvedModel,
103
+ systemPrompt: input.subagent.systemPrompt,
104
+ tools: resolvedTools,
105
+ middleware: middleware,
106
+ name: input.subagent.name,
107
+ ...(input.subagent.responseFormat !== undefined ? { responseFormat: input.subagent.responseFormat } : {}),
108
+ });
109
+ return {
110
+ name: input.subagent.name,
111
+ description: input.subagent.description,
112
+ systemPrompt: input.subagent.systemPrompt,
113
+ runnable,
114
+ };
91
115
  }
92
- const DELEGATED_FAILURE_PLAN_RECONCILIATION_INSTRUCTION = [
93
- "Your previous attempt ended with a tool failure while the todo board still had unfinished work.",
94
- "Do not continue broad investigation from here.",
95
- "If the failed command had malformed arguments you can correct locally, retry that same command once.",
96
- "Otherwise, if a todo board already exists, call write_todos again and keep only the tasks that were actually completed in this session.",
97
- "Remove the unfinished tasks that cannot proceed until the blocker is resolved.",
98
- "Then return a concise blocker report.",
99
- ].join(" ");
100
116
  export function buildBuiltinTaskSubagentMiddleware(input) {
101
117
  const { selectedSubagent, builtinBackend, summarizationModel } = input;
102
118
  const defaultSubagentMiddleware = [
@@ -190,6 +206,16 @@ export function resolveBuiltinMiddlewareBackend(input) {
190
206
  }
191
207
  export async function resolveSubagents(input) {
192
208
  return Promise.all(input.subagents.map(async (subagent) => {
209
+ if (shouldUseControlledBuiltinSubagent(subagent)) {
210
+ return buildControlledBuiltinSubagent({
211
+ subagent,
212
+ binding: input.binding,
213
+ resolveModel: input.resolveModel,
214
+ resolveTools: input.resolveTools,
215
+ createDeclaredMiddlewareResolverOptions: input.createDeclaredMiddlewareResolverOptions,
216
+ resolveBackend: input.resolveBackend,
217
+ });
218
+ }
193
219
  // Only pass DeepAgents-supported subagent fields through to upstream middleware.
194
220
  // Harness-only extensions stay internal instead of becoming a second subagent dialect.
195
221
  return {
@@ -256,118 +282,67 @@ export async function invokeBuiltinTaskTool(input) {
256
282
  configurable: { [UPSTREAM_SESSION_CONFIG_KEY]: `${input.binding.agent.id}:builtin-task` },
257
283
  ...(input.options?.context ? { context: input.options.context } : {}),
258
284
  };
259
- const buildMessages = (recoveryInstruction) => ({
260
- messages: [
261
- ...(recoveryInstruction ? [new SystemMessage({ content: recoveryInstruction })] : []),
262
- new HumanMessage({ content: description }),
263
- ],
264
- });
265
- if (typeof runnable.streamEvents === "function") {
266
- const runWithStreamInspection = async (recoveryInstruction) => {
267
- const projectionState = createStreamEventProjectionState();
268
- const executedToolResults = [];
269
- const events = await runnable.streamEvents(buildMessages(recoveryInstruction), invokeConfig);
270
- for await (const event of events) {
271
- const projectedChunks = projectRuntimeStreamEvent({
272
- event,
273
- allowVisibleStreamDeltas: false,
274
- includeStateStreamOutput: false,
275
- rootAgentId: input.binding.agent.id,
276
- countConfiguredToolsForAgentId: (agentId) => agentId === selectedSubagent.name ? resolvedSubagentTools.length : 0,
277
- toolNameMapping: EMPTY_TOOL_NAME_MAPPING,
278
- primaryTools: [],
279
- state: projectionState,
280
- });
281
- for (const chunk of projectedChunks) {
282
- if (chunk.kind !== "tool-result") {
283
- continue;
284
- }
285
- executedToolResults.push({
286
- toolName: chunk.toolName,
287
- output: chunk.output,
288
- isError: chunk.isError,
289
- });
290
- }
291
- }
292
- return { projectionState, executedToolResults };
293
- };
294
- let { projectionState, executedToolResults } = await runWithStreamInspection();
295
- if (requiresDelegatedExecutionRecovery(projectionState)) {
296
- const initialProjectionState = projectionState;
297
- const initialExecutedToolResults = executedToolResults;
298
- const initialDeterministicOutput = resolveDeterministicFinalOutput({
299
- visibleOutput: initialProjectionState.emittedOutput.trim(),
300
- executedToolResults: initialExecutedToolResults,
301
- });
302
- const recoveryInstruction = projectionState.hasIncompletePlanState && projectionState.emittedToolError
303
- ? `${AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION}\n\n${DELEGATED_FAILURE_PLAN_RECONCILIATION_INSTRUCTION}`
304
- : AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION;
305
- const recovered = await runWithStreamInspection(recoveryInstruction);
306
- const recoveredDeterministicOutput = resolveDeterministicFinalOutput({
307
- visibleOutput: recovered.projectionState.emittedOutput.trim(),
308
- executedToolResults: recovered.executedToolResults,
309
- });
310
- const recoveredHasSubstantiveExecution = recoveredDeterministicOutput.length > 0;
311
- if (recoveredHasSubstantiveExecution) {
312
- projectionState = recovered.projectionState;
313
- executedToolResults = recovered.executedToolResults;
314
- }
315
- else {
316
- projectionState = initialProjectionState;
317
- executedToolResults = initialExecutedToolResults;
318
- if (initialDeterministicOutput) {
319
- projectionState = {
320
- ...projectionState,
321
- emittedOutput: initialDeterministicOutput,
322
- };
323
- }
324
- }
325
- }
326
- if (requiresDelegatedExecutionRecovery(projectionState)) {
327
- throw new Error(formatDelegatedExecutionBlocker(projectionState));
328
- }
329
- if (projectionState.emittedToolError) {
330
- const blockerMessage = resolveDeterministicFinalOutput({
331
- visibleOutput: projectionState.emittedOutput.trim(),
332
- executedToolResults,
333
- }) || formatDelegatedExecutionBlocker(projectionState);
334
- if (hasUnresolvedDelegatedExecution(projectionState) || !projectionState.emittedSuccessfulToolResult) {
335
- throw new Error(blockerMessage);
336
- }
337
- }
338
- const deterministicOutput = resolveDeterministicFinalOutput({
339
- visibleOutput: projectionState.emittedOutput.trim(),
340
- executedToolResults,
285
+ const result = await runnable.invoke({
286
+ messages: [new HumanMessage({ content: description })],
287
+ }, invokeConfig);
288
+ const structuredResponse = typeof result === "object" && result !== null && "structuredResponse" in result
289
+ ? result.structuredResponse
290
+ : undefined;
291
+ if (structuredResponse !== undefined) {
292
+ return JSON.stringify(structuredResponse);
293
+ }
294
+ const taskContent = extractDeepAgentTaskContent(result);
295
+ if (Array.isArray(taskContent)) {
296
+ const filtered = taskContent.filter((block) => {
297
+ const blockType = typeof block === "object" && block !== null && "type" in block
298
+ ? block.type
299
+ : undefined;
300
+ return !(typeof blockType === "string" && INVALID_TOOL_MESSAGE_BLOCK_TYPES.has(blockType));
341
301
  });
342
- if (deterministicOutput) {
343
- return deterministicOutput;
344
- }
345
- if (projectionState.emittedToolResult) {
346
- throw new Error("Delegated investigation performed tool work but did not return surfaced findings.");
302
+ if (filtered.length > 0) {
303
+ return filtered;
347
304
  }
348
305
  }
349
- let result = await runnable.invoke(buildMessages(), invokeConfig);
350
- if (hasIncompletePlanStateInValue(result)) {
351
- result = await runnable.invoke(buildMessages(AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION), invokeConfig);
352
- }
353
- if (hasIncompletePlanStateInValue(result)) {
354
- throw new Error(extractVisibleOutput(result) || extractToolFallbackContext(result) || "Delegated investigation ended before the plan was completed.");
306
+ else if (taskContent !== undefined && taskContent !== null && taskContent !== "") {
307
+ return taskContent;
355
308
  }
356
309
  const visibleOutput = extractVisibleOutput(result);
310
+ if (visibleOutput) {
311
+ return visibleOutput;
312
+ }
357
313
  const fallbackOutput = extractToolFallbackContext(result);
358
- const structuredResponse = typeof result === "object" && result !== null && "structuredResponse" in result
359
- ? result.structuredResponse
360
- : undefined;
361
- return visibleOutput || fallbackOutput || (structuredResponse !== undefined ? JSON.stringify(structuredResponse) : "") || JSON.stringify(result);
314
+ return fallbackOutput || "Task completed";
362
315
  }
363
316
  export async function resolveBuiltinMiddlewareTools(input) {
364
317
  const backend = input.resolveBuiltinMiddlewareBackend(input.binding, input.options);
365
- return createBuiltinMiddlewareTools(backend, {
318
+ const tools = (await createBuiltinMiddlewareTools(backend, {
366
319
  includeTaskTool: false,
367
320
  workspaceRoot: input.binding.harnessRuntime.workspaceRoot,
368
321
  toolRuntimeContext: input.options?.toolRuntimeContext,
369
322
  invokeTaskTool: undefined,
370
- });
323
+ }));
324
+ const builtinTools = getBindingBuiltinToolsConfig(input.binding) ?? {};
325
+ if (builtinTools.todos === false) {
326
+ tools.delete("write_todos");
327
+ tools.delete("read_todos");
328
+ }
329
+ if (builtinTools.filesystem === false) {
330
+ for (const name of [
331
+ "ls",
332
+ "list_files",
333
+ "read_file",
334
+ "write_file",
335
+ "edit_file",
336
+ "glob",
337
+ "grep",
338
+ "search_files",
339
+ "execute",
340
+ "run_command",
341
+ ]) {
342
+ tools.delete(name);
343
+ }
344
+ }
345
+ return tools;
371
346
  }
372
347
  export async function materializeAutomaticSummarizationMiddleware(input) {
373
348
  const primaryModel = getBindingPrimaryModel(input.binding);
@@ -423,6 +398,7 @@ export async function resolveMiddleware(input) {
423
398
  ? await input.resolveLangChainRuntimeExtensionMiddleware(input.binding)
424
399
  : [];
425
400
  const middleware = [
401
+ createContextHygieneMiddleware(),
426
402
  ...declarativeUpstreamMiddleware,
427
403
  ...runtimeExtensionMiddleware,
428
404
  ...(input.runtimeAdapterOptions.middlewareResolver ? input.runtimeAdapterOptions.middlewareResolver(input.binding) : []),
@@ -2,7 +2,7 @@ import { getBindingSkills } from "../../support/compiled-binding.js";
2
2
  import { extractMessageText, normalizeMessageContent } from "../../../utils/message-content.js";
3
3
  import { readSkillMetadata } from "../../skills/skill-metadata.js";
4
4
  import { summarizeAssistantText } from "../direct-builtin-utility.js";
5
- import { renderDurableMemoryContextPrompt, renderSlashCommandSkillInstruction } from "../../prompts/runtime-prompts.js";
5
+ import { renderDurableMemoryContextPrompt, renderSlashCommandSkillInstruction, } from "../../prompts/runtime-prompts.js";
6
6
  import { hasExplicitResourceReference, } from "../../harness/system/runtime-memory-policy.js";
7
7
  const MAX_HISTORY_ASSISTANT_CHARS = 1600;
8
8
  const MAX_HISTORY_ASSISTANT_LINES = 40;
@@ -125,6 +125,42 @@ function buildContextualFollowUpInstruction(inputText, hasDurableMemory) {
125
125
  }
126
126
  return "Answer the user's current follow-up directly from the recalled context when it remains relevant. If recalled memory conflicts with the current system prompt, runtime policy, recovery instruction, available tool evidence, or the user's current request, ignore the recalled memory for this turn. If the current user turn corrects, revokes, deletes, or replaces recalled memory, treat the user's latest statement as newer than the recalled memory for this turn. Do not frame the reply as a fresh URL or page summary unless the current user turn explicitly includes a new resource to inspect.";
127
127
  }
128
+ function isIncidentFollowUpTurn(inputText) {
129
+ const normalized = inputText.trim().toLowerCase();
130
+ if (!normalized || hasExplicitResourceReference(normalized)) {
131
+ return false;
132
+ }
133
+ return /(the rca|deep research.*rca|root cause|go deeper|those issues|these issues|that issue|current incident|kubernetes issues)/i.test(normalized);
134
+ }
135
+ function findLastAssistantText(history) {
136
+ for (let index = history.length - 1; index >= 0; index -= 1) {
137
+ const message = history[index];
138
+ if (message?.role !== "assistant") {
139
+ continue;
140
+ }
141
+ const text = extractMessageText(message.content).trim();
142
+ if (text) {
143
+ return compactHistoryText(text);
144
+ }
145
+ }
146
+ return undefined;
147
+ }
148
+ function buildIncidentFollowUpInstruction(history, inputText) {
149
+ if (!isIncidentFollowUpTurn(inputText)) {
150
+ return undefined;
151
+ }
152
+ const priorAssistantText = findLastAssistantText(history);
153
+ if (!priorAssistantText) {
154
+ return undefined;
155
+ }
156
+ return [
157
+ "Treat the user's current turn as a follow-up on the current incident, not as a generic background-knowledge request.",
158
+ "Use the immediately prior assistant findings below as the default incident context unless the user explicitly overrides it.",
159
+ "",
160
+ "Prior assistant findings:",
161
+ priorAssistantText,
162
+ ].join("\n");
163
+ }
128
164
  export function buildSlashCommandSkillInstruction(binding, input) {
129
165
  const inputText = extractMessageText(input).trim();
130
166
  const match = inputText.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?$/i);
@@ -162,6 +198,7 @@ export function buildInvocationRequest(binding, history, input, options = {}) {
162
198
  suppressHistoryTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
163
199
  });
164
200
  const contextualFollowUpInstruction = buildContextualFollowUpInstruction(inputText, Boolean(memoryInstruction));
201
+ const incidentFollowUpInstruction = buildIncidentFollowUpInstruction(history, inputText);
165
202
  const conversationLanguage = resolveConversationLanguage(history, inputText);
166
203
  const languageInstruction = !inferMessageLanguage(inputText) && conversationLanguage
167
204
  ? {
@@ -175,6 +212,7 @@ export function buildInvocationRequest(binding, history, input, options = {}) {
175
212
  messages: [
176
213
  ...(memoryInstruction ? [{ role: "system", content: memoryInstruction }] : []),
177
214
  ...(contextualFollowUpInstruction ? [{ role: "system", content: contextualFollowUpInstruction }] : []),
215
+ ...(incidentFollowUpInstruction ? [{ role: "system", content: incidentFollowUpInstruction }] : []),
178
216
  ...(languageInstruction ? [{ role: "system", content: languageInstruction }] : []),
179
217
  ...(userInvocableInstruction ? [{ role: "system", content: userInvocableInstruction }] : []),
180
218
  ...messages,
@@ -6,8 +6,29 @@ export function truncateLines(lines, maxChars = 12_000) {
6
6
  }
7
7
  return `${joined.slice(0, maxChars - 18)}\n...[truncated]`;
8
8
  }
9
+ function findTodosArray(value, depth = 0) {
10
+ if (depth > 3 || !isRecord(value)) {
11
+ return [];
12
+ }
13
+ if (Array.isArray(value.todos)) {
14
+ return value.todos;
15
+ }
16
+ const nestedFromUpdate = findTodosArray(value.update, depth + 1);
17
+ if (nestedFromUpdate.length > 0) {
18
+ return nestedFromUpdate;
19
+ }
20
+ const nestedFromOutput = findTodosArray(value.output, depth + 1);
21
+ if (nestedFromOutput.length > 0) {
22
+ return nestedFromOutput;
23
+ }
24
+ const nestedFromData = findTodosArray(value.data, depth + 1);
25
+ if (nestedFromData.length > 0) {
26
+ return nestedFromData;
27
+ }
28
+ return [];
29
+ }
9
30
  export function summarizeBuiltinWriteTodosArgs(args) {
10
- const todos = Array.isArray(args.todos) ? args.todos : [];
31
+ const todos = findTodosArray(args);
11
32
  const items = todos.flatMap((todo) => {
12
33
  if (!isRecord(todo)) {
13
34
  return [];
@@ -16,11 +37,20 @@ export function summarizeBuiltinWriteTodosArgs(args) {
16
37
  ? todo.content.trim()
17
38
  : typeof todo.description === "string" && todo.description.trim().length > 0
18
39
  ? todo.description.trim()
19
- : "";
40
+ : typeof todo.title === "string" && todo.title.trim().length > 0
41
+ ? todo.title.trim()
42
+ : typeof todo.name === "string" && todo.name.trim().length > 0
43
+ ? todo.name.trim()
44
+ : typeof todo.text === "string" && todo.text.trim().length > 0
45
+ ? todo.text.trim()
46
+ : "";
20
47
  const status = typeof todo.status === "string" && todo.status.trim().length > 0 ? todo.status.trim() : "pending";
21
48
  const metadata = isRecord(todo.metadata) ? todo.metadata : undefined;
22
49
  return content ? [{
23
- ...(typeof todo.id === "string" && todo.id.trim().length > 0 ? { id: todo.id.trim() } : {}),
50
+ ...((typeof todo.id === "string" && todo.id.trim().length > 0)
51
+ || typeof todo.id === "number"
52
+ ? { id: String(todo.id).trim() }
53
+ : {}),
24
54
  content,
25
55
  status,
26
56
  ...(typeof todo.ownerAgentId === "string" && todo.ownerAgentId.trim().length > 0 ? { ownerAgentId: todo.ownerAgentId.trim() } : {}),
@@ -237,17 +237,18 @@ function isInternalRuntimeSpillPathErrorValue(value) {
237
237
  }
238
238
  function recordDelegatedFindings(state, value, source = "tool") {
239
239
  if (state.taskDelegationFindingsStack.length === 0) {
240
- return;
240
+ return false;
241
241
  }
242
242
  const normalized = normalizeDelegatedFindingsText(value);
243
243
  if (!normalized) {
244
- return;
244
+ return false;
245
245
  }
246
246
  const current = state.taskDelegationFindingsStack[state.taskDelegationFindingsStack.length - 1] ?? "";
247
247
  if (source === "terminal" && current) {
248
- return;
248
+ return true;
249
249
  }
250
250
  state.taskDelegationFindingsStack[state.taskDelegationFindingsStack.length - 1] = normalized;
251
+ return true;
251
252
  }
252
253
  function updateDelegationState(state, event, countConfiguredToolsForAgentId) {
253
254
  if (typeof event !== "object" || event === null) {
@@ -392,8 +393,8 @@ export function projectRuntimeStreamEvent(params) {
392
393
  if (!allowVisibleContent) {
393
394
  const delegatedTerminalOutput = extractTerminalStreamOutput(event);
394
395
  if (delegatedTerminalOutput) {
395
- state.emittedDelegatedTerminalOutput = true;
396
- recordDelegatedFindings(state, delegatedTerminalOutput, "terminal");
396
+ state.emittedDelegatedTerminalOutput =
397
+ state.emittedDelegatedTerminalOutput || recordDelegatedFindings(state, delegatedTerminalOutput, "terminal");
397
398
  }
398
399
  }
399
400
  if (output && !shouldSuppressVisibleToolCallText(output)) {
@@ -243,6 +243,13 @@ export declare const BUILTIN_MIDDLEWARE_TOOL_DESCRIPTORS: readonly [{
243
243
  readonly name: "task";
244
244
  readonly description: "Delegate a bounded task to a subagent.";
245
245
  }];
246
+ export declare function filterBuiltinMiddlewareToolDescriptors(options?: {
247
+ filesystem?: boolean;
248
+ todos?: boolean;
249
+ }): Array<{
250
+ name: string;
251
+ description: string;
252
+ }>;
246
253
  export declare function createBuiltinMiddlewareTools(backend: BuiltinMiddlewareBackend, options: {
247
254
  includeTaskTool: boolean;
248
255
  invokeTaskTool?: (input: unknown) => Promise<unknown>;
@@ -4,28 +4,10 @@ import { isSandboxBackend } from "deepagents";
4
4
  import { isRecord } from "../../../utils/object.js";
5
5
  import { formatBuiltinTodoSnapshot, isLowSignalTodoContent, summarizeBuiltinWriteTodosArgs, truncateLines } from "../runtime-adapter-support.js";
6
6
  import { maybePersistLargeToolOutput, resolveToolRuntimeContext } from "./tool-output-artifacts.js";
7
- const taskToolSchema = z.preprocess((value) => {
8
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
9
- return value;
10
- }
11
- return normalizeTaskToolInput(value);
12
- }, z.object({
7
+ const taskToolSchema = z.object({
13
8
  description: z.string(),
14
9
  subagent_type: z.string(),
15
- }).passthrough());
16
- function normalizeTaskToolInput(typed) {
17
- const description = typeof typed.description === "string" && typed.description.trim().length > 0
18
- ? typed.description
19
- : undefined;
20
- const subagentType = typeof typed.subagent_type === "string" && typed.subagent_type.trim().length > 0
21
- ? typed.subagent_type
22
- : undefined;
23
- return {
24
- ...typed,
25
- ...(description !== undefined ? { description } : {}),
26
- ...(subagentType !== undefined ? { subagent_type: subagentType } : {}),
27
- };
28
- }
10
+ }).passthrough();
29
11
  export const BUILTIN_MIDDLEWARE_TOOL_DESCRIPTORS = [
30
12
  { name: "write_todos", description: "Create and update the runtime todo board for multi-step work." },
31
13
  { name: "read_todos", description: "Read the current runtime todo board." },
@@ -46,6 +28,30 @@ export const BUILTIN_MIDDLEWARE_TOOL_DESCRIPTORS = [
46
28
  { name: "schedule_task", description: "Create, inspect, update, list, and delete system-level scheduled tasks." },
47
29
  { name: "task", description: "Delegate a bounded task to a subagent." },
48
30
  ];
31
+ export function filterBuiltinMiddlewareToolDescriptors(options) {
32
+ return BUILTIN_MIDDLEWARE_TOOL_DESCRIPTORS.filter((descriptor) => {
33
+ if (options?.todos === false
34
+ && (descriptor.name === "write_todos" || descriptor.name === "read_todos")) {
35
+ return false;
36
+ }
37
+ if (options?.filesystem === false
38
+ && [
39
+ "ls",
40
+ "list_files",
41
+ "read_file",
42
+ "write_file",
43
+ "edit_file",
44
+ "glob",
45
+ "grep",
46
+ "search_files",
47
+ "execute",
48
+ "run_command",
49
+ ].includes(descriptor.name)) {
50
+ return false;
51
+ }
52
+ return true;
53
+ }).map((descriptor) => ({ ...descriptor }));
54
+ }
49
55
  function toDisplayContent(content) {
50
56
  if (typeof content === "string") {
51
57
  return content;
@@ -214,9 +220,12 @@ export async function createBuiltinMiddlewareTools(backend, options) {
214
220
  description: "Create and update the runtime todo board for multi-step work.",
215
221
  schema: z.object({
216
222
  todos: z.array(z.object({
217
- id: z.string().optional(),
223
+ id: z.union([z.string(), z.number()]).optional(),
218
224
  content: z.string().optional(),
219
225
  description: z.string().optional(),
226
+ title: z.string().optional(),
227
+ name: z.string().optional(),
228
+ text: z.string().optional(),
220
229
  status: z.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
221
230
  ownerAgentId: z.string().optional(),
222
231
  startedAt: z.string().optional(),
@@ -615,9 +624,7 @@ export async function createBuiltinMiddlewareTools(backend, options) {
615
624
  name: "task",
616
625
  description: "Delegate a bounded task to a subagent.",
617
626
  schema: taskToolSchema,
618
- invoke: async (input, config) => options.invokeTaskTool(typeof input === "object" && input !== null && !Array.isArray(input)
619
- ? normalizeTaskToolInput(input)
620
- : {}),
627
+ invoke: async (input) => options.invokeTaskTool(input),
621
628
  });
622
629
  }
623
630
  return tools;