deepagents 1.13.1 → 1.13.3

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.
@@ -2,7 +2,7 @@ import { AIMessage, HumanMessage, SystemMessage, ToolMessage, anthropicPromptCac
2
2
  import { Command, REMOVE_ALL_MESSAGES, ReducedValue, StateSchema, getConfig, getCurrentTaskInput, getStore, isCommand } from "@langchain/langgraph";
3
3
  import { z } from "zod/v4";
4
4
  import micromatch from "micromatch";
5
- import { AIMessage as AIMessage$1, HumanMessage as HumanMessage$1, RemoveMessage, getBufferString } from "@langchain/core/messages";
5
+ import { AIMessage as AIMessage$1, HumanMessage as HumanMessage$1, RemoveMessage, SystemMessage as SystemMessage$1, ToolMessage as ToolMessage$1, getBufferString } from "@langchain/core/messages";
6
6
  import * as z$2 from "zod";
7
7
  import { z as z$1 } from "zod";
8
8
  import { ContextOverflowError } from "@langchain/core/errors";
@@ -23,6 +23,22 @@ const EMPTY_CONTENT_WARNING = "System reminder: File exists but has empty conten
23
23
  const MAX_LINE_LENGTH = 5e3;
24
24
  const TOOL_RESULT_TOKEN_LIMIT = 2e4;
25
25
  const TRUNCATION_GUIDANCE = "... [results truncated, try being more specific with your parameters]";
26
+ /**
27
+ * Normalize model- or caller-supplied text pagination bounds.
28
+ *
29
+ * Every backend must slice content and calculate pagination metadata from the
30
+ * same normalized values. Otherwise a fractional or negative argument could
31
+ * return one window while advertising a different `nextOffset`.
32
+ *
33
+ * Binary reads do not use this helper because their backend contract ignores
34
+ * line-based offset and limit values.
35
+ */
36
+ function normalizeReadPagination(offset, limit) {
37
+ return {
38
+ offset: Number.isFinite(offset) ? Math.max(0, Math.floor(offset)) : 0,
39
+ limit: Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0
40
+ };
41
+ }
26
42
  const MIME_TYPES = {
27
43
  ".png": "image/png",
28
44
  ".jpg": "image/jpeg",
@@ -134,40 +150,60 @@ function sanitizeToolCallId(toolCallId) {
134
150
  return toolCallId.replace(/\./g, "_").replace(/\//g, "_").replace(/\\/g, "_");
135
151
  }
136
152
  /**
137
- * Format file content with line numbers (cat -n style).
138
- *
139
- * Chunks lines longer than MAX_LINE_LENGTH with continuation markers (e.g., 5.1, 5.2).
153
+ * Format file content with line numbers and structured source-line boundaries.
140
154
  *
141
- * @param content - File content as string or list of lines
142
- * @param startLine - Starting line number (default: 1)
143
- * @returns Formatted content with line numbers and continuation markers
155
+ * The boundaries let downstream size limiting truncate only after complete
156
+ * source lines without reparsing the rendered gutter. This keeps presentation
157
+ * details (padding, tab separators, and continuation labels) encapsulated in
158
+ * the formatter that creates them.
144
159
  */
145
- function formatContentWithLineNumbers(content, startLine = 1) {
160
+ function formatContentWithLineNumbersAndBoundaries(content, startLine = 1) {
146
161
  let lines;
147
162
  if (typeof content === "string") {
148
163
  lines = content.split("\n");
149
164
  if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
150
165
  } else lines = content;
151
166
  const resultLines = [];
167
+ const sourceLineBoundaries = [];
168
+ let renderedLength = 0;
169
+ const appendRow = (row, sourceLine, completesSourceLine) => {
170
+ if (resultLines.length > 0) renderedLength += 1;
171
+ resultLines.push(row);
172
+ renderedLength += row.length;
173
+ if (completesSourceLine) sourceLineBoundaries.push({
174
+ sourceLine,
175
+ endOffset: renderedLength
176
+ });
177
+ };
152
178
  for (let i = 0; i < lines.length; i++) {
153
179
  const line = lines[i];
154
180
  const lineNum = i + startLine;
155
- if (line.length <= 5e3) resultLines.push(`${lineNum.toString().padStart(6)}\t${line}`);
156
- else {
157
- const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);
158
- for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
159
- const start = chunkIdx * MAX_LINE_LENGTH;
160
- const end = Math.min(start + MAX_LINE_LENGTH, line.length);
161
- const chunk = line.substring(start, end);
162
- if (chunkIdx === 0) resultLines.push(`${lineNum.toString().padStart(6)}\t${chunk}`);
163
- else {
164
- const continuationMarker = `${lineNum}.${chunkIdx}`;
165
- resultLines.push(`${continuationMarker.padStart(6)}\t${chunk}`);
166
- }
167
- }
181
+ if (line.length <= 5e3) {
182
+ appendRow(`${lineNum.toString().padStart(6)}\t${line}`, lineNum, true);
183
+ continue;
184
+ }
185
+ const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);
186
+ for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
187
+ const start = chunkIdx * MAX_LINE_LENGTH;
188
+ const end = Math.min(start + MAX_LINE_LENGTH, line.length);
189
+ const chunk = line.substring(start, end);
190
+ appendRow(`${(chunkIdx === 0 ? `${lineNum}` : `${lineNum}.${chunkIdx}`).padStart(6)}\t${chunk}`, lineNum, chunkIdx === numChunks - 1);
168
191
  }
169
192
  }
170
- return resultLines.join("\n");
193
+ return {
194
+ text: resultLines.join("\n"),
195
+ sourceLineBoundaries
196
+ };
197
+ }
198
+ /**
199
+ * Format file content with line numbers (cat -n style).
200
+ *
201
+ * Lines longer than `MAX_LINE_LENGTH` are split into continuation rows such as
202
+ * `5.1` and `5.2`. Use `formatContentWithLineNumbersAndBoundaries` when a
203
+ * caller also needs safe source-line truncation points.
204
+ */
205
+ function formatContentWithLineNumbers(content, startLine = 1) {
206
+ return formatContentWithLineNumbersAndBoundaries(content, startLine).text;
171
207
  }
172
208
  /**
173
209
  * Check if content is empty and return warning message.
@@ -841,10 +877,23 @@ var StateBackend = class {
841
877
  mimeType: fileDataV2.mimeType
842
878
  };
843
879
  if (typeof fileDataV2.content !== "string") return { error: `File '${filePath}' has binary content but text MIME type` };
844
- return {
845
- content: fileDataV2.content.split("\n").slice(offset, offset + limit).join("\n"),
880
+ const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
881
+ const lines = fileDataV2.content.split("\n");
882
+ const totalLines = lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
883
+ const selected = lines.slice(normalizedOffset, normalizedOffset + normalizedLimit);
884
+ if (selected.length === 0 || normalizedOffset >= totalLines || normalizedLimit === 0) return {
885
+ content: selected.join("\n"),
846
886
  mimeType: fileDataV2.mimeType
847
887
  };
888
+ const endOffset = Math.min(normalizedOffset + selected.length, totalLines);
889
+ return {
890
+ content: selected.join("\n"),
891
+ mimeType: fileDataV2.mimeType,
892
+ totalLines,
893
+ startLine: normalizedOffset + 1,
894
+ endLine: endOffset,
895
+ nextOffset: endOffset < totalLines ? endOffset : void 0
896
+ };
848
897
  }
849
898
  /**
850
899
  * Read file content as raw FileData.
@@ -1560,6 +1609,73 @@ const READ_FILE_TRUNCATION_MSG = `
1560
1609
 
1561
1610
  [Output was truncated due to size limits. The file content is very large. Consider reformatting the file to make it easier to navigate. For example, if this is JSON, use execute(command='jq . {file_path}') to pretty-print it with line breaks. For other formats, you can use appropriate formatting tools to split long lines.]`;
1562
1611
  /**
1612
+ * Render backend pagination metadata as guidance for the model.
1613
+ *
1614
+ * Backends own source-level pagination because only they know how much of the
1615
+ * file was read. The middleware owns presentation: it line-numbers the text
1616
+ * and turns optional metadata into a human-readable footer. Keeping the fields
1617
+ * optional preserves compatibility with custom backends that predate this
1618
+ * contract; those reads simply receive no pagination footer.
1619
+ *
1620
+ * `nextOffset` is the signal that the read is partial. A result at EOF omits it,
1621
+ * so complete reads retain their previous output shape.
1622
+ */
1623
+ function remainingLinesNotice(readResult) {
1624
+ const { startLine, endLine, nextOffset, totalLines } = readResult;
1625
+ if (startLine === void 0 || endLine === void 0 || nextOffset === void 0 || !Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || !Number.isSafeInteger(nextOffset) || startLine < 1 || endLine < startLine || nextOffset !== endLine || totalLines !== void 0 && (!Number.isSafeInteger(totalLines) || totalLines < endLine)) return "";
1626
+ const readCount = endLine - startLine + 1;
1627
+ const readUnit = readCount === 1 ? "line" : "lines";
1628
+ if (totalLines === void 0) return `\n\n[Read ${readCount} ${readUnit} (lines ${startLine}-${endLine}). More lines remain from offset ${nextOffset}.]`;
1629
+ if (endLine >= totalLines) return "";
1630
+ const remaining = totalLines - endLine;
1631
+ return `\n\n[Read ${readCount} ${readUnit} (lines ${startLine}-${endLine} of ${totalLines} total). ${remaining} ${remaining === 1 ? "line" : "lines"} remaining from offset ${nextOffset}.]`;
1632
+ }
1633
+ /**
1634
+ * Fit a line-numbered read into the middleware's output budget without
1635
+ * publishing a resume offset that skips content the model did not see.
1636
+ *
1637
+ * There are two independent forms of limiting:
1638
+ *
1639
+ * 1. The backend paginates the source file with `offset` and `limit`.
1640
+ * 2. The middleware may further shorten that page to fit its token budget.
1641
+ *
1642
+ * If the backend returned lines 1-100 but the middleware only displayed lines
1643
+ * 1-30, forwarding the backend's original `nextOffset: 100` would silently skip
1644
+ * lines 31-100 on the next read. This function therefore truncates only after a
1645
+ * complete source line and rebuilds the remaining-lines notice using the last
1646
+ * line actually displayed.
1647
+ *
1648
+ * Long source lines may occupy several formatted rows (`12`, `12.1`, ...). The
1649
+ * formatter records a structured boundary only after the final chunk, so this
1650
+ * function does not need to inspect or understand the gutter representation.
1651
+ * If no complete source line can fit beside the truncation message, the function
1652
+ * falls back to character truncation and omits pagination guidance rather than
1653
+ * advertising an unsafe offset.
1654
+ */
1655
+ function truncatePaginatedRead(formatted, filePath, readResult, tokenLimit) {
1656
+ const content = formatted.text;
1657
+ const notice = remainingLinesNotice(readResult);
1658
+ if (!tokenLimit || content.length + notice.length < 4 * tokenLimit) return content + notice;
1659
+ const truncationMsg = READ_FILE_TRUNCATION_MSG.replace("{file_path}", filePath);
1660
+ const threshold = 4 * tokenLimit;
1661
+ if (readResult.startLine !== void 0 && readResult.endLine !== void 0) {
1662
+ const finalSourceLine = readResult.endLine;
1663
+ const boundaries = formatted.sourceLineBoundaries.filter((boundary) => boundary.sourceLine <= finalSourceLine);
1664
+ for (let index = boundaries.length - 1; index >= 0; index -= 1) {
1665
+ const boundary = boundaries[index];
1666
+ const adjustedNotice = remainingLinesNotice({
1667
+ totalLines: readResult.totalLines,
1668
+ startLine: readResult.startLine,
1669
+ endLine: boundary.sourceLine,
1670
+ nextOffset: boundary.sourceLine
1671
+ });
1672
+ if (boundary.endOffset + truncationMsg.length + adjustedNotice.length <= threshold) return content.slice(0, boundary.endOffset) + truncationMsg + adjustedNotice;
1673
+ }
1674
+ }
1675
+ const maxContentLength = Math.max(0, threshold - truncationMsg.length);
1676
+ return content.substring(0, maxContentLength) + truncationMsg;
1677
+ }
1678
+ /**
1563
1679
  * Note appended to grep results that were cut short by the match-count cap.
1564
1680
  */
1565
1681
  const GREP_TRUNCATION_NOTE = "Note: the search stopped early because it hit the maximum match count. The matches above are valid but incomplete. Narrow the search (a more specific pattern or a narrower path), or raise max_count, to see the rest.";
@@ -2061,7 +2177,8 @@ function createReadFileTool(backend, options) {
2061
2177
  const permissionError = checkPermission(permissions, "read", input.file_path);
2062
2178
  if (permissionError !== void 0) return toolError(runtime, "read_file", permissionError);
2063
2179
  const resolvedBackend = await resolveBackend(backend, runtime);
2064
- const { file_path, offset = 0, limit = 100 } = input;
2180
+ const { file_path, offset: requestedOffset = 0, limit: requestedLimit = 100 } = input;
2181
+ const { offset, limit } = normalizeReadPagination(requestedOffset, requestedLimit);
2065
2182
  const readResult = await resolvedBackend.read(file_path, offset, limit);
2066
2183
  if (readResult.error) return [{
2067
2184
  type: "text",
@@ -2109,16 +2226,21 @@ function createReadFileTool(backend, options) {
2109
2226
  }
2110
2227
  let content = typeof readResult.content === "string" ? readResult.content : "";
2111
2228
  const lines = content.split("\n");
2112
- if (lines.length > limit) content = lines.slice(0, limit).join("\n");
2113
- let formatted = formatContentWithLineNumbers(content, offset + 1);
2114
- if (toolTokenLimitBeforeEvict && formatted.length >= 4 * toolTokenLimitBeforeEvict) {
2115
- const truncationMsg = READ_FILE_TRUNCATION_MSG.replace("{file_path}", file_path);
2116
- const maxContentLength = 4 * toolTokenLimitBeforeEvict - truncationMsg.length;
2117
- formatted = formatted.substring(0, maxContentLength) + truncationMsg;
2229
+ let paginationResult = readResult;
2230
+ if (lines.length > limit) {
2231
+ content = lines.slice(0, limit).join("\n");
2232
+ if (limit > 0 && readResult.startLine !== void 0 && readResult.endLine !== void 0) {
2233
+ const endLine = Math.min(readResult.startLine + limit - 1, readResult.endLine, readResult.totalLines ?? Number.POSITIVE_INFINITY);
2234
+ paginationResult = {
2235
+ ...readResult,
2236
+ endLine,
2237
+ nextOffset: endLine
2238
+ };
2239
+ }
2118
2240
  }
2119
2241
  return [{
2120
2242
  type: "text",
2121
- text: formatted
2243
+ text: truncatePaginatedRead(formatContentWithLineNumbersAndBoundaries(content, paginationResult.startLine ?? offset + 1), file_path, paginationResult, toolTokenLimitBeforeEvict)
2122
2244
  }];
2123
2245
  }, {
2124
2246
  name: "read_file",
@@ -3236,6 +3358,83 @@ function createSummarizationMiddleware(options) {
3236
3358
  });
3237
3359
  }
3238
3360
  //#endregion
3361
+ //#region src/middleware/utils.ts
3362
+ /**
3363
+ * Utility functions for middleware.
3364
+ *
3365
+ * This module provides shared helpers used across middleware implementations.
3366
+ */
3367
+ /**
3368
+ * Merge custom middleware into an assembled stack by `.name`.
3369
+ *
3370
+ * Matching custom middleware replaces the existing entry in place. New
3371
+ * middleware is appended after the base stack in caller-provided order.
3372
+ */
3373
+ function mergeMiddleware$1(base, custom) {
3374
+ const merged = new Map(base.map((middleware) => [middleware.name, middleware]));
3375
+ for (const middleware of custom) merged.set(middleware.name, middleware);
3376
+ return [...merged.values()];
3377
+ }
3378
+ function middlewareNames(middleware) {
3379
+ return new Set(middleware.map((entry) => entry.name));
3380
+ }
3381
+ function matchingMiddleware(middleware, names) {
3382
+ return middleware.filter((entry) => names.has(entry.name));
3383
+ }
3384
+ /**
3385
+ * Merge custom middleware into default and tail middleware segments.
3386
+ *
3387
+ * Same-name custom entries replace matching defaults in either segment. Novel
3388
+ * custom entries are inserted between the default and tail segments unless
3389
+ * `appendNew` is false.
3390
+ */
3391
+ function mergeMiddlewareStack(defaultMiddleware, customMiddleware, tailMiddleware = [], options = {}) {
3392
+ const defaultMiddlewareNames = middlewareNames(defaultMiddleware);
3393
+ const tailMiddlewareNames = middlewareNames(tailMiddleware);
3394
+ const knownMiddlewareNames = /* @__PURE__ */ new Set([...defaultMiddlewareNames, ...tailMiddlewareNames]);
3395
+ const novelMiddleware = options.appendNew === false ? [] : customMiddleware.filter((entry) => !knownMiddlewareNames.has(entry.name));
3396
+ return [
3397
+ ...mergeMiddleware$1(defaultMiddleware, matchingMiddleware(customMiddleware, defaultMiddlewareNames)),
3398
+ ...novelMiddleware,
3399
+ ...mergeMiddleware$1(tailMiddleware, matchingMiddleware(customMiddleware, tailMiddlewareNames))
3400
+ ];
3401
+ }
3402
+ /**
3403
+ * Append text to a system message.
3404
+ *
3405
+ * Creates a new SystemMessage with the text appended to the existing content.
3406
+ * If the original message has content, the new text is separated by two newlines.
3407
+ *
3408
+ * @param systemMessage - Existing system message or null/undefined.
3409
+ * @param text - Text to add to the system message.
3410
+ * @returns New SystemMessage with the text appended.
3411
+ *
3412
+ * @example
3413
+ * ```typescript
3414
+ * const original = new SystemMessage({ content: "You are a helpful assistant." });
3415
+ * const updated = appendToSystemMessage(original, "Always be concise.");
3416
+ * // Result: SystemMessage with content "You are a helpful assistant.\n\nAlways be concise."
3417
+ * ```
3418
+ */
3419
+ function appendToSystemMessage(systemMessage, text) {
3420
+ if (!systemMessage) return new SystemMessage$1({ content: text });
3421
+ const existingContent = systemMessage.content;
3422
+ if (typeof existingContent === "string") {
3423
+ const newContent = existingContent ? `${existingContent}\n\n${text}` : text;
3424
+ return new SystemMessage$1({ content: newContent });
3425
+ }
3426
+ if (Array.isArray(existingContent)) {
3427
+ const newContent = [...existingContent];
3428
+ const textToAdd = newContent.length > 0 ? `\n\n${text}` : text;
3429
+ newContent.push({
3430
+ type: "text",
3431
+ text: textToAdd
3432
+ });
3433
+ return new SystemMessage$1({ content: newContent });
3434
+ }
3435
+ return new SystemMessage$1({ content: text });
3436
+ }
3437
+ //#endregion
3239
3438
  //#region src/middleware/subagents.ts
3240
3439
  /**
3241
3440
  * Config key used by task-tool callers to request dynamic response format.
@@ -3249,6 +3448,8 @@ const SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY = "__deepagents_subagent_response_form
3249
3448
  * Provides a minimal base prompt that can be extended by specific subagent configurations.
3250
3449
  */
3251
3450
  const DEFAULT_SUBAGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools.";
3451
+ const FORKED_CONTEXT_KEY = "_deepagentsForkedContext";
3452
+ const FORK_RECURSION_REFUSAL = "You are a subagent and cannot delegate to another subagent. Complete this task yourself instead of calling this tool again.";
3252
3453
  /**
3253
3454
  * State keys excluded when passing state to subagents and when returning
3254
3455
  * updates from subagents. Summarization keys are excluded because their
@@ -3261,6 +3462,18 @@ const EXCLUDED_STATE_KEYS = [
3261
3462
  "skillsMetadata",
3262
3463
  "memoryContents",
3263
3464
  "_summarizationEvent",
3465
+ "_summarizationSessionId",
3466
+ FORKED_CONTEXT_KEY
3467
+ ];
3468
+ /**
3469
+ * State keys excluded when inheriting state into a declarative fork.
3470
+ * Narrower than `EXCLUDED_STATE_KEYS`: a fork's mirrored middleware needs
3471
+ * the parent's private channels (skills metadata, memory contents, etc.)
3472
+ * to rebuild an equivalent prompt.
3473
+ */
3474
+ const FORK_EXCLUDED_STATE_KEYS = [
3475
+ "structuredResponse",
3476
+ "_summarizationEvent",
3264
3477
  "_summarizationSessionId"
3265
3478
  ];
3266
3479
  /**
@@ -3270,25 +3483,37 @@ const EXCLUDED_STATE_KEYS = [
3270
3483
  const DEFAULT_GENERAL_PURPOSE_DESCRIPTION = "General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.";
3271
3484
  function getTaskToolDescription(subagentDescriptions) {
3272
3485
  return context`
3273
- Launch an ephemeral subagent to handle a complex, multi-step task in an isolated context window.
3486
+ Launch an ephemeral subagent to handle a complex, multi-step task.
3274
3487
 
3275
3488
  Available agent types and the tools they have access to:
3276
3489
  ${subagentDescriptions.join("\n")}
3277
3490
 
3278
3491
  Specify subagent_type to select the agent. Usage notes:
3279
3492
  - Launch multiple agents concurrently when their tasks are independent, using a single message with multiple tool calls.
3280
- - Each invocation is stateless: the agent sees only the prompt you give it and returns a single final report. Put full detail in the prompt and state exactly what it should return.
3493
+ - Each invocation is stateless by default: the agent sees only the prompt you give it and returns a single final report. Put full detail in the prompt and state exactly what it should return — unless an agent type below says it inherits your conversation instead.
3281
3494
  - The agent's report is not shown to the user; relay a summary yourself.
3282
- - Tell the agent whether to create content, analyze, or only research, since it cannot see the user's intent.
3495
+ - Tell the agent whether to create content, analyze, or only research, since it can't necessarily see the user's intent unless it inherits your conversation, as noted per agent type below.
3283
3496
  - If an agent's description says to use it proactively, do so without waiting to be asked.
3284
3497
  - When only general-purpose is available, use it for any complex, context-heavy task; it has the same capabilities as the main agent.
3285
3498
  `;
3286
3499
  }
3500
+ const FORKED_SUBAGENT_TOOL_NOTE = " (inherits your full conversation and system prompt — no need to restate context here)";
3501
+ const COMPILED_FORKED_SUBAGENT_TOOL_NOTE = " (inherits your conversation history — its system prompt is fixed in its own runnable)";
3502
+ /** Render one subagent's listing line for the task tool description. */
3503
+ function describeSubagentForTool(name, description, forked, compiled = false) {
3504
+ return `- ${name}: ${description}${forked ? compiled ? COMPILED_FORKED_SUBAGENT_TOOL_NOTE : FORKED_SUBAGENT_TOOL_NOTE : ""}`;
3505
+ }
3506
+ const FORK_TASK_PREAMBLE = "[The messages above are a prior conversation you are continuing as the subagent that was just invoked. Any mention in them of delegating to a subagent already happened — you are that subagent, not the one being asked to delegate further. If you try to delegate to another subagent yourself, it will be refused — complete this task directly. Use the specific facts, figures, and identifiers already established in that conversation when completing the task below — do not answer generically when exact details are already available above. Your actual task is below.]\n\n";
3507
+ /**
3508
+ * Whether a declarative subagent spec has `mode: "fork"` set.
3509
+ *
3510
+ * A plain boolean, not a type predicate: `SubAgent` covers both `"fork"` and
3511
+ * `"isolated"`, so there's no distinct type left to narrow to.
3512
+ */
3287
3513
  function isForkedSubAgent(value) {
3288
3514
  if (typeof value !== "object" || value == null) return false;
3289
3515
  if (!("mode" in value)) return false;
3290
- if (value.mode !== "fork") return false;
3291
- return true;
3516
+ return value.mode === "fork";
3292
3517
  }
3293
3518
  /**
3294
3519
  * Base specification for the general-purpose subagent.
@@ -3331,15 +3556,26 @@ const GENERAL_PURPOSE_SUBAGENT = {
3331
3556
  name: "general-purpose",
3332
3557
  description: DEFAULT_GENERAL_PURPOSE_DESCRIPTION,
3333
3558
  systemPrompt: DEFAULT_SUBAGENT_PROMPT,
3334
- mode: "handoff"
3559
+ mode: "isolated"
3335
3560
  };
3561
+ function filterState(state, excludedKeys) {
3562
+ const filtered = {};
3563
+ for (const [key, value] of Object.entries(state)) if (!excludedKeys.includes(key)) filtered[key] = value;
3564
+ return filtered;
3565
+ }
3336
3566
  /**
3337
3567
  * Filter state to exclude certain keys when passing to subagents
3338
3568
  */
3339
3569
  function filterStateForSubagent(state) {
3340
- const filtered = {};
3341
- for (const [key, value] of Object.entries(state)) if (!EXCLUDED_STATE_KEYS.includes(key)) filtered[key] = value;
3342
- return filtered;
3570
+ return filterState(state, EXCLUDED_STATE_KEYS);
3571
+ }
3572
+ /**
3573
+ * Filter state to exclude only the keys a declarative fork must not resume
3574
+ * (structured response, summarization event/session) — see
3575
+ * `FORK_EXCLUDED_STATE_KEYS`.
3576
+ */
3577
+ function filterStateForFork(state) {
3578
+ return filterState(state, FORK_EXCLUDED_STATE_KEYS);
3343
3579
  }
3344
3580
  /**
3345
3581
  * Invalid tool message block types
@@ -3383,6 +3619,15 @@ function stripInFlightAIMessage(messages) {
3383
3619
  const last = messages.at(-1);
3384
3620
  return AIMessage$1.isInstance(last) && (last.tool_calls?.length ?? 0) > 0 ? messages.slice(0, -1) : messages;
3385
3621
  }
3622
+ const ForkedContextStateSchema = z.object({ [FORKED_CONTEXT_KEY]: z.boolean().optional() });
3623
+ function createForkTaskToolMiddleware(taskTool) {
3624
+ return createMiddleware({
3625
+ name: "forkTaskToolMiddleware",
3626
+ stateSchema: ForkedContextStateSchema,
3627
+ tools: [taskTool],
3628
+ beforeAgent: () => ({ [FORKED_CONTEXT_KEY]: true })
3629
+ });
3630
+ }
3386
3631
  /**
3387
3632
  * Create a runnable agent from a declarative `SubAgent` spec.
3388
3633
  *
@@ -3412,6 +3657,17 @@ function createSubAgent(spec, options) {
3412
3657
  });
3413
3658
  }
3414
3659
  /**
3660
+ * Resolve a fork's system prompt: the parent's inherited prompt, with the
3661
+ * fork's own systemPrompt (if any) appended as an addendum rather than
3662
+ * replacing it.
3663
+ */
3664
+ function resolveForkSystemPrompt(parentSystemPrompt, forkAddendum) {
3665
+ if (!forkAddendum) return parentSystemPrompt ?? "";
3666
+ const addendumText = typeof forkAddendum === "string" ? forkAddendum : forkAddendum.text;
3667
+ if (SystemMessage.isInstance(parentSystemPrompt)) return appendToSystemMessage(parentSystemPrompt, addendumText);
3668
+ return parentSystemPrompt ? `${parentSystemPrompt}\n\n${addendumText}` : addendumText;
3669
+ }
3670
+ /**
3415
3671
  * Create subagent instances from specifications.
3416
3672
  *
3417
3673
  * Returns compiled agents, raw specs keyed by name (for on-demand
@@ -3419,13 +3675,18 @@ function createSubAgent(spec, options) {
3419
3675
  * of names that should fork the parent's conversation.
3420
3676
  */
3421
3677
  function getSubagents(options) {
3422
- const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware: gpMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, parentSystemPrompt = null } = options;
3678
+ const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware: gpMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, parentSystemPrompt = null, mirroredTaskTool } = options;
3423
3679
  const defaultSubagentMiddleware = defaultMiddleware || [];
3424
3680
  const generalPurposeMiddlewareBase = gpMiddleware || defaultSubagentMiddleware;
3425
3681
  const agents = {};
3426
3682
  const specsByName = {};
3427
3683
  const subagentDescriptions = [];
3428
3684
  const forkModeNames = /* @__PURE__ */ new Set();
3685
+ const seenNames = new Set(generalPurposeAgent ? ["general-purpose"] : []);
3686
+ for (const agentParams of subagents) {
3687
+ if (seenNames.has(agentParams.name)) throw new Error(`Duplicate subagent name '${agentParams.name}'; each subagent must have a unique name.`);
3688
+ seenNames.add(agentParams.name);
3689
+ }
3429
3690
  if (generalPurposeAgent) {
3430
3691
  const generalPurposeMiddleware = [...generalPurposeMiddlewareBase];
3431
3692
  if (defaultInterruptOn) generalPurposeMiddleware.push(humanInTheLoopMiddleware({ interruptOn: defaultInterruptOn }));
@@ -3439,24 +3700,34 @@ function getSubagents(options) {
3439
3700
  };
3440
3701
  agents["general-purpose"] = createSubAgent(gpSpec);
3441
3702
  specsByName["general-purpose"] = gpSpec;
3442
- subagentDescriptions.push(`- general-purpose: ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION}`);
3703
+ subagentDescriptions.push(describeSubagentForTool("general-purpose", DEFAULT_GENERAL_PURPOSE_DESCRIPTION, false));
3443
3704
  }
3444
3705
  for (const agentParams of subagents) {
3445
3706
  const rawMode = agentParams.mode;
3446
- if (rawMode != null && rawMode !== "handoff" && rawMode !== "fork") throw new Error(`SubAgent '${agentParams.name}' has invalid mode '${rawMode}' — must be "handoff" or "fork".`);
3447
- subagentDescriptions.push(`- ${agentParams.name}: ${agentParams.description}`);
3707
+ if (rawMode != null && rawMode !== "isolated" && rawMode !== "fork" && rawMode !== "handoff") throw new Error(`SubAgent '${agentParams.name}' has invalid mode '${rawMode}' — must be "isolated" or "fork".`);
3708
+ const forked = isForkedSubAgent(agentParams);
3709
+ const compiled = "runnable" in agentParams;
3710
+ subagentDescriptions.push(describeSubagentForTool(agentParams.name, agentParams.description, forked, compiled));
3448
3711
  if ("runnable" in agentParams) {
3449
3712
  agents[agentParams.name] = agentParams.runnable;
3450
3713
  specsByName[agentParams.name] = agentParams;
3451
- if (isForkedSubAgent(agentParams)) forkModeNames.add(agentParams.name);
3452
- } else if (isForkedSubAgent(agentParams)) {
3714
+ if (forked) forkModeNames.add(agentParams.name);
3715
+ continue;
3716
+ }
3717
+ const subagentMiddleware = [...defaultSubagentMiddleware, ...agentParams.middleware ?? []];
3718
+ if (forked) {
3719
+ const rawSkills = agentParams.skills;
3720
+ if (Array.isArray(rawSkills) && rawSkills.length > 0) throw new Error(`SubAgent '${agentParams.name}' cannot set skills under mode: "fork"; the parent's skills are inherited instead.`);
3721
+ const resolvedSystemPrompt = resolveForkSystemPrompt(parentSystemPrompt, agentParams.systemPrompt);
3722
+ const fsIndex = subagentMiddleware.findIndex((m) => m.name === "FilesystemMiddleware");
3723
+ subagentMiddleware.splice(fsIndex + 1, 0, createForkTaskToolMiddleware(mirroredTaskTool));
3453
3724
  const resolvedSpec = {
3454
3725
  ...agentParams,
3455
- systemPrompt: parentSystemPrompt ?? "",
3726
+ systemPrompt: resolvedSystemPrompt,
3456
3727
  mode: void 0,
3457
3728
  model: agentParams.model ?? defaultModel,
3458
3729
  tools: agentParams.tools ?? defaultTools,
3459
- middleware: [...defaultSubagentMiddleware, ...agentParams.middleware ?? []],
3730
+ middleware: subagentMiddleware,
3460
3731
  interruptOn: agentParams.interruptOn ?? defaultInterruptOn ?? void 0
3461
3732
  };
3462
3733
  agents[agentParams.name] = createSubAgent(resolvedSpec);
@@ -3465,10 +3736,10 @@ function getSubagents(options) {
3465
3736
  } else {
3466
3737
  const resolvedSpec = {
3467
3738
  ...agentParams,
3468
- mode: "handoff",
3739
+ mode: "isolated",
3469
3740
  model: agentParams.model ?? defaultModel,
3470
3741
  tools: agentParams.tools ?? defaultTools,
3471
- middleware: [...defaultSubagentMiddleware, ...agentParams.middleware ?? []],
3742
+ middleware: subagentMiddleware,
3472
3743
  interruptOn: agentParams.interruptOn ?? defaultInterruptOn ?? void 0
3473
3744
  };
3474
3745
  agents[agentParams.name] = createSubAgent(resolvedSpec);
@@ -3487,16 +3758,12 @@ function getSubagents(options) {
3487
3758
  */
3488
3759
  function createTaskTool(options) {
3489
3760
  const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, taskDescription, parentSystemPrompt = null } = options;
3490
- const { agents: subagentGraphs, specsByName, descriptions: subagentDescriptions, forkModeNames } = getSubagents({
3491
- defaultModel,
3492
- defaultTools,
3493
- defaultMiddleware,
3494
- generalPurposeMiddleware,
3495
- defaultInterruptOn,
3496
- subagents,
3497
- generalPurposeAgent,
3498
- parentSystemPrompt
3499
- });
3761
+ const subagentNames = [...generalPurposeAgent ? ["general-purpose"] : [], ...subagents.map((spec) => spec.name)];
3762
+ const subagentDescriptions = [...generalPurposeAgent ? [describeSubagentForTool("general-purpose", DEFAULT_GENERAL_PURPOSE_DESCRIPTION, false)] : [], ...subagents.map((spec) => describeSubagentForTool(spec.name, spec.description, isForkedSubAgent(spec), "runnable" in spec))];
3763
+ const finalTaskDescription = taskDescription ? taskDescription : getTaskToolDescription(subagentDescriptions);
3764
+ let subagentGraphs = {};
3765
+ let specsByName = {};
3766
+ let forkModeNames = /* @__PURE__ */ new Set();
3500
3767
  function selectSubagent(subagentType, config) {
3501
3768
  const spec = specsByName[subagentType];
3502
3769
  const responseFormat = config.configurable?.[SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY];
@@ -3504,18 +3771,19 @@ function createTaskTool(options) {
3504
3771
  if ("runnable" in spec || responseFormat == null) return subagentGraphs[subagentType];
3505
3772
  return createSubAgent(spec, { responseFormat });
3506
3773
  }
3507
- const finalTaskDescription = taskDescription ? taskDescription : getTaskToolDescription(subagentDescriptions);
3508
- return tool(async (input, config) => {
3774
+ async function runTask(input, config) {
3509
3775
  const { description, subagent_type } = input;
3776
+ const currentState = getCurrentTaskInput();
3777
+ if (currentState[FORKED_CONTEXT_KEY]) return FORK_RECURSION_REFUSAL;
3510
3778
  if (!(subagent_type in subagentGraphs)) {
3511
3779
  const allowedTypes = Object.keys(subagentGraphs).map((k) => `\`${k}\``).join(", ");
3512
3780
  throw new Error(`Error: invoked agent of type ${subagent_type}, the only allowed types are ${allowedTypes}`);
3513
3781
  }
3514
3782
  const shouldFork = forkModeNames.has(subagent_type);
3515
3783
  const subagent = selectSubagent(subagent_type, config);
3516
- const currentState = getCurrentTaskInput();
3517
- const subagentState = filterStateForSubagent(currentState);
3518
- if (shouldFork) subagentState.messages = [...getEffectiveMessages(stripInFlightAIMessage(currentState.messages ?? []), currentState), new HumanMessage$1({ content: description })];
3784
+ const spec = specsByName[subagent_type];
3785
+ const subagentState = shouldFork && !("runnable" in spec) ? filterStateForFork(currentState) : filterStateForSubagent(currentState);
3786
+ if (shouldFork) subagentState.messages = [...getEffectiveMessages(stripInFlightAIMessage(currentState.messages ?? []), currentState), new HumanMessage$1({ content: FORK_TASK_PREAMBLE + description })];
3519
3787
  else subagentState.messages = [new HumanMessage$1({ content: description })];
3520
3788
  subagentState._summarizationSessionId = `session_${crypto.randomUUID().substring(0, 8)}`;
3521
3789
  const subagentConfig = {
@@ -3542,14 +3810,35 @@ function createTaskTool(options) {
3542
3810
  return content;
3543
3811
  }
3544
3812
  return returnCommandWithStateUpdate(result, config.toolCall.id);
3545
- }, {
3813
+ }
3814
+ const taskToolSchema = z.object({
3815
+ description: z.string().describe("The task to execute with the selected agent"),
3816
+ subagent_type: z.string().describe(`Name of the agent to use. Available: ${subagentNames.join(", ")}`)
3817
+ });
3818
+ const taskTool = tool(runTask, {
3546
3819
  name: "task",
3547
3820
  description: finalTaskDescription,
3548
- schema: z.object({
3549
- description: z.string().describe("The task to execute with the selected agent"),
3550
- subagent_type: z.string().describe(`Name of the agent to use. Available: ${Object.keys(subagentGraphs).join(", ")}`)
3821
+ schema: taskToolSchema
3822
+ });
3823
+ const { agents, specsByName: resolvedSpecsByName, forkModeNames: resolvedForkModeNames } = getSubagents({
3824
+ defaultModel,
3825
+ defaultTools,
3826
+ defaultMiddleware,
3827
+ generalPurposeMiddleware,
3828
+ defaultInterruptOn,
3829
+ subagents,
3830
+ generalPurposeAgent,
3831
+ parentSystemPrompt,
3832
+ mirroredTaskTool: tool(runTask, {
3833
+ name: "task",
3834
+ description: finalTaskDescription,
3835
+ schema: taskToolSchema
3551
3836
  })
3552
3837
  });
3838
+ subagentGraphs = agents;
3839
+ specsByName = resolvedSpecsByName;
3840
+ forkModeNames = resolvedForkModeNames;
3841
+ return taskTool;
3553
3842
  }
3554
3843
  /**
3555
3844
  * Create subagent middleware with task tool
@@ -4551,43 +4840,6 @@ function createSkillsMiddleware(options) {
4551
4840
  });
4552
4841
  }
4553
4842
  //#endregion
4554
- //#region src/middleware/utils.ts
4555
- /**
4556
- * Merge custom middleware into an assembled stack by `.name`.
4557
- *
4558
- * Matching custom middleware replaces the existing entry in place. New
4559
- * middleware is appended after the base stack in caller-provided order.
4560
- */
4561
- function mergeMiddleware$1(base, custom) {
4562
- const merged = new Map(base.map((middleware) => [middleware.name, middleware]));
4563
- for (const middleware of custom) merged.set(middleware.name, middleware);
4564
- return [...merged.values()];
4565
- }
4566
- function middlewareNames(middleware) {
4567
- return new Set(middleware.map((entry) => entry.name));
4568
- }
4569
- function matchingMiddleware(middleware, names) {
4570
- return middleware.filter((entry) => names.has(entry.name));
4571
- }
4572
- /**
4573
- * Merge custom middleware into default and tail middleware segments.
4574
- *
4575
- * Same-name custom entries replace matching defaults in either segment. Novel
4576
- * custom entries are inserted between the default and tail segments unless
4577
- * `appendNew` is false.
4578
- */
4579
- function mergeMiddlewareStack(defaultMiddleware, customMiddleware, tailMiddleware = [], options = {}) {
4580
- const defaultMiddlewareNames = middlewareNames(defaultMiddleware);
4581
- const tailMiddlewareNames = middlewareNames(tailMiddleware);
4582
- const knownMiddlewareNames = /* @__PURE__ */ new Set([...defaultMiddlewareNames, ...tailMiddlewareNames]);
4583
- const novelMiddleware = options.appendNew === false ? [] : customMiddleware.filter((entry) => !knownMiddlewareNames.has(entry.name));
4584
- return [
4585
- ...mergeMiddleware$1(defaultMiddleware, matchingMiddleware(customMiddleware, defaultMiddlewareNames)),
4586
- ...novelMiddleware,
4587
- ...mergeMiddleware$1(tailMiddleware, matchingMiddleware(customMiddleware, tailMiddlewareNames))
4588
- ];
4589
- }
4590
- //#endregion
4591
4843
  //#region src/middleware/completion_callback.ts
4592
4844
  /**
4593
4845
  * Callback middleware for async subagents.
@@ -5447,8 +5699,9 @@ function hasToolName(tool) {
5447
5699
  return tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string";
5448
5700
  }
5449
5701
  /**
5450
- * Create middleware that removes excluded tools after all tool-injecting
5451
- * middleware has had a chance to add tools to the request.
5702
+ * Create middleware that hides excluded tools from the model and rejects calls
5703
+ * to them. Exclusions calibrate the agent per model; they are not a security
5704
+ * boundary.
5452
5705
  *
5453
5706
  * @internal
5454
5707
  */
@@ -5460,6 +5713,16 @@ function createToolExclusionMiddleware(excludedTools) {
5460
5713
  ...request,
5461
5714
  tools: request.tools?.filter((tool) => !hasToolName(tool) || !excludedTools.has(tool.name))
5462
5715
  });
5716
+ },
5717
+ wrapToolCall(request, handler) {
5718
+ const { name, id } = request.toolCall;
5719
+ if (!excludedTools.has(name)) return handler(request);
5720
+ return new ToolMessage$1({
5721
+ content: `Error: ${name} is not available.`,
5722
+ tool_call_id: id ?? "",
5723
+ name,
5724
+ status: "error"
5725
+ });
5463
5726
  }
5464
5727
  });
5465
5728
  }
@@ -6205,8 +6468,18 @@ function createDeepAgent(params = {}) {
6205
6468
  providerHint: getModelProvider(model),
6206
6469
  identifierHint: getModelIdentifier(model)
6207
6470
  });
6208
- const filesystemTools = FILESYSTEM_TOOL_NAMES.filter((toolName) => !harnessProfile.excludedTools.has(toolName));
6209
- const profileFilesystemTools = filesystemTools.length === FILESYSTEM_TOOL_NAMES.length ? void 0 : filesystemTools.includes("read_file") ? filesystemTools : ["read_file", ...filesystemTools];
6471
+ const computeProfileFilesystemTools = (profile) => {
6472
+ const filesystemTools = FILESYSTEM_TOOL_NAMES.filter((toolName) => !profile.excludedTools.has(toolName));
6473
+ return filesystemTools.length === FILESYSTEM_TOOL_NAMES.length ? void 0 : filesystemTools.includes("read_file") ? filesystemTools : ["read_file", ...filesystemTools];
6474
+ };
6475
+ const profileFilesystemTools = computeProfileFilesystemTools(harnessProfile);
6476
+ const resolveSubagentProfile = (subagentModel) => {
6477
+ if (subagentModel == null || subagentModel === model) return harnessProfile;
6478
+ return typeof subagentModel === "string" ? resolveHarnessProfile({ spec: subagentModel }) : resolveHarnessProfile({
6479
+ providerHint: getModelProvider(subagentModel),
6480
+ identifierHint: getModelIdentifier(subagentModel)
6481
+ });
6482
+ };
6210
6483
  const toolOverrides = harnessProfile.toolDescriptionOverrides;
6211
6484
  const effectiveTools = Object.keys(toolOverrides).length > 0 ? tools.map((t) => t.name in toolOverrides ? Object.assign(Object.create(Object.getPrototypeOf(t)), t, { description: toolOverrides[t.name] }) : t) : tools;
6212
6485
  const anthropicModel = isAnthropicModel(model);
@@ -6242,44 +6515,50 @@ function createDeepAgent(params = {}) {
6242
6515
  * Only the general-purpose subagent inherits the main agent's skills.
6243
6516
  * If a custom subagent needs skills, it must specify its own `skills` array.
6244
6517
  */
6245
- const createSubagentDefaultMiddleware = (input) => {
6518
+ const createSubagentDefaultMiddleware = (input, subagentProfile, forked) => {
6246
6519
  const effectivePermissions = input.permissions ?? permissions;
6247
6520
  return [
6248
6521
  createFilesystemMiddleware({
6249
6522
  backend,
6250
6523
  permissions: effectivePermissions,
6251
- tools: profileFilesystemTools
6524
+ tools: computeProfileFilesystemTools(subagentProfile)
6252
6525
  }),
6253
6526
  createSummarizationMiddleware({ backend }),
6254
6527
  createPatchToolCallsMiddleware(),
6255
- ...input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
6528
+ ...!forked && input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
6256
6529
  backend,
6257
6530
  sources: input.skills
6258
6531
  })] : []
6259
6532
  ];
6260
6533
  };
6261
- const buildSubagentMiddleware = (input, isForkable) => {
6262
- let subagentMiddleware = mergeMiddlewareStack(createSubagentDefaultMiddleware(input), input.middleware ?? [], [
6263
- ...resolveMiddleware(harnessProfile.extraMiddleware),
6534
+ const buildSubagentMiddleware = (input) => {
6535
+ const subagentProfile = resolveSubagentProfile(input.model);
6536
+ const forked = isForkedSubAgent(input);
6537
+ const subagentDefaultMiddleware = createSubagentDefaultMiddleware(input, subagentProfile, forked);
6538
+ if (forked && skills != null && skills.length > 0) subagentDefaultMiddleware.unshift(createSkillsMiddleware({
6539
+ backend,
6540
+ sources: skills
6541
+ }));
6542
+ let subagentMiddleware = mergeMiddlewareStack(subagentDefaultMiddleware, forked && customMiddleware.length > 0 ? mergeMiddleware$1(customMiddleware, input.middleware ?? []) : input.middleware ?? [], [
6543
+ ...resolveMiddleware(subagentProfile.extraMiddleware),
6264
6544
  ...cacheMiddleware,
6265
- ...isForkable ? memoryMiddleware : []
6545
+ ...forked && memory != null && memory.length > 0 ? [createMemoryMiddleware({
6546
+ backend,
6547
+ sources: memory,
6548
+ addCacheControl: anthropicModel
6549
+ })] : []
6266
6550
  ]);
6267
- if (harnessProfile.excludedMiddleware.size > 0) subagentMiddleware = subagentMiddleware.filter((middleware) => !harnessProfile.excludedMiddleware.has(middleware.name));
6551
+ if (subagentProfile.excludedMiddleware.size > 0) subagentMiddleware = subagentMiddleware.filter((middleware) => !subagentProfile.excludedMiddleware.has(middleware.name));
6552
+ if (subagentProfile.excludedTools.size > 0) subagentMiddleware.push(createToolExclusionMiddleware(subagentProfile.excludedTools));
6268
6553
  return subagentMiddleware;
6269
6554
  };
6270
6555
  const normalizeSubagentSpec = (input) => ({
6271
6556
  ...input,
6272
- tools: input.tools ?? [],
6273
- middleware: buildSubagentMiddleware(input, false)
6274
- });
6275
- const normalizeForkedSubagentSpec = (input) => ({
6276
- ...input,
6277
- tools: input.tools ?? [],
6278
- middleware: buildSubagentMiddleware(input, true)
6557
+ middleware: buildSubagentMiddleware(input)
6279
6558
  });
6280
6559
  const allSubagents = subagents;
6281
6560
  const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
6282
- const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : isForkedSubAgent(item) ? normalizeForkedSubagentSpec(item) : normalizeSubagentSpec(item));
6561
+ const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : normalizeSubagentSpec(item));
6283
6562
  const gpConfig = harnessProfile.generalPurposeSubagent;
6284
6563
  if (!(gpConfig?.enabled === false) && !inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
6285
6564
  const gpSystemPrompt = gpConfig?.systemPrompt ?? applyProfilePrompt(harnessProfile, GENERAL_PURPOSE_SUBAGENT.systemPrompt);
@@ -6771,10 +7050,23 @@ var StoreBackend = class {
6771
7050
  mimeType: fileDataV2.mimeType
6772
7051
  };
6773
7052
  if (typeof fileDataV2.content !== "string") return { error: `File '${filePath}' has binary content but text MIME type` };
6774
- return {
6775
- content: fileDataV2.content.split("\n").slice(offset, offset + limit).join("\n"),
7053
+ const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
7054
+ const lines = fileDataV2.content.split("\n");
7055
+ const totalLines = lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
7056
+ const selected = lines.slice(normalizedOffset, normalizedOffset + normalizedLimit);
7057
+ if (selected.length === 0 || normalizedOffset >= totalLines || normalizedLimit === 0) return {
7058
+ content: selected.join("\n"),
6776
7059
  mimeType: fileDataV2.mimeType
6777
7060
  };
7061
+ const endOffset = Math.min(normalizedOffset + selected.length, totalLines);
7062
+ return {
7063
+ content: selected.join("\n"),
7064
+ mimeType: fileDataV2.mimeType,
7065
+ totalLines,
7066
+ startLine: normalizedOffset + 1,
7067
+ endLine: endOffset,
7068
+ nextOffset: endOffset < totalLines ? endOffset : void 0
7069
+ };
6778
7070
  } catch (e) {
6779
7071
  return { error: e.message };
6780
7072
  }
@@ -7040,12 +7332,20 @@ function splitLinesKeepEnds(content) {
7040
7332
  return lines;
7041
7333
  }
7042
7334
  function sliceReadContent(content, offset, limit) {
7043
- if (!content || content.trim() === "") return { content };
7335
+ if (!content) return { content };
7044
7336
  const lines = splitLinesKeepEnds(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
7045
7337
  const startIndex = offset;
7046
7338
  const endIndex = Math.min(startIndex + limit, lines.length);
7047
7339
  if (startIndex >= lines.length) return { error: `Line offset ${offset} exceeds file length (${lines.length} lines)` };
7048
- return { content: lines.slice(startIndex, endIndex).join("") };
7340
+ const selected = lines.slice(startIndex, endIndex);
7341
+ if (selected.length === 0 || offset < 0 || limit <= 0) return { content: selected.join("") };
7342
+ return {
7343
+ content: selected.join(""),
7344
+ totalLines: lines.length,
7345
+ startLine: startIndex + 1,
7346
+ endLine: endIndex,
7347
+ nextOffset: endIndex < lines.length ? endIndex : void 0
7348
+ };
7049
7349
  }
7050
7350
  function isLangSmithNotFoundError(error) {
7051
7351
  if (typeof error !== "object" || error === null) return false;
@@ -7524,9 +7824,11 @@ var ContextHubBackend = class ContextHubBackend {
7524
7824
  }
7525
7825
  const content = cache[hubPath];
7526
7826
  if (content === void 0) return { error: `File '${filePath}' not found` };
7527
- const sliced = sliceReadContent(content, offset, limit);
7827
+ const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
7828
+ const sliced = sliceReadContent(content, normalizedOffset, normalizedLimit);
7528
7829
  if (sliced.error) return { error: sliced.error };
7529
7830
  return {
7831
+ ...sliced,
7530
7832
  content: sliced.content ?? "",
7531
7833
  mimeType: TEXT_MIME_TYPE
7532
7834
  };
@@ -7865,6 +8167,7 @@ function buildFindCommand(searchPath) {
7865
8167
  const findBase = `find -L ${quotedPath} ${`\\( -path /proc -o -path /sys -o -path /dev -o -path /run -o -path ${quotedPath}/proc -o -path ${quotedPath}/sys -o -path ${quotedPath}/dev -o -path ${quotedPath}/run \\) -prune`} -o -not -path ${quotedPath}`;
7866
8168
  return `{ ${`if find /dev/null -maxdepth 0 -printf '' 2>/dev/null; then ${findBase} -printf '%s\\t%T@\\t%y\\t%p\\n' 2>/dev/null; elif stat -c %s /dev/null >/dev/null 2>&1; then ${findBase} -exec sh -c '${STAT_C_SCRIPT}' _ {} +; else ${findBase} -exec stat -f '%z\t%m\t%Sp\t%N' {} + 2>/dev/null; fi || true`}; } | head -n 50001`;
7867
8169
  }
8170
+ const READ_METADATA_PREFIX = "__DEEPAGENTS_READ_METADATA__";
7868
8171
  /**
7869
8172
  * Pure POSIX shell command for reading files with line numbers.
7870
8173
  * Uses awk for line numbering with offset/limit — works on any Linux including Alpine.
@@ -7878,9 +8181,32 @@ function buildReadCommand(filePath, offset, limit) {
7878
8181
  return [
7879
8182
  `if [ ! -f ${quotedPath} ]; then echo "Error: File not found"; exit 1; fi`,
7880
8183
  `if [ ! -s ${quotedPath} ]; then echo "System reminder: File exists but has empty contents"; exit 0; fi`,
7881
- `awk 'NR >= ${start} && NR <= ${end} { printf "%6d\\t%s\\n", NR, $0 }' ${quotedPath}`
8184
+ `awk 'NR >= ${start} && NR <= ${end} { printf "%6d\\t%s\\n", NR, $0 } END { printf "${READ_METADATA_PREFIX}\\t%d\\n", NR }' ${quotedPath}`
7882
8185
  ].join("; ");
7883
8186
  }
8187
+ function parseReadOutput(output, offset, limit) {
8188
+ const rows = output.split("\n");
8189
+ let metadataIndex = -1;
8190
+ for (let index = rows.length - 1; index >= 0; index -= 1) if (rows[index].startsWith(`${READ_METADATA_PREFIX}\t`)) {
8191
+ metadataIndex = index;
8192
+ break;
8193
+ }
8194
+ if (metadataIndex === -1) return { content: output };
8195
+ const totalLines = Number(rows[metadataIndex].slice(29));
8196
+ if (!Number.isSafeInteger(totalLines) || totalLines < 0) return { content: output };
8197
+ const contentRows = rows.slice(0, metadataIndex);
8198
+ const content = contentRows.length > 0 ? `${contentRows.join("\n")}\n` : "";
8199
+ const startOffset = Math.floor(offset);
8200
+ const endOffset = Math.min(startOffset + Math.floor(limit), totalLines);
8201
+ if (startOffset >= totalLines || endOffset <= startOffset) return { content };
8202
+ return {
8203
+ content,
8204
+ totalLines,
8205
+ startLine: startOffset + 1,
8206
+ endLine: endOffset,
8207
+ nextOffset: endOffset < totalLines ? endOffset : void 0
8208
+ };
8209
+ }
7884
8210
  /**
7885
8211
  * Build a grep command for literal (fixed-string) search.
7886
8212
  * Uses grep -rHnF for recursive, with-filename, with-line-number, fixed-string search.
@@ -7958,15 +8284,17 @@ var BaseSandbox = class {
7958
8284
  mimeType
7959
8285
  };
7960
8286
  }
7961
- if (limit === 0) return {
8287
+ const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
8288
+ if (normalizedLimit === 0) return {
7962
8289
  content: "",
7963
8290
  mimeType
7964
8291
  };
7965
- const command = buildReadCommand(filePath, offset, limit);
8292
+ const command = buildReadCommand(filePath, normalizedOffset, normalizedLimit);
7966
8293
  const result = await this.execute(command);
7967
8294
  if (result.exitCode !== 0) return { error: `File '${filePath}' not found` };
8295
+ const parsed = parseReadOutput(result.output, normalizedOffset, normalizedLimit);
7968
8296
  return {
7969
- content: result.output,
8297
+ ...result.truncated ? { content: parsed.content } : parsed,
7970
8298
  mimeType
7971
8299
  };
7972
8300
  }
@@ -8401,6 +8729,6 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
8401
8729
  }
8402
8730
  };
8403
8731
  //#endregion
8404
- export { DEFAULT_GENERAL_PURPOSE_DESCRIPTION as A, StateBackend as B, MAX_SKILL_DESCRIPTION_LENGTH as C, createMemoryMiddleware as D, createSkillsMiddleware as E, createSubAgentMiddleware as F, resolveBackend as G, applyGrepMaxCount as H, computeSummarizationDefaults as I, checkEmptyContent as J, adaptBackendProtocol as K, createSummarizationMiddleware as L, GENERAL_PURPOSE_SUBAGENT as M, SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY as N, filesValue as O, createSubAgent as P, createFilesystemMiddleware as R, createCompletionCallbackMiddleware as S, MAX_SKILL_NAME_LENGTH as T, isSandboxBackend as U, SandboxError as V, isSandboxProtocol as W, isTextMimeType as X, getMimeType as Y, performStringReplacement as Z, createHarnessProfile as _, ASYNC_TASK_SYSTEM_PROMPT as a, createAsyncSubAgentMiddleware as b, TASK_SYSTEM_PROMPT as c, registerHarnessProfile as d, generalPurposeSubagentConfigSchema as f, EMPTY_HARNESS_PROFILE as g, serializeProfile as h, StoreBackend as i, DEFAULT_SUBAGENT_PROMPT as j, createPatchToolCallsMiddleware as k, createDeepAgent as l, parseHarnessProfileConfig as m, BaseSandbox as n, BASE_AGENT_PROMPT as o, harnessProfileConfigSchema as p, adaptSandboxProtocol as q, ContextHubBackend as r, EXECUTION_SYSTEM_PROMPT as s, LangSmithSandbox as t, getHarnessProfile as u, REQUIRED_MIDDLEWARE_NAMES as v, MAX_SKILL_FILE_SIZE as w, isAsyncSubAgent as x, ConfigurationError as y, CompositeBackend as z };
8732
+ export { DEFAULT_GENERAL_PURPOSE_DESCRIPTION as A, StateBackend as B, MAX_SKILL_DESCRIPTION_LENGTH as C, createMemoryMiddleware as D, createSkillsMiddleware as E, createSubAgentMiddleware as F, resolveBackend as G, applyGrepMaxCount as H, computeSummarizationDefaults as I, checkEmptyContent as J, adaptBackendProtocol as K, createSummarizationMiddleware as L, GENERAL_PURPOSE_SUBAGENT as M, SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY as N, filesValue as O, createSubAgent as P, performStringReplacement as Q, createFilesystemMiddleware as R, createCompletionCallbackMiddleware as S, MAX_SKILL_NAME_LENGTH as T, isSandboxBackend as U, SandboxError as V, isSandboxProtocol as W, isTextMimeType as X, getMimeType as Y, normalizeReadPagination as Z, createHarnessProfile as _, ASYNC_TASK_SYSTEM_PROMPT as a, createAsyncSubAgentMiddleware as b, TASK_SYSTEM_PROMPT as c, registerHarnessProfile as d, generalPurposeSubagentConfigSchema as f, EMPTY_HARNESS_PROFILE as g, serializeProfile as h, StoreBackend as i, DEFAULT_SUBAGENT_PROMPT as j, createPatchToolCallsMiddleware as k, createDeepAgent as l, parseHarnessProfileConfig as m, BaseSandbox as n, BASE_AGENT_PROMPT as o, harnessProfileConfigSchema as p, adaptSandboxProtocol as q, ContextHubBackend as r, EXECUTION_SYSTEM_PROMPT as s, LangSmithSandbox as t, getHarnessProfile as u, REQUIRED_MIDDLEWARE_NAMES as v, MAX_SKILL_FILE_SIZE as w, isAsyncSubAgent as x, ConfigurationError as y, CompositeBackend as z };
8405
8733
 
8406
- //# sourceMappingURL=langsmith-zm0ILQsV.js.map
8734
+ //# sourceMappingURL=langsmith-BYWZnEVh.js.map