@earendil-works/pi-durable 1.0.3 → 1.1.0
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 +44 -0
- package/README.md +2 -0
- package/dist/env/node-watch.d.ts.map +1 -1
- package/dist/env/node-watch.js +89 -35
- package/dist/env/node-watch.js.map +1 -1
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +8 -1
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.js +1 -1
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +29 -1
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +125 -24
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +3 -3
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +4 -3
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/output.d.ts.map +1 -1
- package/dist/harness/output.js +15 -3
- package/dist/harness/output.js.map +1 -1
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +98 -5
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/tool.d.ts +1 -1
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +15 -5
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/types.d.ts +18 -2
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/session/session.d.ts +5 -3
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +5 -4
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +2 -0
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +18 -1
- package/dist/session/transaction.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +4 -0
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/memory.d.ts +2 -0
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +72 -18
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/scan.d.ts +18 -0
- package/dist/storage/scan.d.ts.map +1 -0
- package/dist/storage/scan.js +28 -0
- package/dist/storage/scan.js.map +1 -0
- package/dist/storage/sqlite/cloudflare.d.ts +46 -0
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
- package/dist/storage/sqlite/cloudflare.js +106 -0
- package/dist/storage/sqlite/cloudflare.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +2 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +68 -16
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/testing/env-conformance.d.ts.map +1 -1
- package/dist/testing/env-conformance.js +34 -0
- package/dist/testing/env-conformance.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +58 -0
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tools/bash.d.ts +21 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +85 -38
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +4 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/types.d.ts +30 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -4
package/dist/tools/bash.d.ts
CHANGED
|
@@ -5,7 +5,13 @@ declare const bashSchema: Type.TObject<{
|
|
|
5
5
|
command: Type.TString;
|
|
6
6
|
timeout: Type.TOptional<Type.TNumber>;
|
|
7
7
|
}>;
|
|
8
|
+
declare const powershellSchema: Type.TObject<{
|
|
9
|
+
command: Type.TString;
|
|
10
|
+
timeout: Type.TOptional<Type.TNumber>;
|
|
11
|
+
}>;
|
|
8
12
|
export type BashToolInput = Static<typeof bashSchema>;
|
|
13
|
+
export type PowerShellToolInput = Static<typeof powershellSchema>;
|
|
14
|
+
/** A command about to run, which `prepare` may change: the script, its working directory and environment. */
|
|
9
15
|
export interface BashExecution {
|
|
10
16
|
command: string;
|
|
11
17
|
cwd: string;
|
|
@@ -17,13 +23,25 @@ export interface BashToolOptions {
|
|
|
17
23
|
commandPrefix?: string;
|
|
18
24
|
prepare?: BashPrepare;
|
|
19
25
|
}
|
|
26
|
+
export interface PowerShellToolOptions {
|
|
27
|
+
/** Lines run before each command. */
|
|
28
|
+
commandPrefix?: string;
|
|
29
|
+
prepare?: BashPrepare;
|
|
30
|
+
/** PowerShell programs to try in order; default `pwsh`, then `powershell`. A program that cannot start is skipped. */
|
|
31
|
+
programs?: readonly string[];
|
|
32
|
+
}
|
|
20
33
|
/**
|
|
21
34
|
* Runs a command through the environment's shell. Its output streams to `api.output()`, where the Harness keeps the
|
|
22
35
|
* tail within the default limits; the result content is that retained output. The retained window goes to the
|
|
23
|
-
* environment, which may omit output outside it and report how much it omitted, so dropped counts stay exact. Output
|
|
24
|
-
* file whose path is reported as a diagnostic. A nonzero exit or timeout throws, which
|
|
25
|
-
* carries the output and diagnostics.
|
|
36
|
+
* environment, which may omit output outside it and report how much it omitted, so dropped counts stay exact. Output
|
|
37
|
+
* beyond the limits is spilled to a file whose path is reported as a diagnostic. A nonzero exit or timeout throws, which
|
|
38
|
+
* makes an error result that still carries the output and diagnostics.
|
|
26
39
|
*/
|
|
27
40
|
export declare function createBashTool(options?: BashToolOptions): ToolRegistration<typeof bashSchema>;
|
|
41
|
+
/**
|
|
42
|
+
* Runs a PowerShell command, like `bash` but through PowerShell instead of the environment's shell: `pwsh` (PowerShell
|
|
43
|
+
* 7), else Windows PowerShell, started directly with the command as an argument, so no other shell parses it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createPowerShellTool(options?: PowerShellToolOptions): ToolRegistration<typeof powershellSchema>;
|
|
28
46
|
export {};
|
|
29
47
|
//# sourceMappingURL=bash.d.ts.map
|
package/dist/tools/bash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM9E,QAAA,MAAM,UAAU;;;EAGd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM9E,QAAA,MAAM,UAAU;;;EAGd,CAAC;AAEH,QAAA,MAAM,gBAAgB;;;EAGpB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AACtD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAElE,6GAA6G;AAC7G,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtH,MAAM,WAAW,eAAe;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACrC,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,sHAAsH;IACtH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AA0ED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,gBAAgB,CAAC,OAAO,UAAU,CAAC,CAa7F;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,OAAO,gBAAgB,CAAC,CAgB/G"}
|
package/dist/tools/bash.js
CHANGED
|
@@ -7,6 +7,10 @@ const bashSchema = Type.Object({
|
|
|
7
7
|
command: Type.String({ description: "Bash command to execute" }),
|
|
8
8
|
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
9
9
|
});
|
|
10
|
+
const powershellSchema = Type.Object({
|
|
11
|
+
command: Type.String({ description: "PowerShell command to execute" }),
|
|
12
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
13
|
+
});
|
|
10
14
|
function validateTimeout(timeout) {
|
|
11
15
|
if (timeout === undefined)
|
|
12
16
|
return;
|
|
@@ -17,12 +21,63 @@ function validateTimeout(timeout) {
|
|
|
17
21
|
throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`);
|
|
18
22
|
}
|
|
19
23
|
}
|
|
24
|
+
/** The execution of one call: the command with its prefix, in the environment's working directory, then `prepare`. */
|
|
25
|
+
async function prepareExecution(command, options, api, context) {
|
|
26
|
+
const execution = {
|
|
27
|
+
command: options?.commandPrefix ? `${options.commandPrefix}\n${command}` : command,
|
|
28
|
+
cwd: requireEnv(api).cwd,
|
|
29
|
+
env: {},
|
|
30
|
+
inheritEnv: true,
|
|
31
|
+
};
|
|
32
|
+
await options?.prepare?.(execution, api, context);
|
|
33
|
+
return execution;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run each command form in turn until one starts, streaming output to `api.output()` within the retained window, and
|
|
37
|
+
* turn the result into the tool's outcome: a spill diagnostic, and a thrown error for a failure or a nonzero exit.
|
|
38
|
+
*/
|
|
39
|
+
async function runCommand(commands, execution, timeout, api, context) {
|
|
40
|
+
const env = requireEnv(api);
|
|
41
|
+
let result;
|
|
42
|
+
for (const command of commands) {
|
|
43
|
+
result = await env.exec(command, {
|
|
44
|
+
cwd: execution.cwd,
|
|
45
|
+
env: execution.env,
|
|
46
|
+
inheritEnv: execution.inheritEnv,
|
|
47
|
+
...(timeout === undefined ? {} : { timeout }),
|
|
48
|
+
onOutput: (text, _context, info) => api.output(text, info.skipped),
|
|
49
|
+
spill: { afterBytes: DEFAULT_MAX_BYTES, afterLines: DEFAULT_MAX_LINES },
|
|
50
|
+
// An environment may then omit output outside the retained tail and report the omission.
|
|
51
|
+
...(api.outputWindow === undefined ? {} : { window: api.outputWindow }),
|
|
52
|
+
}, context);
|
|
53
|
+
// A program that could not start produced no output; the next form may.
|
|
54
|
+
if (result.ok || result.error.code !== "spawn_error")
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if (result === undefined)
|
|
58
|
+
throw new Error("No command to run");
|
|
59
|
+
const spillPath = result.ok ? result.value.spillPath : result.error.spillPath;
|
|
60
|
+
if (spillPath !== undefined) {
|
|
61
|
+
api.diagnostic({ severity: "info", code: "full_output", message: `Full output: ${spillPath}` });
|
|
62
|
+
}
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
if (result.error.code === "aborted" && context.abortSignal?.aborted)
|
|
65
|
+
throw result.error;
|
|
66
|
+
if (result.error.code === "timeout")
|
|
67
|
+
throw new Error(`Command timed out after ${timeout} seconds`);
|
|
68
|
+
if (result.error.code === "aborted")
|
|
69
|
+
throw new Error("Command aborted");
|
|
70
|
+
throw result.error;
|
|
71
|
+
}
|
|
72
|
+
if (result.value.exitCode !== 0)
|
|
73
|
+
throw new Error(`Command exited with code ${result.value.exitCode}`);
|
|
74
|
+
}
|
|
20
75
|
/**
|
|
21
76
|
* Runs a command through the environment's shell. Its output streams to `api.output()`, where the Harness keeps the
|
|
22
77
|
* tail within the default limits; the result content is that retained output. The retained window goes to the
|
|
23
|
-
* environment, which may omit output outside it and report how much it omitted, so dropped counts stay exact. Output
|
|
24
|
-
* file whose path is reported as a diagnostic. A nonzero exit or timeout throws, which
|
|
25
|
-
* carries the output and diagnostics.
|
|
78
|
+
* environment, which may omit output outside it and report how much it omitted, so dropped counts stay exact. Output
|
|
79
|
+
* beyond the limits is spilled to a file whose path is reported as a diagnostic. A nonzero exit or timeout throws, which
|
|
80
|
+
* makes an error result that still carries the output and diagnostics.
|
|
26
81
|
*/
|
|
27
82
|
export function createBashTool(options) {
|
|
28
83
|
return defineTool({
|
|
@@ -31,41 +86,33 @@ export function createBashTool(options) {
|
|
|
31
86
|
parameters: bashSchema,
|
|
32
87
|
outputLimits: { retain: "tail" },
|
|
33
88
|
async execute(args, api, context) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (result.error.code === "timeout")
|
|
62
|
-
throw new Error(`Command timed out after ${timeout} seconds`);
|
|
63
|
-
if (result.error.code === "aborted")
|
|
64
|
-
throw new Error("Command aborted");
|
|
65
|
-
throw result.error;
|
|
66
|
-
}
|
|
67
|
-
if (result.value.exitCode !== 0)
|
|
68
|
-
throw new Error(`Command exited with code ${result.value.exitCode}`);
|
|
89
|
+
validateTimeout(args.timeout);
|
|
90
|
+
const execution = await prepareExecution(args.command, options, api, context);
|
|
91
|
+
await runCommand([execution.command], execution, args.timeout, api, context);
|
|
92
|
+
return {};
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Output in UTF-8 whatever the console's code page, as the coding agent's `powershell` tool does. */
|
|
97
|
+
const UTF8_OUTPUT = "try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}";
|
|
98
|
+
const POWERSHELL_ARGS = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command"];
|
|
99
|
+
/**
|
|
100
|
+
* Runs a PowerShell command, like `bash` but through PowerShell instead of the environment's shell: `pwsh` (PowerShell
|
|
101
|
+
* 7), else Windows PowerShell, started directly with the command as an argument, so no other shell parses it.
|
|
102
|
+
*/
|
|
103
|
+
export function createPowerShellTool(options) {
|
|
104
|
+
const programs = options?.programs ?? ["pwsh", "powershell"];
|
|
105
|
+
return defineTool({
|
|
106
|
+
name: "powershell",
|
|
107
|
+
description: `Execute a PowerShell command in the current working directory. Returns combined stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
|
|
108
|
+
parameters: powershellSchema,
|
|
109
|
+
outputLimits: { retain: "tail" },
|
|
110
|
+
async execute(args, api, context) {
|
|
111
|
+
validateTimeout(args.timeout);
|
|
112
|
+
const execution = await prepareExecution(args.command, options, api, context);
|
|
113
|
+
const script = `${UTF8_OUTPUT}\n${execution.command}`;
|
|
114
|
+
const commands = programs.map((program) => [program, ...POWERSHELL_ARGS, script]);
|
|
115
|
+
await runCommand(commands, execution, args.timeout, api, context);
|
|
69
116
|
return {};
|
|
70
117
|
},
|
|
71
118
|
});
|
package/dist/tools/bash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { type Static, Type } from \"typebox\";\nimport { defineTool } from \"../harness/define.ts\";\nimport type { ToolExecutionApi, ToolRegistration } from \"../harness/types.ts\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from \"../truncate.ts\";\nimport { requireEnv } from \"./env.ts\";\n\nconst MAX_TIMEOUT_SECONDS = 2_147_483_647 / 1000;\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashExecution {\n\tcommand: string;\n\tcwd: string;\n\tenv: Record<string, string>;\n\tinheritEnv: boolean;\n}\n\nexport type BashPrepare = (execution: BashExecution, api: ToolExecutionApi, context: Context) => void | Promise<void>;\n\nexport interface BashToolOptions {\n\tcommandPrefix?: string;\n\tprepare?: BashPrepare;\n}\n\nfunction validateTimeout(timeout: number | undefined): void {\n\tif (timeout === undefined) return;\n\tif (!Number.isFinite(timeout) || timeout <= 0) {\n\t\tthrow new Error(\"Invalid timeout: must be a finite number of seconds\");\n\t}\n\tif (timeout > MAX_TIMEOUT_SECONDS) {\n\t\tthrow new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`);\n\t}\n}\n\n/**\n * Runs a command through the environment's shell. Its output streams to `api.output()`, where the Harness keeps the\n * tail within the default limits; the result content is that retained output. The retained window goes to the\n * environment, which may omit output outside it and report how much it omitted, so dropped counts stay exact. Output beyond the limits is spilled to a\n * file whose path is reported as a diagnostic. A nonzero exit or timeout throws, which makes an error result that still\n * carries the output and diagnostics.\n */\nexport function createBashTool(options?: BashToolOptions): ToolRegistration<typeof bashSchema> {\n\treturn defineTool({\n\t\tname: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns combined stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\toutputLimits: { retain: \"tail\" },\n\t\tasync execute(args, api, context) {\n\t\t\tconst { command, timeout } = args;\n\t\t\tvalidateTimeout(timeout);\n\t\t\tconst env = requireEnv(api);\n\t\t\tconst execution: BashExecution = {\n\t\t\t\tcommand: options?.commandPrefix ? `${options.commandPrefix}\\n${command}` : command,\n\t\t\t\tcwd: env.cwd,\n\t\t\t\tenv: {},\n\t\t\t\tinheritEnv: true,\n\t\t\t};\n\t\t\tawait options?.prepare?.(execution, api, context);\n\t\t\tconst result = await env.exec(\n\t\t\t\texecution.command,\n\t\t\t\t{\n\t\t\t\t\tcwd: execution.cwd,\n\t\t\t\t\tenv: execution.env,\n\t\t\t\t\tinheritEnv: execution.inheritEnv,\n\t\t\t\t\t...(timeout === undefined ? {} : { timeout }),\n\t\t\t\t\tonOutput: (text, _context, info) => api.output(text, info.skipped),\n\t\t\t\t\tspill: { afterBytes: DEFAULT_MAX_BYTES, afterLines: DEFAULT_MAX_LINES },\n\t\t\t\t\t// An environment may then omit output outside the retained tail and report the omission.\n\t\t\t\t\t...(api.outputWindow === undefined ? {} : { window: api.outputWindow }),\n\t\t\t\t},\n\t\t\t\tcontext,\n\t\t\t);\n\t\t\tconst spillPath = result.ok ? result.value.spillPath : result.error.spillPath;\n\t\t\tif (spillPath !== undefined) {\n\t\t\t\tapi.diagnostic({ severity: \"info\", code: \"full_output\", message: `Full output: ${spillPath}` });\n\t\t\t}\n\t\t\tif (!result.ok) {\n\t\t\t\tif (result.error.code === \"aborted\" && context.abortSignal?.aborted) throw result.error;\n\t\t\t\tif (result.error.code === \"timeout\") throw new Error(`Command timed out after ${timeout} seconds`);\n\t\t\t\tif (result.error.code === \"aborted\") throw new Error(\"Command aborted\");\n\t\t\t\tthrow result.error;\n\t\t\t}\n\t\t\tif (result.value.exitCode !== 0) throw new Error(`Command exited with code ${result.value.exitCode}`);\n\t\t\treturn {};\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,mBAAmB,GAAG,aAAa,GAAG,IAAI,CAAC;AAEjD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9B,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;IAChE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,mDAAmD,EAAE,CAAC,CAAC;CACzG,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;IACtE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,mDAAmD,EAAE,CAAC,CAAC;CACzG,CAAC,CAAC;AA4BH,SAAS,eAAe,CAAC,OAA2B;IACnD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO;IAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,OAAO,GAAG,mBAAmB,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,+BAA+B,mBAAmB,UAAU,CAAC,CAAC;IAC/E,CAAC;AACF,CAAC;AAED,sHAAsH;AACtH,KAAK,UAAU,gBAAgB,CAC9B,OAAe,EACf,OAAsE,EACtE,GAAqB,EACrB,OAAgB;IAEhB,MAAM,SAAS,GAAkB;QAChC,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,aAAa,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO;QAClF,GAAG,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG;QACxB,GAAG,EAAE,EAAE;QACP,UAAU,EAAE,IAAI;KAChB,CAAC;IACF,MAAM,OAAO,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,UAAU,CACxB,QAAiD,EACjD,SAAwB,EACxB,OAA2B,EAC3B,GAAqB,EACrB,OAAgB;IAEhB,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,MAAwD,CAAC;IAC7D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAChC,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CACtB,OAAO,EACP;YACC,GAAG,EAAE,SAAS,CAAC,GAAG;YAClB,GAAG,EAAE,SAAS,CAAC,GAAG;YAClB,UAAU,EAAE,SAAS,CAAC,UAAU;YAChC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;YAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC;YAClE,KAAK,EAAE,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,iBAAiB,EAAE;YACvE,yFAAyF;YACzF,GAAG,CAAC,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;SACvE,EACD,OAAO,CACP,CAAC;QACF,wEAAwE;QACxE,IAAI,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,aAAa;YAAE,MAAM;IAC7D,CAAC;IACD,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;IAC9E,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC7B,GAAG,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,SAAS,EAAE,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAChB,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,OAAO;YAAE,MAAM,MAAM,CAAC,KAAK,CAAC;QACxF,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,UAAU,CAAC,CAAC;QACnG,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACxE,MAAM,MAAM,CAAC,KAAK,CAAC;IACpB,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;AACvG,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAyB;IACvD,OAAO,UAAU,CAAC;QACjB,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,4HAA4H,iBAAiB,aAAa,iBAAiB,GAAG,IAAI,0HAA0H;QACzT,UAAU,EAAE,UAAU;QACtB,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;QAChC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO;YAC/B,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC9B,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YAC9E,MAAM,UAAU,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YAC7E,OAAO,EAAE,CAAC;QACX,CAAC;KACD,CAAC,CAAC;AACJ,CAAC;AAED,sGAAsG;AACtG,MAAM,WAAW,GAAG,yEAAyE,CAAC;AAC9F,MAAM,eAAe,GAAG,CAAC,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,QAAQ,EAAE,UAAU,CAAU,CAAC;AAE7G;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA+B;IACnE,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC7D,OAAO,UAAU,CAAC;QACjB,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,kIAAkI,iBAAiB,aAAa,iBAAiB,GAAG,IAAI,0HAA0H;QAC/T,UAAU,EAAE,gBAAgB;QAC5B,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;QAChC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO;YAC/B,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC9B,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YAC9E,MAAM,MAAM,GAAG,GAAG,WAAW,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;YACtD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;YAClF,MAAM,UAAU,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YAClE,OAAO,EAAE,CAAC;QACX,CAAC;KACD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { type Static, Type } from \"typebox\";\nimport { defineTool } from \"../harness/define.ts\";\nimport type { ToolExecutionApi, ToolRegistration } from \"../harness/types.ts\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from \"../truncate.ts\";\nimport { requireEnv } from \"./env.ts\";\n\nconst MAX_TIMEOUT_SECONDS = 2_147_483_647 / 1000;\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nconst powershellSchema = Type.Object({\n\tcommand: Type.String({ description: \"PowerShell command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\nexport type PowerShellToolInput = Static<typeof powershellSchema>;\n\n/** A command about to run, which `prepare` may change: the script, its working directory and environment. */\nexport interface BashExecution {\n\tcommand: string;\n\tcwd: string;\n\tenv: Record<string, string>;\n\tinheritEnv: boolean;\n}\n\nexport type BashPrepare = (execution: BashExecution, api: ToolExecutionApi, context: Context) => void | Promise<void>;\n\nexport interface BashToolOptions {\n\tcommandPrefix?: string;\n\tprepare?: BashPrepare;\n}\n\nexport interface PowerShellToolOptions {\n\t/** Lines run before each command. */\n\tcommandPrefix?: string;\n\tprepare?: BashPrepare;\n\t/** PowerShell programs to try in order; default `pwsh`, then `powershell`. A program that cannot start is skipped. */\n\tprograms?: readonly string[];\n}\n\nfunction validateTimeout(timeout: number | undefined): void {\n\tif (timeout === undefined) return;\n\tif (!Number.isFinite(timeout) || timeout <= 0) {\n\t\tthrow new Error(\"Invalid timeout: must be a finite number of seconds\");\n\t}\n\tif (timeout > MAX_TIMEOUT_SECONDS) {\n\t\tthrow new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`);\n\t}\n}\n\n/** The execution of one call: the command with its prefix, in the environment's working directory, then `prepare`. */\nasync function prepareExecution(\n\tcommand: string,\n\toptions: { commandPrefix?: string; prepare?: BashPrepare } | undefined,\n\tapi: ToolExecutionApi,\n\tcontext: Context,\n): Promise<BashExecution> {\n\tconst execution: BashExecution = {\n\t\tcommand: options?.commandPrefix ? `${options.commandPrefix}\\n${command}` : command,\n\t\tcwd: requireEnv(api).cwd,\n\t\tenv: {},\n\t\tinheritEnv: true,\n\t};\n\tawait options?.prepare?.(execution, api, context);\n\treturn execution;\n}\n\n/**\n * Run each command form in turn until one starts, streaming output to `api.output()` within the retained window, and\n * turn the result into the tool's outcome: a spill diagnostic, and a thrown error for a failure or a nonzero exit.\n */\nasync function runCommand(\n\tcommands: readonly (string | readonly string[])[],\n\texecution: BashExecution,\n\ttimeout: number | undefined,\n\tapi: ToolExecutionApi,\n\tcontext: Context,\n): Promise<void> {\n\tconst env = requireEnv(api);\n\tlet result: Awaited<ReturnType<typeof env.exec>> | undefined;\n\tfor (const command of commands) {\n\t\tresult = await env.exec(\n\t\t\tcommand,\n\t\t\t{\n\t\t\t\tcwd: execution.cwd,\n\t\t\t\tenv: execution.env,\n\t\t\t\tinheritEnv: execution.inheritEnv,\n\t\t\t\t...(timeout === undefined ? {} : { timeout }),\n\t\t\t\tonOutput: (text, _context, info) => api.output(text, info.skipped),\n\t\t\t\tspill: { afterBytes: DEFAULT_MAX_BYTES, afterLines: DEFAULT_MAX_LINES },\n\t\t\t\t// An environment may then omit output outside the retained tail and report the omission.\n\t\t\t\t...(api.outputWindow === undefined ? {} : { window: api.outputWindow }),\n\t\t\t},\n\t\t\tcontext,\n\t\t);\n\t\t// A program that could not start produced no output; the next form may.\n\t\tif (result.ok || result.error.code !== \"spawn_error\") break;\n\t}\n\tif (result === undefined) throw new Error(\"No command to run\");\n\tconst spillPath = result.ok ? result.value.spillPath : result.error.spillPath;\n\tif (spillPath !== undefined) {\n\t\tapi.diagnostic({ severity: \"info\", code: \"full_output\", message: `Full output: ${spillPath}` });\n\t}\n\tif (!result.ok) {\n\t\tif (result.error.code === \"aborted\" && context.abortSignal?.aborted) throw result.error;\n\t\tif (result.error.code === \"timeout\") throw new Error(`Command timed out after ${timeout} seconds`);\n\t\tif (result.error.code === \"aborted\") throw new Error(\"Command aborted\");\n\t\tthrow result.error;\n\t}\n\tif (result.value.exitCode !== 0) throw new Error(`Command exited with code ${result.value.exitCode}`);\n}\n\n/**\n * Runs a command through the environment's shell. Its output streams to `api.output()`, where the Harness keeps the\n * tail within the default limits; the result content is that retained output. The retained window goes to the\n * environment, which may omit output outside it and report how much it omitted, so dropped counts stay exact. Output\n * beyond the limits is spilled to a file whose path is reported as a diagnostic. A nonzero exit or timeout throws, which\n * makes an error result that still carries the output and diagnostics.\n */\nexport function createBashTool(options?: BashToolOptions): ToolRegistration<typeof bashSchema> {\n\treturn defineTool({\n\t\tname: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns combined stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\toutputLimits: { retain: \"tail\" },\n\t\tasync execute(args, api, context) {\n\t\t\tvalidateTimeout(args.timeout);\n\t\t\tconst execution = await prepareExecution(args.command, options, api, context);\n\t\t\tawait runCommand([execution.command], execution, args.timeout, api, context);\n\t\t\treturn {};\n\t\t},\n\t});\n}\n\n/** Output in UTF-8 whatever the console's code page, as the coding agent's `powershell` tool does. */\nconst UTF8_OUTPUT = \"try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}\";\nconst POWERSHELL_ARGS = [\"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\"] as const;\n\n/**\n * Runs a PowerShell command, like `bash` but through PowerShell instead of the environment's shell: `pwsh` (PowerShell\n * 7), else Windows PowerShell, started directly with the command as an argument, so no other shell parses it.\n */\nexport function createPowerShellTool(options?: PowerShellToolOptions): ToolRegistration<typeof powershellSchema> {\n\tconst programs = options?.programs ?? [\"pwsh\", \"powershell\"];\n\treturn defineTool({\n\t\tname: \"powershell\",\n\t\tdescription: `Execute a PowerShell command in the current working directory. Returns combined stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: powershellSchema,\n\t\toutputLimits: { retain: \"tail\" },\n\t\tasync execute(args, api, context) {\n\t\t\tvalidateTimeout(args.timeout);\n\t\t\tconst execution = await prepareExecution(args.command, options, api, context);\n\t\t\tconst script = `${UTF8_OUTPUT}\\n${execution.command}`;\n\t\t\tconst commands = programs.map((program) => [program, ...POWERSHELL_ARGS, script]);\n\t\t\tawait runCommand(commands, execution, args.timeout, api, context);\n\t\t\treturn {};\n\t\t},\n\t});\n}\n"]}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { type BashExecution, type BashPrepare, type BashToolInput, type BashToolOptions, createBashTool, } from "./bash.ts";
|
|
1
|
+
export { type BashExecution, type BashPrepare, type BashToolInput, type BashToolOptions, createBashTool, createPowerShellTool, type PowerShellToolInput, type PowerShellToolOptions, } from "./bash.ts";
|
|
2
2
|
export { createEditTool, type EditToolDetails, type EditToolInput } from "./edit.ts";
|
|
3
3
|
export { createReadTool, type ReadToolDetails, type ReadToolInput } from "./read.ts";
|
|
4
4
|
export { createWriteTool, type WriteToolInput } from "./write.ts";
|
|
5
|
-
/** `read`, `write`, `edit`, and `bash`; nothing installs it automatically. */
|
|
5
|
+
/** `read`, `write`, `edit`, and `bash`; nothing installs it automatically. `createPowerShellTool()` adds `powershell`. */
|
|
6
6
|
export declare const CodingTools: import("../index.ts").Extension<import("../index.ts").ToolRegistration<import("typebox").TObject<{
|
|
7
7
|
command: import("typebox").TString;
|
|
8
8
|
timeout: import("typebox").TOptional<import("typebox").TNumber>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,cAAc,EACd,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAElE,0HAA0H;AAC1H,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;yCAGtB,CAAC"}
|
package/dist/tools/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import { createBashTool } from "./bash.js";
|
|
|
3
3
|
import { createEditTool } from "./edit.js";
|
|
4
4
|
import { createReadTool } from "./read.js";
|
|
5
5
|
import { createWriteTool } from "./write.js";
|
|
6
|
-
export { createBashTool, } from "./bash.js";
|
|
6
|
+
export { createBashTool, createPowerShellTool, } from "./bash.js";
|
|
7
7
|
export { createEditTool } from "./edit.js";
|
|
8
8
|
export { createReadTool } from "./read.js";
|
|
9
9
|
export { createWriteTool } from "./write.js";
|
|
10
|
-
/** `read`, `write`, `edit`, and `bash`; nothing installs it automatically. */
|
|
10
|
+
/** `read`, `write`, `edit`, and `bash`; nothing installs it automatically. `createPowerShellTool()` adds `powershell`. */
|
|
11
11
|
export const CodingTools = defineExtension({
|
|
12
12
|
name: "coding-tools",
|
|
13
13
|
tools: [createReadTool(), createWriteTool(), createEditTool(), createBashTool()],
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAKN,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAKN,cAAc,EACd,oBAAoB,GAGpB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAA4C,MAAM,WAAW,CAAC;AACrF,OAAO,EAAE,cAAc,EAA4C,MAAM,WAAW,CAAC;AACrF,OAAO,EAAE,eAAe,EAAuB,MAAM,YAAY,CAAC;AAElE,0HAA0H;AAC1H,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC;IAC1C,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,CAAC,cAAc,EAAE,EAAE,eAAe,EAAE,EAAE,cAAc,EAAE,EAAE,cAAc,EAAE,CAAC;CAChF,CAAC,CAAC","sourcesContent":["import { defineExtension } from \"../harness/define.ts\";\nimport { createBashTool } from \"./bash.ts\";\nimport { createEditTool } from \"./edit.ts\";\nimport { createReadTool } from \"./read.ts\";\nimport { createWriteTool } from \"./write.ts\";\n\nexport {\n\ttype BashExecution,\n\ttype BashPrepare,\n\ttype BashToolInput,\n\ttype BashToolOptions,\n\tcreateBashTool,\n\tcreatePowerShellTool,\n\ttype PowerShellToolInput,\n\ttype PowerShellToolOptions,\n} from \"./bash.ts\";\nexport { createEditTool, type EditToolDetails, type EditToolInput } from \"./edit.ts\";\nexport { createReadTool, type ReadToolDetails, type ReadToolInput } from \"./read.ts\";\nexport { createWriteTool, type WriteToolInput } from \"./write.ts\";\n\n/** `read`, `write`, `edit`, and `bash`; nothing installs it automatically. `createPowerShellTool()` adds `powershell`. */\nexport const CodingTools = defineExtension({\n\tname: \"coding-tools\",\n\ttools: [createReadTool(), createWriteTool(), createEditTool(), createBashTool()],\n});\n"]}
|
package/dist/tools/read.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAW5C,OAAO,KAAK,EAAkB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAIN,KAAK,gBAAgB,EAGrB,MAAM,gBAAgB,CAAC;AAKxB,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC7B,yEAAyE;IACzE,UAAU,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;CAC/C,CAAC;AAmCF,kHAAkH;AAClH,wBAAgB,cAAc,IAAI,gBAAgB,CAAC,OAAO,UAAU,EAAE,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAW5C,OAAO,KAAK,EAAkB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAIN,KAAK,gBAAgB,EAGrB,MAAM,gBAAgB,CAAC;AAKxB,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC7B,yEAAyE;IACzE,UAAU,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;CAC/C,CAAC;AAmCF,kHAAkH;AAClH,wBAAgB,cAAc,IAAI,gBAAgB,CAAC,OAAO,UAAU,EAAE,eAAe,CAAC,CA2BrF"}
|
package/dist/tools/read.js
CHANGED
|
@@ -50,13 +50,15 @@ export function createReadTool() {
|
|
|
50
50
|
const absolutePath = await resolveReadToolPath(env, path, context);
|
|
51
51
|
const reader = getOrThrow(await env.openBinaryReader(absolutePath, undefined, context));
|
|
52
52
|
try {
|
|
53
|
-
// A concurrent writer can change the file between the scan and the reads
|
|
53
|
+
// A concurrent writer can change the file between the scan and the reads. Appending (a growing log) leaves
|
|
54
|
+
// the scanned bytes as they were; a file that shrank or was rewritten in place is read again once.
|
|
54
55
|
for (let attempt = 0;; attempt++) {
|
|
55
56
|
const before = getOrThrow(await reader.info(context));
|
|
56
57
|
const result = await readText(reader, before, path, offset, limit, context);
|
|
57
58
|
const after = getOrThrow(await reader.info(context));
|
|
58
|
-
if (after.size === before.size && after.mtimeMs === before.mtimeMs)
|
|
59
|
+
if (after.size > before.size || (after.size === before.size && after.mtimeMs === before.mtimeMs)) {
|
|
59
60
|
return result;
|
|
61
|
+
}
|
|
60
62
|
if (attempt === 1)
|
|
61
63
|
throw new Error(`${path} changed while it was read`);
|
|
62
64
|
}
|
package/dist/tools/read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { type Static, Type } from \"typebox\";\nimport {\n\ttype BinaryReader,\n\ttype FileInfo,\n\tgetOrThrow,\n\ttype LineScan,\n\trangeDecoder,\n\tstartsWithBom,\n} from \"../env/index.ts\";\nimport { defineTool } from \"../harness/define.ts\";\nimport { characterEnd } from \"../harness/output.ts\";\nimport type { ToolDiagnostic, ToolRegistration } from \"../harness/types.ts\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n\ttruncateHeadOf,\n\tutf8ByteLength,\n} from \"../truncate.ts\";\nimport { requireEnv } from \"./env.ts\";\nimport { detectSupportedImageMimeTypeOf } from \"./image.ts\";\nimport { resolveReadToolPath } from \"./path-utils.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport type ReadToolDetails = {\n\t/** How the shown text was cut; the text itself is the result content. */\n\ttruncation?: Omit<TruncationResult, \"content\">;\n};\n\nconst READ_CHUNK = 64 * 1024;\n\n/** `Array.prototype.slice`'s conversion of an index: NaN is 0, other values truncate toward zero. */\nfunction sliceIndex(value: number): number {\n\treturn Number.isNaN(value) ? 0 : Math.trunc(value);\n}\n\n/**\n * The decoded start of bytes `[start, end)` of the file, decoded as part of the whole file: all of it, or enough for\n * `truncateHeadOf` (more than `DEFAULT_MAX_BYTES + 1` bytes, or `DEFAULT_MAX_LINES` newlines).\n */\nasync function readHead(\n\treader: BinaryReader,\n\tstart: number,\n\tend: number,\n\tskipBom: boolean,\n\tcontext: Context,\n): Promise<string> {\n\tconst decoder = rangeDecoder();\n\tlet text = \"\";\n\tlet newlines = 0;\n\tfor (let position = skipBom && start === 0 ? 3 : start; position < end; ) {\n\t\tconst bytes = getOrThrow(await reader.read(position, Math.min(READ_CHUNK, end - position), context));\n\t\tif (bytes.length === 0) break;\n\t\tposition += bytes.length;\n\t\tconst decoded = decoder.decode(bytes, { stream: true });\n\t\ttext += decoded;\n\t\tfor (let index = decoded.indexOf(\"\\n\"); index !== -1; index = decoded.indexOf(\"\\n\", index + 1)) newlines++;\n\t\tif (newlines >= DEFAULT_MAX_LINES || utf8ByteLength(text) > DEFAULT_MAX_BYTES + 1) return text;\n\t}\n\treturn text + decoder.decode();\n}\n\n/** Reads text files. Remarks about truncation and continuation are diagnostics; the content is only file text. */\nexport function createReadTool(): ToolRegistration<typeof readSchema, ReadToolDetails> {\n\treturn defineTool({\n\t\tname: \"read\",\n\t\tdescription: `Read the contents of a text file. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\tasync execute(args, api, context) {\n\t\t\tconst { path, offset, limit } = args;\n\t\t\tconst env = requireEnv(api);\n\t\t\tconst absolutePath = await resolveReadToolPath(env, path, context);\n\t\t\tconst reader = getOrThrow(await env.openBinaryReader(absolutePath, undefined, context));\n\t\t\ttry {\n\t\t\t\t// A concurrent writer can change the file between the scan and the reads; retry once from a fresh scan.\n\t\t\t\tfor (let attempt = 0; ; attempt++) {\n\t\t\t\t\tconst before = getOrThrow(await reader.info(context));\n\t\t\t\t\tconst result = await readText(reader, before, path, offset, limit, context);\n\t\t\t\t\tconst after = getOrThrow(await reader.info(context));\n\t\t\t\t\tif (after.size === before.size && after.mtimeMs === before.mtimeMs) return result;\n\t\t\t\t\tif (attempt === 1) throw new Error(`${path} changed while it was read`);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tawait reader.close(context);\n\t\t\t}\n\t\t},\n\t});\n}\n\n/**\n * The read result for the opened file. It equals decoding the whole file with `TextDecoder`, splitting it on `\\n`,\n * and bounding the selected lines with `truncateHead`, while reading only one scan's worth of the file plus the head.\n */\nasync function readText(\n\treader: BinaryReader,\n\tinfo: FileInfo,\n\tpath: string,\n\toffset: number | undefined,\n\tlimit: number | undefined,\n\tcontext: Context,\n) {\n\tconst mimeType = await detectSupportedImageMimeTypeOf({\n\t\tsize: info.size,\n\t\tread: async (position, length) => getOrThrow(await reader.read(position, length, context)),\n\t});\n\tif (mimeType) {\n\t\t// Image content is not supported yet.\n\t\treturn {\n\t\t\tcontent: [],\n\t\t\tisError: true,\n\t\t\tdiagnostics: [\n\t\t\t\t{\n\t\t\t\t\tseverity: \"error\" as const,\n\t\t\t\t\tcode: \"unsupported_image\",\n\t\t\t\t\tmessage: `${path} is an image (${mimeType}); reading images is not supported`,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\tconst startLineDisplay = startLine + 1;\n\t// Lines are selected like `allLines.slice(startLine, endLine)`, which truncates fractional indices.\n\tconst sliceStart = sliceIndex(startLine);\n\t// One pass finds the line count and the selection; a selection past the last line ends with it, as `slice` does,\n\t// and an empty one (a zero or negative limit) is scanned as one line and then ignored.\n\t// A start beyond any file is scanned from 0 only to count lines; the offset check below then fails as before.\n\tconst scanStart = Number.isSafeInteger(sliceStart) ? sliceStart : 0;\n\tconst requestedEnd = limit === undefined ? undefined : Math.max(scanStart + 1, sliceIndex(startLine + limit));\n\tconst scanEnd = requestedEnd !== undefined && Number.isSafeInteger(requestedEnd) ? requestedEnd : undefined;\n\tconst scanOf = async (endLine: number | undefined): Promise<LineScan> =>\n\t\tgetOrThrow(\n\t\t\tawait reader.scanLines({ startLine: scanStart, ...(endLine === undefined ? {} : { endLine }) }, context),\n\t\t);\n\tlet scan = await scanOf(scanEnd);\n\tconst totalFileLines = scan.newlines + 1;\n\tif (startLine >= totalFileLines) {\n\t\tthrow new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n\t}\n\n\tlet userLimitedLines: number | undefined;\n\tlet selectedLineCount = totalFileLines - sliceStart;\n\tif (limit !== undefined) {\n\t\tconst endLine = Math.min(startLine + limit, totalFileLines);\n\t\tuserLimitedLines = endLine - startLine;\n\t\t// `slice` counts a negative end from the end of the lines, which only the line count tells; scan again for it.\n\t\tconst relativeEnd = sliceIndex(endLine);\n\t\tconst sliceEnd = relativeEnd < 0 ? Math.max(totalFileLines + relativeEnd, 0) : relativeEnd;\n\t\tselectedLineCount = Math.max(0, sliceEnd - sliceStart);\n\t\tif (selectedLineCount > 0 && relativeEnd < 0) scan = await scanOf(sliceEnd);\n\t}\n\tconst empty = selectedLineCount === 0;\n\t// Counted like `truncateHead`: a trailing newline adds no line, and empty text has none.\n\tconst endsWithNewline = !empty && scan.lastLineStart === scan.end && scan.lastLineStart > scan.start;\n\tconst totals = {\n\t\tlines: empty || scan.selectedBytes === 0 ? 0 : selectedLineCount - (endsWithNewline ? 1 : 0),\n\t\tbytes: empty ? 0 : scan.selectedBytes,\n\t};\n\tconst firstBytes = getOrThrow(await reader.read(0, 3, context));\n\tconst head = empty ? \"\" : await readHead(reader, scan.start, scan.end, startsWithBom(firstBytes), context);\n\n\tconst { content: headText, ...truncation } = truncateHeadOf(head, totals);\n\tconst diagnostics: ToolDiagnostic[] = [];\n\tlet outputText = headText;\n\tlet details: ReadToolDetails | undefined;\n\tif (truncation.firstLineExceedsLimit) {\n\t\t// Show the start of the line, cut at the byte limit on a character boundary. Like `allLines[startLine]`, a\n\t\t// fractional start line names no line.\n\t\tconst integral = Number.isInteger(startLine);\n\t\tconst lineBytes = new TextEncoder().encode(integral ? (head.split(\"\\n\")[0] ?? \"\") : \"\");\n\t\tconst lineSize = integral ? scan.firstLineBytes : 0;\n\t\tconst end = characterEnd(lineBytes, DEFAULT_MAX_BYTES);\n\t\toutputText = new TextDecoder().decode(lineBytes.subarray(0, end));\n\t\tdiagnostics.push({\n\t\t\tseverity: \"warn\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Line ${startLineDisplay} is ${formatSize(lineSize)}, exceeds the ${formatSize(DEFAULT_MAX_BYTES)} limit; showing its first ${formatSize(end)}. Use bash: sed -n '${startLineDisplay}p' ${path} | tail -c +${end + 1}`,\n\t\t});\n\t\tdetails = { truncation: { ...truncation, outputBytes: end, outputLines: 1 } };\n\t} else if (truncation.truncated) {\n\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\tconst nextOffset = endLineDisplay + 1;\n\t\tconst limitText = truncation.truncatedBy === \"lines\" ? \"\" : ` (${formatSize(DEFAULT_MAX_BYTES)} limit)`;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}${limitText}. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t\tdetails = { truncation };\n\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < totalFileLines) {\n\t\tconst remaining = totalFileLines - (startLine + userLimitedLines);\n\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tmessage: `${remaining} more lines in file. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t}\n\n\treturn {\n\t\tcontent: outputText === \"\" ? [] : [{ type: \"text\" as const, text: outputText }],\n\t\t...(details === undefined ? {} : { details }),\n\t\tdiagnostics,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAGN,UAAU,EAEV,YAAY,EACZ,aAAa,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EACN,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EAEV,cAAc,EACd,cAAc,GACd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,iDAAiD,EAAE,CAAC;IACrF,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC,CAAC;IACpG,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC,CAAC;CACrF,CAAC,CAAC;AASH,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;AAE7B,qGAAqG;AACrG,SAAS,UAAU,CAAC,KAAa;IAChC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,QAAQ,CACtB,MAAoB,EACpB,KAAa,EACb,GAAW,EACX,OAAgB,EAChB,OAAgB;IAEhB,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;IAC/B,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,IAAI,QAAQ,GAAG,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,GAAG,GAAG,GAAI,CAAC;QAC1E,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,GAAG,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACrG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM;QAC9B,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,IAAI,IAAI,OAAO,CAAC;QAChB,KAAK,IAAI,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC;YAAE,QAAQ,EAAE,CAAC;QAC3G,IAAI,QAAQ,IAAI,iBAAiB,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,iBAAiB,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;IAChG,CAAC;IACD,OAAO,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;AAChC,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,cAAc;IAC7B,OAAO,UAAU,CAAC;QACjB,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,4DAA4D,iBAAiB,aAAa,iBAAiB,GAAG,IAAI,kIAAkI;QACjQ,UAAU,EAAE,UAAU;QACtB,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO;YAC/B,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;YACrC,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YACnE,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,gBAAgB,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;YACxF,IAAI,CAAC;gBACJ,2GAA2G;gBAC3G,mGAAmG;gBACnG,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;oBACnC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;oBACtD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;oBAC5E,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;oBACrD,IAAI,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;wBAClG,OAAO,MAAM,CAAC;oBACf,CAAC;oBACD,IAAI,OAAO,KAAK,CAAC;wBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,4BAA4B,CAAC,CAAC;gBACzE,CAAC;YACF,CAAC;oBAAS,CAAC;gBACV,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;QACF,CAAC;KACD,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,QAAQ,CACtB,MAAoB,EACpB,IAAc,EACd,IAAY,EACZ,MAA0B,EAC1B,KAAyB,EACzB,OAAgB;IAEhB,MAAM,QAAQ,GAAG,MAAM,8BAA8B,CAAC;QACrD,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;KAC1F,CAAC,CAAC;IACH,IAAI,QAAQ,EAAE,CAAC;QACd,sCAAsC;QACtC,OAAO;YACN,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,IAAI;YACb,WAAW,EAAE;gBACZ;oBACC,QAAQ,EAAE,OAAgB;oBAC1B,IAAI,EAAE,mBAAmB;oBACzB,OAAO,EAAE,GAAG,IAAI,iBAAiB,QAAQ,oCAAoC;iBAC7E;aACD;SACD,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,MAAM,gBAAgB,GAAG,SAAS,GAAG,CAAC,CAAC;IACvC,oGAAoG;IACpG,MAAM,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACzC,iHAAiH;IACjH,uFAAuF;IACvF,8GAA8G;IAC9G,MAAM,SAAS,GAAG,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,EAAE,UAAU,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC;IAC9G,MAAM,OAAO,GAAG,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5G,MAAM,MAAM,GAAG,KAAK,EAAE,OAA2B,EAAqB,EAAE,CACvE,UAAU,CACT,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,CACxG,CAAC;IACH,IAAI,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;IACzC,IAAI,SAAS,IAAI,cAAc,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,UAAU,MAAM,2BAA2B,cAAc,eAAe,CAAC,CAAC;IAC3F,CAAC;IAED,IAAI,gBAAoC,CAAC;IACzC,IAAI,iBAAiB,GAAG,cAAc,GAAG,UAAU,CAAC;IACpD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,KAAK,EAAE,cAAc,CAAC,CAAC;QAC5D,gBAAgB,GAAG,OAAO,GAAG,SAAS,CAAC;QACvC,+GAA+G;QAC/G,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,GAAG,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAC3F,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;QACvD,IAAI,iBAAiB,GAAG,CAAC,IAAI,WAAW,GAAG,CAAC;YAAE,IAAI,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC7E,CAAC;IACD,MAAM,KAAK,GAAG,iBAAiB,KAAK,CAAC,CAAC;IACtC,yFAAyF;IACzF,MAAM,eAAe,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC;IACrG,MAAM,MAAM,GAAG;QACd,KAAK,EAAE,KAAK,IAAI,IAAI,CAAC,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5F,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa;KACrC,CAAC;IACF,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;IAE3G,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1E,MAAM,WAAW,GAAqB,EAAE,CAAC;IACzC,IAAI,UAAU,GAAG,QAAQ,CAAC;IAC1B,IAAI,OAAoC,CAAC;IACzC,IAAI,UAAU,CAAC,qBAAqB,EAAE,CAAC;QACtC,2GAA2G;QAC3G,uCAAuC;QACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxF,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QACvD,UAAU,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAClE,WAAW,CAAC,IAAI,CAAC;YAChB,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,QAAQ,gBAAgB,OAAO,UAAU,CAAC,QAAQ,CAAC,iBAAiB,UAAU,CAAC,iBAAiB,CAAC,6BAA6B,UAAU,CAAC,GAAG,CAAC,uBAAuB,gBAAgB,MAAM,IAAI,eAAe,GAAG,GAAG,CAAC,EAAE;SAC/N,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,UAAU,EAAE,EAAE,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;IAC/E,CAAC;SAAM,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QACjC,MAAM,cAAc,GAAG,gBAAgB,GAAG,UAAU,CAAC,WAAW,GAAG,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,cAAc,GAAG,CAAC,CAAC;QACtC,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,iBAAiB,CAAC,SAAS,CAAC;QACxG,WAAW,CAAC,IAAI,CAAC;YAChB,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,iBAAiB,gBAAgB,IAAI,cAAc,OAAO,cAAc,GAAG,SAAS,gBAAgB,UAAU,eAAe;SACtI,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,UAAU,EAAE,CAAC;IAC1B,CAAC;SAAM,IAAI,gBAAgB,KAAK,SAAS,IAAI,SAAS,GAAG,gBAAgB,GAAG,cAAc,EAAE,CAAC;QAC5F,MAAM,SAAS,GAAG,cAAc,GAAG,CAAC,SAAS,GAAG,gBAAgB,CAAC,CAAC;QAClE,MAAM,UAAU,GAAG,SAAS,GAAG,gBAAgB,GAAG,CAAC,CAAC;QACpD,WAAW,CAAC,IAAI,CAAC;YAChB,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,GAAG,SAAS,mCAAmC,UAAU,eAAe;SACjF,CAAC,CAAC;IACJ,CAAC;IAED,OAAO;QACN,OAAO,EAAE,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC/E,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,WAAW;KACX,CAAC;AACH,CAAC","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { type Static, Type } from \"typebox\";\nimport {\n\ttype BinaryReader,\n\ttype FileInfo,\n\tgetOrThrow,\n\ttype LineScan,\n\trangeDecoder,\n\tstartsWithBom,\n} from \"../env/index.ts\";\nimport { defineTool } from \"../harness/define.ts\";\nimport { characterEnd } from \"../harness/output.ts\";\nimport type { ToolDiagnostic, ToolRegistration } from \"../harness/types.ts\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n\ttruncateHeadOf,\n\tutf8ByteLength,\n} from \"../truncate.ts\";\nimport { requireEnv } from \"./env.ts\";\nimport { detectSupportedImageMimeTypeOf } from \"./image.ts\";\nimport { resolveReadToolPath } from \"./path-utils.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport type ReadToolDetails = {\n\t/** How the shown text was cut; the text itself is the result content. */\n\ttruncation?: Omit<TruncationResult, \"content\">;\n};\n\nconst READ_CHUNK = 64 * 1024;\n\n/** `Array.prototype.slice`'s conversion of an index: NaN is 0, other values truncate toward zero. */\nfunction sliceIndex(value: number): number {\n\treturn Number.isNaN(value) ? 0 : Math.trunc(value);\n}\n\n/**\n * The decoded start of bytes `[start, end)` of the file, decoded as part of the whole file: all of it, or enough for\n * `truncateHeadOf` (more than `DEFAULT_MAX_BYTES + 1` bytes, or `DEFAULT_MAX_LINES` newlines).\n */\nasync function readHead(\n\treader: BinaryReader,\n\tstart: number,\n\tend: number,\n\tskipBom: boolean,\n\tcontext: Context,\n): Promise<string> {\n\tconst decoder = rangeDecoder();\n\tlet text = \"\";\n\tlet newlines = 0;\n\tfor (let position = skipBom && start === 0 ? 3 : start; position < end; ) {\n\t\tconst bytes = getOrThrow(await reader.read(position, Math.min(READ_CHUNK, end - position), context));\n\t\tif (bytes.length === 0) break;\n\t\tposition += bytes.length;\n\t\tconst decoded = decoder.decode(bytes, { stream: true });\n\t\ttext += decoded;\n\t\tfor (let index = decoded.indexOf(\"\\n\"); index !== -1; index = decoded.indexOf(\"\\n\", index + 1)) newlines++;\n\t\tif (newlines >= DEFAULT_MAX_LINES || utf8ByteLength(text) > DEFAULT_MAX_BYTES + 1) return text;\n\t}\n\treturn text + decoder.decode();\n}\n\n/** Reads text files. Remarks about truncation and continuation are diagnostics; the content is only file text. */\nexport function createReadTool(): ToolRegistration<typeof readSchema, ReadToolDetails> {\n\treturn defineTool({\n\t\tname: \"read\",\n\t\tdescription: `Read the contents of a text file. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\tasync execute(args, api, context) {\n\t\t\tconst { path, offset, limit } = args;\n\t\t\tconst env = requireEnv(api);\n\t\t\tconst absolutePath = await resolveReadToolPath(env, path, context);\n\t\t\tconst reader = getOrThrow(await env.openBinaryReader(absolutePath, undefined, context));\n\t\t\ttry {\n\t\t\t\t// A concurrent writer can change the file between the scan and the reads. Appending (a growing log) leaves\n\t\t\t\t// the scanned bytes as they were; a file that shrank or was rewritten in place is read again once.\n\t\t\t\tfor (let attempt = 0; ; attempt++) {\n\t\t\t\t\tconst before = getOrThrow(await reader.info(context));\n\t\t\t\t\tconst result = await readText(reader, before, path, offset, limit, context);\n\t\t\t\t\tconst after = getOrThrow(await reader.info(context));\n\t\t\t\t\tif (after.size > before.size || (after.size === before.size && after.mtimeMs === before.mtimeMs)) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t\tif (attempt === 1) throw new Error(`${path} changed while it was read`);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tawait reader.close(context);\n\t\t\t}\n\t\t},\n\t});\n}\n\n/**\n * The read result for the opened file. It equals decoding the whole file with `TextDecoder`, splitting it on `\\n`,\n * and bounding the selected lines with `truncateHead`, while reading only one scan's worth of the file plus the head.\n */\nasync function readText(\n\treader: BinaryReader,\n\tinfo: FileInfo,\n\tpath: string,\n\toffset: number | undefined,\n\tlimit: number | undefined,\n\tcontext: Context,\n) {\n\tconst mimeType = await detectSupportedImageMimeTypeOf({\n\t\tsize: info.size,\n\t\tread: async (position, length) => getOrThrow(await reader.read(position, length, context)),\n\t});\n\tif (mimeType) {\n\t\t// Image content is not supported yet.\n\t\treturn {\n\t\t\tcontent: [],\n\t\t\tisError: true,\n\t\t\tdiagnostics: [\n\t\t\t\t{\n\t\t\t\t\tseverity: \"error\" as const,\n\t\t\t\t\tcode: \"unsupported_image\",\n\t\t\t\t\tmessage: `${path} is an image (${mimeType}); reading images is not supported`,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\tconst startLineDisplay = startLine + 1;\n\t// Lines are selected like `allLines.slice(startLine, endLine)`, which truncates fractional indices.\n\tconst sliceStart = sliceIndex(startLine);\n\t// One pass finds the line count and the selection; a selection past the last line ends with it, as `slice` does,\n\t// and an empty one (a zero or negative limit) is scanned as one line and then ignored.\n\t// A start beyond any file is scanned from 0 only to count lines; the offset check below then fails as before.\n\tconst scanStart = Number.isSafeInteger(sliceStart) ? sliceStart : 0;\n\tconst requestedEnd = limit === undefined ? undefined : Math.max(scanStart + 1, sliceIndex(startLine + limit));\n\tconst scanEnd = requestedEnd !== undefined && Number.isSafeInteger(requestedEnd) ? requestedEnd : undefined;\n\tconst scanOf = async (endLine: number | undefined): Promise<LineScan> =>\n\t\tgetOrThrow(\n\t\t\tawait reader.scanLines({ startLine: scanStart, ...(endLine === undefined ? {} : { endLine }) }, context),\n\t\t);\n\tlet scan = await scanOf(scanEnd);\n\tconst totalFileLines = scan.newlines + 1;\n\tif (startLine >= totalFileLines) {\n\t\tthrow new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n\t}\n\n\tlet userLimitedLines: number | undefined;\n\tlet selectedLineCount = totalFileLines - sliceStart;\n\tif (limit !== undefined) {\n\t\tconst endLine = Math.min(startLine + limit, totalFileLines);\n\t\tuserLimitedLines = endLine - startLine;\n\t\t// `slice` counts a negative end from the end of the lines, which only the line count tells; scan again for it.\n\t\tconst relativeEnd = sliceIndex(endLine);\n\t\tconst sliceEnd = relativeEnd < 0 ? Math.max(totalFileLines + relativeEnd, 0) : relativeEnd;\n\t\tselectedLineCount = Math.max(0, sliceEnd - sliceStart);\n\t\tif (selectedLineCount > 0 && relativeEnd < 0) scan = await scanOf(sliceEnd);\n\t}\n\tconst empty = selectedLineCount === 0;\n\t// Counted like `truncateHead`: a trailing newline adds no line, and empty text has none.\n\tconst endsWithNewline = !empty && scan.lastLineStart === scan.end && scan.lastLineStart > scan.start;\n\tconst totals = {\n\t\tlines: empty || scan.selectedBytes === 0 ? 0 : selectedLineCount - (endsWithNewline ? 1 : 0),\n\t\tbytes: empty ? 0 : scan.selectedBytes,\n\t};\n\tconst firstBytes = getOrThrow(await reader.read(0, 3, context));\n\tconst head = empty ? \"\" : await readHead(reader, scan.start, scan.end, startsWithBom(firstBytes), context);\n\n\tconst { content: headText, ...truncation } = truncateHeadOf(head, totals);\n\tconst diagnostics: ToolDiagnostic[] = [];\n\tlet outputText = headText;\n\tlet details: ReadToolDetails | undefined;\n\tif (truncation.firstLineExceedsLimit) {\n\t\t// Show the start of the line, cut at the byte limit on a character boundary. Like `allLines[startLine]`, a\n\t\t// fractional start line names no line.\n\t\tconst integral = Number.isInteger(startLine);\n\t\tconst lineBytes = new TextEncoder().encode(integral ? (head.split(\"\\n\")[0] ?? \"\") : \"\");\n\t\tconst lineSize = integral ? scan.firstLineBytes : 0;\n\t\tconst end = characterEnd(lineBytes, DEFAULT_MAX_BYTES);\n\t\toutputText = new TextDecoder().decode(lineBytes.subarray(0, end));\n\t\tdiagnostics.push({\n\t\t\tseverity: \"warn\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Line ${startLineDisplay} is ${formatSize(lineSize)}, exceeds the ${formatSize(DEFAULT_MAX_BYTES)} limit; showing its first ${formatSize(end)}. Use bash: sed -n '${startLineDisplay}p' ${path} | tail -c +${end + 1}`,\n\t\t});\n\t\tdetails = { truncation: { ...truncation, outputBytes: end, outputLines: 1 } };\n\t} else if (truncation.truncated) {\n\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\tconst nextOffset = endLineDisplay + 1;\n\t\tconst limitText = truncation.truncatedBy === \"lines\" ? \"\" : ` (${formatSize(DEFAULT_MAX_BYTES)} limit)`;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tcode: \"truncated\",\n\t\t\tmessage: `Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}${limitText}. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t\tdetails = { truncation };\n\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < totalFileLines) {\n\t\tconst remaining = totalFileLines - (startLine + userLimitedLines);\n\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\tdiagnostics.push({\n\t\t\tseverity: \"info\",\n\t\t\tmessage: `${remaining} more lines in file. Use offset=${nextOffset} to continue.`,\n\t\t});\n\t}\n\n\treturn {\n\t\tcontent: outputText === \"\" ? [] : [{ type: \"text\" as const, text: outputText }],\n\t\t...(details === undefined ? {} : { details }),\n\t\tdiagnostics,\n\t};\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -175,7 +175,9 @@ export interface TaskRuntime<I, S, R, H extends object> extends DocumentObserver
|
|
|
175
175
|
/** Undefined when the entry is absent, not visible, or has another kind. */
|
|
176
176
|
entry<D extends JsonValue>(token: Entry<D>, id: EntryId, context: Context): Promise<TypedEntry<D> | undefined>;
|
|
177
177
|
/** Committed raw active transcript and model context, optionally cut off at the visible entry `at`. */
|
|
178
|
-
context(conversationId: ConversationId, context: Context,
|
|
178
|
+
context(conversationId: ConversationId, context: Context, options?: {
|
|
179
|
+
readonly at?: EntryId;
|
|
180
|
+
}): Promise<ContextView>;
|
|
179
181
|
/** The Harness clock. */
|
|
180
182
|
now(): number;
|
|
181
183
|
/** Forward a non-fatal failure to `HarnessOptions.onReport`. */
|
|
@@ -468,6 +470,14 @@ type TaskRecordBase<I, R> = {
|
|
|
468
470
|
readonly background: boolean;
|
|
469
471
|
/** Durable abort mark checked before run-mode progress is committed. */
|
|
470
472
|
readonly abortRequested: boolean;
|
|
473
|
+
/**
|
|
474
|
+
* Wall-clock milliseconds of the first change to `running`, stamped by the Session. Kept through waits and recovery,
|
|
475
|
+
* so the span to `endedAt` includes them. Absent before the task first runs, and on records written by earlier
|
|
476
|
+
* versions.
|
|
477
|
+
*/
|
|
478
|
+
readonly startedAt?: number;
|
|
479
|
+
/** Wall-clock milliseconds of the change to `terminal`, stamped by the Session; absent while live. */
|
|
480
|
+
readonly endedAt?: number;
|
|
471
481
|
};
|
|
472
482
|
/** Complete replacement record for one durable task state machine. */
|
|
473
483
|
export type TaskRecord<I, S, R> = TaskRecordBase<I, R> & ({
|
|
@@ -530,20 +540,29 @@ export type Page<T, C> = {
|
|
|
530
540
|
readonly items: readonly T[];
|
|
531
541
|
readonly next?: C;
|
|
532
542
|
};
|
|
533
|
-
/**
|
|
543
|
+
/**
|
|
544
|
+
* Backend-owned JSON continuation state that callers only round-trip to the same scan. It carries the scan's order: a
|
|
545
|
+
* scan given a cursor continues in that order, and rejects a different `order` in its query.
|
|
546
|
+
*/
|
|
534
547
|
export type Cursor = Readonly<Record<string, JsonValue>>;
|
|
548
|
+
/** ID order of a scan: `ascending` is oldest first, `descending` newest first. */
|
|
549
|
+
export type ScanOrder = "ascending" | "descending";
|
|
535
550
|
/** Optional filters for an ordered conversation scan. */
|
|
536
551
|
export type ConversationQuery = {
|
|
537
552
|
readonly ownerConversationId?: ConversationId;
|
|
538
553
|
readonly ownerTaskId?: TaskId;
|
|
554
|
+
/** Default `ascending`; with a cursor, the cursor's order. */
|
|
555
|
+
readonly order?: ScanOrder;
|
|
539
556
|
};
|
|
540
|
-
/** Inclusive ID bounds for a
|
|
557
|
+
/** Inclusive ID bounds for a scan of one conversation's fork-aware history. */
|
|
541
558
|
export type EntryQuery = {
|
|
542
559
|
readonly conversationId: ConversationId;
|
|
543
560
|
/** Oldest entry ID that may be returned. */
|
|
544
561
|
readonly minEntryId?: EntryId;
|
|
545
562
|
/** Newest entry ID that may be returned. */
|
|
546
563
|
readonly maxEntryId?: EntryId;
|
|
564
|
+
/** Default `descending`; with a cursor, the cursor's order. */
|
|
565
|
+
readonly order?: ScanOrder;
|
|
547
566
|
};
|
|
548
567
|
/** Optional filters for an ordered scan of durable task records. */
|
|
549
568
|
export type TaskQuery = {
|
|
@@ -552,11 +571,15 @@ export type TaskQuery = {
|
|
|
552
571
|
readonly status?: TaskState<JsonValue, JsonValue>["status"];
|
|
553
572
|
readonly abortRequested?: boolean;
|
|
554
573
|
readonly background?: boolean;
|
|
574
|
+
/** Default `ascending`; with a cursor, the cursor's order. */
|
|
575
|
+
readonly order?: ScanOrder;
|
|
555
576
|
};
|
|
556
577
|
/** Optional filters for an ordered scan of submission records. */
|
|
557
578
|
export type SubmissionQuery = {
|
|
558
579
|
readonly conversationId?: ConversationId;
|
|
559
580
|
readonly status?: SubmissionRecord["status"];
|
|
581
|
+
/** Default `ascending`; with a cursor, the cursor's order. */
|
|
582
|
+
readonly order?: ScanOrder;
|
|
560
583
|
};
|
|
561
584
|
/** Current state or one historical commit sequence used for document membership and content reads. */
|
|
562
585
|
export type DocumentPoint = Seq | "current";
|
|
@@ -793,7 +816,7 @@ export interface Storage {
|
|
|
793
816
|
mintId<I extends Id<string>>(): Promise<I>;
|
|
794
817
|
/** Look up one conversation by exact ID. */
|
|
795
818
|
conversation(id: ConversationId, context: Context): Promise<ConversationRecord | undefined>;
|
|
796
|
-
/** Scan conversations in ascending ID
|
|
819
|
+
/** Scan conversations in `query.order` (default ascending) by ID. */
|
|
797
820
|
scanConversations(query: ConversationQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<ConversationRecord, Cursor>>;
|
|
798
821
|
/** Look up one global entry and the sequence of the commit that persisted it. */
|
|
799
822
|
entry(id: EntryId, context: Context): Promise<{
|
|
@@ -812,15 +835,15 @@ export interface Storage {
|
|
|
812
835
|
findLatestHeadMarker(conversationId: ConversationId, atOrBeforeEntryId: EntryId | undefined, context: Context): Promise<(EntryRecord & {
|
|
813
836
|
readonly head: EntryId;
|
|
814
837
|
}) | undefined>;
|
|
815
|
-
/** Scan the inclusive visible range
|
|
838
|
+
/** Scan the inclusive visible range in `query.order` (default descending), returning at most `limit` entries. */
|
|
816
839
|
scanEntries(query: EntryQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<EntryRecord, Cursor>>;
|
|
817
840
|
/** Look up the latest complete record for one task. */
|
|
818
841
|
task(id: TaskId, context: Context): Promise<TaskRecord<JsonValue, JsonValue, JsonValue> | undefined>;
|
|
819
|
-
/** Scan task records matching every supplied filter. */
|
|
842
|
+
/** Scan task records matching every supplied filter in `query.order` (default ascending) by ID. */
|
|
820
843
|
scanTasks(query: TaskQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<TaskRecord<JsonValue, JsonValue, JsonValue>, Cursor>>;
|
|
821
844
|
/** Look up the latest complete record for one admitted submission. */
|
|
822
845
|
submission(id: SubmissionId, context: Context): Promise<SubmissionRecord | undefined>;
|
|
823
|
-
/** Scan submissions matching every supplied filter in ascending ID
|
|
846
|
+
/** Scan submissions matching every supplied filter in `query.order` (default ascending) by ID. */
|
|
824
847
|
scanSubmissions(query: SubmissionQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<SubmissionRecord, Cursor>>;
|
|
825
848
|
/** Find a submission by its conversation-scoped host deduplication key. */
|
|
826
849
|
submissionByRequest(conversationId: ConversationId, requestId: string, context: Context): Promise<SubmissionRecord | undefined>;
|