@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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ExecShell } from "../../../vendor/senpi-machine-ops/index.js";
|
|
2
|
+
import type { ToolMachine } from "./tool-machine.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Pluggable operations for the bash tool.
|
|
5
|
+
* Override these to delegate command execution to remote systems (for example SSH).
|
|
6
|
+
*/
|
|
7
|
+
export interface BashOperations {
|
|
8
|
+
/**
|
|
9
|
+
* Execute a command and stream output.
|
|
10
|
+
* @param command The command to execute
|
|
11
|
+
* @param cwd Working directory
|
|
12
|
+
* @param options Execution options
|
|
13
|
+
* @returns Promise resolving to the exit code. Report signal terminations as 128 + signal number;
|
|
14
|
+
* a null exit code is treated as a failed command.
|
|
15
|
+
*/
|
|
16
|
+
exec: (command: string, cwd: string, options: {
|
|
17
|
+
onData: (data: Buffer, stream?: "stdout" | "stderr") => void;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
timeout?: number;
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
redactSecretsInOutput?: boolean;
|
|
22
|
+
}) => Promise<{
|
|
23
|
+
exitCode: number | null;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/** What a shell run needs from the host it runs for: the shell, the cwd check, the environment and pid tracking. */
|
|
27
|
+
export interface ShellExecHost {
|
|
28
|
+
/** The shell the command runs in; undefined lets the machine choose (`/bin/sh -c` on POSIX). */
|
|
29
|
+
resolveShell(): ExecShell | undefined;
|
|
30
|
+
/** Rejects when `cwd` does not exist where the command runs. */
|
|
31
|
+
checkCwd(cwd: string, signal: AbortSignal | undefined): Promise<void>;
|
|
32
|
+
/** "exact" runs with the tool's environment alone; "overlay" lays it over the machine's curated base. */
|
|
33
|
+
readonly envMode: "exact" | "overlay";
|
|
34
|
+
/** The environment when the caller passes none. */
|
|
35
|
+
defaultEnv(): NodeJS.ProcessEnv | undefined;
|
|
36
|
+
/** The number of a terminating signal, for the 128 + n exit code. */
|
|
37
|
+
signalNumber(signal: string): number | undefined;
|
|
38
|
+
onSpawn?(pid: number): void;
|
|
39
|
+
onExit?(pid: number): void;
|
|
40
|
+
/** Runs once the exec settles, success or not. */
|
|
41
|
+
afterExec?(): void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A host that knows only the tool machine: the machine's shell and environment, the cwd checked there, and the signal
|
|
45
|
+
* numbers of the machine's own OS.
|
|
46
|
+
*/
|
|
47
|
+
export declare function machineShellExecHost(machine: ToolMachine, shell?: ExecShell): ShellExecHost;
|
|
48
|
+
/**
|
|
49
|
+
* Shell execution used by the built-in shell tools: one `process.exec` of the operation layer (own process group,
|
|
50
|
+
* abort/timeout kill the group). The tool keeps its own output handling.
|
|
51
|
+
*/
|
|
52
|
+
export declare function createShellOperations(shellName: string, machine: ToolMachine, host: ShellExecHost): BashOperations;
|
|
53
|
+
//# sourceMappingURL=shell-operations.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { posixSignalTable } from "./posix-signals.js";
|
|
2
|
+
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
3
|
+
const MAX_TIMEOUT_SECONDS = MAX_TIMEOUT_MS / 1000;
|
|
4
|
+
function resolveTimeoutMs(timeout) {
|
|
5
|
+
if (timeout === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
8
|
+
throw new Error("Invalid timeout: must be a finite number of seconds");
|
|
9
|
+
}
|
|
10
|
+
const timeoutMs = timeout * 1000;
|
|
11
|
+
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
12
|
+
throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`);
|
|
13
|
+
}
|
|
14
|
+
return timeoutMs;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A host that knows only the tool machine: the machine's shell and environment, the cwd checked there, and the signal
|
|
18
|
+
* numbers of the machine's own OS.
|
|
19
|
+
*/
|
|
20
|
+
export function machineShellExecHost(machine, shell) {
|
|
21
|
+
return {
|
|
22
|
+
resolveShell: () => shell,
|
|
23
|
+
checkCwd: async (cwd, signal) => {
|
|
24
|
+
await machine.ops.files.stat({ path: cwd }, machine.context(signal));
|
|
25
|
+
},
|
|
26
|
+
envMode: "overlay",
|
|
27
|
+
defaultEnv: () => undefined,
|
|
28
|
+
signalNumber: (signal) => posixSignalTable(machine.ops.info.host().os)[signal],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Shell execution used by the built-in shell tools: one `process.exec` of the operation layer (own process group,
|
|
33
|
+
* abort/timeout kill the group). The tool keeps its own output handling.
|
|
34
|
+
*/
|
|
35
|
+
export function createShellOperations(shellName, machine, host) {
|
|
36
|
+
return {
|
|
37
|
+
exec: async (command, cwd, { onData, signal, timeout, env }) => {
|
|
38
|
+
const timeoutMs = resolveTimeoutMs(timeout);
|
|
39
|
+
if (signal?.aborted) {
|
|
40
|
+
throw new Error("aborted");
|
|
41
|
+
}
|
|
42
|
+
const shell = host.resolveShell();
|
|
43
|
+
try {
|
|
44
|
+
await host.checkCwd(cwd, signal);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error(`Working directory does not exist: ${cwd}\nCannot execute ${shellName} commands.`);
|
|
48
|
+
}
|
|
49
|
+
let streamCallbackError;
|
|
50
|
+
let hasStreamCallbackError = false;
|
|
51
|
+
// A failing output callback stops the command the same way an abort does.
|
|
52
|
+
const callbackAbort = new AbortController();
|
|
53
|
+
const failStream = (error) => {
|
|
54
|
+
if (hasStreamCallbackError)
|
|
55
|
+
return;
|
|
56
|
+
streamCallbackError = error;
|
|
57
|
+
hasStreamCallbackError = true;
|
|
58
|
+
callbackAbort.abort();
|
|
59
|
+
};
|
|
60
|
+
const handleData = (data, stream) => {
|
|
61
|
+
if (hasStreamCallbackError)
|
|
62
|
+
return;
|
|
63
|
+
try {
|
|
64
|
+
const callbackResult = onData(data, stream);
|
|
65
|
+
if (callbackResult && typeof callbackResult.then === "function") {
|
|
66
|
+
void Promise.resolve(callbackResult).catch(failStream);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
failStream(error);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const execSignal = signal ? AbortSignal.any([signal, callbackAbort.signal]) : callbackAbort.signal;
|
|
74
|
+
let result;
|
|
75
|
+
try {
|
|
76
|
+
result = await machine.ops.process.exec({
|
|
77
|
+
command,
|
|
78
|
+
cwd,
|
|
79
|
+
env: env ?? host.defaultEnv(),
|
|
80
|
+
envMode: host.envMode,
|
|
81
|
+
...(shell !== undefined ? { shell } : {}),
|
|
82
|
+
timeoutMs: timeoutMs ?? Number.POSITIVE_INFINITY,
|
|
83
|
+
onOutput: handleData,
|
|
84
|
+
...(host.onSpawn !== undefined ? { onSpawn: host.onSpawn } : {}),
|
|
85
|
+
...(host.onExit !== undefined ? { onExit: host.onExit } : {}),
|
|
86
|
+
}, machine.context(execSignal));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (hasStreamCallbackError)
|
|
90
|
+
throw streamCallbackError;
|
|
91
|
+
if (signal?.aborted)
|
|
92
|
+
throw new Error("aborted");
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
host.afterExec?.();
|
|
97
|
+
}
|
|
98
|
+
if (hasStreamCallbackError) {
|
|
99
|
+
throw streamCallbackError;
|
|
100
|
+
}
|
|
101
|
+
if (signal?.aborted) {
|
|
102
|
+
throw new Error("aborted");
|
|
103
|
+
}
|
|
104
|
+
if (result.timedOut) {
|
|
105
|
+
throw new Error(`timeout:${timeout}`);
|
|
106
|
+
}
|
|
107
|
+
// A signal-killed shell has no exit code. Use the standard shell convention so
|
|
108
|
+
// callers do not mistake the termination for a successful command.
|
|
109
|
+
return {
|
|
110
|
+
exitCode: result.exitCode ?? (result.signal ? 128 + (host.signalNumber(result.signal) ?? 0) : 1),
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=shell-operations.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { TextContent } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { BashToolDetails } from "./bash-schema.ts";
|
|
4
|
+
import type { OutputAccumulator, OutputSnapshot } from "./output-accumulator-core.ts";
|
|
5
|
+
export interface FormattedShellOutput {
|
|
6
|
+
text: string;
|
|
7
|
+
content: TextContent[];
|
|
8
|
+
details: BashToolDetails | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* One shell run's output stream: optional secret redaction per stream, throttled partial updates, and the terminal
|
|
12
|
+
* flush that settles the spill before the result is built.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createShellOutput(output: OutputAccumulator, onUpdate: AgentToolUpdateCallback<BashToolDetails | undefined> | undefined, redactSecretsInOutput: boolean): {
|
|
15
|
+
handleData: (data: Uint8Array, stream?: string) => void;
|
|
16
|
+
finishOutput: () => Promise<OutputSnapshot>;
|
|
17
|
+
formatOutput: (snapshot: OutputSnapshot, emptyText?: string) => FormattedShellOutput;
|
|
18
|
+
clearUpdateTimer: () => void;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=shell-output.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { modelOnlyText } from "./model-only-text.js";
|
|
2
|
+
import { SecretOutputRedactor } from "./secret-output-redactor.js";
|
|
3
|
+
import { DEFAULT_MAX_BYTES, formatSize } from "./truncate.js";
|
|
4
|
+
const BASH_UPDATE_THROTTLE_MS = 100;
|
|
5
|
+
/**
|
|
6
|
+
* One shell run's output stream: optional secret redaction per stream, throttled partial updates, and the terminal
|
|
7
|
+
* flush that settles the spill before the result is built.
|
|
8
|
+
*/
|
|
9
|
+
export function createShellOutput(output, onUpdate, redactSecretsInOutput) {
|
|
10
|
+
const streams = redactSecretsInOutput
|
|
11
|
+
? new Map()
|
|
12
|
+
: undefined;
|
|
13
|
+
let acceptingOutput = true;
|
|
14
|
+
let updateTimer;
|
|
15
|
+
let updateDirty = false;
|
|
16
|
+
let lastUpdateAt = 0;
|
|
17
|
+
let outputUpdateError;
|
|
18
|
+
let hasOutputUpdateError = false;
|
|
19
|
+
const emitOutputUpdate = () => {
|
|
20
|
+
if (!onUpdate || !updateDirty || hasOutputUpdateError)
|
|
21
|
+
return;
|
|
22
|
+
updateDirty = false;
|
|
23
|
+
lastUpdateAt = Date.now();
|
|
24
|
+
const snapshot = output.snapshot({ persistIfTruncated: true });
|
|
25
|
+
try {
|
|
26
|
+
onUpdate({
|
|
27
|
+
content: [{ type: "text", text: snapshot.content || "" }],
|
|
28
|
+
details: {
|
|
29
|
+
truncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,
|
|
30
|
+
fullOutputPath: snapshot.fullOutputPath,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
outputUpdateError = error;
|
|
36
|
+
hasOutputUpdateError = true;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const clearUpdateTimer = () => {
|
|
40
|
+
if (updateTimer) {
|
|
41
|
+
clearTimeout(updateTimer);
|
|
42
|
+
updateTimer = undefined;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const scheduleOutputUpdate = () => {
|
|
46
|
+
if (!onUpdate)
|
|
47
|
+
return;
|
|
48
|
+
updateDirty = true;
|
|
49
|
+
const delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);
|
|
50
|
+
if (delay <= 0) {
|
|
51
|
+
clearUpdateTimer();
|
|
52
|
+
emitOutputUpdate();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
updateTimer ??= setTimeout(() => {
|
|
56
|
+
updateTimer = undefined;
|
|
57
|
+
emitOutputUpdate();
|
|
58
|
+
}, delay);
|
|
59
|
+
};
|
|
60
|
+
const handleData = (data, stream = "stdout") => {
|
|
61
|
+
if (!acceptingOutput)
|
|
62
|
+
return;
|
|
63
|
+
if (streams) {
|
|
64
|
+
let state = streams.get(stream);
|
|
65
|
+
if (!state) {
|
|
66
|
+
state = { redactor: new SecretOutputRedactor(), decoder: new TextDecoder() };
|
|
67
|
+
streams.set(stream, state);
|
|
68
|
+
}
|
|
69
|
+
output.appendText(state.redactor.push(state.decoder.decode(data, { stream: true })));
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
output.append(data);
|
|
73
|
+
scheduleOutputUpdate();
|
|
74
|
+
};
|
|
75
|
+
const finishOutput = async () => {
|
|
76
|
+
acceptingOutput = false;
|
|
77
|
+
clearUpdateTimer();
|
|
78
|
+
let primaryError;
|
|
79
|
+
let hasPrimaryError = false;
|
|
80
|
+
let snapshot;
|
|
81
|
+
try {
|
|
82
|
+
if (streams) {
|
|
83
|
+
for (const { redactor, decoder } of streams.values()) {
|
|
84
|
+
output.appendText(redactor.push(decoder.decode()) + redactor.finish());
|
|
85
|
+
}
|
|
86
|
+
updateDirty = true;
|
|
87
|
+
}
|
|
88
|
+
output.finish();
|
|
89
|
+
emitOutputUpdate();
|
|
90
|
+
snapshot = output.snapshot({ persistIfTruncated: true });
|
|
91
|
+
if (hasOutputUpdateError) {
|
|
92
|
+
primaryError = outputUpdateError;
|
|
93
|
+
hasPrimaryError = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
primaryError = error;
|
|
98
|
+
hasPrimaryError = true;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
await output.closeTempFile();
|
|
102
|
+
}
|
|
103
|
+
catch (closeError) {
|
|
104
|
+
if (hasPrimaryError) {
|
|
105
|
+
throw new AggregateError([primaryError, closeError], "Bash output finalization and spill cleanup failed");
|
|
106
|
+
}
|
|
107
|
+
throw closeError;
|
|
108
|
+
}
|
|
109
|
+
if (hasPrimaryError) {
|
|
110
|
+
try {
|
|
111
|
+
await output.removeTempFile();
|
|
112
|
+
}
|
|
113
|
+
catch (unlinkError) {
|
|
114
|
+
if (!(unlinkError instanceof Error))
|
|
115
|
+
throw unlinkError;
|
|
116
|
+
throw new AggregateError([primaryError, unlinkError], "Bash output finalization and spill cleanup failed");
|
|
117
|
+
}
|
|
118
|
+
throw primaryError;
|
|
119
|
+
}
|
|
120
|
+
if (snapshot === undefined) {
|
|
121
|
+
throw new Error("Bash output finalization produced no snapshot");
|
|
122
|
+
}
|
|
123
|
+
return snapshot;
|
|
124
|
+
};
|
|
125
|
+
const formatOutput = (snapshot, emptyText = "(no output)") => {
|
|
126
|
+
const truncation = snapshot.truncation;
|
|
127
|
+
const text = snapshot.content || emptyText;
|
|
128
|
+
let notice;
|
|
129
|
+
let details;
|
|
130
|
+
if (truncation.truncated) {
|
|
131
|
+
details = { truncation, fullOutputPath: snapshot.fullOutputPath };
|
|
132
|
+
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
133
|
+
const endLine = truncation.totalLines;
|
|
134
|
+
if (truncation.lastLinePartial) {
|
|
135
|
+
const lastLineSize = formatSize(output.getLastLineBytes());
|
|
136
|
+
notice = `[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${snapshot.fullOutputPath}]`;
|
|
137
|
+
}
|
|
138
|
+
else if (truncation.truncatedBy === "lines") {
|
|
139
|
+
notice = `[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${snapshot.fullOutputPath}]`;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
notice = `[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${snapshot.fullOutputPath}]`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const content = [{ type: "text", text: notice === undefined ? text : `${text}\n` }];
|
|
146
|
+
if (notice !== undefined)
|
|
147
|
+
content.push(modelOnlyText(notice));
|
|
148
|
+
return { text: notice === undefined ? text : `${text}\n\n${notice}`, content, details };
|
|
149
|
+
};
|
|
150
|
+
return { handleData, finishOutput, formatOutput, clearUpdateTimer };
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=shell-output.js.map
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
import { decodeUtf8, encodeUtf8, utf8ByteLength as byteLength } from "./encoding.js";
|
|
1
2
|
const MAX_PENDING_TAIL_CHUNKS = 10;
|
|
2
|
-
function byteLength(text) {
|
|
3
|
-
return Buffer.byteLength(text, "utf-8");
|
|
4
|
-
}
|
|
5
3
|
export class TailWindow {
|
|
6
4
|
constructor(maxBytes) {
|
|
7
5
|
this.chunks = [];
|
|
@@ -34,13 +32,13 @@ export class TailWindow {
|
|
|
34
32
|
return this.startsAtBoundary;
|
|
35
33
|
}
|
|
36
34
|
replaceWithTail(text, startsAtLineBoundary) {
|
|
37
|
-
const buffer =
|
|
35
|
+
const buffer = encodeUtf8(text);
|
|
38
36
|
let start = Math.max(0, buffer.length - this.maxBytes);
|
|
39
37
|
while (start < buffer.length && (buffer[start] & 0xc0) === 0x80) {
|
|
40
38
|
start++;
|
|
41
39
|
}
|
|
42
40
|
this.startsAtBoundary = start === 0 ? startsAtLineBoundary : buffer[start - 1] === 0x0a;
|
|
43
|
-
const tail = buffer.subarray(start)
|
|
41
|
+
const tail = decodeUtf8(buffer.subarray(start));
|
|
44
42
|
this.chunks = tail.length > 0 ? [tail] : [];
|
|
45
43
|
this.bytes = byteLength(tail);
|
|
46
44
|
}
|
|
@@ -61,13 +59,13 @@ export class TailWindow {
|
|
|
61
59
|
if (this.bytes <= this.maxBytes) {
|
|
62
60
|
return;
|
|
63
61
|
}
|
|
64
|
-
const buffer =
|
|
62
|
+
const buffer = encodeUtf8(this.joined());
|
|
65
63
|
let start = Math.max(0, buffer.length - this.maxBytes);
|
|
66
64
|
while (start < buffer.length && (buffer[start] & 0xc0) === 0x80) {
|
|
67
65
|
start++;
|
|
68
66
|
}
|
|
69
67
|
this.startsAtBoundary = start === 0 ? this.startsAtBoundary : buffer[start - 1] === 0x0a;
|
|
70
|
-
const tail = buffer.subarray(start)
|
|
68
|
+
const tail = decodeUtf8(buffer.subarray(start));
|
|
71
69
|
this.chunks = tail.length > 0 ? [tail] : [];
|
|
72
70
|
this.bytes = byteLength(tail);
|
|
73
71
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MachineOps, OpContext, OpName, PolicyHooks } from "../../../vendor/senpi-machine-ops/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The hooks the tool path honours: only `beforeOp`, once per tool call. The tools run the extension filesystem policy
|
|
4
|
+
* themselves and sub-operations carry no hooks, so `filesystem` and `afterOp` are rejected at compile time instead of
|
|
5
|
+
* being silently ignored.
|
|
6
|
+
*/
|
|
7
|
+
export type ToolPathPolicy = Pick<PolicyHooks, "beforeOp"> & {
|
|
8
|
+
readonly filesystem?: never;
|
|
9
|
+
readonly afterOp?: never;
|
|
10
|
+
};
|
|
11
|
+
/** One MachineOps bound to the tool path: principal `local-session`, no scope root (the tools resolve paths). */
|
|
12
|
+
export interface ToolMachine {
|
|
13
|
+
readonly ops: MachineOps;
|
|
14
|
+
/**
|
|
15
|
+
* Runs `PolicyHooks.beforeOp` once for one tool call; a denial rejects with `Error(reason)` before any effect.
|
|
16
|
+
* Without a `beforeOp` it returns undefined synchronously, so a tool that awaits only a returned promise keeps
|
|
17
|
+
* its exact pre-layer timing (bash emits its first partial update before any await).
|
|
18
|
+
*/
|
|
19
|
+
begin(op: OpName, args: unknown, signal: AbortSignal | undefined): Promise<void> | undefined;
|
|
20
|
+
/** Context for a sub-operation inside a call `begin` already admitted. */
|
|
21
|
+
context(signal: AbortSignal | undefined, pathLock?: "caller"): OpContext;
|
|
22
|
+
}
|
|
23
|
+
/** Binds the tool path to the MachineOps `resolveOps` returns, read on every use. */
|
|
24
|
+
export declare function bindToolMachine(resolveOps: () => MachineOps, policy?: ToolPathPolicy): ToolMachine;
|
|
25
|
+
/** A ToolMachine over a host-supplied MachineOps (a remote device, a sandbox): the tools' only effect channel. */
|
|
26
|
+
export declare function createToolMachine(options: {
|
|
27
|
+
ops: MachineOps;
|
|
28
|
+
policy?: ToolPathPolicy;
|
|
29
|
+
}): ToolMachine;
|
|
30
|
+
//# sourceMappingURL=tool-machine.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-path limits over the machine's own defaults. The tools keep their own truncation (truncate.ts, the output
|
|
3
|
+
* accumulator), so the operation layer reads files whole and streams exec output uncaptured.
|
|
4
|
+
*/
|
|
5
|
+
function toolPathLimits(machineLimits) {
|
|
6
|
+
return {
|
|
7
|
+
...machineLimits,
|
|
8
|
+
readMaxBytes: Number.POSITIVE_INFINITY,
|
|
9
|
+
execStdoutMaxBytes: 0,
|
|
10
|
+
execStderrMaxBytes: 0,
|
|
11
|
+
execTimeoutDefaultMs: Number.POSITIVE_INFINITY,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const NEVER_ABORTED = new AbortController().signal;
|
|
15
|
+
/** Binds the tool path to the MachineOps `resolveOps` returns, read on every use. */
|
|
16
|
+
export function bindToolMachine(resolveOps, policy) {
|
|
17
|
+
const context = (signal, pathLock) => ({
|
|
18
|
+
scope: { id: "local-session" },
|
|
19
|
+
principal: { kind: "local-session", id: "local-session" },
|
|
20
|
+
signal: signal ?? NEVER_ABORTED,
|
|
21
|
+
limits: toolPathLimits(resolveOps().limits),
|
|
22
|
+
// The tools run the extension filesystem policy themselves (with their own error text) and the gate below
|
|
23
|
+
// already ran once for the call, so sub-operations carry no hooks.
|
|
24
|
+
policy: {},
|
|
25
|
+
...(pathLock !== undefined ? { pathLock } : {}),
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
get ops() {
|
|
29
|
+
return resolveOps();
|
|
30
|
+
},
|
|
31
|
+
begin(op, args, signal) {
|
|
32
|
+
const beforeOp = policy?.beforeOp;
|
|
33
|
+
if (beforeOp === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
return beforeOp(op, args, { ...context(signal), policy: policy ?? {} }).then((decision) => {
|
|
36
|
+
if (!decision.allow)
|
|
37
|
+
throw new Error(decision.reason);
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
context,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** A ToolMachine over a host-supplied MachineOps (a remote device, a sandbox): the tools' only effect channel. */
|
|
44
|
+
export function createToolMachine(options) {
|
|
45
|
+
return bindToolMachine(() => options.ops, options.policy);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=tool-machine.js.map
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Never returns partial lines (except bash tail truncation edge case).
|
|
9
9
|
*/
|
|
10
|
+
import { decodeUtf8, encodeUtf8, utf8ByteLength } from "./encoding.js";
|
|
10
11
|
export const DEFAULT_MAX_LINES = 2000;
|
|
11
12
|
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
13
|
export const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line
|
|
@@ -44,7 +45,7 @@ export function formatSize(bytes) {
|
|
|
44
45
|
export function truncateHead(content, options = {}) {
|
|
45
46
|
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
46
47
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
47
|
-
const totalBytes =
|
|
48
|
+
const totalBytes = utf8ByteLength(content);
|
|
48
49
|
const lines = splitLinesForCounting(content);
|
|
49
50
|
const totalLines = lines.length;
|
|
50
51
|
// Check if no truncation needed
|
|
@@ -64,7 +65,7 @@ export function truncateHead(content, options = {}) {
|
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
// Check if first line alone exceeds byte limit
|
|
67
|
-
const firstLineBytes =
|
|
68
|
+
const firstLineBytes = utf8ByteLength(lines[0]);
|
|
68
69
|
if (firstLineBytes > maxBytes) {
|
|
69
70
|
return {
|
|
70
71
|
content: "",
|
|
@@ -86,7 +87,7 @@ export function truncateHead(content, options = {}) {
|
|
|
86
87
|
let truncatedBy = "lines";
|
|
87
88
|
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
88
89
|
const line = lines[i];
|
|
89
|
-
const lineBytes =
|
|
90
|
+
const lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline
|
|
90
91
|
if (outputBytesCount + lineBytes > maxBytes) {
|
|
91
92
|
truncatedBy = "bytes";
|
|
92
93
|
break;
|
|
@@ -99,7 +100,7 @@ export function truncateHead(content, options = {}) {
|
|
|
99
100
|
truncatedBy = "lines";
|
|
100
101
|
}
|
|
101
102
|
const outputContent = outputLinesArr.join("\n");
|
|
102
|
-
const finalOutputBytes =
|
|
103
|
+
const finalOutputBytes = utf8ByteLength(outputContent);
|
|
103
104
|
return {
|
|
104
105
|
content: outputContent,
|
|
105
106
|
truncated: true,
|
|
@@ -123,7 +124,7 @@ export function truncateHead(content, options = {}) {
|
|
|
123
124
|
export function truncateTail(content, options = {}) {
|
|
124
125
|
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
125
126
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
126
|
-
const totalBytes =
|
|
127
|
+
const totalBytes = utf8ByteLength(content);
|
|
127
128
|
const lines = splitLinesForCounting(content);
|
|
128
129
|
const totalLines = lines.length;
|
|
129
130
|
// Check if no truncation needed
|
|
@@ -149,7 +150,7 @@ export function truncateTail(content, options = {}) {
|
|
|
149
150
|
let lastLinePartial = false;
|
|
150
151
|
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
151
152
|
const line = lines[i];
|
|
152
|
-
const lineBytes =
|
|
153
|
+
const lineBytes = utf8ByteLength(line) + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
|
|
153
154
|
if (outputBytesCount + lineBytes > maxBytes) {
|
|
154
155
|
truncatedBy = "bytes";
|
|
155
156
|
// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
|
|
@@ -157,7 +158,7 @@ export function truncateTail(content, options = {}) {
|
|
|
157
158
|
if (outputLinesArr.length === 0) {
|
|
158
159
|
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
159
160
|
outputLinesArr.unshift(truncatedLine);
|
|
160
|
-
outputBytesCount =
|
|
161
|
+
outputBytesCount = utf8ByteLength(truncatedLine);
|
|
161
162
|
lastLinePartial = true;
|
|
162
163
|
}
|
|
163
164
|
break;
|
|
@@ -170,7 +171,7 @@ export function truncateTail(content, options = {}) {
|
|
|
170
171
|
truncatedBy = "lines";
|
|
171
172
|
}
|
|
172
173
|
const outputContent = outputLinesArr.join("\n");
|
|
173
|
-
const finalOutputBytes =
|
|
174
|
+
const finalOutputBytes = utf8ByteLength(outputContent);
|
|
174
175
|
return {
|
|
175
176
|
content: outputContent,
|
|
176
177
|
truncated: true,
|
|
@@ -190,7 +191,7 @@ export function truncateTail(content, options = {}) {
|
|
|
190
191
|
* Handles multi-byte UTF-8 characters correctly.
|
|
191
192
|
*/
|
|
192
193
|
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
193
|
-
const buf =
|
|
194
|
+
const buf = encodeUtf8(str);
|
|
194
195
|
if (buf.length <= maxBytes) {
|
|
195
196
|
return str;
|
|
196
197
|
}
|
|
@@ -200,7 +201,7 @@ function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
|
200
201
|
while (start < buf.length && (buf[start] & 0xc0) === 0x80) {
|
|
201
202
|
start++;
|
|
202
203
|
}
|
|
203
|
-
return buf.slice(start)
|
|
204
|
+
return decodeUtf8(buf.slice(start));
|
|
204
205
|
}
|
|
205
206
|
/**
|
|
206
207
|
* Truncate a single line to max characters, adding [truncated] suffix.
|
|
@@ -217,7 +218,7 @@ export function truncateLine(line, maxChars = GREP_MAX_LINE_LENGTH) {
|
|
|
217
218
|
* `…N chars truncated…` marker, like Codex does for tool output. Cuts only at character boundaries.
|
|
218
219
|
*/
|
|
219
220
|
export function truncateMiddle(content, maxBytes) {
|
|
220
|
-
const buf =
|
|
221
|
+
const buf = encodeUtf8(content);
|
|
221
222
|
const totalLines = splitLinesForCounting(content).length;
|
|
222
223
|
if (buf.length <= maxBytes) {
|
|
223
224
|
return { content, truncated: false, removedChars: 0, totalBytes: buf.length, totalLines };
|
|
@@ -230,9 +231,9 @@ export function truncateMiddle(content, maxBytes) {
|
|
|
230
231
|
let tailStart = buf.length - (maxBytes - Math.floor(maxBytes / 2));
|
|
231
232
|
while (tailStart < buf.length && !isBoundary(tailStart))
|
|
232
233
|
tailStart++;
|
|
233
|
-
const head = buf.subarray(0, headEnd)
|
|
234
|
-
const tail = buf.subarray(tailStart)
|
|
235
|
-
const removedChars = Array.from(buf.subarray(headEnd, tailStart)
|
|
234
|
+
const head = decodeUtf8(buf.subarray(0, headEnd));
|
|
235
|
+
const tail = decodeUtf8(buf.subarray(tailStart));
|
|
236
|
+
const removedChars = Array.from(decodeUtf8(buf.subarray(headEnd, tailStart))).length;
|
|
236
237
|
return {
|
|
237
238
|
content: `${head}…${removedChars} chars truncated…${tail}`,
|
|
238
239
|
truncated: true,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { type Static, Type } from "typebox";
|
|
3
|
+
import type { FilesystemPolicyChecker, ToolDefinition } from "../extensions/types.ts";
|
|
4
|
+
import { type FileMutationQueue, type ToolPathCanonicalizer, type ToolPathResolver } from "./file-tool-host.ts";
|
|
5
|
+
import type { ToolMachine } from "./tool-machine.ts";
|
|
6
|
+
import { type WriteBaseline, type WriteToolDetails } from "./write-result.ts";
|
|
7
|
+
export declare const writeSchema: Type.TObject<{
|
|
8
|
+
path: Type.TString;
|
|
9
|
+
content: Type.TString;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const writeToolSystemPromptContribution: {
|
|
12
|
+
readonly snippet: "Create or overwrite files";
|
|
13
|
+
readonly guidelines: readonly ["write only for new files or full rewrites."];
|
|
14
|
+
readonly fullGuidelines: readonly ["Use write only for new files or complete rewrites."];
|
|
15
|
+
};
|
|
16
|
+
export type WriteToolInput = Static<typeof writeSchema>;
|
|
17
|
+
/**
|
|
18
|
+
* Pluggable operations for the write tool.
|
|
19
|
+
* Override these to delegate file writing to remote systems (for example SSH).
|
|
20
|
+
*/
|
|
21
|
+
export interface WriteOperations {
|
|
22
|
+
/** Write content to a file */
|
|
23
|
+
writeFile: (absolutePath: string, content: string) => Promise<void>;
|
|
24
|
+
/** Create directory recursively */
|
|
25
|
+
mkdir: (dir: string) => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface CoreWriteToolOptions {
|
|
28
|
+
/** Operation layer and policy gate. */
|
|
29
|
+
machine: ToolMachine;
|
|
30
|
+
/** Custom operations for file writing. Default: writes through `machine` */
|
|
31
|
+
operations?: WriteOperations;
|
|
32
|
+
/** Extension-registered filesystem policy checker. */
|
|
33
|
+
filesystemPolicy?: FilesystemPolicyChecker;
|
|
34
|
+
/** Default: POSIX resolution against the cwd. */
|
|
35
|
+
resolvePath?: ToolPathResolver;
|
|
36
|
+
/** The directory handed to `operations.mkdir`. Default: POSIX dirname. */
|
|
37
|
+
dirname?: (absolutePath: string) => string;
|
|
38
|
+
/** Default: one queue per path string in this isolate, shared with the core edit tool. */
|
|
39
|
+
withMutationQueue?: FileMutationQueue;
|
|
40
|
+
/** The path `filesystemPolicy` judges. Default: the resolved path as is. */
|
|
41
|
+
canonicalizePath?: ToolPathCanonicalizer;
|
|
42
|
+
/** The content before the write, for the result's patch. Default: read on `machine`; none with custom operations. */
|
|
43
|
+
readBaseline?: (absolutePath: string) => Promise<WriteBaseline>;
|
|
44
|
+
}
|
|
45
|
+
export declare function createCoreWriteToolDefinition(cwd: string, options: CoreWriteToolOptions): ToolDefinition<typeof writeSchema, WriteToolDetails | undefined>;
|
|
46
|
+
export declare function createCoreWriteTool(cwd: string, options: CoreWriteToolOptions): AgentTool<typeof writeSchema>;
|
|
47
|
+
//# sourceMappingURL=write-core.d.ts.map
|