@shanepadgett/tau-agent 0.33.1 → 0.34.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/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -16,11 +16,12 @@ import {
|
|
|
16
16
|
import { Text } from "@earendil-works/pi-tui";
|
|
17
17
|
import { Type } from "typebox";
|
|
18
18
|
|
|
19
|
-
type Language = "python3" | "
|
|
19
|
+
type Language = "python3" | "node" | "deno";
|
|
20
20
|
|
|
21
21
|
interface Runtimes {
|
|
22
22
|
python3: string | undefined;
|
|
23
|
-
|
|
23
|
+
node: string | undefined;
|
|
24
|
+
deno: string | undefined;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
interface StoredScript {
|
|
@@ -40,15 +41,34 @@ function detectRuntimes(): Runtimes {
|
|
|
40
41
|
// runtime not installed
|
|
41
42
|
}
|
|
42
43
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
43
|
-
let
|
|
44
|
+
let node: string | undefined;
|
|
44
45
|
if (major > 22 || (major === 22 && minor >= 6)) {
|
|
45
|
-
|
|
46
|
+
node = process.execPath;
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
+
let deno: string | undefined;
|
|
49
|
+
try {
|
|
50
|
+
execFileSync("deno", ["--version"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
51
|
+
deno = "deno";
|
|
52
|
+
} catch {
|
|
53
|
+
// runtime not installed
|
|
54
|
+
}
|
|
55
|
+
return { python3, node, deno };
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
function capitalize(lang: Language): string {
|
|
51
|
-
|
|
59
|
+
if (lang === "python3") return "Python 3";
|
|
60
|
+
if (lang === "node") return "Node";
|
|
61
|
+
return "Deno";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatLangList(langs: readonly Language[]): string {
|
|
65
|
+
const names = langs.map(capitalize);
|
|
66
|
+
if (names.length <= 2) return names.join(" or ");
|
|
67
|
+
return `${names.slice(0, -1).join(", ")}, or ${names[names.length - 1]}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function languageLabel(lang: string): string {
|
|
71
|
+
return lang === "node" ? "node (Node.js)" : lang;
|
|
52
72
|
}
|
|
53
73
|
|
|
54
74
|
function newScriptId(): string {
|
|
@@ -65,9 +85,7 @@ function applyEdits(source: string, edits: ReadonlyArray<{ oldText: string; newT
|
|
|
65
85
|
for (const edit of edits) {
|
|
66
86
|
const idx = next.indexOf(edit.oldText);
|
|
67
87
|
if (idx === -1) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
"An edits oldText was not found in the script. Copy the exact text from the script you wrote.",
|
|
70
|
-
);
|
|
88
|
+
throw new Error("edits oldText not found. Copy exact text from the script you wrote.");
|
|
71
89
|
}
|
|
72
90
|
next = next.slice(0, idx) + edit.newText + next.slice(idx + edit.oldText.length);
|
|
73
91
|
}
|
|
@@ -90,14 +108,64 @@ function renderEditsPreview(edits: ReadonlyArray<{ oldText: string; newText: str
|
|
|
90
108
|
.join("\n");
|
|
91
109
|
}
|
|
92
110
|
|
|
111
|
+
function resolveScriptSource(
|
|
112
|
+
scripts: Map<string, StoredScript>,
|
|
113
|
+
language: Language,
|
|
114
|
+
params: {
|
|
115
|
+
script?: string;
|
|
116
|
+
scriptId?: string;
|
|
117
|
+
edits?: ReadonlyArray<{ oldText: string; newText: string }>;
|
|
118
|
+
},
|
|
119
|
+
): { scriptId: string; source: string } {
|
|
120
|
+
const edits = params.edits;
|
|
121
|
+
if (edits && edits.length > 0) {
|
|
122
|
+
const scriptId = params.scriptId;
|
|
123
|
+
if (!scriptId) throw new Error("edits require scriptId from the failed run.");
|
|
124
|
+
const stored = scripts.get(scriptId);
|
|
125
|
+
if (!stored) throw new Error(`No stored script for scriptId ${scriptId}. Evicted; resend full script.`);
|
|
126
|
+
if (stored.language !== language) {
|
|
127
|
+
throw new Error(`Language mismatch: scriptId ${scriptId} is ${stored.language}, not ${language}.`);
|
|
128
|
+
}
|
|
129
|
+
return { scriptId, source: applyEdits(stored.source, edits) };
|
|
130
|
+
}
|
|
131
|
+
if (typeof params.script !== "string" || params.script.length === 0) {
|
|
132
|
+
throw new Error("Provide script, or edits + scriptId.");
|
|
133
|
+
}
|
|
134
|
+
return { scriptId: params.scriptId ?? newScriptId(), source: params.script };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function formatTruncatedTail(text: string): { body: string; note: string } {
|
|
138
|
+
const trunc = truncateTail(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
139
|
+
const note = trunc.truncated
|
|
140
|
+
? `\n\n[output truncated: kept tail ${trunc.outputLines} / ${trunc.totalLines} lines]`
|
|
141
|
+
: "";
|
|
142
|
+
return { body: trunc.content, note };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function successScriptResult(stdout: string): { content: [{ type: "text"; text: string }]; details: undefined } {
|
|
146
|
+
const { body, note } = formatTruncatedTail(stdout.trim());
|
|
147
|
+
const out = body.trim();
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: out ? `${out}${note}` : `(no output)${note}` }],
|
|
150
|
+
details: undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function throwScriptFailure(scriptId: string, result: { stdout: string; stderr: string }): never {
|
|
155
|
+
const diag = result.stderr.trim() || result.stdout.trim();
|
|
156
|
+
const { body, note } = formatTruncatedTail(diag);
|
|
157
|
+
const detail = body ? `${body}${note}\n\n` : "";
|
|
158
|
+
throw new Error(`${detail}scriptId: ${scriptId}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
93
161
|
export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
94
162
|
const runtimes = detectRuntimes();
|
|
95
|
-
const detected = (["python3", "
|
|
96
|
-
(lang): lang is Language =>
|
|
163
|
+
const detected = (["python3", "node", "deno"] as const).filter(
|
|
164
|
+
(lang): lang is Language => runtimes[lang] !== undefined,
|
|
97
165
|
);
|
|
98
166
|
if (detected.length === 0) return;
|
|
99
167
|
|
|
100
|
-
const langPhrase = detected
|
|
168
|
+
const langPhrase = formatLangList(detected);
|
|
101
169
|
|
|
102
170
|
const scripts = new Map<string, StoredScript>();
|
|
103
171
|
let tempDir: string | undefined;
|
|
@@ -112,14 +180,13 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
112
180
|
}
|
|
113
181
|
|
|
114
182
|
function resolveCommand(language: Language): string {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
183
|
+
const cmd = runtimes[language];
|
|
184
|
+
if (cmd) return cmd;
|
|
185
|
+
if (language === "python3") throw new Error("Python 3 is not available on this machine.");
|
|
186
|
+
if (language === "node") {
|
|
187
|
+
throw new Error("Node unavailable (needs Node >= 22.6 with --experimental-strip-types).");
|
|
119
188
|
}
|
|
120
|
-
|
|
121
|
-
if (!cmd) throw new Error("TypeScript is not available (requires Node >= 22.6).");
|
|
122
|
-
return cmd;
|
|
189
|
+
throw new Error("Deno is not available on this machine.");
|
|
123
190
|
}
|
|
124
191
|
|
|
125
192
|
async function ensureTempDir(): Promise<string> {
|
|
@@ -138,7 +205,12 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
138
205
|
const dir = await ensureTempDir();
|
|
139
206
|
const file = join(dir, language === "python3" ? "_run.py" : "_run.ts");
|
|
140
207
|
await writeFile(file, source, "utf8");
|
|
141
|
-
const args =
|
|
208
|
+
const args =
|
|
209
|
+
language === "python3"
|
|
210
|
+
? [file]
|
|
211
|
+
: language === "node"
|
|
212
|
+
? ["--experimental-strip-types", file]
|
|
213
|
+
: ["run", "-A", file];
|
|
142
214
|
const result = await pi.exec(command, args, { cwd, signal, timeout: TIMEOUT_MS });
|
|
143
215
|
return {
|
|
144
216
|
...result,
|
|
@@ -149,13 +221,13 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
149
221
|
|
|
150
222
|
const paramsSchema = Type.Object(
|
|
151
223
|
{
|
|
152
|
-
language: StringEnum(detected, {
|
|
224
|
+
language: StringEnum(detected, {
|
|
225
|
+
description: "Execution runtime.",
|
|
226
|
+
}),
|
|
153
227
|
script: Type.Optional(
|
|
154
|
-
Type.String({ description: "Full
|
|
155
|
-
),
|
|
156
|
-
scriptId: Type.Optional(
|
|
157
|
-
Type.String({ description: "scriptId returned by a failed run. Required when retrying with edits." }),
|
|
228
|
+
Type.String({ description: "Full source for a new run. Omit when retrying with edits." }),
|
|
158
229
|
),
|
|
230
|
+
scriptId: Type.Optional(Type.String({ description: "From a failed run. Required with edits." })),
|
|
159
231
|
edits: Type.Optional(
|
|
160
232
|
Type.Array(
|
|
161
233
|
Type.Object({
|
|
@@ -163,8 +235,7 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
163
235
|
newText: Type.String({ description: "Replacement text." }),
|
|
164
236
|
}),
|
|
165
237
|
{
|
|
166
|
-
description:
|
|
167
|
-
"Targeted oldText/newText patches applied to the stored script before rerun. Prefer this over resending the whole script.",
|
|
238
|
+
description: "Patches applied to the stored script before rerun.",
|
|
168
239
|
},
|
|
169
240
|
),
|
|
170
241
|
),
|
|
@@ -172,15 +243,26 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
172
243
|
{ additionalProperties: false },
|
|
173
244
|
);
|
|
174
245
|
|
|
246
|
+
const runtimeNotes = [
|
|
247
|
+
detected.includes("node")
|
|
248
|
+
? "language=node: local Node.js via node --experimental-strip-types (not tsc/ts-node)."
|
|
249
|
+
: undefined,
|
|
250
|
+
detected.includes("deno") ? "language=deno: local Deno via deno run -A." : undefined,
|
|
251
|
+
].filter((note): note is string => note !== undefined);
|
|
252
|
+
|
|
175
253
|
const tool = defineTool<typeof paramsSchema, undefined>({
|
|
176
254
|
name: "script_runner",
|
|
177
255
|
label: "Script Runner",
|
|
178
|
-
description:
|
|
179
|
-
|
|
256
|
+
description: [
|
|
257
|
+
`Run a ${langPhrase} script in the project cwd and return stdout.`,
|
|
258
|
+
...runtimeNotes,
|
|
259
|
+
"On non-zero exit: result includes scriptId. Retry with same language + scriptId + edits [{oldText,newText}]; do not resend the full script unless the approach is wrong.",
|
|
260
|
+
`Available: ${langPhrase}.`,
|
|
261
|
+
].join("\n"),
|
|
262
|
+
promptSnippet: `Run ${langPhrase} scripts; on failure retry with edits + scriptId.`,
|
|
180
263
|
promptGuidelines: [
|
|
181
|
-
`Prefer script_runner over bash for ${langPhrase} when computation, data handling, or bulk file work is cleaner than chaining
|
|
182
|
-
|
|
183
|
-
`script_runner never exposes the script file path, and you already know the script you sent. Never try to read it back.`,
|
|
264
|
+
`Prefer script_runner over bash for ${langPhrase} when computation, data handling, or bulk file work is cleaner than chaining tools.`,
|
|
265
|
+
"script_runner never exposes the script path; you already have the source. Never try to read it back.",
|
|
184
266
|
],
|
|
185
267
|
parameters: paramsSchema,
|
|
186
268
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -188,65 +270,23 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
188
270
|
if (signal?.aborted) {
|
|
189
271
|
return { content: [{ type: "text", text: "Cancelled." }], details: undefined };
|
|
190
272
|
}
|
|
191
|
-
|
|
192
273
|
const command = resolveCommand(language);
|
|
193
|
-
const
|
|
194
|
-
let scriptId: string | undefined = params.scriptId;
|
|
195
|
-
let source: string;
|
|
196
|
-
|
|
197
|
-
if (edits && edits.length > 0) {
|
|
198
|
-
if (!scriptId) throw new Error("edits require the scriptId returned by the failed run.");
|
|
199
|
-
const stored = scripts.get(scriptId);
|
|
200
|
-
if (!stored) {
|
|
201
|
-
throw new Error(
|
|
202
|
-
`No stored script for scriptId ${scriptId}. It may have been evicted; resend the full script.`,
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
if (stored.language !== language) {
|
|
206
|
-
throw new Error(`Language mismatch: scriptId ${scriptId} is ${stored.language}, not ${language}.`);
|
|
207
|
-
}
|
|
208
|
-
source = applyEdits(stored.source, edits);
|
|
209
|
-
} else {
|
|
210
|
-
if (typeof params.script !== "string" || params.script.length === 0) {
|
|
211
|
-
throw new Error("Provide a script for a new run, or edits + scriptId to retry.");
|
|
212
|
-
}
|
|
213
|
-
source = params.script;
|
|
214
|
-
if (!scriptId) scriptId = newScriptId();
|
|
215
|
-
}
|
|
216
|
-
|
|
274
|
+
const { scriptId, source } = resolveScriptSource(scripts, language, params);
|
|
217
275
|
remember(scriptId, { language, source });
|
|
218
|
-
await onUpdate?.({
|
|
219
|
-
|
|
276
|
+
await onUpdate?.({
|
|
277
|
+
content: [{ type: "text", text: `Running ${languageLabel(language)}...` }],
|
|
278
|
+
details: undefined,
|
|
279
|
+
});
|
|
220
280
|
const result = await runScript(language, command, source, ctx.cwd, signal);
|
|
221
281
|
if (result.code === 0 && !result.killed) {
|
|
222
282
|
scripts.delete(scriptId);
|
|
223
|
-
|
|
224
|
-
maxLines: DEFAULT_MAX_LINES,
|
|
225
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
226
|
-
});
|
|
227
|
-
const out = trunc.content.trim();
|
|
228
|
-
const note = trunc.truncated
|
|
229
|
-
? `\n\n[output truncated: kept tail ${trunc.outputLines} / ${trunc.totalLines} lines]`
|
|
230
|
-
: "";
|
|
231
|
-
return {
|
|
232
|
-
content: [{ type: "text", text: out ? `${out}${note}` : `(no output)${note}` }],
|
|
233
|
-
details: undefined,
|
|
234
|
-
};
|
|
283
|
+
return successScriptResult(result.stdout);
|
|
235
284
|
}
|
|
236
|
-
|
|
237
|
-
const diag = result.stderr.trim() || result.stdout.trim();
|
|
238
|
-
const trunc = truncateTail(diag, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
239
|
-
const note = trunc.truncated
|
|
240
|
-
? `\n\n[output truncated: kept tail ${trunc.outputLines} / ${trunc.totalLines} lines]`
|
|
241
|
-
: "";
|
|
242
|
-
const detail = trunc.content ? `${trunc.content}${note}\n\n` : "";
|
|
243
|
-
throw new Error(
|
|
244
|
-
`${detail}scriptId: ${scriptId}\nRetry with edits: [{oldText,newText}] using the same language and scriptId; do not resend the full script.`,
|
|
245
|
-
);
|
|
285
|
+
throwScriptFailure(scriptId, result);
|
|
246
286
|
},
|
|
247
287
|
renderCall(args, theme, context) {
|
|
248
288
|
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
249
|
-
const header = `${theme.fg("toolTitle", theme.bold("script_runner"))} ${theme.fg("muted", args.language)}`;
|
|
289
|
+
const header = `${theme.fg("toolTitle", theme.bold("script_runner"))} ${theme.fg("muted", languageLabel(args.language))}`;
|
|
250
290
|
const edits = args.edits;
|
|
251
291
|
const body =
|
|
252
292
|
edits && edits.length > 0
|
|
@@ -146,25 +146,11 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
|
|
|
146
146
|
attentionHoldId = undefined;
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
async function
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
async function collectCommandFailures(
|
|
150
|
+
projectRoot: string,
|
|
151
|
+
changed: readonly CommandConfig[],
|
|
152
152
|
notify: (message: string, type?: "info" | "warning" | "error") => void,
|
|
153
|
-
): Promise<
|
|
154
|
-
if (!settings.enabled || settings.commands.length === 0) return;
|
|
155
|
-
|
|
156
|
-
const projectRoot = await resolveProjectRoot(cwd);
|
|
157
|
-
const paths = await walkFiles(projectRoot);
|
|
158
|
-
const changed = await scanChangedCommands(projectRoot, settings.commands, paths, turnPaths, turnStart);
|
|
159
|
-
if (!sessionActive || changed.length === 0) return;
|
|
160
|
-
|
|
161
|
-
notify(
|
|
162
|
-
changed.length === 1
|
|
163
|
-
? `silent-command-runner: running ${changed[0]?.name ?? "command"}`
|
|
164
|
-
: `silent-command-runner: running ${changed.length} commands`,
|
|
165
|
-
"info",
|
|
166
|
-
);
|
|
167
|
-
|
|
153
|
+
): Promise<FailedCommandDetails[] | undefined> {
|
|
168
154
|
const failures: FailedCommandDetails[] = [];
|
|
169
155
|
const controller = new AbortController();
|
|
170
156
|
abortController = controller;
|
|
@@ -175,32 +161,61 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
|
|
|
175
161
|
try {
|
|
176
162
|
result = await runCommand(pi, projectRoot, command, controller.signal, settings.maxOutputBytes);
|
|
177
163
|
} catch (error: unknown) {
|
|
178
|
-
if (controller.signal.aborted) return;
|
|
164
|
+
if (controller.signal.aborted) return undefined;
|
|
179
165
|
throw error;
|
|
180
166
|
}
|
|
181
|
-
if (controller.signal.aborted) return;
|
|
167
|
+
if (controller.signal.aborted) return undefined;
|
|
182
168
|
if (result.code !== 0 || result.killed) failures.push(result);
|
|
183
169
|
}
|
|
184
170
|
} finally {
|
|
185
171
|
if (abortController === controller) abortController = undefined;
|
|
186
172
|
}
|
|
187
|
-
|
|
173
|
+
return failures;
|
|
174
|
+
}
|
|
188
175
|
|
|
176
|
+
function reportCommandResults(
|
|
177
|
+
changed: readonly CommandConfig[],
|
|
178
|
+
failures: readonly FailedCommandDetails[],
|
|
179
|
+
notify: (message: string, type?: "info" | "warning" | "error") => void,
|
|
180
|
+
): void {
|
|
189
181
|
const failedNames = new Set(failures.map((failure) => failure.name));
|
|
190
182
|
const passed = changed.filter((command) => !failedNames.has(command.name));
|
|
191
183
|
if (passed.length > 0) notify(`silent-command-runner: passed ${formatCommandNames(passed)}`, "info");
|
|
192
184
|
if (failures.length === 0) return;
|
|
193
|
-
|
|
194
185
|
pi.sendMessage<FailureDetails>(
|
|
195
186
|
{
|
|
196
187
|
customType: MESSAGE_TYPE,
|
|
197
188
|
content: formatAgentMessage(failures),
|
|
198
189
|
display: true,
|
|
199
|
-
details: { failed: failures },
|
|
190
|
+
details: { failed: [...failures] },
|
|
200
191
|
},
|
|
201
192
|
{ deliverAs: "followUp" },
|
|
202
193
|
);
|
|
203
194
|
}
|
|
195
|
+
|
|
196
|
+
async function runChangedCommands(
|
|
197
|
+
cwd: string,
|
|
198
|
+
turnStart: number,
|
|
199
|
+
notify: (message: string, type?: "info" | "warning" | "error") => void,
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
if (!settings.enabled || settings.commands.length === 0) return;
|
|
202
|
+
|
|
203
|
+
const projectRoot = await resolveProjectRoot(cwd);
|
|
204
|
+
const paths = await walkFiles(projectRoot);
|
|
205
|
+
const changed = await scanChangedCommands(projectRoot, settings.commands, paths, turnPaths, turnStart);
|
|
206
|
+
if (!sessionActive || changed.length === 0) return;
|
|
207
|
+
|
|
208
|
+
notify(
|
|
209
|
+
changed.length === 1
|
|
210
|
+
? `silent-command-runner: running ${changed[0]?.name ?? "command"}`
|
|
211
|
+
: `silent-command-runner: running ${changed.length} commands`,
|
|
212
|
+
"info",
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const failures = await collectCommandFailures(projectRoot, changed, notify);
|
|
216
|
+
if (failures === undefined || !sessionActive) return;
|
|
217
|
+
reportCommandResults(changed, failures, notify);
|
|
218
|
+
}
|
|
204
219
|
}
|
|
205
220
|
|
|
206
221
|
function normalizeSettings(value: typeof silentCommandRunnerSettings.defaults): Settings {
|