@code-yeongyu/senpi 2026.11.15-2 → 2026.11.15-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.
- package/CHANGELOG.md +19 -0
- package/dist/bundle/chunks/{chunk-Q4RJ4WLE.js → chunk-225TDKTA.js} +4 -4
- package/dist/bundle/chunks/{chunk-QSU2456D.js → chunk-3KR3CDGM.js} +1 -1
- package/dist/bundle/chunks/{chunk-AOTHPTMU.js → chunk-5GCZFK4X.js} +2 -2
- package/dist/bundle/chunks/{chunk-BMADOJHZ.js → chunk-5QIFH624.js} +1 -1
- package/dist/bundle/chunks/{chunk-THYIV26N.js → chunk-GQEXLLWJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-3RILB2OD.js → chunk-OJOPOUAY.js} +9 -9
- package/dist/bundle/chunks/{chunk-64FJ4QQJ.js → chunk-PPCAJQDJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-74HCKLRH.js → chunk-TEXE4HT6.js} +1 -1
- package/dist/bundle/chunks/{chunk-RRFDLGX6.js → chunk-TNYFCGI7.js} +156 -156
- package/dist/bundle/chunks/{cli-main-GNQY2LTU.js → cli-main-CV3ODX4A.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-D3DNH6XL.js → hand-entry-2NN7ZTY6.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-ATBPY4M5.js → interactive-mode-TGHZWM2T.js} +1 -1
- package/dist/bundle/chunks/{main-OMFVSMUR.js → main-B7WEY4JW.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-ACQ5N4UB.js → multi-session-host-53YLL2OD.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-2VMRV4HL.js → package-manager-cli-2U54AVKW.js} +1 -1
- package/dist/bundle/chunks/{role-BTH5PVME.js → role-4IXZZAL4.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-XUK5SYKH.js → rpc-mode-RRYXENML.js} +1 -1
- package/dist/bundle/chunks/{runtime-session-VO3X4OE3.js → runtime-session-KKHEYZOH.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-Y5EIFFPY.js → session-control-endpoint-HVULSIUR.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +331 -331
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.js +34 -3
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.d.ts +2 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/failover.d.ts +2 -0
- package/dist/core/extensions/builtin/anthropic-subscription/failover.js +22 -4
- package/dist/core/extensions/builtin/anthropic-subscription/index.js +7 -2
- package/dist/core/extensions/builtin/anthropic-subscription/limit-recheck.d.ts +15 -0
- package/dist/core/extensions/builtin/anthropic-subscription/limit-recheck.js +55 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-stream.d.ts +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/stream-guidance.js +8 -5
- package/dist/core/extensions/builtin/anthropic-subscription/stream.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/stream.js +3 -1
- package/dist/core/extensions/builtin/terminal/extension-state.js +15 -0
- package/dist/core/extensions/builtin/terminal/notify.d.ts +2 -1
- package/dist/core/extensions/builtin/terminal/notify.js +15 -6
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +5 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.js +41 -1
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +4 -0
- package/dist/core/extensions/loader.js +5 -0
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.js +5 -0
- package/dist/core/extensions/types.d.ts +7 -0
- package/dist/core/run-background-jobs.d.ts +33 -0
- package/dist/core/run-background-jobs.js +47 -0
- package/dist/core/tools/bash-core.d.ts +50 -0
- package/dist/core/tools/bash-core.js +169 -0
- package/dist/core/tools/bash-schema.d.ts +48 -0
- package/dist/core/tools/bash-schema.js +34 -0
- package/dist/core/tools/bash.d.ts +4 -70
- package/dist/core/tools/bash.js +32 -409
- package/dist/core/tools/coding-tools-core.d.ts +24 -0
- package/dist/core/tools/coding-tools-core.js +14 -0
- package/dist/core/tools/edit-core.d.ts +56 -0
- package/dist/core/tools/edit-core.js +175 -0
- package/dist/core/tools/edit-diff.d.ts +0 -5
- package/dist/core/tools/edit-diff.js +0 -32
- package/dist/core/tools/edit-preview.d.ts +7 -0
- package/dist/core/tools/edit-preview.js +34 -0
- package/dist/core/tools/edit.d.ts +5 -43
- package/dist/core/tools/edit.js +10 -158
- package/dist/core/tools/encoding.d.ts +10 -0
- package/dist/core/tools/encoding.js +58 -0
- package/dist/core/tools/file-tool-host.d.ts +25 -0
- package/dist/core/tools/file-tool-host.js +109 -0
- package/dist/core/tools/fs-error-code.d.ts +2 -0
- package/dist/core/tools/fs-error-code.js +15 -0
- package/dist/core/tools/machine-output-spill.d.ts +11 -0
- package/dist/core/tools/machine-output-spill.js +65 -0
- package/dist/core/tools/machine.d.ts +4 -24
- package/dist/core/tools/machine.js +4 -44
- package/dist/core/tools/output-accumulator-core.d.ts +84 -0
- package/dist/core/tools/output-accumulator-core.js +200 -0
- package/dist/core/tools/output-accumulator.d.ts +9 -61
- package/dist/core/tools/output-accumulator.js +90 -265
- package/dist/core/tools/path-utils.js +3 -48
- package/dist/core/tools/posix-signals.d.ts +3 -0
- package/dist/core/tools/posix-signals.js +113 -0
- package/dist/core/tools/read-core.d.ts +64 -0
- package/dist/core/tools/read-core.js +180 -0
- package/dist/core/tools/read-text.d.ts +23 -0
- package/dist/core/tools/read-text.js +90 -0
- package/dist/core/tools/read.d.ts +6 -47
- package/dist/core/tools/read.js +13 -218
- package/dist/core/tools/renderers/bash.d.ts +0 -1
- package/dist/core/tools/renderers/bash.js +0 -1
- package/dist/core/tools/renderers/edit.d.ts +1 -1
- package/dist/core/tools/renderers/edit.js +1 -1
- package/dist/core/tools/secret-output-redactor.js +2 -1
- package/dist/core/tools/shell-operations.d.ts +53 -0
- package/dist/core/tools/shell-operations.js +115 -0
- package/dist/core/tools/shell-output.d.ts +20 -0
- package/dist/core/tools/shell-output.js +152 -0
- package/dist/core/tools/tail-window.js +5 -7
- package/dist/core/tools/tool-machine.d.ts +30 -0
- package/dist/core/tools/tool-machine.js +47 -0
- package/dist/core/tools/truncate.js +15 -14
- package/dist/core/tools/write-core.d.ts +47 -0
- package/dist/core/tools/write-core.js +108 -0
- package/dist/core/tools/write-result.d.ts +0 -1
- package/dist/core/tools/write-result.js +0 -13
- package/dist/core/tools/write.d.ts +6 -31
- package/dist/core/tools/write.js +25 -77
- package/dist/core/turn-origin.d.ts +4 -2
- package/dist/core/turn-origin.js +15 -8
- package/dist/modes/interactive/session-control-session-commands.js +1 -1
- package/dist/modes/rpc/connection-handler.js +2 -2
- package/dist/modes/rpc/effect-commands.d.ts +1 -4
- package/dist/modes/rpc/method-classes-schema-session.d.ts +1 -0
- package/dist/modes/rpc/method-classes-schema-session.js +5 -1
- package/dist/modes/rpc/method-classes-schema.d.ts +1 -0
- package/dist/modes/rpc/pause-command-validation.js +2 -0
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +2 -4
- package/dist/modes/rpc/rpc-session-command-types.d.ts +1 -0
- package/dist/modes/rpc/session-control-actions-data.d.ts +7 -3
- package/dist/modes/rpc/session-control-actions-data.js +12 -6
- package/dist/tools-core.d.ts +14 -0
- package/dist/tools-core.js +9 -0
- package/dist/utils/image-mime.d.ts +4 -0
- package/dist/utils/image-mime.js +99 -0
- package/dist/utils/mime.d.ts +1 -1
- package/dist/utils/mime.js +2 -97
- package/docs/extensions.md +7 -1
- package/docs/rpc.md +14 -0
- package/package.json +11 -7
- package/vendor/senpi-machine-ops/files/local-files.js +34 -5
|
@@ -1,286 +1,111 @@
|
|
|
1
|
-
// allow: SIZE_OK - one cohesive streaming-output state machine owns decoding, truncation, spill, and terminal cleanup.
|
|
2
1
|
import { randomBytes } from "node:crypto";
|
|
3
2
|
import { createWriteStream } from "node:fs";
|
|
4
3
|
import { open, rm } from "node:fs/promises";
|
|
5
4
|
import { tmpdir } from "node:os";
|
|
6
5
|
import { join } from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateTail } from "./truncate.js";
|
|
6
|
+
import { OutputAccumulator as CoreOutputAccumulator, joinHeadAndTail, } from "./output-accumulator-core.js";
|
|
9
7
|
function defaultTempFilePath(prefix) {
|
|
10
8
|
const id = randomBytes(8).toString("hex");
|
|
11
9
|
return join(tmpdir(), `${prefix}-${id}.log`);
|
|
12
10
|
}
|
|
13
|
-
function
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* Incrementally tracks streaming output with bounded memory.
|
|
18
|
-
*
|
|
19
|
-
* Appends decode chunks with a streaming UTF-8 decoder, keeps only a decoded
|
|
20
|
-
* tail for display snapshots, and opens a temp file when the full output needs
|
|
21
|
-
* to be preserved.
|
|
22
|
-
*/
|
|
23
|
-
export class OutputAccumulator {
|
|
24
|
-
constructor(options = {}) {
|
|
25
|
-
this.decoder = new TextDecoder();
|
|
26
|
-
this.rawChunks = [];
|
|
27
|
-
this.totalRawBytes = 0;
|
|
28
|
-
this.totalDecodedBytes = 0;
|
|
29
|
-
this.completedLines = 0;
|
|
30
|
-
this.totalLines = 0;
|
|
31
|
-
this.currentLineBytes = 0;
|
|
32
|
-
this.hasOpenLine = false;
|
|
33
|
-
this.finished = false;
|
|
34
|
-
this.maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
35
|
-
this.maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
36
|
-
this.tempFilePrefix = options.tempFilePrefix ?? "pi-output";
|
|
37
|
-
this.tail = new TailWindow(Math.max(this.maxBytes * 2, 1));
|
|
38
|
-
}
|
|
39
|
-
append(data) {
|
|
40
|
-
if (this.finished) {
|
|
41
|
-
throw new Error("Cannot append to a finished output accumulator");
|
|
42
|
-
}
|
|
43
|
-
const text = this.decoder.decode(data, { stream: true });
|
|
44
|
-
this.totalRawBytes += data.length;
|
|
45
|
-
this.appendDecodedText(text, byteLength(text));
|
|
46
|
-
if (this.tempFileStream || this.shouldUseTempFile()) {
|
|
47
|
-
this.ensureTempFile();
|
|
48
|
-
this.tempFileStream?.write(data);
|
|
49
|
-
}
|
|
50
|
-
else if (data.length > 0) {
|
|
51
|
-
this.rawChunks.push(data);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
appendText(text) {
|
|
55
|
-
if (this.finished) {
|
|
56
|
-
throw new Error("Cannot append to a finished output accumulator");
|
|
57
|
-
}
|
|
58
|
-
if (text.length === 0) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const bytes = byteLength(text);
|
|
62
|
-
this.totalRawBytes += bytes;
|
|
63
|
-
this.appendDecodedText(text, bytes);
|
|
64
|
-
if (this.tempFileStream || this.shouldUseTempFile()) {
|
|
65
|
-
this.ensureTempFile();
|
|
66
|
-
this.tempFileStream?.write(text);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
this.rawChunks.push(text);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
finish() {
|
|
73
|
-
if (this.finished) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
this.finished = true;
|
|
77
|
-
const finalText = this.decoder.decode();
|
|
78
|
-
this.appendDecodedText(finalText, byteLength(finalText));
|
|
79
|
-
if (this.shouldUseTempFile()) {
|
|
80
|
-
this.ensureTempFile();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
snapshot(options = {}) {
|
|
84
|
-
const tailTruncation = truncateTail(this.getSnapshotText(), {
|
|
85
|
-
maxLines: this.maxLines,
|
|
86
|
-
maxBytes: this.maxBytes,
|
|
87
|
-
});
|
|
88
|
-
const truncated = this.totalLines > this.maxLines || this.totalDecodedBytes > this.maxBytes;
|
|
89
|
-
const truncatedBy = truncated
|
|
90
|
-
? (tailTruncation.truncatedBy ?? (this.totalDecodedBytes > this.maxBytes ? "bytes" : "lines"))
|
|
91
|
-
: null;
|
|
92
|
-
const truncation = {
|
|
93
|
-
...tailTruncation,
|
|
94
|
-
truncated,
|
|
95
|
-
truncatedBy,
|
|
96
|
-
totalLines: this.totalLines,
|
|
97
|
-
totalBytes: this.totalDecodedBytes,
|
|
98
|
-
maxLines: this.maxLines,
|
|
99
|
-
maxBytes: this.maxBytes,
|
|
100
|
-
};
|
|
101
|
-
if (options.persistIfTruncated && truncation.truncated) {
|
|
102
|
-
this.ensureTempFile();
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
content: truncation.content,
|
|
106
|
-
truncation,
|
|
107
|
-
fullOutputPath: this.tempFilePath,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
async closeTempFile() {
|
|
111
|
-
const stream = this.takeTempFileStream();
|
|
112
|
-
if (!stream) {
|
|
11
|
+
function destroyStream(stream) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
if (stream.closed) {
|
|
14
|
+
resolve();
|
|
113
15
|
return;
|
|
114
16
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
17
|
+
stream.once("close", resolve);
|
|
18
|
+
stream.destroy();
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function endStream(stream) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
let finished = false;
|
|
24
|
+
let closed = false;
|
|
25
|
+
let streamError;
|
|
26
|
+
const settle = () => {
|
|
27
|
+
if (!closed) {
|
|
28
|
+
return;
|
|
126
29
|
}
|
|
127
|
-
|
|
128
|
-
|
|
30
|
+
stream.off("finish", onFinish);
|
|
31
|
+
stream.off("error", onError);
|
|
32
|
+
stream.off("close", onClose);
|
|
33
|
+
if (streamError) {
|
|
34
|
+
reject(streamError);
|
|
129
35
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
await new Promise((resolve, reject) => {
|
|
134
|
-
let finished = false;
|
|
135
|
-
let closed = false;
|
|
136
|
-
let streamError;
|
|
137
|
-
const settle = () => {
|
|
138
|
-
if (!closed) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
stream.off("finish", onFinish);
|
|
142
|
-
stream.off("error", onError);
|
|
143
|
-
stream.off("close", onClose);
|
|
144
|
-
if (streamError) {
|
|
145
|
-
reject(streamError);
|
|
146
|
-
}
|
|
147
|
-
else if (finished) {
|
|
148
|
-
resolve();
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
reject(new Error("Output spill stream closed before finish"));
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
const onError = (error) => {
|
|
155
|
-
streamError ??= error;
|
|
156
|
-
};
|
|
157
|
-
const onFinish = () => {
|
|
158
|
-
finished = true;
|
|
159
|
-
};
|
|
160
|
-
const onClose = () => {
|
|
161
|
-
closed = true;
|
|
162
|
-
if (!finished && !stream.destroyed) {
|
|
163
|
-
stream.destroy();
|
|
164
|
-
}
|
|
165
|
-
settle();
|
|
166
|
-
};
|
|
167
|
-
stream.once("error", onError);
|
|
168
|
-
stream.once("finish", onFinish);
|
|
169
|
-
stream.once("close", onClose);
|
|
170
|
-
stream.end();
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
try {
|
|
175
|
-
await this.removeTempFile();
|
|
36
|
+
else if (finished) {
|
|
37
|
+
resolve();
|
|
176
38
|
}
|
|
177
|
-
|
|
178
|
-
|
|
39
|
+
else {
|
|
40
|
+
reject(new Error("Output spill stream closed before finish"));
|
|
179
41
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const chunks = this.rawChunks.map((chunk) => typeof chunk === "string" ? Buffer.from(chunk, "utf-8") : chunk);
|
|
192
|
-
return { content: new TextDecoder().decode(Buffer.concat(chunks)), truncated: false };
|
|
193
|
-
}
|
|
194
|
-
const file = await open(this.tempFilePath, "r");
|
|
195
|
-
try {
|
|
196
|
-
const size = (await file.stat()).size;
|
|
197
|
-
if (size <= maxBytes) {
|
|
198
|
-
return { content: new TextDecoder().decode(await file.readFile()), truncated: false };
|
|
42
|
+
};
|
|
43
|
+
const onError = (error) => {
|
|
44
|
+
streamError ??= error;
|
|
45
|
+
};
|
|
46
|
+
const onFinish = () => {
|
|
47
|
+
finished = true;
|
|
48
|
+
};
|
|
49
|
+
const onClose = () => {
|
|
50
|
+
closed = true;
|
|
51
|
+
if (!finished && !stream.destroyed) {
|
|
52
|
+
stream.destroy();
|
|
199
53
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
appendDecodedText(text, bytes) {
|
|
224
|
-
if (text.length === 0) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
this.totalDecodedBytes += bytes;
|
|
228
|
-
this.tail.append(text, bytes, !this.hasOpenLine);
|
|
229
|
-
let newlines = 0;
|
|
230
|
-
let lastNewline = -1;
|
|
231
|
-
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) {
|
|
232
|
-
newlines++;
|
|
233
|
-
lastNewline = i;
|
|
234
|
-
}
|
|
235
|
-
if (newlines === 0) {
|
|
236
|
-
this.currentLineBytes += bytes;
|
|
237
|
-
this.hasOpenLine = true;
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
this.completedLines += newlines;
|
|
241
|
-
const tail = text.slice(lastNewline + 1);
|
|
242
|
-
this.currentLineBytes = byteLength(tail);
|
|
243
|
-
this.hasOpenLine = tail.length > 0;
|
|
244
|
-
}
|
|
245
|
-
this.totalLines = this.completedLines + (this.hasOpenLine ? 1 : 0);
|
|
246
|
-
}
|
|
247
|
-
getSnapshotText() {
|
|
248
|
-
const text = this.tail.text();
|
|
249
|
-
if (this.tail.startsAtLineBoundary) {
|
|
250
|
-
return text;
|
|
251
|
-
}
|
|
252
|
-
const firstNewline = text.indexOf("\n");
|
|
253
|
-
return firstNewline === -1 ? text : text.slice(firstNewline + 1);
|
|
254
|
-
}
|
|
255
|
-
shouldUseTempFile() {
|
|
256
|
-
return (this.totalRawBytes > this.maxBytes || this.totalDecodedBytes > this.maxBytes || this.totalLines > this.maxLines);
|
|
257
|
-
}
|
|
258
|
-
ensureTempFile() {
|
|
259
|
-
if (this.tempFilePath) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
this.tempFilePath = defaultTempFilePath(this.tempFilePrefix);
|
|
263
|
-
this.tempFileStream = createWriteStream(this.tempFilePath);
|
|
264
|
-
this.tempFileStream.on("error", (error) => {
|
|
265
|
-
this.tempFileError ??= error;
|
|
266
|
-
});
|
|
267
|
-
for (const chunk of this.rawChunks) {
|
|
268
|
-
this.tempFileStream.write(chunk);
|
|
269
|
-
}
|
|
270
|
-
this.rawChunks = [];
|
|
54
|
+
settle();
|
|
55
|
+
};
|
|
56
|
+
stream.once("error", onError);
|
|
57
|
+
stream.once("finish", onFinish);
|
|
58
|
+
stream.once("close", onClose);
|
|
59
|
+
stream.end();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function readTempFile(path, maxBytes) {
|
|
63
|
+
const file = await open(path, "r");
|
|
64
|
+
try {
|
|
65
|
+
const size = (await file.stat()).size;
|
|
66
|
+
if (size <= maxBytes) {
|
|
67
|
+
return { content: new TextDecoder().decode(await file.readFile()), truncated: false };
|
|
68
|
+
}
|
|
69
|
+
const headBytes = Math.floor(maxBytes / 2);
|
|
70
|
+
const tailBytes = maxBytes - headBytes;
|
|
71
|
+
const head = Buffer.alloc(headBytes);
|
|
72
|
+
const tail = Buffer.alloc(tailBytes);
|
|
73
|
+
await file.read(head, 0, headBytes, 0);
|
|
74
|
+
await file.read(tail, 0, tailBytes, size - tailBytes);
|
|
75
|
+
return { content: joinHeadAndTail(head, tail, size - headBytes - tailBytes), truncated: true };
|
|
271
76
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
const path = this.tempFilePath;
|
|
277
|
-
await rm(path, { force: true });
|
|
278
|
-
this.tempFilePath = undefined;
|
|
77
|
+
finally {
|
|
78
|
+
await file.close();
|
|
279
79
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
80
|
+
}
|
|
81
|
+
/** The local spill: a temp file under the OS temp directory, streamed as output arrives. */
|
|
82
|
+
export const createTempFileSpill = (prefix) => {
|
|
83
|
+
const path = defaultTempFilePath(prefix);
|
|
84
|
+
const stream = createWriteStream(path);
|
|
85
|
+
let writeError;
|
|
86
|
+
stream.on("error", (error) => {
|
|
87
|
+
writeError ??= error;
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
path,
|
|
91
|
+
write: (chunk) => {
|
|
92
|
+
stream.write(chunk);
|
|
93
|
+
},
|
|
94
|
+
close: async () => {
|
|
95
|
+
if (writeError) {
|
|
96
|
+
await destroyStream(stream);
|
|
97
|
+
throw writeError;
|
|
98
|
+
}
|
|
99
|
+
await endStream(stream);
|
|
100
|
+
},
|
|
101
|
+
remove: () => rm(path, { force: true }),
|
|
102
|
+
readFull: (maxBytes) => readTempFile(path, maxBytes),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/** The output accumulator of the local tools: it spills to a temp file unless the caller supplies a spill. */
|
|
106
|
+
export class OutputAccumulator extends CoreOutputAccumulator {
|
|
107
|
+
constructor(options = {}) {
|
|
108
|
+
super({ ...options, spill: options.spill ?? createTempFileSpill });
|
|
284
109
|
}
|
|
285
110
|
}
|
|
286
111
|
//# sourceMappingURL=output-accumulator.js.map
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
import { accessSync, constants } from "node:fs";
|
|
2
2
|
import { access } from "node:fs/promises";
|
|
3
3
|
import { normalizePath, resolvePath } from "../../utils/paths.js";
|
|
4
|
-
|
|
5
|
-
function tryMacOSScreenshotPath(filePath) {
|
|
6
|
-
return filePath.replace(/ (AM|PM)\./gi, `${NARROW_NO_BREAK_SPACE}$1.`);
|
|
7
|
-
}
|
|
8
|
-
function tryNFDVariant(filePath) {
|
|
9
|
-
// macOS stores filenames in NFD (decomposed) form, try converting user input to NFD
|
|
10
|
-
return filePath.normalize("NFD");
|
|
11
|
-
}
|
|
12
|
-
function tryCurlyQuoteVariant(filePath) {
|
|
13
|
-
// macOS uses U+2019 (right single quotation mark) in screenshot names like "Capture d'écran"
|
|
14
|
-
// Users typically type U+0027 (straight apostrophe)
|
|
15
|
-
return filePath.replace(/'/g, "\u2019");
|
|
16
|
-
}
|
|
17
|
-
// Windows Explorer "Copy as path" wraps the path in double quotes (senpi#2170).
|
|
18
|
-
function unquotedPath(filePath) {
|
|
19
|
-
const match = /^@?(["'])(.+)\1$/.exec(filePath.trim());
|
|
20
|
-
return match?.[2];
|
|
21
|
-
}
|
|
4
|
+
import { resolveReadPathVariants, tryCurlyQuoteVariant, tryMacOSScreenshotPath, tryNFDVariant, unquotedPath, } from "./file-tool-host.js";
|
|
22
5
|
function fileExists(filePath) {
|
|
23
6
|
try {
|
|
24
7
|
accessSync(filePath, constants.F_OK);
|
|
@@ -78,35 +61,7 @@ export function resolveReadPath(filePath, cwd) {
|
|
|
78
61
|
}
|
|
79
62
|
return resolved;
|
|
80
63
|
}
|
|
81
|
-
export
|
|
82
|
-
|
|
83
|
-
if (await pathExists(resolved)) {
|
|
84
|
-
return resolved;
|
|
85
|
-
}
|
|
86
|
-
const unquoted = unquotedPath(filePath);
|
|
87
|
-
if (unquoted !== undefined) {
|
|
88
|
-
return resolveReadPathAsync(unquoted, cwd);
|
|
89
|
-
}
|
|
90
|
-
// Try macOS AM/PM variant (narrow no-break space before AM/PM)
|
|
91
|
-
const amPmVariant = tryMacOSScreenshotPath(resolved);
|
|
92
|
-
if (amPmVariant !== resolved && (await pathExists(amPmVariant))) {
|
|
93
|
-
return amPmVariant;
|
|
94
|
-
}
|
|
95
|
-
// Try NFD variant (macOS stores filenames in NFD form)
|
|
96
|
-
const nfdVariant = tryNFDVariant(resolved);
|
|
97
|
-
if (nfdVariant !== resolved && (await pathExists(nfdVariant))) {
|
|
98
|
-
return nfdVariant;
|
|
99
|
-
}
|
|
100
|
-
// Try curly quote variant (macOS uses U+2019 in screenshot names)
|
|
101
|
-
const curlyVariant = tryCurlyQuoteVariant(resolved);
|
|
102
|
-
if (curlyVariant !== resolved && (await pathExists(curlyVariant))) {
|
|
103
|
-
return curlyVariant;
|
|
104
|
-
}
|
|
105
|
-
// Try combined NFD + curly quote (for French macOS screenshots like "Capture d'écran")
|
|
106
|
-
const nfdCurlyVariant = tryCurlyQuoteVariant(nfdVariant);
|
|
107
|
-
if (nfdCurlyVariant !== resolved && (await pathExists(nfdCurlyVariant))) {
|
|
108
|
-
return nfdCurlyVariant;
|
|
109
|
-
}
|
|
110
|
-
return resolved;
|
|
64
|
+
export function resolveReadPathAsync(filePath, cwd) {
|
|
65
|
+
return resolveReadPathVariants(filePath, cwd, resolveToCwd, pathExists);
|
|
111
66
|
}
|
|
112
67
|
//# sourceMappingURL=path-utils.js.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Signal numbers by machine OS, for the 128 + n exit code of a signal-killed shell on a machine this process cannot
|
|
2
|
+
// ask `os.constants.signals` about (test/tools-core/posix-signals.test.ts checks the host's table against Node's).
|
|
3
|
+
// The table every OS got before the tables were split by OS; no OS may number fewer of these.
|
|
4
|
+
const COMMON = {
|
|
5
|
+
SIGHUP: 1,
|
|
6
|
+
SIGINT: 2,
|
|
7
|
+
SIGQUIT: 3,
|
|
8
|
+
SIGILL: 4,
|
|
9
|
+
SIGTRAP: 5,
|
|
10
|
+
SIGABRT: 6,
|
|
11
|
+
SIGFPE: 8,
|
|
12
|
+
SIGKILL: 9,
|
|
13
|
+
SIGSEGV: 11,
|
|
14
|
+
SIGPIPE: 13,
|
|
15
|
+
SIGALRM: 14,
|
|
16
|
+
SIGTERM: 15,
|
|
17
|
+
};
|
|
18
|
+
const LINUX = {
|
|
19
|
+
SIGHUP: 1,
|
|
20
|
+
SIGINT: 2,
|
|
21
|
+
SIGQUIT: 3,
|
|
22
|
+
SIGILL: 4,
|
|
23
|
+
SIGTRAP: 5,
|
|
24
|
+
SIGABRT: 6,
|
|
25
|
+
SIGIOT: 6,
|
|
26
|
+
SIGBUS: 7,
|
|
27
|
+
SIGFPE: 8,
|
|
28
|
+
SIGKILL: 9,
|
|
29
|
+
SIGUSR1: 10,
|
|
30
|
+
SIGSEGV: 11,
|
|
31
|
+
SIGUSR2: 12,
|
|
32
|
+
SIGPIPE: 13,
|
|
33
|
+
SIGALRM: 14,
|
|
34
|
+
SIGTERM: 15,
|
|
35
|
+
SIGSTKFLT: 16,
|
|
36
|
+
SIGCHLD: 17,
|
|
37
|
+
SIGCONT: 18,
|
|
38
|
+
SIGSTOP: 19,
|
|
39
|
+
SIGTSTP: 20,
|
|
40
|
+
SIGTTIN: 21,
|
|
41
|
+
SIGTTOU: 22,
|
|
42
|
+
SIGURG: 23,
|
|
43
|
+
SIGXCPU: 24,
|
|
44
|
+
SIGXFSZ: 25,
|
|
45
|
+
SIGVTALRM: 26,
|
|
46
|
+
SIGPROF: 27,
|
|
47
|
+
SIGWINCH: 28,
|
|
48
|
+
SIGIO: 29,
|
|
49
|
+
SIGPOLL: 29,
|
|
50
|
+
SIGPWR: 30,
|
|
51
|
+
SIGSYS: 31,
|
|
52
|
+
};
|
|
53
|
+
// Darwin and the BSDs share this numbering.
|
|
54
|
+
const BSD = {
|
|
55
|
+
SIGHUP: 1,
|
|
56
|
+
SIGINT: 2,
|
|
57
|
+
SIGQUIT: 3,
|
|
58
|
+
SIGILL: 4,
|
|
59
|
+
SIGTRAP: 5,
|
|
60
|
+
SIGABRT: 6,
|
|
61
|
+
SIGIOT: 6,
|
|
62
|
+
SIGEMT: 7,
|
|
63
|
+
SIGFPE: 8,
|
|
64
|
+
SIGKILL: 9,
|
|
65
|
+
SIGBUS: 10,
|
|
66
|
+
SIGSEGV: 11,
|
|
67
|
+
SIGSYS: 12,
|
|
68
|
+
SIGPIPE: 13,
|
|
69
|
+
SIGALRM: 14,
|
|
70
|
+
SIGTERM: 15,
|
|
71
|
+
SIGURG: 16,
|
|
72
|
+
SIGSTOP: 17,
|
|
73
|
+
SIGTSTP: 18,
|
|
74
|
+
SIGCONT: 19,
|
|
75
|
+
SIGCHLD: 20,
|
|
76
|
+
SIGTTIN: 21,
|
|
77
|
+
SIGTTOU: 22,
|
|
78
|
+
SIGIO: 23,
|
|
79
|
+
SIGXCPU: 24,
|
|
80
|
+
SIGXFSZ: 25,
|
|
81
|
+
SIGVTALRM: 26,
|
|
82
|
+
SIGPROF: 27,
|
|
83
|
+
SIGWINCH: 28,
|
|
84
|
+
SIGINFO: 29,
|
|
85
|
+
SIGUSR1: 30,
|
|
86
|
+
SIGUSR2: 31,
|
|
87
|
+
};
|
|
88
|
+
// Node's win32 numbers (libuv), over COMMON for the names Node does not define there.
|
|
89
|
+
const WIN32 = {
|
|
90
|
+
...COMMON,
|
|
91
|
+
SIGHUP: 1,
|
|
92
|
+
SIGINT: 2,
|
|
93
|
+
SIGILL: 4,
|
|
94
|
+
SIGABRT: 22,
|
|
95
|
+
SIGFPE: 8,
|
|
96
|
+
SIGKILL: 9,
|
|
97
|
+
SIGSEGV: 11,
|
|
98
|
+
SIGTERM: 15,
|
|
99
|
+
SIGBREAK: 21,
|
|
100
|
+
SIGWINCH: 28,
|
|
101
|
+
};
|
|
102
|
+
const BSD_FAMILY = new Set(["darwin", "freebsd", "openbsd", "netbsd", "dragonfly"]);
|
|
103
|
+
/** The signal table of a machine's OS; an OS without its own table gets the common POSIX numbers. */
|
|
104
|
+
export function posixSignalTable(os) {
|
|
105
|
+
if (os === "linux" || os === "android")
|
|
106
|
+
return LINUX;
|
|
107
|
+
if (BSD_FAMILY.has(os))
|
|
108
|
+
return BSD;
|
|
109
|
+
if (os === "win32")
|
|
110
|
+
return WIN32;
|
|
111
|
+
return COMMON;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=posix-signals.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { type ReadFolder } from "@earendil-works/pi-agent-core/read-folders";
|
|
3
|
+
import type { ModelImageResizeOptions } from "@earendil-works/pi-ai";
|
|
4
|
+
import { type Static, Type } from "typebox";
|
|
5
|
+
import type { ProcessImageOptions, ProcessImageResult } from "../../utils/image-process.ts";
|
|
6
|
+
import type { FilesystemPolicyChecker, ToolDefinition } from "../extensions/types.ts";
|
|
7
|
+
import { type ToolPathCanonicalizer } from "./file-tool-host.ts";
|
|
8
|
+
import type { ToolMachine } from "./tool-machine.ts";
|
|
9
|
+
import { type ReadToolDetails } from "./read-text.ts";
|
|
10
|
+
declare const readSchema: Type.TObject<{
|
|
11
|
+
path: Type.TString;
|
|
12
|
+
offset: Type.TOptional<Type.TNumber>;
|
|
13
|
+
limit: Type.TOptional<Type.TNumber>;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const readToolSystemPromptContribution: {
|
|
16
|
+
readonly snippet: "Read file contents";
|
|
17
|
+
readonly guidelines: readonly ["Files: read, not cat/sed."];
|
|
18
|
+
readonly fullGuidelines: readonly ["Use read to examine files instead of cat or sed."];
|
|
19
|
+
};
|
|
20
|
+
export type ReadToolInput = Static<typeof readSchema>;
|
|
21
|
+
export type { ReadToolDetails } from "./read-text.ts";
|
|
22
|
+
/**
|
|
23
|
+
* Pluggable operations for the read tool.
|
|
24
|
+
* Override these to delegate file reading to remote systems (for example SSH).
|
|
25
|
+
*/
|
|
26
|
+
export interface ReadOperations {
|
|
27
|
+
/** Read file contents */
|
|
28
|
+
readFile: (absolutePath: string) => Promise<Uint8Array>;
|
|
29
|
+
/** Check if file is readable (throw if not) */
|
|
30
|
+
access: (absolutePath: string) => Promise<void>;
|
|
31
|
+
/** Detect image MIME type, return null or undefined for non-images */
|
|
32
|
+
detectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;
|
|
33
|
+
}
|
|
34
|
+
/** Turns an image file's bytes into the inline image the model receives, or a note why it cannot. */
|
|
35
|
+
export type ReadImageProcessor = (bytes: Uint8Array, mimeType: string, options: ProcessImageOptions) => Promise<ProcessImageResult>;
|
|
36
|
+
export interface CoreReadToolOptions {
|
|
37
|
+
/** Operation layer and policy gate. */
|
|
38
|
+
machine: ToolMachine;
|
|
39
|
+
/** Structural folder. Default options select the measured folder; omit here for verbatim reads. */
|
|
40
|
+
folder?: ReadFolder;
|
|
41
|
+
/** Whether to auto-resize images. Default: true */
|
|
42
|
+
autoResizeImages?: boolean;
|
|
43
|
+
/** Fallback resize profile when the execution context has no model metadata. */
|
|
44
|
+
resizeOptions?: ModelImageResizeOptions;
|
|
45
|
+
/** Read the model from the execution context. False for a runtime without a session, whose context refuses `model` (senpi#5223). Default: true */
|
|
46
|
+
contextModel?: boolean;
|
|
47
|
+
/** Custom operations for file reading. Default: reads through `machine` */
|
|
48
|
+
operations?: ReadOperations;
|
|
49
|
+
/** Extension-registered filesystem policy checker. */
|
|
50
|
+
filesystemPolicy?: FilesystemPolicyChecker;
|
|
51
|
+
/** Resolves the model's path, trying spelling variants. Default: POSIX resolution, variants probed on `machine`. */
|
|
52
|
+
resolveReadPath?: (path: string, cwd: string) => Promise<string>;
|
|
53
|
+
/** The path `filesystemPolicy` judges. Default: the resolved path as is. */
|
|
54
|
+
canonicalizePath?: ToolPathCanonicalizer;
|
|
55
|
+
/** Image detection for the default operations. Default: the file's leading bytes read on `machine`. */
|
|
56
|
+
detectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;
|
|
57
|
+
/** Default: inline PNG, JPEG, GIF and WebP as they are; other formats are omitted with a note. */
|
|
58
|
+
processImage?: ReadImageProcessor;
|
|
59
|
+
}
|
|
60
|
+
export declare function createCoreReadToolDefinition(cwd: string, options: CoreReadToolOptions): ToolDefinition<typeof readSchema, ReadToolDetails | undefined>;
|
|
61
|
+
/** The read tool for a host whose only effect channel is `machine`; reads fold like the local tool's by default. */
|
|
62
|
+
export declare function createCoreReadTool(cwd: string, options: CoreReadToolOptions): AgentTool<typeof readSchema>;
|
|
63
|
+
export { readSchema };
|
|
64
|
+
//# sourceMappingURL=read-core.d.ts.map
|