@shanepadgett/tau-agent 0.33.0 → 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 +130 -93
- 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 = "
|
|
19
|
+
type Language = "python3" | "node" | "deno";
|
|
20
20
|
|
|
21
21
|
interface Runtimes {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
python3: string | undefined;
|
|
23
|
+
node: string | undefined;
|
|
24
|
+
deno: string | undefined;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
interface StoredScript {
|
|
@@ -32,26 +33,42 @@ const TIMEOUT_MS = 120_000;
|
|
|
32
33
|
const MAX_STORED = 8;
|
|
33
34
|
|
|
34
35
|
function detectRuntimes(): Runtimes {
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} catch {
|
|
42
|
-
// runtime not installed
|
|
43
|
-
}
|
|
36
|
+
let python3: string | undefined;
|
|
37
|
+
try {
|
|
38
|
+
execFileSync("python3", ["--version"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
39
|
+
python3 = "python3";
|
|
40
|
+
} catch {
|
|
41
|
+
// runtime not installed
|
|
44
42
|
}
|
|
45
43
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
46
|
-
let
|
|
44
|
+
let node: string | undefined;
|
|
47
45
|
if (major > 22 || (major === 22 && minor >= 6)) {
|
|
48
|
-
|
|
46
|
+
node = process.execPath;
|
|
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
|
|
49
54
|
}
|
|
50
|
-
return {
|
|
55
|
+
return { python3, node, deno };
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
function capitalize(lang: Language): string {
|
|
54
|
-
|
|
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;
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
function newScriptId(): string {
|
|
@@ -68,9 +85,7 @@ function applyEdits(source: string, edits: ReadonlyArray<{ oldText: string; newT
|
|
|
68
85
|
for (const edit of edits) {
|
|
69
86
|
const idx = next.indexOf(edit.oldText);
|
|
70
87
|
if (idx === -1) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
"An edits oldText was not found in the script. Copy the exact text from the script you wrote.",
|
|
73
|
-
);
|
|
88
|
+
throw new Error("edits oldText not found. Copy exact text from the script you wrote.");
|
|
74
89
|
}
|
|
75
90
|
next = next.slice(0, idx) + edit.newText + next.slice(idx + edit.oldText.length);
|
|
76
91
|
}
|
|
@@ -93,14 +108,64 @@ function renderEditsPreview(edits: ReadonlyArray<{ oldText: string; newText: str
|
|
|
93
108
|
.join("\n");
|
|
94
109
|
}
|
|
95
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
|
+
|
|
96
161
|
export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
97
162
|
const runtimes = detectRuntimes();
|
|
98
|
-
const detected = (["
|
|
99
|
-
(lang): lang is Language =>
|
|
163
|
+
const detected = (["python3", "node", "deno"] as const).filter(
|
|
164
|
+
(lang): lang is Language => runtimes[lang] !== undefined,
|
|
100
165
|
);
|
|
101
166
|
if (detected.length === 0) return;
|
|
102
167
|
|
|
103
|
-
const langPhrase = detected
|
|
168
|
+
const langPhrase = formatLangList(detected);
|
|
104
169
|
|
|
105
170
|
const scripts = new Map<string, StoredScript>();
|
|
106
171
|
let tempDir: string | undefined;
|
|
@@ -115,14 +180,13 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
115
180
|
}
|
|
116
181
|
|
|
117
182
|
function resolveCommand(language: Language): string {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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).");
|
|
122
188
|
}
|
|
123
|
-
|
|
124
|
-
if (!cmd) throw new Error("TypeScript is not available (requires Node >= 22.6).");
|
|
125
|
-
return cmd;
|
|
189
|
+
throw new Error("Deno is not available on this machine.");
|
|
126
190
|
}
|
|
127
191
|
|
|
128
192
|
async function ensureTempDir(): Promise<string> {
|
|
@@ -139,9 +203,14 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
139
203
|
signal: AbortSignal | undefined,
|
|
140
204
|
): Promise<ExecResult> {
|
|
141
205
|
const dir = await ensureTempDir();
|
|
142
|
-
const file = join(dir, language === "
|
|
206
|
+
const file = join(dir, language === "python3" ? "_run.py" : "_run.ts");
|
|
143
207
|
await writeFile(file, source, "utf8");
|
|
144
|
-
const args =
|
|
208
|
+
const args =
|
|
209
|
+
language === "python3"
|
|
210
|
+
? [file]
|
|
211
|
+
: language === "node"
|
|
212
|
+
? ["--experimental-strip-types", file]
|
|
213
|
+
: ["run", "-A", file];
|
|
145
214
|
const result = await pi.exec(command, args, { cwd, signal, timeout: TIMEOUT_MS });
|
|
146
215
|
return {
|
|
147
216
|
...result,
|
|
@@ -152,13 +221,13 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
152
221
|
|
|
153
222
|
const paramsSchema = Type.Object(
|
|
154
223
|
{
|
|
155
|
-
language: StringEnum(detected, {
|
|
224
|
+
language: StringEnum(detected, {
|
|
225
|
+
description: "Execution runtime.",
|
|
226
|
+
}),
|
|
156
227
|
script: Type.Optional(
|
|
157
|
-
Type.String({ description: "Full
|
|
158
|
-
),
|
|
159
|
-
scriptId: Type.Optional(
|
|
160
|
-
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." }),
|
|
161
229
|
),
|
|
230
|
+
scriptId: Type.Optional(Type.String({ description: "From a failed run. Required with edits." })),
|
|
162
231
|
edits: Type.Optional(
|
|
163
232
|
Type.Array(
|
|
164
233
|
Type.Object({
|
|
@@ -166,8 +235,7 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
166
235
|
newText: Type.String({ description: "Replacement text." }),
|
|
167
236
|
}),
|
|
168
237
|
{
|
|
169
|
-
description:
|
|
170
|
-
"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.",
|
|
171
239
|
},
|
|
172
240
|
),
|
|
173
241
|
),
|
|
@@ -175,15 +243,26 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
175
243
|
{ additionalProperties: false },
|
|
176
244
|
);
|
|
177
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
|
+
|
|
178
253
|
const tool = defineTool<typeof paramsSchema, undefined>({
|
|
179
254
|
name: "script_runner",
|
|
180
255
|
label: "Script Runner",
|
|
181
|
-
description:
|
|
182
|
-
|
|
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.`,
|
|
183
263
|
promptGuidelines: [
|
|
184
|
-
`Prefer script_runner over bash for ${langPhrase} when computation, data handling, or bulk file work is cleaner than chaining
|
|
185
|
-
|
|
186
|
-
`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.",
|
|
187
266
|
],
|
|
188
267
|
parameters: paramsSchema,
|
|
189
268
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -191,65 +270,23 @@ export default function scriptRunnerExtension(pi: ExtensionAPI): void {
|
|
|
191
270
|
if (signal?.aborted) {
|
|
192
271
|
return { content: [{ type: "text", text: "Cancelled." }], details: undefined };
|
|
193
272
|
}
|
|
194
|
-
|
|
195
273
|
const command = resolveCommand(language);
|
|
196
|
-
const
|
|
197
|
-
let scriptId: string | undefined = params.scriptId;
|
|
198
|
-
let source: string;
|
|
199
|
-
|
|
200
|
-
if (edits && edits.length > 0) {
|
|
201
|
-
if (!scriptId) throw new Error("edits require the scriptId returned by the failed run.");
|
|
202
|
-
const stored = scripts.get(scriptId);
|
|
203
|
-
if (!stored) {
|
|
204
|
-
throw new Error(
|
|
205
|
-
`No stored script for scriptId ${scriptId}. It may have been evicted; resend the full script.`,
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
if (stored.language !== language) {
|
|
209
|
-
throw new Error(`Language mismatch: scriptId ${scriptId} is ${stored.language}, not ${language}.`);
|
|
210
|
-
}
|
|
211
|
-
source = applyEdits(stored.source, edits);
|
|
212
|
-
} else {
|
|
213
|
-
if (typeof params.script !== "string" || params.script.length === 0) {
|
|
214
|
-
throw new Error("Provide a script for a new run, or edits + scriptId to retry.");
|
|
215
|
-
}
|
|
216
|
-
source = params.script;
|
|
217
|
-
if (!scriptId) scriptId = newScriptId();
|
|
218
|
-
}
|
|
219
|
-
|
|
274
|
+
const { scriptId, source } = resolveScriptSource(scripts, language, params);
|
|
220
275
|
remember(scriptId, { language, source });
|
|
221
|
-
await onUpdate?.({
|
|
222
|
-
|
|
276
|
+
await onUpdate?.({
|
|
277
|
+
content: [{ type: "text", text: `Running ${languageLabel(language)}...` }],
|
|
278
|
+
details: undefined,
|
|
279
|
+
});
|
|
223
280
|
const result = await runScript(language, command, source, ctx.cwd, signal);
|
|
224
281
|
if (result.code === 0 && !result.killed) {
|
|
225
282
|
scripts.delete(scriptId);
|
|
226
|
-
|
|
227
|
-
maxLines: DEFAULT_MAX_LINES,
|
|
228
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
229
|
-
});
|
|
230
|
-
const out = trunc.content.trim();
|
|
231
|
-
const note = trunc.truncated
|
|
232
|
-
? `\n\n[output truncated: kept tail ${trunc.outputLines} / ${trunc.totalLines} lines]`
|
|
233
|
-
: "";
|
|
234
|
-
return {
|
|
235
|
-
content: [{ type: "text", text: out ? `${out}${note}` : `(no output)${note}` }],
|
|
236
|
-
details: undefined,
|
|
237
|
-
};
|
|
283
|
+
return successScriptResult(result.stdout);
|
|
238
284
|
}
|
|
239
|
-
|
|
240
|
-
const diag = result.stderr.trim() || result.stdout.trim();
|
|
241
|
-
const trunc = truncateTail(diag, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
242
|
-
const note = trunc.truncated
|
|
243
|
-
? `\n\n[output truncated: kept tail ${trunc.outputLines} / ${trunc.totalLines} lines]`
|
|
244
|
-
: "";
|
|
245
|
-
const detail = trunc.content ? `${trunc.content}${note}\n\n` : "";
|
|
246
|
-
throw new Error(
|
|
247
|
-
`${detail}scriptId: ${scriptId}\nRetry with edits: [{oldText,newText}] using the same language and scriptId; do not resend the full script.`,
|
|
248
|
-
);
|
|
285
|
+
throwScriptFailure(scriptId, result);
|
|
249
286
|
},
|
|
250
287
|
renderCall(args, theme, context) {
|
|
251
288
|
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
252
|
-
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))}`;
|
|
253
290
|
const edits = args.edits;
|
|
254
291
|
const body =
|
|
255
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 {
|