@earendil-works/pi-durable 0.99.1 → 1.0.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 +7 -0
- package/README.md +557 -46
- package/dist/entries.d.ts +20 -2
- package/dist/entries.d.ts.map +1 -1
- package/dist/entries.js +14 -1
- package/dist/entries.js.map +1 -1
- package/dist/env/index.d.ts +18 -38
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js +2 -0
- package/dist/env/index.js.map +1 -1
- package/dist/env/node.d.ts +2 -0
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +53 -58
- package/dist/env/node.js.map +1 -1
- package/dist/harness/agent.d.ts +31 -0
- package/dist/harness/agent.d.ts.map +1 -0
- package/dist/harness/agent.js +209 -0
- package/dist/harness/agent.js.map +1 -0
- package/dist/harness/compaction.d.ts +56 -0
- package/dist/harness/compaction.d.ts.map +1 -0
- package/dist/harness/compaction.js +382 -0
- package/dist/harness/compaction.js.map +1 -0
- package/dist/harness/context.d.ts +8 -0
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +32 -21
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/define.d.ts +18 -0
- package/dist/harness/define.d.ts.map +1 -0
- package/dist/harness/define.js +25 -0
- package/dist/harness/define.js.map +1 -0
- package/dist/harness/events.d.ts +166 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +319 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/generation.d.ts +36 -7
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +356 -82
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +141 -106
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +59 -0
- package/dist/harness/inbox.d.ts.map +1 -0
- package/dist/harness/inbox.js +97 -0
- package/dist/harness/inbox.js.map +1 -0
- package/dist/harness/json.d.ts +10 -0
- package/dist/harness/json.d.ts.map +1 -0
- package/dist/harness/json.js +33 -0
- package/dist/harness/json.js.map +1 -0
- package/dist/harness/live.d.ts +66 -7
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +71 -7
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/output.d.ts +62 -0
- package/dist/harness/output.d.ts.map +1 -0
- package/dist/harness/output.js +261 -0
- package/dist/harness/output.js.map +1 -0
- package/dist/harness/prompt.d.ts +9 -6
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +57 -14
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/registry.d.ts +2 -3
- package/dist/harness/registry.d.ts.map +1 -1
- package/dist/harness/registry.js +69 -277
- package/dist/harness/registry.js.map +1 -1
- package/dist/harness/scheduler.d.ts +42 -12
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +649 -75
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +14 -9
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +76 -34
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +49 -0
- package/dist/harness/task-graph.d.ts.map +1 -0
- package/dist/harness/task-graph.js +163 -0
- package/dist/harness/task-graph.js.map +1 -0
- package/dist/harness/tool.d.ts +36 -0
- package/dist/harness/tool.d.ts.map +1 -0
- package/dist/harness/tool.js +415 -0
- package/dist/harness/tool.js.map +1 -0
- package/dist/harness/types.d.ts +330 -125
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +18 -0
- package/dist/harness/usage.d.ts.map +1 -0
- package/dist/harness/usage.js +57 -0
- package/dist/harness/usage.js.map +1 -0
- package/dist/harness/view.d.ts +43 -0
- package/dist/harness/view.d.ts.map +1 -0
- package/dist/harness/view.js +189 -0
- package/dist/harness/view.js.map +1 -0
- package/dist/index.d.ts +13 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/session/observation.d.ts +18 -11
- package/dist/session/observation.d.ts.map +1 -1
- package/dist/session/observation.js +24 -13
- package/dist/session/observation.js.map +1 -1
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +23 -5
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +12 -3
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +90 -31
- package/dist/session/transaction.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +22 -2
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +1 -1
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/database.d.ts +26 -19
- package/dist/storage/sqlite/database.d.ts.map +1 -1
- package/dist/storage/sqlite/database.js.map +1 -1
- package/dist/storage/sqlite/index.d.ts +1 -1
- package/dist/storage/sqlite/index.d.ts.map +1 -1
- package/dist/storage/sqlite/index.js.map +1 -1
- package/dist/storage/sqlite/migrations.js +7 -7
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/node.d.ts +23 -7
- package/dist/storage/sqlite/node.d.ts.map +1 -1
- package/dist/storage/sqlite/node.js +129 -47
- package/dist/storage/sqlite/node.js.map +1 -1
- package/dist/storage/sqlite/storage.d.ts +12 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +121 -129
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +0 -1
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +34 -2
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tools/bash.d.ts +28 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +70 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit-diff.d.ts +89 -0
- package/dist/tools/edit-diff.d.ts.map +1 -0
- package/dist/tools/edit-diff.js +386 -0
- package/dist/tools/edit-diff.js.map +1 -0
- package/dist/tools/edit.d.ts +18 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +117 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/env.d.ts +5 -0
- package/dist/tools/env.d.ts.map +1 -0
- package/dist/tools/env.js +7 -0
- package/dist/tools/env.js.map +1 -0
- package/dist/tools/file-mutation-queue.d.ts +9 -0
- package/dist/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/tools/file-mutation-queue.js +52 -0
- package/dist/tools/file-mutation-queue.js.map +1 -0
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.d.ts.map +1 -0
- package/dist/tools/image.js +92 -0
- package/dist/tools/image.js.map +1 -0
- package/dist/tools/index.d.ts +23 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/path-utils.d.ts +5 -0
- package/dist/tools/path-utils.d.ts.map +1 -0
- package/dist/tools/path-utils.js +26 -0
- package/dist/tools/path-utils.js.map +1 -0
- package/dist/tools/read.d.ts +17 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +101 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +31 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/{env/utils/truncate.d.ts → truncate.d.ts} +1 -17
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +146 -0
- package/dist/truncate.js.map +1 -0
- package/dist/types.d.ts +97 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +32 -14
- package/dist/env/utils/adaptive-publisher.d.ts +0 -24
- package/dist/env/utils/adaptive-publisher.d.ts.map +0 -1
- package/dist/env/utils/adaptive-publisher.js +0 -79
- package/dist/env/utils/adaptive-publisher.js.map +0 -1
- package/dist/env/utils/output-capture.d.ts +0 -31
- package/dist/env/utils/output-capture.d.ts.map +0 -1
- package/dist/env/utils/output-capture.js +0 -238
- package/dist/env/utils/output-capture.js.map +0 -1
- package/dist/env/utils/truncate.d.ts.map +0 -1
- package/dist/env/utils/truncate.js +0 -294
- package/dist/env/utils/truncate.js.map +0 -1
- package/dist/harness/config.d.ts +0 -20
- package/dist/harness/config.d.ts.map +0 -1
- package/dist/harness/config.js +0 -18
- package/dist/harness/config.js.map +0 -1
- package/docs/chord-delta-findings.md +0 -358
- package/docs/pico-v5-chord-usage.md +0 -373
- package/docs/pico-v5-handoff.md +0 -581
- package/docs/pico-v5.md +0 -3084
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { getOrThrow } from "../env/index.js";
|
|
3
|
+
import { defineTool } from "../harness/define.js";
|
|
4
|
+
import { characterEnd } from "../harness/output.js";
|
|
5
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead } from "../truncate.js";
|
|
6
|
+
import { requireEnv } from "./env.js";
|
|
7
|
+
import { detectSupportedImageMimeType } from "./image.js";
|
|
8
|
+
import { resolveReadToolPath } from "./path-utils.js";
|
|
9
|
+
const readSchema = Type.Object({
|
|
10
|
+
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
11
|
+
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
12
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
13
|
+
});
|
|
14
|
+
/** Reads text files. Remarks about truncation and continuation are diagnostics; the content is only file text. */
|
|
15
|
+
export function createReadTool() {
|
|
16
|
+
return defineTool({
|
|
17
|
+
name: "read",
|
|
18
|
+
description: `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.`,
|
|
19
|
+
parameters: readSchema,
|
|
20
|
+
async execute(args, api, context) {
|
|
21
|
+
const { path, offset, limit } = args;
|
|
22
|
+
const env = requireEnv(api);
|
|
23
|
+
const absolutePath = await resolveReadToolPath(env, path, context);
|
|
24
|
+
const bytes = getOrThrow(await env.readBinaryFile(absolutePath, context));
|
|
25
|
+
const mimeType = detectSupportedImageMimeType(bytes);
|
|
26
|
+
if (mimeType) {
|
|
27
|
+
// Image content is not supported yet.
|
|
28
|
+
return {
|
|
29
|
+
content: [],
|
|
30
|
+
isError: true,
|
|
31
|
+
diagnostics: [
|
|
32
|
+
{
|
|
33
|
+
severity: "error",
|
|
34
|
+
code: "unsupported_image",
|
|
35
|
+
message: `${path} is an image (${mimeType}); reading images is not supported`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const textContent = new TextDecoder().decode(bytes);
|
|
41
|
+
const allLines = textContent.split("\n");
|
|
42
|
+
const totalFileLines = allLines.length;
|
|
43
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
44
|
+
const startLineDisplay = startLine + 1;
|
|
45
|
+
if (startLine >= allLines.length) {
|
|
46
|
+
throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
|
|
47
|
+
}
|
|
48
|
+
let selectedContent;
|
|
49
|
+
let userLimitedLines;
|
|
50
|
+
if (limit !== undefined) {
|
|
51
|
+
const endLine = Math.min(startLine + limit, allLines.length);
|
|
52
|
+
selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
53
|
+
userLimitedLines = endLine - startLine;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
selectedContent = allLines.slice(startLine).join("\n");
|
|
57
|
+
}
|
|
58
|
+
const { content: headText, ...truncation } = truncateHead(selectedContent);
|
|
59
|
+
const diagnostics = [];
|
|
60
|
+
let outputText = headText;
|
|
61
|
+
let details;
|
|
62
|
+
if (truncation.firstLineExceedsLimit) {
|
|
63
|
+
// Show the start of the line, cut at the byte limit on a character boundary.
|
|
64
|
+
const lineBytes = new TextEncoder().encode(allLines[startLine]);
|
|
65
|
+
const end = characterEnd(lineBytes, DEFAULT_MAX_BYTES);
|
|
66
|
+
outputText = new TextDecoder().decode(lineBytes.subarray(0, end));
|
|
67
|
+
diagnostics.push({
|
|
68
|
+
severity: "warn",
|
|
69
|
+
code: "truncated",
|
|
70
|
+
message: `Line ${startLineDisplay} is ${formatSize(lineBytes.byteLength)}, exceeds the ${formatSize(DEFAULT_MAX_BYTES)} limit; showing its first ${formatSize(end)}. Use bash: sed -n '${startLineDisplay}p' ${path} | tail -c +${end + 1}`,
|
|
71
|
+
});
|
|
72
|
+
details = { truncation: { ...truncation, outputBytes: end, outputLines: 1 } };
|
|
73
|
+
}
|
|
74
|
+
else if (truncation.truncated) {
|
|
75
|
+
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
76
|
+
const nextOffset = endLineDisplay + 1;
|
|
77
|
+
const limitText = truncation.truncatedBy === "lines" ? "" : ` (${formatSize(DEFAULT_MAX_BYTES)} limit)`;
|
|
78
|
+
diagnostics.push({
|
|
79
|
+
severity: "info",
|
|
80
|
+
code: "truncated",
|
|
81
|
+
message: `Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}${limitText}. Use offset=${nextOffset} to continue.`,
|
|
82
|
+
});
|
|
83
|
+
details = { truncation };
|
|
84
|
+
}
|
|
85
|
+
else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
86
|
+
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
87
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
88
|
+
diagnostics.push({
|
|
89
|
+
severity: "info",
|
|
90
|
+
message: `${remaining} more lines in file. Use offset=${nextOffset} to continue.`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
content: outputText === "" ? [] : [{ type: "text", text: outputText }],
|
|
95
|
+
...(details === undefined ? {} : { details }),
|
|
96
|
+
diagnostics,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport { getOrThrow } 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 { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from \"../truncate.ts\";\nimport { requireEnv } from \"./env.ts\";\nimport { detectSupportedImageMimeType } 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\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 bytes = getOrThrow(await env.readBinaryFile(absolutePath, context));\n\t\t\tconst mimeType = detectSupportedImageMimeType(bytes);\n\t\t\tif (mimeType) {\n\t\t\t\t// Image content is not supported yet.\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [],\n\t\t\t\t\tisError: true,\n\t\t\t\t\tdiagnostics: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tseverity: \"error\",\n\t\t\t\t\t\t\tcode: \"unsupported_image\",\n\t\t\t\t\t\t\tmessage: `${path} is an image (${mimeType}); reading images is not supported`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst textContent = new TextDecoder().decode(bytes);\n\t\t\tconst allLines = textContent.split(\"\\n\");\n\t\t\tconst totalFileLines = allLines.length;\n\t\t\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\t\t\tconst startLineDisplay = startLine + 1;\n\t\t\tif (startLine >= allLines.length) {\n\t\t\t\tthrow new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);\n\t\t\t}\n\n\t\t\tlet selectedContent: string;\n\t\t\tlet userLimitedLines: number | undefined;\n\t\t\tif (limit !== undefined) {\n\t\t\t\tconst endLine = Math.min(startLine + limit, allLines.length);\n\t\t\t\tselectedContent = allLines.slice(startLine, endLine).join(\"\\n\");\n\t\t\t\tuserLimitedLines = endLine - startLine;\n\t\t\t} else {\n\t\t\t\tselectedContent = allLines.slice(startLine).join(\"\\n\");\n\t\t\t}\n\n\t\t\tconst { content: headText, ...truncation } = truncateHead(selectedContent);\n\t\t\tconst diagnostics: ToolDiagnostic[] = [];\n\t\t\tlet outputText = headText;\n\t\t\tlet details: ReadToolDetails | undefined;\n\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t// Show the start of the line, cut at the byte limit on a character boundary.\n\t\t\t\tconst lineBytes = new TextEncoder().encode(allLines[startLine]);\n\t\t\t\tconst end = characterEnd(lineBytes, DEFAULT_MAX_BYTES);\n\t\t\t\toutputText = new TextDecoder().decode(lineBytes.subarray(0, end));\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\tseverity: \"warn\",\n\t\t\t\t\tcode: \"truncated\",\n\t\t\t\t\tmessage: `Line ${startLineDisplay} is ${formatSize(lineBytes.byteLength)}, 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\t\t});\n\t\t\t\tdetails = { truncation: { ...truncation, outputBytes: end, outputLines: 1 } };\n\t\t\t} else if (truncation.truncated) {\n\t\t\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\t\t\tconst nextOffset = endLineDisplay + 1;\n\t\t\t\tconst limitText = truncation.truncatedBy === \"lines\" ? \"\" : ` (${formatSize(DEFAULT_MAX_BYTES)} limit)`;\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\tseverity: \"info\",\n\t\t\t\t\tcode: \"truncated\",\n\t\t\t\t\tmessage: `Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}${limitText}. Use offset=${nextOffset} to continue.`,\n\t\t\t\t});\n\t\t\t\tdetails = { truncation };\n\t\t\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {\n\t\t\t\tconst remaining = allLines.length - (startLine + userLimitedLines);\n\t\t\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\tseverity: \"info\",\n\t\t\t\t\tmessage: `${remaining} more lines in file. Use offset=${nextOffset} to continue.`,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: outputText === \"\" ? [] : [{ type: \"text\", text: outputText }],\n\t\t\t\t...(details === undefined ? {} : { details }),\n\t\t\t\tdiagnostics,\n\t\t\t};\n\t\t},\n\t});\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Static, Type } from "typebox";
|
|
2
|
+
import type { ToolRegistration } from "../harness/types.ts";
|
|
3
|
+
declare const writeSchema: Type.TObject<{
|
|
4
|
+
path: Type.TString;
|
|
5
|
+
content: Type.TString;
|
|
6
|
+
}>;
|
|
7
|
+
export type WriteToolInput = Static<typeof writeSchema>;
|
|
8
|
+
export declare function createWriteTool(): ToolRegistration<typeof writeSchema>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAK5D,QAAA,MAAM,WAAW;;;EAGf,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAExD,wBAAgB,eAAe,IAAI,gBAAgB,CAAC,OAAO,WAAW,CAAC,CAuBtE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { getOrThrow } from "../env/index.js";
|
|
3
|
+
import { defineTool } from "../harness/define.js";
|
|
4
|
+
import { requireEnv } from "./env.js";
|
|
5
|
+
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
6
|
+
import { resolveToolPath } from "./path-utils.js";
|
|
7
|
+
const writeSchema = Type.Object({
|
|
8
|
+
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
9
|
+
content: Type.String({ description: "Content to write to the file" }),
|
|
10
|
+
});
|
|
11
|
+
export function createWriteTool() {
|
|
12
|
+
return defineTool({
|
|
13
|
+
name: "write",
|
|
14
|
+
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
|
15
|
+
parameters: writeSchema,
|
|
16
|
+
async execute(args, api, context) {
|
|
17
|
+
const { path, content } = args;
|
|
18
|
+
const env = requireEnv(api);
|
|
19
|
+
const absolutePath = await resolveToolPath(env, path, context);
|
|
20
|
+
return withFileMutationQueue(env, absolutePath, async () => {
|
|
21
|
+
if (context.abortSignal?.aborted)
|
|
22
|
+
throw new Error("Operation aborted");
|
|
23
|
+
getOrThrow(await env.writeFile(absolutePath, content, context));
|
|
24
|
+
if (context.abortSignal?.aborted)
|
|
25
|
+
throw new Error("Operation aborted");
|
|
26
|
+
return { content: [{ type: "text", text: `Successfully wrote to ${path}` }] };
|
|
27
|
+
}, context);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;IACtF,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,8BAA8B,EAAE,CAAC;CACrE,CAAC,CAAC;AAIH,MAAM,UAAU,eAAe;IAC9B,OAAO,UAAU,CAAC;QACjB,IAAI,EAAE,OAAO;QACb,WAAW,EACV,iIAAiI;QAClI,UAAU,EAAE,WAAW;QACvB,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO;YAC/B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;YAC/B,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YAC/D,OAAO,qBAAqB,CAC3B,GAAG,EACH,YAAY,EACZ,KAAK,IAAI,EAAE;gBACV,IAAI,OAAO,CAAC,WAAW,EAAE,OAAO;oBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;gBACvE,UAAU,CAAC,MAAM,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;gBAChE,IAAI,OAAO,CAAC,WAAW,EAAE,OAAO;oBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;gBACvE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;YAC/E,CAAC,EACD,OAAO,CACP,CAAC;QACH,CAAC;KACD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport { getOrThrow } from \"../env/index.ts\";\nimport { defineTool } from \"../harness/define.ts\";\nimport type { ToolRegistration } from \"../harness/types.ts\";\nimport { requireEnv } from \"./env.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToolPath } from \"./path-utils.ts\";\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport type WriteToolInput = Static<typeof writeSchema>;\n\nexport function createWriteTool(): ToolRegistration<typeof writeSchema> {\n\treturn defineTool({\n\t\tname: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tparameters: writeSchema,\n\t\tasync execute(args, api, context) {\n\t\t\tconst { path, content } = args;\n\t\t\tconst env = requireEnv(api);\n\t\t\tconst absolutePath = await resolveToolPath(env, path, context);\n\t\t\treturn withFileMutationQueue(\n\t\t\t\tenv,\n\t\t\t\tabsolutePath,\n\t\t\t\tasync () => {\n\t\t\t\t\tif (context.abortSignal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t\tgetOrThrow(await env.writeFile(absolutePath, content, context));\n\t\t\t\t\tif (context.abortSignal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t\treturn { content: [{ type: \"text\", text: `Successfully wrote to ${path}` }] };\n\t\t\t\t},\n\t\t\t\tcontext,\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
* - Line limit (default: 2000 lines)
|
|
6
6
|
* - Byte limit (default: 50KB)
|
|
7
7
|
*
|
|
8
|
-
* Never returns partial lines
|
|
8
|
+
* Never returns partial lines. Tool output streams are bounded by `src/harness/output.ts` instead.
|
|
9
9
|
*/
|
|
10
10
|
export declare const DEFAULT_MAX_LINES = 2000;
|
|
11
11
|
export declare const DEFAULT_MAX_BYTES: number;
|
|
12
|
-
export declare const GREP_MAX_LINE_LENGTH = 500;
|
|
13
12
|
export interface TruncationResult {
|
|
14
13
|
/** The truncated content */
|
|
15
14
|
content: string;
|
|
@@ -53,19 +52,4 @@ export declare function formatSize(bytes: number): string;
|
|
|
53
52
|
* returns empty content with firstLineExceedsLimit=true.
|
|
54
53
|
*/
|
|
55
54
|
export declare function truncateHead(content: string, options?: TruncationOptions): TruncationResult;
|
|
56
|
-
/**
|
|
57
|
-
* Truncate content from the tail (keep last N lines/bytes).
|
|
58
|
-
* Suitable for bash output where you want to see the end (errors, final results).
|
|
59
|
-
*
|
|
60
|
-
* May return partial first line if the last line of original content exceeds byte limit.
|
|
61
|
-
*/
|
|
62
|
-
export declare function truncateTail(content: string, options?: TruncationOptions): TruncationResult;
|
|
63
|
-
/**
|
|
64
|
-
* Truncate a single line to max characters, adding [truncated] suffix.
|
|
65
|
-
* Used for grep match lines.
|
|
66
|
-
*/
|
|
67
|
-
export declare function truncateLine(line: string, maxChars?: number): {
|
|
68
|
-
text: string;
|
|
69
|
-
wasTruncated: boolean;
|
|
70
|
-
};
|
|
71
55
|
//# sourceMappingURL=truncate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAChC,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC;IACtC,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,eAAe,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,qBAAqB,EAAE,OAAO,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AASD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0BtD;AASD;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAgF/F"}
|
package/dist/truncate.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared truncation utilities for tool outputs.
|
|
3
|
+
*
|
|
4
|
+
* Truncation is based on two independent limits - whichever is hit first wins:
|
|
5
|
+
* - Line limit (default: 2000 lines)
|
|
6
|
+
* - Byte limit (default: 50KB)
|
|
7
|
+
*
|
|
8
|
+
* Never returns partial lines. Tool output streams are bounded by `src/harness/output.ts` instead.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_MAX_LINES = 2000;
|
|
11
|
+
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
|
+
const runtimeBuffer = globalThis.Buffer;
|
|
13
|
+
const nonAsciiPattern = /[^\x00-\x7f]/;
|
|
14
|
+
export function utf8ByteLength(content) {
|
|
15
|
+
if (runtimeBuffer)
|
|
16
|
+
return runtimeBuffer.byteLength(content, "utf8");
|
|
17
|
+
const firstNonAscii = content.search(nonAsciiPattern);
|
|
18
|
+
if (firstNonAscii === -1)
|
|
19
|
+
return content.length;
|
|
20
|
+
let bytes = firstNonAscii;
|
|
21
|
+
for (let i = firstNonAscii; i < content.length; i++) {
|
|
22
|
+
const code = content.charCodeAt(i);
|
|
23
|
+
if (code <= 0x7f) {
|
|
24
|
+
bytes += 1;
|
|
25
|
+
}
|
|
26
|
+
else if (code <= 0x7ff) {
|
|
27
|
+
bytes += 2;
|
|
28
|
+
}
|
|
29
|
+
else if (code >= 0xd800 && code <= 0xdbff && i + 1 < content.length) {
|
|
30
|
+
const next = content.charCodeAt(i + 1);
|
|
31
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
32
|
+
bytes += 4;
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
bytes += 3;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
bytes += 3;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return bytes;
|
|
44
|
+
}
|
|
45
|
+
function splitLinesForCounting(content) {
|
|
46
|
+
if (content.length === 0)
|
|
47
|
+
return [];
|
|
48
|
+
const lines = content.split("\n");
|
|
49
|
+
if (content.endsWith("\n"))
|
|
50
|
+
lines.pop();
|
|
51
|
+
return lines;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Format bytes as human-readable size.
|
|
55
|
+
*/
|
|
56
|
+
export function formatSize(bytes) {
|
|
57
|
+
if (bytes < 1024) {
|
|
58
|
+
return `${bytes}B`;
|
|
59
|
+
}
|
|
60
|
+
else if (bytes < 1024 * 1024) {
|
|
61
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Truncate content from the head (keep first N lines/bytes).
|
|
69
|
+
* Suitable for file reads where you want to see the beginning.
|
|
70
|
+
*
|
|
71
|
+
* Never returns partial lines. If first line exceeds byte limit,
|
|
72
|
+
* returns empty content with firstLineExceedsLimit=true.
|
|
73
|
+
*/
|
|
74
|
+
export function truncateHead(content, options = {}) {
|
|
75
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
76
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
77
|
+
const totalBytes = utf8ByteLength(content);
|
|
78
|
+
const lines = splitLinesForCounting(content);
|
|
79
|
+
const totalLines = lines.length;
|
|
80
|
+
// Check if no truncation needed
|
|
81
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
82
|
+
return {
|
|
83
|
+
content,
|
|
84
|
+
truncated: false,
|
|
85
|
+
truncatedBy: null,
|
|
86
|
+
totalLines,
|
|
87
|
+
totalBytes,
|
|
88
|
+
outputLines: totalLines,
|
|
89
|
+
outputBytes: totalBytes,
|
|
90
|
+
lastLinePartial: false,
|
|
91
|
+
firstLineExceedsLimit: false,
|
|
92
|
+
maxLines,
|
|
93
|
+
maxBytes,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Check if first line alone exceeds byte limit
|
|
97
|
+
const firstLineBytes = utf8ByteLength(lines[0]);
|
|
98
|
+
if (firstLineBytes > maxBytes) {
|
|
99
|
+
return {
|
|
100
|
+
content: "",
|
|
101
|
+
truncated: true,
|
|
102
|
+
truncatedBy: "bytes",
|
|
103
|
+
totalLines,
|
|
104
|
+
totalBytes,
|
|
105
|
+
outputLines: 0,
|
|
106
|
+
outputBytes: 0,
|
|
107
|
+
lastLinePartial: false,
|
|
108
|
+
firstLineExceedsLimit: true,
|
|
109
|
+
maxLines,
|
|
110
|
+
maxBytes,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Collect complete lines that fit
|
|
114
|
+
const outputLinesArr = [];
|
|
115
|
+
let outputBytesCount = 0;
|
|
116
|
+
let truncatedBy = "lines";
|
|
117
|
+
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
118
|
+
const line = lines[i];
|
|
119
|
+
const lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline
|
|
120
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
121
|
+
truncatedBy = "bytes";
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
outputLinesArr.push(line);
|
|
125
|
+
outputBytesCount += lineBytes;
|
|
126
|
+
}
|
|
127
|
+
// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.
|
|
128
|
+
if (truncatedBy !== "bytes")
|
|
129
|
+
truncatedBy = outputLinesArr.length < totalLines ? "lines" : "bytes";
|
|
130
|
+
const outputContent = outputLinesArr.join("\n");
|
|
131
|
+
const finalOutputBytes = utf8ByteLength(outputContent);
|
|
132
|
+
return {
|
|
133
|
+
content: outputContent,
|
|
134
|
+
truncated: true,
|
|
135
|
+
truncatedBy,
|
|
136
|
+
totalLines,
|
|
137
|
+
totalBytes,
|
|
138
|
+
outputLines: outputLinesArr.length,
|
|
139
|
+
outputBytes: finalOutputBytes,
|
|
140
|
+
lastLinePartial: false,
|
|
141
|
+
firstLineExceedsLimit: false,
|
|
142
|
+
maxLines,
|
|
143
|
+
maxBytes,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=truncate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared truncation utilities for tool outputs.\n *\n * Truncation is based on two independent limits - whichever is hit first wins:\n * - Line limit (default: 2000 lines)\n * - Byte limit (default: 50KB)\n *\n * Never returns partial lines. Tool output streams are bounded by `src/harness/output.ts` instead.\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\n\nexport interface TruncationResult {\n\t/** The truncated content */\n\tcontent: string;\n\t/** Whether truncation occurred */\n\ttruncated: boolean;\n\t/** Which limit was hit: \"lines\", \"bytes\", or null if not truncated */\n\ttruncatedBy: \"lines\" | \"bytes\" | null;\n\t/** Total number of lines in the original content */\n\ttotalLines: number;\n\t/** Total number of bytes in the original content */\n\ttotalBytes: number;\n\t/** Number of complete lines in the truncated output */\n\toutputLines: number;\n\t/** Number of bytes in the truncated output */\n\toutputBytes: number;\n\t/** Whether the last line was partially truncated (only for tail truncation edge case) */\n\tlastLinePartial: boolean;\n\t/** Whether the first line exceeded the byte limit (for head truncation) */\n\tfirstLineExceedsLimit: boolean;\n\t/** The max lines limit that was applied */\n\tmaxLines: number;\n\t/** The max bytes limit that was applied */\n\tmaxBytes: number;\n}\n\nexport interface TruncationOptions {\n\t/** Maximum number of lines (default: 2000) */\n\tmaxLines?: number;\n\t/** Maximum number of bytes (default: 50KB) */\n\tmaxBytes?: number;\n}\n\ninterface RuntimeBuffer {\n\tbyteLength(content: string, encoding: \"utf8\"): number;\n}\n\nconst runtimeBuffer = (globalThis as { Buffer?: RuntimeBuffer }).Buffer;\nconst nonAsciiPattern = /[^\\x00-\\x7f]/;\n\nexport function utf8ByteLength(content: string): number {\n\tif (runtimeBuffer) return runtimeBuffer.byteLength(content, \"utf8\");\n\n\tconst firstNonAscii = content.search(nonAsciiPattern);\n\tif (firstNonAscii === -1) return content.length;\n\n\tlet bytes = firstNonAscii;\n\tfor (let i = firstNonAscii; i < content.length; i++) {\n\t\tconst code = content.charCodeAt(i);\n\t\tif (code <= 0x7f) {\n\t\t\tbytes += 1;\n\t\t} else if (code <= 0x7ff) {\n\t\t\tbytes += 2;\n\t\t} else if (code >= 0xd800 && code <= 0xdbff && i + 1 < content.length) {\n\t\t\tconst next = content.charCodeAt(i + 1);\n\t\t\tif (next >= 0xdc00 && next <= 0xdfff) {\n\t\t\t\tbytes += 4;\n\t\t\t\ti++;\n\t\t\t} else {\n\t\t\t\tbytes += 3;\n\t\t\t}\n\t\t} else {\n\t\t\tbytes += 3;\n\t\t}\n\t}\n\treturn bytes;\n}\n\nfunction splitLinesForCounting(content: string): string[] {\n\tif (content.length === 0) return [];\n\tconst lines = content.split(\"\\n\");\n\tif (content.endsWith(\"\\n\")) lines.pop();\n\treturn lines;\n}\n\n/**\n * Format bytes as human-readable size.\n */\nexport function formatSize(bytes: number): string {\n\tif (bytes < 1024) {\n\t\treturn `${bytes}B`;\n\t} else if (bytes < 1024 * 1024) {\n\t\treturn `${(bytes / 1024).toFixed(1)}KB`;\n\t} else {\n\t\treturn `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n\t}\n}\n\n/**\n * Truncate content from the head (keep first N lines/bytes).\n * Suitable for file reads where you want to see the beginning.\n *\n * Never returns partial lines. If first line exceeds byte limit,\n * returns empty content with firstLineExceedsLimit=true.\n */\nexport function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = utf8ByteLength(content);\n\tconst lines = splitLinesForCounting(content);\n\tconst totalLines = lines.length;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\n\t\t\ttruncated: false,\n\t\t\ttruncatedBy: null,\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: totalLines,\n\t\t\toutputBytes: totalBytes,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: false,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Check if first line alone exceeds byte limit\n\tconst firstLineBytes = utf8ByteLength(lines[0]);\n\tif (firstLineBytes > maxBytes) {\n\t\treturn {\n\t\t\tcontent: \"\",\n\t\t\ttruncated: true,\n\t\t\ttruncatedBy: \"bytes\",\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: 0,\n\t\t\toutputBytes: 0,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: true,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Collect complete lines that fit\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\n\tfor (let i = 0; i < lines.length && i < maxLines; i++) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.push(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.\n\tif (truncatedBy !== \"bytes\") truncatedBy = outputLinesArr.length < totalLines ? \"lines\" : \"bytes\";\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = utf8ByteLength(outputContent);\n\n\treturn {\n\t\tcontent: outputContent,\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\ttotalLines,\n\t\ttotalBytes,\n\t\toutputLines: outputLinesArr.length,\n\t\toutputBytes: finalOutputBytes,\n\t\tlastLinePartial: false,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines,\n\t\tmaxBytes,\n\t};\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AttachedReplicatedState, Context, Draft, JsonValue } from "@earendil-works/chord";
|
|
2
2
|
import type { Op } from "@earendil-works/chord/delta";
|
|
3
3
|
import type { Message, Models } from "@earendil-works/pi-ai";
|
|
4
|
-
import type {
|
|
4
|
+
import type { ExecutionEnv } from "./env/index.ts";
|
|
5
|
+
import type { Agent, ContextView, ConversationHandle, RegistrySnapshot, Settings, SettledTask } from "./harness/types.ts";
|
|
5
6
|
/** JSON object used as the root of every durable document. */
|
|
6
7
|
export type JsonObject = {
|
|
7
8
|
[key: string]: JsonValue;
|
|
@@ -104,27 +105,49 @@ export type RunningTask<I, S, R> = TaskRecord<I, S, R> & {
|
|
|
104
105
|
readonly status: "running";
|
|
105
106
|
}>;
|
|
106
107
|
};
|
|
107
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Next state a task commits for itself: a replacement checkpoint, a wait, or its outcome. A returned `terminal` state is
|
|
110
|
+
* stored as `completing` while ordinary owned work below the task is live (spec §5.5).
|
|
111
|
+
*/
|
|
108
112
|
export type NextTaskState<S, R> = Extract<TaskState<S, R>, {
|
|
109
|
-
readonly status: "running" | "terminal";
|
|
113
|
+
readonly status: "running" | "waiting" | "terminal";
|
|
110
114
|
}>;
|
|
111
115
|
/**
|
|
112
116
|
* Runs one checkpoint phase. It must commit a changed checkpoint or a terminal outcome through `runtime.commit()`;
|
|
113
117
|
* returning without durable progress faults the task.
|
|
114
118
|
*/
|
|
115
119
|
export type PhaseHandler<I, P, S, R, H extends object> = (task: RunningTask<I, P, R>, runtime: TaskRuntime<I, S, R, H>, context: Context) => Promise<void>;
|
|
120
|
+
/** Dispatches one hook of a task to every matching registered handler, in registry order of the phase snapshot. */
|
|
121
|
+
export interface HookRunner<H extends object> {
|
|
122
|
+
/**
|
|
123
|
+
* Call `invoke` with each matching handler named `name`. An ordinary throw from `invoke` is reported and the next
|
|
124
|
+
* handler runs; once the invocation is signalled, the error propagates. Composition happens inside `invoke`.
|
|
125
|
+
*/
|
|
126
|
+
each<K extends keyof H>(name: K, invoke: (handler: NonNullable<H[K]>) => void | Promise<void>): Promise<void>;
|
|
127
|
+
}
|
|
116
128
|
/**
|
|
117
129
|
* Operations of one task invocation. Every operation rejects after the invocation ends; watches acquired through it
|
|
118
|
-
* stop at invocation end.
|
|
130
|
+
* stop at invocation end.
|
|
119
131
|
*/
|
|
120
|
-
export interface TaskRuntime<I, S, R,
|
|
132
|
+
export interface TaskRuntime<I, S, R, H extends object> extends DocumentObserver, DocumentReader {
|
|
121
133
|
readonly taskId: TaskId<R>;
|
|
122
134
|
readonly conversationId: ConversationId;
|
|
123
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Aborted when the run is signalled by `abortTask()`, the Harness closes, or the invocation ends. Work still using it
|
|
137
|
+
* after the invocation ended, such as a detached wait, is cancelled; it could not write anything anyway.
|
|
138
|
+
*/
|
|
124
139
|
readonly signal: AbortSignal;
|
|
125
140
|
/** Registry snapshot of the current phase; refreshed at every phase boundary. */
|
|
126
141
|
readonly registry: RegistrySnapshot;
|
|
142
|
+
/** The task's conversation's agent, resolved at most once per phase, at first use, and fixed for the phase. */
|
|
143
|
+
agent(context: Context): Promise<Agent>;
|
|
144
|
+
/** `HarnessOptions.settings`, resolved at each access. */
|
|
145
|
+
readonly settings: Settings;
|
|
127
146
|
readonly models: Models;
|
|
147
|
+
/** Calls `HarnessOptions.env` for the task's conversation; rejects with its error. */
|
|
148
|
+
env(context: Context): Promise<ExecutionEnv | undefined>;
|
|
149
|
+
/** Handlers of this task's name from the extensions its conversation selects, in extension order. */
|
|
150
|
+
readonly hooks: HookRunner<H>;
|
|
128
151
|
/**
|
|
129
152
|
* Commit on the Session line after rereading the task. Rejects when the task is terminal, the invocation ended, the
|
|
130
153
|
* Harness is closing, or, in a run invocation, the task carries an abort mark. A returned state replaces the task's
|
|
@@ -136,6 +159,21 @@ export interface TaskRuntime<I, S, R, _H extends object> extends DocumentObserve
|
|
|
136
159
|
memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;
|
|
137
160
|
/** Store `candidate` unless a memo already exists; return the durable winner. */
|
|
138
161
|
memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;
|
|
162
|
+
/** Committed task record. */
|
|
163
|
+
getTask<T>(id: TaskId<T>, context: Context): Promise<TaskRecord<JsonValue, JsonValue, T> | undefined>;
|
|
164
|
+
/** Resolve with the task's terminal receipt; rejects when the invocation ends. */
|
|
165
|
+
waitForTask<T>(id: TaskId<T>, context: Context): Promise<SettledTask<T>>;
|
|
166
|
+
/** Outcomes of terminal tasks, in order; rejects when one is missing or not terminal. Used after a wait. */
|
|
167
|
+
outcomes<T>(ids: readonly TaskId<T>[], context: Context): Promise<TaskOutcome<T>[]>;
|
|
168
|
+
/**
|
|
169
|
+
* Invocation-bound handle of an existing conversation, for example one this task owns; `undefined` when absent. Its
|
|
170
|
+
* operations and the submissions it returns reject after the invocation ends; admitted work stays durable.
|
|
171
|
+
*/
|
|
172
|
+
conversation(id: ConversationId, context: Context): Promise<ConversationHandle | undefined>;
|
|
173
|
+
/** Committed entry visible from the task's conversation. */
|
|
174
|
+
entry(id: EntryId, context: Context): Promise<EntryRecord | undefined>;
|
|
175
|
+
/** Undefined when the entry is absent, not visible, or has another kind. */
|
|
176
|
+
entry<D extends JsonValue>(token: Entry<D>, id: EntryId, context: Context): Promise<TypedEntry<D> | undefined>;
|
|
139
177
|
/** Committed raw active transcript and model context, optionally cut off at the visible entry `at`. */
|
|
140
178
|
context(conversationId: ConversationId, context: Context, at?: EntryId): Promise<ContextView>;
|
|
141
179
|
/** The Harness clock. */
|
|
@@ -176,13 +214,25 @@ export interface Task<I, S extends {
|
|
|
176
214
|
}, R, H extends object> {
|
|
177
215
|
readonly definition: TaskDefinition<I, S, R, H>;
|
|
178
216
|
}
|
|
217
|
+
/** Who owns a task: its conversation (a top-level task) or another task of the same conversation (a child task). */
|
|
218
|
+
export type TaskOwnership = {
|
|
219
|
+
readonly kind: "conversation";
|
|
220
|
+
} | {
|
|
221
|
+
readonly kind: "task";
|
|
222
|
+
readonly taskId: TaskId;
|
|
223
|
+
};
|
|
224
|
+
/** How a waiting task treats the tasks it waits on (spec §5.5). */
|
|
225
|
+
export type JoinPolicy = "failFast" | "allSettled";
|
|
179
226
|
/** Creation options for a durable task. */
|
|
180
227
|
export type TaskOptions = {
|
|
181
|
-
/**
|
|
228
|
+
/** Required: a task always names its owner (spec §5.5). */
|
|
229
|
+
readonly ownership: TaskOwnership;
|
|
230
|
+
/**
|
|
231
|
+
* Default: the owner task's conversation, or the transaction's bound conversation; required for conversation-owned
|
|
232
|
+
* tasks created by Session commits that are not bound to a conversation.
|
|
233
|
+
*/
|
|
182
234
|
readonly conversationId?: ConversationId;
|
|
183
|
-
/**
|
|
184
|
-
readonly after?: readonly TaskId[];
|
|
185
|
-
/** Excluded from ordinary idle waits and conversation aborts. */
|
|
235
|
+
/** Conversation-owned tasks only: excluded from ordinary idle waits, conversation aborts, and cascades. */
|
|
186
236
|
readonly background?: boolean;
|
|
187
237
|
};
|
|
188
238
|
/** Ownership selected explicitly whenever a conversation is created. */
|
|
@@ -373,7 +423,7 @@ export type TaskOutcome<R> = {
|
|
|
373
423
|
};
|
|
374
424
|
/** Complete durable execution state of a task. */
|
|
375
425
|
export type TaskState<S, R> = {
|
|
376
|
-
/** Eligible for scheduling
|
|
426
|
+
/** Eligible for scheduling. */
|
|
377
427
|
readonly status: "pending";
|
|
378
428
|
/** Complete durable state from which execution resumes. */
|
|
379
429
|
readonly checkpoint: S;
|
|
@@ -384,6 +434,18 @@ export type TaskState<S, R> = {
|
|
|
384
434
|
/** Complete durable state from which execution resumes. */
|
|
385
435
|
readonly checkpoint: S;
|
|
386
436
|
readonly outcome?: never;
|
|
437
|
+
} | {
|
|
438
|
+
/** Parked without an invocation until every task in `on` is terminal; then resumes at `checkpoint`. */
|
|
439
|
+
readonly status: "waiting";
|
|
440
|
+
readonly checkpoint: S;
|
|
441
|
+
readonly on: readonly TaskId[];
|
|
442
|
+
readonly policy: JoinPolicy;
|
|
443
|
+
readonly outcome?: never;
|
|
444
|
+
} | {
|
|
445
|
+
/** Outcome decided; becomes terminal once no ordinary owned work below is live. Runs no more code. */
|
|
446
|
+
readonly status: "completing";
|
|
447
|
+
readonly checkpoint?: never;
|
|
448
|
+
readonly outcome: TaskOutcome<R>;
|
|
387
449
|
} | {
|
|
388
450
|
/** Permanently settled durable result receipt. */
|
|
389
451
|
readonly status: "terminal";
|
|
@@ -400,9 +462,9 @@ type TaskRecordBase<I, R> = {
|
|
|
400
462
|
readonly version: number;
|
|
401
463
|
/** Original task input retained while the task is live or terminal. */
|
|
402
464
|
readonly input: I;
|
|
403
|
-
/**
|
|
404
|
-
readonly
|
|
405
|
-
/** Whether this task is excluded from ordinary idle waits
|
|
465
|
+
/** Owning task of a child task; absent for a task its conversation owns. Immutable. */
|
|
466
|
+
readonly owner?: TaskId;
|
|
467
|
+
/** Whether this conversation-owned task is excluded from ordinary idle waits, conversation aborts, and cascades. */
|
|
406
468
|
readonly background: boolean;
|
|
407
469
|
/** Durable abort mark checked before run-mode progress is committed. */
|
|
408
470
|
readonly abortRequested: boolean;
|
|
@@ -410,13 +472,13 @@ type TaskRecordBase<I, R> = {
|
|
|
410
472
|
/** Complete replacement record for one durable task state machine. */
|
|
411
473
|
export type TaskRecord<I, S, R> = TaskRecordBase<I, R> & ({
|
|
412
474
|
readonly state: Extract<TaskState<S, R>, {
|
|
413
|
-
readonly status: "pending" | "running";
|
|
475
|
+
readonly status: "pending" | "running" | "waiting";
|
|
414
476
|
}>;
|
|
415
|
-
/** Small first-writer-wins values retained while the task
|
|
477
|
+
/** Small first-writer-wins values retained while the task can run. */
|
|
416
478
|
readonly memos?: Readonly<Record<string, JsonValue>>;
|
|
417
479
|
} | {
|
|
418
480
|
readonly state: Extract<TaskState<S, R>, {
|
|
419
|
-
readonly status: "terminal";
|
|
481
|
+
readonly status: "completing" | "terminal";
|
|
420
482
|
}>;
|
|
421
483
|
readonly memos?: never;
|
|
422
484
|
});
|
|
@@ -487,7 +549,7 @@ export type EntryQuery = {
|
|
|
487
549
|
export type TaskQuery = {
|
|
488
550
|
readonly conversationId?: ConversationId;
|
|
489
551
|
readonly kind?: string;
|
|
490
|
-
readonly status?:
|
|
552
|
+
readonly status?: TaskState<JsonValue, JsonValue>["status"];
|
|
491
553
|
readonly abortRequested?: boolean;
|
|
492
554
|
readonly background?: boolean;
|
|
493
555
|
};
|
|
@@ -606,7 +668,13 @@ export interface Tx {
|
|
|
606
668
|
task(id: TaskId): Promise<TaskRecord<JsonValue, JsonValue, JsonValue> | undefined>;
|
|
607
669
|
scanConversations(query: ConversationQuery, limit: number, cursor?: Cursor): Promise<Page<ConversationRecord, Cursor>>;
|
|
608
670
|
scanEntries(query: EntryQuery, limit: number, cursor?: Cursor): Promise<Page<EntryRecord, Cursor>>;
|
|
671
|
+
/** Newest visible entry of the conversation that carries a `head`. */
|
|
672
|
+
latestHeadMarker(conversationId: ConversationId): Promise<(EntryRecord & {
|
|
673
|
+
readonly head: EntryId;
|
|
674
|
+
}) | undefined>;
|
|
609
675
|
scanTasks(query: TaskQuery, limit: number, cursor?: Cursor): Promise<Page<TaskRecord<JsonValue, JsonValue, JsonValue>, Cursor>>;
|
|
676
|
+
/** Committed submission with a conversation-scoped request ID. */
|
|
677
|
+
submissionByRequest(conversationId: ConversationId, requestId: string): Promise<SubmissionRecord | undefined>;
|
|
610
678
|
/** Create a conversation with explicitly selected ownership. */
|
|
611
679
|
createConversation(options: {
|
|
612
680
|
readonly ownership: ConversationOwnership;
|
|
@@ -621,13 +689,23 @@ export interface Tx {
|
|
|
621
689
|
appendEntry<D extends JsonValue>(token: Entry<D>, conversationId: ConversationId, value: TypedEntryDraft<NoInfer<D>>): Promise<TypedEntry<D>>;
|
|
622
690
|
createTask<I, S extends {
|
|
623
691
|
phase: string;
|
|
624
|
-
}, R, H extends object>(task: Task<I, S, R, H>, input: I, options
|
|
692
|
+
}, R, H extends object>(task: Task<I, S, R, H>, input: I, options: TaskOptions): Promise<TaskId<R>>;
|
|
693
|
+
/**
|
|
694
|
+
* Create a raw submission record with a fresh ID. No admission rules apply: no busy check, no inbox queueing, no
|
|
695
|
+
* placement. Use `Conversation.submit()` or a conversation handle unless the caller implements admission itself.
|
|
696
|
+
*/
|
|
697
|
+
createSubmission(create: SubmissionCreate): Promise<SubmissionRecord>;
|
|
625
698
|
/**
|
|
626
699
|
* Settle a queued or placed submission; only a placed input can be answered, and a settled submission stays
|
|
627
700
|
* unchanged. Resolved against this transaction's latest record of the submission, so it works after table writes.
|
|
628
701
|
* Run tasks settle the inputs they answer.
|
|
629
702
|
*/
|
|
630
703
|
settleSubmission(id: SubmissionId, settlement: SubmissionSettlement): void;
|
|
704
|
+
/**
|
|
705
|
+
* Place a queued submission at `entry`: an input becomes `placed`, a write `done`. Resolved like
|
|
706
|
+
* `settleSubmission()`. Inbox boundaries place the submissions they select.
|
|
707
|
+
*/
|
|
708
|
+
placeSubmission(id: SubmissionId, entry: EntryId): void;
|
|
631
709
|
doc<T extends JsonObject>(token: SessionDocToken<T>): Promise<Draft<T>>;
|
|
632
710
|
doc<T extends JsonObject>(token: ConversationDocToken<T>, conversationId: ConversationId): Promise<Draft<T>>;
|
|
633
711
|
doc<T extends JsonObject>(token: TaskDocToken<T>, taskId: TaskId): Promise<Draft<T>>;
|