@caupulican/pi-adaptative 0.80.22 → 0.80.23
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.
- package/CHANGELOG.md +22 -0
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +28 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +10 -76
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +16 -7
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/exec.d.ts +20 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +52 -19
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +6 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +33 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/message-retention.d.ts +26 -0
- package/dist/core/message-retention.d.ts.map +1 -0
- package/dist/core/message-retention.js +95 -0
- package/dist/core/message-retention.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +14 -6
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +4 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/session-manager.d.ts +3 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +45 -9
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +12 -0
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tools/git-filter.d.ts +9 -1
- package/dist/core/tools/git-filter.d.ts.map +1 -1
- package/dist/core/tools/git-filter.js +94 -8
- package/dist/core/tools/git-filter.js.map +1 -1
- package/dist/core/tools/read.d.ts +31 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +164 -33
- package/dist/core/tools/read.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +37 -4
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +54 -18
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/jsonl.d.ts +0 -7
- package/dist/modes/rpc/jsonl.d.ts.map +1 -1
- package/dist/modes/rpc/jsonl.js +17 -0
- package/dist/modes/rpc/jsonl.js.map +1 -1
- package/dist/utils/safe-write-stream.d.ts +10 -0
- package/dist/utils/safe-write-stream.d.ts.map +1 -0
- package/dist/utils/safe-write-stream.js +16 -0
- package/dist/utils/safe-write-stream.js.map +1 -0
- package/dist/utils/sleep.d.ts +3 -1
- package/dist/utils/sleep.d.ts.map +1 -1
- package/dist/utils/sleep.js +10 -4
- package/dist/utils/sleep.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.80.23] - 2026-06-12
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Fixed JavaScript heap exhaustion in long-running sessions by bounding in-memory retention across the stack:
|
|
8
|
+
- Session load now compacts oversized tool result details, so resuming or branching on top of large session files no longer pins their full payloads in memory.
|
|
9
|
+
- Interactive scrollback components retain tool result details under a dedicated budget (512KB estimate per result), matching resumed-session semantics for pathological payloads while keeping designed display payloads (bash output window, diffs) intact.
|
|
10
|
+
- Scrollback components share one built-in toolset per working directory instead of allocating a full toolset per tool call.
|
|
11
|
+
- Bounded subprocess output retention:
|
|
12
|
+
- `pi.exec`/`execCommand` keeps a rolling tail per stream (default 16 MiB, configurable via `maxBuffer`) and reports `stdoutTruncated`/`stderrTruncated` on the result.
|
|
13
|
+
- The git output filter spills oversized git output (default over 48 MiB, `PI_GIT_FILTER_MAX_RETAINED_BYTES` to tune) to a temp file, discloses the cap in the filtered output, and reuses the spill file as the full-output artifact instead of materializing extra in-memory copies.
|
|
14
|
+
- Package manager command capture is bounded and fails loudly instead of accumulating unbounded output.
|
|
15
|
+
- Hot reload now unsubscribes the replaced extension generation's `pi.events` handlers from the shared event bus, so repeated reloads no longer pin old extension module graphs or double-process bus events.
|
|
16
|
+
- Fixed abort-listener accumulation in retry backoff sleeps (`utils/sleep.ts` and the openai-codex provider): each completed sleep now detaches its listener instead of leaving it on the signal for the signal's lifetime.
|
|
17
|
+
- Best-effort temp-file writes (bash full-output capture, git filter overflow spill) now handle stream errors instead of crashing the process with an uncaught `error` event on disk failures; the git overflow path discloses a failed spill in stderr instead of referencing a broken artifact.
|
|
18
|
+
- Session file rewrites (migration, branch operations) are now atomic (write-then-rename), removing the torn-file window when a crash or a second pi process appending to the same session interleaves with a truncate-in-place rewrite.
|
|
19
|
+
- Codex websocket debug stats and SSE-fallback flags are now cleared with the rest of the session resources on session replacement and dispose, instead of accumulating per session id for the process lifetime.
|
|
20
|
+
- User input during an auto-retry backoff now queues as steering and is incorporated into the retried turn, instead of starting a concurrent run that raced and cancelled the pending retry. `isRetrying` is true from the moment `auto_retry_start` is observable, and the interactive editor routes submissions during the retry window through the steering path.
|
|
21
|
+
- Oversized reads no longer spike the heap: the read tool streams line slices for files beyond 16 MiB (any region stays reachable in batches via offset continuation, with true line numbers), images beyond a 128 MiB pathology guard return guidance instead of loading, CLI `@file` attachments are bounded with a leading window plus a read-tool pointer, oversized SKILL.md files are skipped with a diagnostic, and the session loader skips lines too large to hold in a string instead of failing the whole resume.
|
|
22
|
+
- Delimiter-less streams can no longer grow line-assembly buffers without bound: RPC JSONL input, the Anthropic SSE parser, and the Codex SSE parser each cap their buffers (64 MiB) and discard or fail cleanly.
|
|
23
|
+
- Hot reload no longer freezes the UI: the chat scrollback rebuild is chunked with yields (also applies to resume, tree navigation, and display toggles). Plain Up arrow on an empty editor recalls queued messages for editing, and a `>>` prefix queues a follow-up message — both work in terminals that swallow the alt-chord bindings.
|
|
24
|
+
|
|
3
25
|
## [0.80.22] - 2026-06-12
|
|
4
26
|
|
|
5
27
|
### Added
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-processor.d.ts","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-processor.d.ts","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAUtD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAClC,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,sEAAsE;AACtE,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAkGpH","sourcesContent":["/**\n * Process @file CLI arguments into text content and image attachments\n */\n\nimport { access, open, readFile, stat } from \"node:fs/promises\";\nimport type { ImageContent } from \"@caupulican/pi-ai\";\nimport chalk from \"chalk\";\nimport { resolve } from \"path\";\nimport { resolveReadPath } from \"../core/tools/path-utils.ts\";\nimport { formatDimensionNote, resizeImage } from \"../utils/image-resize.ts\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../utils/mime.ts\";\n\nconst MAX_ATTACHMENT_TEXT_BYTES = 16 * 1024 * 1024;\nconst MAX_ATTACHMENT_IMAGE_BYTES = 128 * 1024 * 1024;\n\nexport interface ProcessedFiles {\n\ttext: string;\n\timages: ImageContent[];\n}\n\nexport interface ProcessFileOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n}\n\n/** Process @file arguments into text content and image attachments */\nexport async function processFileArguments(fileArgs: string[], options?: ProcessFileOptions): Promise<ProcessedFiles> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tlet text = \"\";\n\tconst images: ImageContent[] = [];\n\n\tfor (const fileArg of fileArgs) {\n\t\t// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)\n\t\tconst absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));\n\n\t\t// Check if file exists\n\t\ttry {\n\t\t\tawait access(absolutePath);\n\t\t} catch {\n\t\t\tconsole.error(chalk.red(`Error: File not found: ${absolutePath}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Check if file is empty\n\t\tconst stats = await stat(absolutePath);\n\t\tif (stats.size === 0) {\n\t\t\t// Skip empty files\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);\n\n\t\tif (mimeType) {\n\t\t\t// Pathology guard only: real screenshots/photos sit far below this.\n\t\t\tif (stats.size > MAX_ATTACHMENT_IMAGE_BYTES) {\n\t\t\t\ttext += `<file name=\"${absolutePath}\">[Image is ${Math.round(stats.size / (1024 * 1024))}MB, beyond the inline decode guard; not attached.]</file>\\n`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Handle image file\n\t\t\tconst content = await readFile(absolutePath);\n\n\t\t\tlet attachment: ImageContent;\n\t\t\tlet dimensionNote: string | undefined;\n\n\t\t\tif (autoResizeImages) {\n\t\t\t\tconst resized = await resizeImage(content, mimeType);\n\t\t\t\tif (!resized) {\n\t\t\t\t\ttext += `<file name=\"${absolutePath}\">[Image omitted: could not be resized below the inline image size limit.]</file>\\n`;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tdimensionNote = formatDimensionNote(resized);\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType: resized.mimeType,\n\t\t\t\t\tdata: resized.data,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType,\n\t\t\t\t\tdata: content.toString(\"base64\"),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\timages.push(attachment);\n\n\t\t\t// Add text reference to image with optional dimension note\n\t\t\tif (dimensionNote) {\n\t\t\t\ttext += `<file name=\"${absolutePath}\">${dimensionNote}</file>\\n`;\n\t\t\t} else {\n\t\t\t\ttext += `<file name=\"${absolutePath}\"></file>\\n`;\n\t\t\t}\n\t\t} else {\n\t\t\t// Handle text file\n\t\t\ttry {\n\t\t\t\t// Bound the attachment: a giant file would spike the heap and overflow\n\t\t\t\t// the context anyway. The leading window plus a note keeps the rest\n\t\t\t\t// reachable in batches through the read tool.\n\t\t\t\tif (stats.size > MAX_ATTACHMENT_TEXT_BYTES) {\n\t\t\t\t\tconst handle = await open(absolutePath, \"r\");\n\t\t\t\t\tlet head: string;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst buffer = Buffer.allocUnsafe(MAX_ATTACHMENT_TEXT_BYTES);\n\t\t\t\t\t\tconst { bytesRead } = await handle.read(buffer, 0, MAX_ATTACHMENT_TEXT_BYTES, 0);\n\t\t\t\t\t\thead = buffer.subarray(0, bytesRead).toString(\"utf-8\");\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tawait handle.close();\n\t\t\t\t\t}\n\t\t\t\t\tconst lastNewline = head.lastIndexOf(\"\\n\");\n\t\t\t\t\tif (lastNewline > 0) head = head.slice(0, lastNewline);\n\t\t\t\t\ttext += `<file name=\"${absolutePath}\">\\n${head}\\n[File is ${Math.round(stats.size / (1024 * 1024))}MB; attached the leading window only. Read further slices with the read tool using offset.]\\n</file>\\n`;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst content = await readFile(absolutePath, \"utf-8\");\n\t\t\t\ttext += `<file name=\"${absolutePath}\">\\n${content}\\n</file>\\n`;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(chalk.red(`Error: Could not read file ${absolutePath}: ${message}`));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { text, images };\n}\n"]}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Process @file CLI arguments into text content and image attachments
|
|
3
3
|
*/
|
|
4
|
-
import { access, readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { access, open, readFile, stat } from "node:fs/promises";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { resolve } from "path";
|
|
7
7
|
import { resolveReadPath } from "../core/tools/path-utils.js";
|
|
8
8
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize.js";
|
|
9
9
|
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime.js";
|
|
10
|
+
const MAX_ATTACHMENT_TEXT_BYTES = 16 * 1024 * 1024;
|
|
11
|
+
const MAX_ATTACHMENT_IMAGE_BYTES = 128 * 1024 * 1024;
|
|
10
12
|
/** Process @file arguments into text content and image attachments */
|
|
11
13
|
export async function processFileArguments(fileArgs, options) {
|
|
12
14
|
const autoResizeImages = options?.autoResizeImages ?? true;
|
|
@@ -31,6 +33,11 @@ export async function processFileArguments(fileArgs, options) {
|
|
|
31
33
|
}
|
|
32
34
|
const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
|
|
33
35
|
if (mimeType) {
|
|
36
|
+
// Pathology guard only: real screenshots/photos sit far below this.
|
|
37
|
+
if (stats.size > MAX_ATTACHMENT_IMAGE_BYTES) {
|
|
38
|
+
text += `<file name="${absolutePath}">[Image is ${Math.round(stats.size / (1024 * 1024))}MB, beyond the inline decode guard; not attached.]</file>\n`;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
34
41
|
// Handle image file
|
|
35
42
|
const content = await readFile(absolutePath);
|
|
36
43
|
let attachment;
|
|
@@ -67,6 +74,26 @@ export async function processFileArguments(fileArgs, options) {
|
|
|
67
74
|
else {
|
|
68
75
|
// Handle text file
|
|
69
76
|
try {
|
|
77
|
+
// Bound the attachment: a giant file would spike the heap and overflow
|
|
78
|
+
// the context anyway. The leading window plus a note keeps the rest
|
|
79
|
+
// reachable in batches through the read tool.
|
|
80
|
+
if (stats.size > MAX_ATTACHMENT_TEXT_BYTES) {
|
|
81
|
+
const handle = await open(absolutePath, "r");
|
|
82
|
+
let head;
|
|
83
|
+
try {
|
|
84
|
+
const buffer = Buffer.allocUnsafe(MAX_ATTACHMENT_TEXT_BYTES);
|
|
85
|
+
const { bytesRead } = await handle.read(buffer, 0, MAX_ATTACHMENT_TEXT_BYTES, 0);
|
|
86
|
+
head = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await handle.close();
|
|
90
|
+
}
|
|
91
|
+
const lastNewline = head.lastIndexOf("\n");
|
|
92
|
+
if (lastNewline > 0)
|
|
93
|
+
head = head.slice(0, lastNewline);
|
|
94
|
+
text += `<file name="${absolutePath}">\n${head}\n[File is ${Math.round(stats.size / (1024 * 1024))}MB; attached the leading window only. Read further slices with the read tool using offset.]\n</file>\n`;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
70
97
|
const content = await readFile(absolutePath, "utf-8");
|
|
71
98
|
text += `<file name="${absolutePath}">\n${content}\n</file>\n`;
|
|
72
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-processor.js","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-processor.js","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Process @file CLI arguments into text content and image attachments\n */\n\nimport { access, open, readFile, stat } from \"node:fs/promises\";\nimport type { ImageContent } from \"@caupulican/pi-ai\";\nimport chalk from \"chalk\";\nimport { resolve } from \"path\";\nimport { resolveReadPath } from \"../core/tools/path-utils.ts\";\nimport { formatDimensionNote, resizeImage } from \"../utils/image-resize.ts\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../utils/mime.ts\";\n\nconst MAX_ATTACHMENT_TEXT_BYTES = 16 * 1024 * 1024;\nconst MAX_ATTACHMENT_IMAGE_BYTES = 128 * 1024 * 1024;\n\nexport interface ProcessedFiles {\n\ttext: string;\n\timages: ImageContent[];\n}\n\nexport interface ProcessFileOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n}\n\n/** Process @file arguments into text content and image attachments */\nexport async function processFileArguments(fileArgs: string[], options?: ProcessFileOptions): Promise<ProcessedFiles> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tlet text = \"\";\n\tconst images: ImageContent[] = [];\n\n\tfor (const fileArg of fileArgs) {\n\t\t// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)\n\t\tconst absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));\n\n\t\t// Check if file exists\n\t\ttry {\n\t\t\tawait access(absolutePath);\n\t\t} catch {\n\t\t\tconsole.error(chalk.red(`Error: File not found: ${absolutePath}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Check if file is empty\n\t\tconst stats = await stat(absolutePath);\n\t\tif (stats.size === 0) {\n\t\t\t// Skip empty files\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);\n\n\t\tif (mimeType) {\n\t\t\t// Pathology guard only: real screenshots/photos sit far below this.\n\t\t\tif (stats.size > MAX_ATTACHMENT_IMAGE_BYTES) {\n\t\t\t\ttext += `<file name=\"${absolutePath}\">[Image is ${Math.round(stats.size / (1024 * 1024))}MB, beyond the inline decode guard; not attached.]</file>\\n`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Handle image file\n\t\t\tconst content = await readFile(absolutePath);\n\n\t\t\tlet attachment: ImageContent;\n\t\t\tlet dimensionNote: string | undefined;\n\n\t\t\tif (autoResizeImages) {\n\t\t\t\tconst resized = await resizeImage(content, mimeType);\n\t\t\t\tif (!resized) {\n\t\t\t\t\ttext += `<file name=\"${absolutePath}\">[Image omitted: could not be resized below the inline image size limit.]</file>\\n`;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tdimensionNote = formatDimensionNote(resized);\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType: resized.mimeType,\n\t\t\t\t\tdata: resized.data,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType,\n\t\t\t\t\tdata: content.toString(\"base64\"),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\timages.push(attachment);\n\n\t\t\t// Add text reference to image with optional dimension note\n\t\t\tif (dimensionNote) {\n\t\t\t\ttext += `<file name=\"${absolutePath}\">${dimensionNote}</file>\\n`;\n\t\t\t} else {\n\t\t\t\ttext += `<file name=\"${absolutePath}\"></file>\\n`;\n\t\t\t}\n\t\t} else {\n\t\t\t// Handle text file\n\t\t\ttry {\n\t\t\t\t// Bound the attachment: a giant file would spike the heap and overflow\n\t\t\t\t// the context anyway. The leading window plus a note keeps the rest\n\t\t\t\t// reachable in batches through the read tool.\n\t\t\t\tif (stats.size > MAX_ATTACHMENT_TEXT_BYTES) {\n\t\t\t\t\tconst handle = await open(absolutePath, \"r\");\n\t\t\t\t\tlet head: string;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst buffer = Buffer.allocUnsafe(MAX_ATTACHMENT_TEXT_BYTES);\n\t\t\t\t\t\tconst { bytesRead } = await handle.read(buffer, 0, MAX_ATTACHMENT_TEXT_BYTES, 0);\n\t\t\t\t\t\thead = buffer.subarray(0, bytesRead).toString(\"utf-8\");\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tawait handle.close();\n\t\t\t\t\t}\n\t\t\t\t\tconst lastNewline = head.lastIndexOf(\"\\n\");\n\t\t\t\t\tif (lastNewline > 0) head = head.slice(0, lastNewline);\n\t\t\t\t\ttext += `<file name=\"${absolutePath}\">\\n${head}\\n[File is ${Math.round(stats.size / (1024 * 1024))}MB; attached the leading window only. Read further slices with the read tool using offset.]\\n</file>\\n`;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst content = await readFile(absolutePath, \"utf-8\");\n\t\t\t\ttext += `<file name=\"${absolutePath}\">\\n${content}\\n</file>\\n`;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(chalk.red(`Error: Could not read file ${absolutePath}: ${message}`));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { text, images };\n}\n"]}
|