@cydm/pie 1.0.6 → 1.0.7
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/README.md +151 -9
- package/dist/builtin/extensions/ask-user/index.js +2 -3
- package/dist/builtin/extensions/kimi-attachments/index.js +3 -3
- package/dist/builtin/extensions/plan-mode/index.js +85 -87
- package/dist/builtin/extensions/subagent/index.js +73 -8
- package/dist/builtin/extensions/todo/index.js +51 -22
- package/dist/builtin/skills/browser-tools/CHANGELOG.md +2 -44
- package/dist/builtin/skills/browser-tools/README.md +10 -99
- package/dist/builtin/skills/browser-tools/SKILL.md +21 -174
- package/dist/builtin/skills/browser-tools/package.json +6 -13
- package/dist/builtin/skills/browser-tools/playwright-cli.js +24 -0
- package/dist/builtin/skills/pie-unity-rpc/SKILL.md +121 -0
- package/dist/builtin/skills/pie-unity-rpc/pie-unity-rpc.js +417 -0
- package/dist/builtin/skills/skill-creator/SKILL.md +17 -17
- package/dist/builtin/skills/skill-creator/eval-viewer/generate_review.mjs +285 -0
- package/dist/builtin/skills/skill-creator/eval-viewer/viewer.html +1 -1
- package/dist/builtin/skills/skill-creator/scripts/aggregate_benchmark.mjs +271 -0
- package/dist/builtin/skills/skill-creator/scripts/claude_cli.mjs +115 -0
- package/dist/builtin/skills/skill-creator/scripts/generate_report.mjs +224 -0
- package/dist/builtin/skills/skill-creator/scripts/improve_description.mjs +198 -0
- package/dist/builtin/skills/skill-creator/scripts/package_skill.mjs +132 -0
- package/dist/builtin/skills/skill-creator/scripts/pie_runner.mjs +115 -0
- package/dist/builtin/skills/skill-creator/scripts/quick_validate.mjs +44 -0
- package/dist/builtin/skills/skill-creator/scripts/run_eval.mjs +169 -0
- package/dist/builtin/skills/skill-creator/scripts/run_loop.mjs +297 -0
- package/dist/builtin/skills/skill-creator/scripts/skill_metadata.mjs +134 -0
- package/dist/chunks/{chunk-MWFBYJOI.js → chunk-A5JSJAPK.js} +3973 -1313
- package/dist/chunks/chunk-BHNULR7U.js +7991 -0
- package/dist/chunks/chunk-GDTN4UPJ.js +701 -0
- package/dist/chunks/{src-EGWRDMLB.js → src-3X3HBT2G.js} +1 -2
- package/dist/chunks/typescript-GSKWJIO4.js +210747 -0
- package/dist/cli.js +14664 -11973
- package/models.schema.json +238 -0
- package/package.json +34 -8
- package/dist/builtin/skills/browser-tools/browser-content.js +0 -103
- package/dist/builtin/skills/browser-tools/browser-cookies.js +0 -35
- package/dist/builtin/skills/browser-tools/browser-eval.js +0 -49
- package/dist/builtin/skills/browser-tools/browser-hn-scraper.js +0 -108
- package/dist/builtin/skills/browser-tools/browser-nav.js +0 -44
- package/dist/builtin/skills/browser-tools/browser-pick.js +0 -162
- package/dist/builtin/skills/browser-tools/browser-screenshot.js +0 -34
- package/dist/builtin/skills/browser-tools/browser-start.js +0 -86
- package/dist/builtin/skills/skill-creator/eval-viewer/generate_review.py +0 -471
- package/dist/builtin/skills/skill-creator/scripts/__init__.py +0 -0
- package/dist/builtin/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
- package/dist/builtin/skills/skill-creator/scripts/generate_report.py +0 -326
- package/dist/builtin/skills/skill-creator/scripts/improve_description.py +0 -247
- package/dist/builtin/skills/skill-creator/scripts/package_skill.py +0 -136
- package/dist/builtin/skills/skill-creator/scripts/quick_validate.py +0 -103
- package/dist/builtin/skills/skill-creator/scripts/run_eval.py +0 -310
- package/dist/builtin/skills/skill-creator/scripts/run_loop.py +0 -328
- package/dist/builtin/skills/skill-creator/scripts/utils.py +0 -47
- package/dist/chunks/capabilities-FENCOHVA.js +0 -9
- package/dist/chunks/chunk-JYBXCEJJ.js +0 -315
- package/dist/chunks/chunk-RID3574D.js +0 -2718
- package/dist/chunks/chunk-TBJ25UWB.js +0 -3657
- package/dist/chunks/chunk-XZXLO7YB.js +0 -322
- package/dist/chunks/file-logger-AL5VVZHH.js +0 -22
- package/dist/chunks/src-WRUACRN2.js +0 -132
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
classifyShellCommand,
|
|
4
|
+
createCodeIntelTool,
|
|
5
|
+
createLightweightCodeIntelProvider,
|
|
6
|
+
createSharedWebFetchTool,
|
|
7
|
+
createSharedWebSearchTool,
|
|
8
|
+
interpretShellExit,
|
|
9
|
+
requestInteraction
|
|
10
|
+
} from "./chunk-BHNULR7U.js";
|
|
11
|
+
import {
|
|
12
|
+
Type
|
|
13
|
+
} from "./chunk-A5JSJAPK.js";
|
|
14
|
+
|
|
15
|
+
// src/capabilities/bash/index.ts
|
|
16
|
+
import { spawn } from "child_process";
|
|
17
|
+
import { createWriteStream, existsSync, statSync, writeFileSync } from "fs";
|
|
18
|
+
import { tmpdir } from "os";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
import { randomBytes } from "crypto";
|
|
21
|
+
|
|
22
|
+
// src/capabilities/bash/truncate.ts
|
|
23
|
+
var DEFAULT_MAX_LINES = 2e3;
|
|
24
|
+
var DEFAULT_MAX_BYTES = 50 * 1024;
|
|
25
|
+
function formatSize(bytes) {
|
|
26
|
+
if (bytes < 1024) {
|
|
27
|
+
return `${bytes}B`;
|
|
28
|
+
} else if (bytes < 1024 * 1024) {
|
|
29
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
30
|
+
} else {
|
|
31
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function truncateTail(content, options = {}) {
|
|
35
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
36
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
37
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
38
|
+
const lines = content.split("\n");
|
|
39
|
+
const totalLines = lines.length;
|
|
40
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
41
|
+
return {
|
|
42
|
+
content,
|
|
43
|
+
truncated: false,
|
|
44
|
+
truncatedBy: null,
|
|
45
|
+
totalLines,
|
|
46
|
+
totalBytes,
|
|
47
|
+
outputLines: totalLines,
|
|
48
|
+
outputBytes: totalBytes,
|
|
49
|
+
lastLinePartial: false,
|
|
50
|
+
maxLines,
|
|
51
|
+
maxBytes
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const outputLinesArr = [];
|
|
55
|
+
let outputBytesCount = 0;
|
|
56
|
+
let truncatedBy = "lines";
|
|
57
|
+
let lastLinePartial = false;
|
|
58
|
+
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
59
|
+
const line = lines[i];
|
|
60
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
|
|
61
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
62
|
+
truncatedBy = "bytes";
|
|
63
|
+
if (outputLinesArr.length === 0) {
|
|
64
|
+
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
65
|
+
outputLinesArr.unshift(truncatedLine);
|
|
66
|
+
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
67
|
+
lastLinePartial = true;
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
outputLinesArr.unshift(line);
|
|
72
|
+
outputBytesCount += lineBytes;
|
|
73
|
+
}
|
|
74
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
75
|
+
truncatedBy = "lines";
|
|
76
|
+
}
|
|
77
|
+
const outputContent = outputLinesArr.join("\n");
|
|
78
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
79
|
+
return {
|
|
80
|
+
content: outputContent,
|
|
81
|
+
truncated: true,
|
|
82
|
+
truncatedBy,
|
|
83
|
+
totalLines,
|
|
84
|
+
totalBytes,
|
|
85
|
+
outputLines: outputLinesArr.length,
|
|
86
|
+
outputBytes: finalOutputBytes,
|
|
87
|
+
lastLinePartial,
|
|
88
|
+
maxLines,
|
|
89
|
+
maxBytes
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
93
|
+
const buf = Buffer.from(str, "utf-8");
|
|
94
|
+
if (buf.length <= maxBytes) {
|
|
95
|
+
return str;
|
|
96
|
+
}
|
|
97
|
+
let start = buf.length - maxBytes;
|
|
98
|
+
while (start < buf.length && (buf[start] & 192) === 128) {
|
|
99
|
+
start++;
|
|
100
|
+
}
|
|
101
|
+
return buf.slice(start).toString("utf-8");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/capabilities/bash/index.ts
|
|
105
|
+
var bashSchema = Type.Object({
|
|
106
|
+
command: Type.String({ description: "The bash command to execute" }),
|
|
107
|
+
timeout: Type.Optional(
|
|
108
|
+
Type.Number({
|
|
109
|
+
description: "Timeout in seconds. Default: 120s (2 minutes). For longer tasks, explicitly set a higher value: { timeout: 600 } for npm install, { timeout: 600 } for cargo build, etc."
|
|
110
|
+
})
|
|
111
|
+
),
|
|
112
|
+
cwd: Type.Optional(Type.String({ description: "Working directory for the command" })),
|
|
113
|
+
shell: Type.Optional(Type.Union([
|
|
114
|
+
Type.Literal("auto"),
|
|
115
|
+
Type.Literal("bash"),
|
|
116
|
+
Type.Literal("powershell"),
|
|
117
|
+
Type.Literal("cmd")
|
|
118
|
+
], { description: "Shell to use. Defaults to bash on macOS/Linux and PowerShell on Windows. Use this only for platform-specific commands." }))
|
|
119
|
+
});
|
|
120
|
+
var DEFAULT_TIMEOUT_SECONDS = 120;
|
|
121
|
+
function validateBashCommand(command) {
|
|
122
|
+
const value = typeof command === "string" ? command : "";
|
|
123
|
+
const trimmed = value.trim();
|
|
124
|
+
if (!trimmed) {
|
|
125
|
+
throw new Error("Command cannot be empty. Pass a real shell command in the command argument.");
|
|
126
|
+
}
|
|
127
|
+
if (/^:\s/.test(trimmed) || /^:\s*(?:\d+[\.)]?)?\s*$/.test(trimmed) || /^:\s*\d+[\.)]/.test(trimmed)) {
|
|
128
|
+
throw new Error(`Invalid bash command "${value}". Do not pass a no-op, numbered plan item, or plan fragment; pass the actual shell command to run.`);
|
|
129
|
+
}
|
|
130
|
+
if (trimmed.includes("<|tool")) {
|
|
131
|
+
throw new Error("Invalid bash command. The command argument must be only a real shell command, not tool-call markup.");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function buildEffectiveCommand(command, options) {
|
|
135
|
+
const prefix = options?.commandPrefix?.trim();
|
|
136
|
+
if (!prefix) {
|
|
137
|
+
return command;
|
|
138
|
+
}
|
|
139
|
+
validateBashCommand(prefix);
|
|
140
|
+
return `${prefix} && ${command}`;
|
|
141
|
+
}
|
|
142
|
+
function resolveShell(shell) {
|
|
143
|
+
if (!shell || shell === "auto") {
|
|
144
|
+
return process.platform === "win32" ? "powershell" : "bash";
|
|
145
|
+
}
|
|
146
|
+
return shell;
|
|
147
|
+
}
|
|
148
|
+
function buildSpawnCommand(shell, command) {
|
|
149
|
+
if (shell === "powershell") {
|
|
150
|
+
return { file: process.platform === "win32" ? "powershell.exe" : "pwsh", args: ["-NoProfile", "-NonInteractive", "-Command", command], shell: false };
|
|
151
|
+
}
|
|
152
|
+
if (shell === "cmd") {
|
|
153
|
+
return { file: "cmd.exe", args: ["/d", "/s", "/c", command], shell: false };
|
|
154
|
+
}
|
|
155
|
+
return { file: command, args: [], shell: true };
|
|
156
|
+
}
|
|
157
|
+
async function requestHighRiskConfirmation(request) {
|
|
158
|
+
const response = await requestInteraction({
|
|
159
|
+
type: "confirm",
|
|
160
|
+
id: `bash:${Date.now()}`,
|
|
161
|
+
prompt: `Run high-risk ${request.shell} command?`,
|
|
162
|
+
detail: [
|
|
163
|
+
`Class: ${request.commandClass}`,
|
|
164
|
+
`Warnings: ${request.riskWarnings.join("; ")}`,
|
|
165
|
+
"",
|
|
166
|
+
request.command
|
|
167
|
+
].join("\n"),
|
|
168
|
+
timeoutMs: 12e4
|
|
169
|
+
});
|
|
170
|
+
return response.type === "confirm" && response.confirmed === true;
|
|
171
|
+
}
|
|
172
|
+
function validateWorkingDirectory(cwd) {
|
|
173
|
+
if (!existsSync(cwd)) {
|
|
174
|
+
throw new Error(`Invalid bash cwd: ${cwd} does not exist. Use the current workspace path or pass an existing directory in cwd.`);
|
|
175
|
+
}
|
|
176
|
+
let stat;
|
|
177
|
+
try {
|
|
178
|
+
stat = statSync(cwd);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
throw new Error(`Invalid bash cwd: cannot access ${cwd}: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
+
}
|
|
182
|
+
if (!stat.isDirectory()) {
|
|
183
|
+
throw new Error(`Invalid bash cwd: ${cwd} is not a directory. Pass an existing directory in cwd.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function normalizeWorkingDirectory(cwd, defaultCwd) {
|
|
187
|
+
const requestedCwd = typeof cwd === "string" && cwd.trim().length > 0 ? cwd.trim() : void 0;
|
|
188
|
+
return requestedCwd ?? defaultCwd ?? process.cwd();
|
|
189
|
+
}
|
|
190
|
+
function getTempFilePath() {
|
|
191
|
+
const id = randomBytes(8).toString("hex");
|
|
192
|
+
return join(tmpdir(), `pie-bash-${id}.log`);
|
|
193
|
+
}
|
|
194
|
+
function finishTempFileStream(stream) {
|
|
195
|
+
if (!stream) return Promise.resolve();
|
|
196
|
+
return new Promise((resolve2, reject) => {
|
|
197
|
+
const onError = (error) => {
|
|
198
|
+
stream.off("error", onError);
|
|
199
|
+
reject(error);
|
|
200
|
+
};
|
|
201
|
+
stream.once("error", onError);
|
|
202
|
+
stream.end(() => {
|
|
203
|
+
stream.off("error", onError);
|
|
204
|
+
resolve2();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function buildBashRecoveryHint(command, output) {
|
|
209
|
+
if (/\bnpm ERR!|\bnpm error\b/i.test(output) && /\b(?:ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|fetch failed|socket hang up|registry|proxy)\b/i.test(output)) {
|
|
210
|
+
return "npm could not reach the registry. Treat this as an environment/network/proxy issue, not a source-code failure; retry npm install/npm ci when network is available or check npm proxy/registry settings.";
|
|
211
|
+
}
|
|
212
|
+
if (/\bnpm\s+run\b/.test(command) && /(?:^|\n)\s*(?:sh|bash|zsh):\s+[\w@./-]+:\s+command not found\b/i.test(output)) {
|
|
213
|
+
return "This npm script could not find a local executable. Check whether dependencies were installed successfully in cwd, whether node_modules exists, and whether a prior npm install/npm ci failed before treating this as a code failure.";
|
|
214
|
+
}
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
function killProcessTree(pid) {
|
|
218
|
+
if (process.platform === "win32") {
|
|
219
|
+
try {
|
|
220
|
+
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
221
|
+
stdio: "ignore",
|
|
222
|
+
detached: true
|
|
223
|
+
});
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
try {
|
|
228
|
+
process.kill(-pid, "SIGKILL");
|
|
229
|
+
} catch {
|
|
230
|
+
try {
|
|
231
|
+
process.kill(pid, "SIGKILL");
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function createBashTool(defaultCwd, options) {
|
|
238
|
+
return {
|
|
239
|
+
name: "bash",
|
|
240
|
+
label: "bash",
|
|
241
|
+
description: `Execute a shell command in the current working directory. The public tool name remains bash for compatibility; on Windows shell:auto uses PowerShell, otherwise bash. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Default timeout: 120s. Use timeout parameter for longer tasks (e.g., npm install -> 600).`,
|
|
242
|
+
parameters: bashSchema,
|
|
243
|
+
execute: async (_toolCallId, { command, timeout, cwd, shell }, signal, onUpdate) => {
|
|
244
|
+
validateBashCommand(command);
|
|
245
|
+
const resolvedShell = resolveShell(shell);
|
|
246
|
+
const effectiveCommand = buildEffectiveCommand(command, options);
|
|
247
|
+
const workingDir = normalizeWorkingDirectory(cwd, defaultCwd);
|
|
248
|
+
validateWorkingDirectory(workingDir);
|
|
249
|
+
const risk = classifyShellCommand(effectiveCommand, resolvedShell);
|
|
250
|
+
if (risk.destructive && !options?.isYoloMode?.()) {
|
|
251
|
+
const approved = await (options?.confirm ?? requestHighRiskConfirmation)({
|
|
252
|
+
command: effectiveCommand,
|
|
253
|
+
shell: resolvedShell,
|
|
254
|
+
commandClass: risk.commandClass,
|
|
255
|
+
riskWarnings: risk.riskWarnings
|
|
256
|
+
}).catch(() => false);
|
|
257
|
+
if (!approved) {
|
|
258
|
+
return {
|
|
259
|
+
content: [{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: `Blocked high-risk shell command (${risk.commandClass}): ${risk.riskWarnings.join("; ")}. Ask the user for explicit confirmation or use a safer diagnostic command first.`
|
|
262
|
+
}],
|
|
263
|
+
details: {
|
|
264
|
+
exitCode: null,
|
|
265
|
+
signal: null,
|
|
266
|
+
timedOut: false,
|
|
267
|
+
shell: resolvedShell,
|
|
268
|
+
commandClass: risk.commandClass,
|
|
269
|
+
riskWarnings: risk.riskWarnings,
|
|
270
|
+
permissionDecision: {
|
|
271
|
+
allowed: false,
|
|
272
|
+
reason: "destructive command blocked by shell policy",
|
|
273
|
+
requiresPermission: true
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
isError: true
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const timeoutMs = (timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
|
|
281
|
+
return new Promise((resolve2, reject) => {
|
|
282
|
+
if (signal?.aborted) {
|
|
283
|
+
reject(new Error("Operation aborted"));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
let tempFilePath;
|
|
287
|
+
let tempFileStream;
|
|
288
|
+
let totalBytes = 0;
|
|
289
|
+
const chunks = [];
|
|
290
|
+
let chunksBytes = 0;
|
|
291
|
+
const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
|
|
292
|
+
let timedOut = false;
|
|
293
|
+
const spawnCommand = buildSpawnCommand(resolvedShell, effectiveCommand);
|
|
294
|
+
const child = spawn(spawnCommand.file, spawnCommand.args, {
|
|
295
|
+
shell: spawnCommand.shell,
|
|
296
|
+
cwd: workingDir,
|
|
297
|
+
env: process.env,
|
|
298
|
+
detached: true
|
|
299
|
+
// Allows killing entire process tree
|
|
300
|
+
});
|
|
301
|
+
const timeoutId = setTimeout(() => {
|
|
302
|
+
timedOut = true;
|
|
303
|
+
if (child.pid) {
|
|
304
|
+
killProcessTree(child.pid);
|
|
305
|
+
}
|
|
306
|
+
}, timeoutMs);
|
|
307
|
+
const onAbort = () => {
|
|
308
|
+
clearTimeout(timeoutId);
|
|
309
|
+
if (child.pid) {
|
|
310
|
+
killProcessTree(child.pid);
|
|
311
|
+
}
|
|
312
|
+
reject(new Error("Operation aborted"));
|
|
313
|
+
};
|
|
314
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
315
|
+
child.stdout?.on("data", (data) => {
|
|
316
|
+
totalBytes += data.length;
|
|
317
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
318
|
+
tempFilePath = getTempFilePath();
|
|
319
|
+
tempFileStream = createWriteStream(tempFilePath);
|
|
320
|
+
for (const chunk of chunks) {
|
|
321
|
+
tempFileStream.write(chunk);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (tempFileStream) {
|
|
325
|
+
tempFileStream.write(data);
|
|
326
|
+
}
|
|
327
|
+
chunks.push(data);
|
|
328
|
+
chunksBytes += data.length;
|
|
329
|
+
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
330
|
+
const removed = chunks.shift();
|
|
331
|
+
chunksBytes -= removed.length;
|
|
332
|
+
}
|
|
333
|
+
if (onUpdate) {
|
|
334
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
335
|
+
const fullText = fullBuffer.toString("utf-8");
|
|
336
|
+
const truncation = truncateTail(fullText);
|
|
337
|
+
onUpdate({
|
|
338
|
+
content: [{ type: "text", text: truncation.content || "" }],
|
|
339
|
+
details: {
|
|
340
|
+
exitCode: null,
|
|
341
|
+
signal: null,
|
|
342
|
+
timedOut: false,
|
|
343
|
+
truncation: truncation.truncated ? {
|
|
344
|
+
truncated: true,
|
|
345
|
+
truncatedBy: truncation.truncatedBy,
|
|
346
|
+
totalLines: truncation.totalLines,
|
|
347
|
+
outputLines: truncation.outputLines
|
|
348
|
+
} : void 0,
|
|
349
|
+
fullOutputPath: tempFilePath,
|
|
350
|
+
shell: resolvedShell,
|
|
351
|
+
commandClass: risk.commandClass,
|
|
352
|
+
riskWarnings: risk.riskWarnings,
|
|
353
|
+
permissionDecision: {
|
|
354
|
+
allowed: true,
|
|
355
|
+
requiresPermission: risk.requiresPermission
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
child.stderr?.on("data", (data) => {
|
|
362
|
+
totalBytes += data.length;
|
|
363
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
364
|
+
tempFilePath = getTempFilePath();
|
|
365
|
+
tempFileStream = createWriteStream(tempFilePath);
|
|
366
|
+
for (const chunk of chunks) {
|
|
367
|
+
tempFileStream.write(chunk);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (tempFileStream) {
|
|
371
|
+
tempFileStream.write(data);
|
|
372
|
+
}
|
|
373
|
+
chunks.push(data);
|
|
374
|
+
chunksBytes += data.length;
|
|
375
|
+
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
376
|
+
const removed = chunks.shift();
|
|
377
|
+
chunksBytes -= removed.length;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
child.on("close", async (code, sig) => {
|
|
381
|
+
try {
|
|
382
|
+
clearTimeout(timeoutId);
|
|
383
|
+
signal?.removeEventListener("abort", onAbort);
|
|
384
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
385
|
+
let fullOutput = fullBuffer.toString("utf-8");
|
|
386
|
+
const truncation = truncateTail(fullOutput);
|
|
387
|
+
let outputText = truncation.content || "(no output)";
|
|
388
|
+
if (truncation.truncated && !tempFilePath) {
|
|
389
|
+
tempFilePath = getTempFilePath();
|
|
390
|
+
writeFileSync(tempFilePath, fullOutput, "utf-8");
|
|
391
|
+
}
|
|
392
|
+
await finishTempFileStream(tempFileStream);
|
|
393
|
+
const details = {
|
|
394
|
+
exitCode: code,
|
|
395
|
+
signal: sig?.toString() ?? null,
|
|
396
|
+
timedOut,
|
|
397
|
+
shell: resolvedShell,
|
|
398
|
+
commandClass: risk.commandClass,
|
|
399
|
+
riskWarnings: risk.riskWarnings,
|
|
400
|
+
permissionDecision: {
|
|
401
|
+
allowed: true,
|
|
402
|
+
requiresPermission: risk.requiresPermission
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
const semantic = interpretShellExit(effectiveCommand, code);
|
|
406
|
+
details.semanticExitMeaning = semantic.semanticExitMeaning;
|
|
407
|
+
if (truncation.truncated) {
|
|
408
|
+
details.truncation = {
|
|
409
|
+
truncated: true,
|
|
410
|
+
truncatedBy: truncation.truncatedBy,
|
|
411
|
+
totalLines: truncation.totalLines,
|
|
412
|
+
outputLines: truncation.outputLines
|
|
413
|
+
};
|
|
414
|
+
details.fullOutputPath = tempFilePath;
|
|
415
|
+
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
416
|
+
const endLine = truncation.totalLines;
|
|
417
|
+
if (truncation.lastLinePartial) {
|
|
418
|
+
const lastLineSize = formatSize(
|
|
419
|
+
Buffer.byteLength(fullOutput.split("\n").pop() || "", "utf-8")
|
|
420
|
+
);
|
|
421
|
+
outputText += `
|
|
422
|
+
|
|
423
|
+
[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;
|
|
424
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
425
|
+
outputText += `
|
|
426
|
+
|
|
427
|
+
[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;
|
|
428
|
+
} else {
|
|
429
|
+
outputText += `
|
|
430
|
+
|
|
431
|
+
[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (timedOut) {
|
|
435
|
+
outputText += `
|
|
436
|
+
|
|
437
|
+
[Command timed out after ${timeout ?? DEFAULT_TIMEOUT_SECONDS} seconds]`;
|
|
438
|
+
}
|
|
439
|
+
const recoveryHint = buildBashRecoveryHint(command, outputText);
|
|
440
|
+
if (recoveryHint) {
|
|
441
|
+
outputText += `
|
|
442
|
+
|
|
443
|
+
[Recovery hint: ${recoveryHint}]`;
|
|
444
|
+
}
|
|
445
|
+
const content = [{ type: "text", text: outputText }];
|
|
446
|
+
resolve2({ content, details, isError: timedOut || semantic.isError });
|
|
447
|
+
} catch (error) {
|
|
448
|
+
reject(error);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
child.on("error", (err) => {
|
|
452
|
+
clearTimeout(timeoutId);
|
|
453
|
+
signal?.removeEventListener("abort", onAbort);
|
|
454
|
+
if (tempFileStream) {
|
|
455
|
+
tempFileStream.end();
|
|
456
|
+
}
|
|
457
|
+
reject(new Error(`Failed to start bash command in ${workingDir}: ${err.message}. Check that the command is executable and cwd exists.`));
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/capabilities/code-intel.ts
|
|
465
|
+
import * as fs from "node:fs";
|
|
466
|
+
import * as path from "node:path";
|
|
467
|
+
var projectCache = /* @__PURE__ */ new Map();
|
|
468
|
+
var typescriptModulePromise;
|
|
469
|
+
function loadTypeScript() {
|
|
470
|
+
typescriptModulePromise ??= import("./typescript-GSKWJIO4.js");
|
|
471
|
+
return typescriptModulePromise;
|
|
472
|
+
}
|
|
473
|
+
function findTsconfig(startPath, projectRoot) {
|
|
474
|
+
let dir = fs.existsSync(startPath) && fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);
|
|
475
|
+
const root = path.resolve(projectRoot);
|
|
476
|
+
while (dir.startsWith(root)) {
|
|
477
|
+
const candidate = path.join(dir, "tsconfig.json");
|
|
478
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
479
|
+
const parent = path.dirname(dir);
|
|
480
|
+
if (parent === dir) break;
|
|
481
|
+
dir = parent;
|
|
482
|
+
}
|
|
483
|
+
const rootCandidate = path.join(root, "tsconfig.json");
|
|
484
|
+
return fs.existsSync(rootCandidate) ? rootCandidate : void 0;
|
|
485
|
+
}
|
|
486
|
+
function readConfig(ts, configPath) {
|
|
487
|
+
const config = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
488
|
+
if (config.error) {
|
|
489
|
+
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, "\n"));
|
|
490
|
+
}
|
|
491
|
+
const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
|
|
492
|
+
return { fileNames: parsed.fileNames, options: parsed.options };
|
|
493
|
+
}
|
|
494
|
+
function walkJsTs(root, limit = 1e3) {
|
|
495
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", "dist", "build", ".git", "Library", "Temp", "obj"]);
|
|
496
|
+
const out = [];
|
|
497
|
+
const visit = (dir) => {
|
|
498
|
+
if (out.length >= limit) return;
|
|
499
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
500
|
+
if (skip.has(entry.name)) continue;
|
|
501
|
+
const full = path.join(dir, entry.name);
|
|
502
|
+
if (entry.isDirectory()) visit(full);
|
|
503
|
+
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
504
|
+
if (out.length >= limit) return;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
if (fs.existsSync(root)) visit(root);
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
function createProject(ts, projectRoot, focusPath) {
|
|
511
|
+
const configPath = findTsconfig(focusPath ?? projectRoot, projectRoot);
|
|
512
|
+
const key = configPath ?? `loose:${projectRoot}`;
|
|
513
|
+
const cached = projectCache.get(key);
|
|
514
|
+
if (cached) return cached;
|
|
515
|
+
const config = configPath ? readConfig(ts, configPath) : {
|
|
516
|
+
fileNames: walkJsTs(projectRoot),
|
|
517
|
+
options: {
|
|
518
|
+
allowJs: true,
|
|
519
|
+
checkJs: true,
|
|
520
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
521
|
+
target: ts.ScriptTarget.ES2022,
|
|
522
|
+
module: ts.ModuleKind.ESNext,
|
|
523
|
+
jsx: ts.JsxEmit.ReactJSX
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const versions = new Map(config.fileNames.map((file) => [path.resolve(file), "0"]));
|
|
527
|
+
const host = {
|
|
528
|
+
getScriptFileNames: () => [...versions.keys()],
|
|
529
|
+
getScriptVersion: (fileName) => versions.get(path.resolve(fileName)) ?? "0",
|
|
530
|
+
getScriptSnapshot: (fileName) => {
|
|
531
|
+
if (!fs.existsSync(fileName)) return void 0;
|
|
532
|
+
return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName, "utf8"));
|
|
533
|
+
},
|
|
534
|
+
getCurrentDirectory: () => configPath ? path.dirname(configPath) : projectRoot,
|
|
535
|
+
getCompilationSettings: () => config.options,
|
|
536
|
+
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
537
|
+
fileExists: ts.sys.fileExists,
|
|
538
|
+
readFile: ts.sys.readFile,
|
|
539
|
+
readDirectory: ts.sys.readDirectory,
|
|
540
|
+
directoryExists: ts.sys.directoryExists,
|
|
541
|
+
getDirectories: ts.sys.getDirectories
|
|
542
|
+
};
|
|
543
|
+
const project = {
|
|
544
|
+
configPath,
|
|
545
|
+
service: ts.createLanguageService(host),
|
|
546
|
+
fileNames: [...versions.keys()],
|
|
547
|
+
options: config.options
|
|
548
|
+
};
|
|
549
|
+
projectCache.set(key, project);
|
|
550
|
+
return project;
|
|
551
|
+
}
|
|
552
|
+
function offsetOf(ts, sourceFile, line, character) {
|
|
553
|
+
if (!line || !character) throw new Error("operation requires path, line, and character");
|
|
554
|
+
return ts.getPositionOfLineAndCharacter(sourceFile, Math.max(0, line - 1), Math.max(0, character - 1));
|
|
555
|
+
}
|
|
556
|
+
function loc(fileName, start = 0) {
|
|
557
|
+
const text = fs.existsSync(fileName) ? fs.readFileSync(fileName, "utf8").slice(0, start) : "";
|
|
558
|
+
const lines = text.split(/\r?\n/);
|
|
559
|
+
return `${fileName}:${lines.length}:${lines[lines.length - 1].length + 1}`;
|
|
560
|
+
}
|
|
561
|
+
function diagnosticText(ts, d) {
|
|
562
|
+
const where = d.file && typeof d.start === "number" ? `${loc(d.file.fileName, d.start)} ` : "";
|
|
563
|
+
return `${where}${ts.flattenDiagnosticMessageText(d.messageText, "\n")}`;
|
|
564
|
+
}
|
|
565
|
+
function navItems(items, fileName) {
|
|
566
|
+
const out = [];
|
|
567
|
+
const visit = (item, prefix = "") => {
|
|
568
|
+
const span = item.spans[0];
|
|
569
|
+
out.push(`${prefix}${item.text} (${item.kind}) ${span ? loc(fileName, span.start) : fileName}`);
|
|
570
|
+
for (const child of item.childItems ?? []) visit(child, `${prefix}${item.text}.`);
|
|
571
|
+
};
|
|
572
|
+
for (const item of items) visit(item);
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
575
|
+
function createTypeScriptCodeIntelProvider() {
|
|
576
|
+
const lightweight = createLightweightCodeIntelProvider();
|
|
577
|
+
return {
|
|
578
|
+
async run(input, context) {
|
|
579
|
+
try {
|
|
580
|
+
const ts = await loadTypeScript();
|
|
581
|
+
const filePath = context.resolvePath(input.path);
|
|
582
|
+
const project = createProject(ts, context.projectRoot, filePath);
|
|
583
|
+
const files = filePath ? [filePath] : project.fileNames;
|
|
584
|
+
if (input.operation === "diagnostics") {
|
|
585
|
+
const diagnostics = files.flatMap((file) => [
|
|
586
|
+
...project.service.getSyntacticDiagnostics(file),
|
|
587
|
+
...project.service.getSemanticDiagnostics(file)
|
|
588
|
+
]);
|
|
589
|
+
return {
|
|
590
|
+
text: diagnostics.length ? diagnostics.map((diagnostic) => diagnosticText(ts, diagnostic)).join("\n") : "No TypeScript diagnostics found.",
|
|
591
|
+
details: {
|
|
592
|
+
operation: input.operation,
|
|
593
|
+
projectRoot: context.projectRoot,
|
|
594
|
+
path: filePath,
|
|
595
|
+
provider: "typescript_service",
|
|
596
|
+
resultCount: diagnostics.length,
|
|
597
|
+
diagnosticCount: diagnostics.length,
|
|
598
|
+
tsconfigPath: project.configPath
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
if (input.operation === "symbols") {
|
|
603
|
+
const query = input.query?.toLowerCase();
|
|
604
|
+
const symbols = files.flatMap((file) => navItems(project.service.getNavigationBarItems(file), file)).filter((line) => !query || line.toLowerCase().includes(query));
|
|
605
|
+
return {
|
|
606
|
+
text: symbols.length ? symbols.join("\n") : "No symbols found.",
|
|
607
|
+
details: { operation: input.operation, projectRoot: context.projectRoot, path: filePath, provider: "typescript_service", resultCount: symbols.length, tsconfigPath: project.configPath }
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (!filePath) throw new Error(`${input.operation} requires path`);
|
|
611
|
+
const source = project.service.getProgram()?.getSourceFile(filePath);
|
|
612
|
+
if (!source) throw new Error(`File is not part of the TypeScript project: ${input.path}`);
|
|
613
|
+
const offset = offsetOf(ts, source, input.line, input.character);
|
|
614
|
+
if (input.operation === "definition") {
|
|
615
|
+
const defs = project.service.getDefinitionAtPosition(filePath, offset) ?? [];
|
|
616
|
+
return {
|
|
617
|
+
text: defs.length ? defs.map((def) => `${def.name} ${loc(def.fileName, def.textSpan.start)}`).join("\n") : "No definition found.",
|
|
618
|
+
details: { operation: input.operation, projectRoot: context.projectRoot, path: filePath, provider: "typescript_service", resultCount: defs.length, tsconfigPath: project.configPath }
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
if (input.operation === "references") {
|
|
622
|
+
const refs = project.service.getReferencesAtPosition(filePath, offset) ?? [];
|
|
623
|
+
return {
|
|
624
|
+
text: refs.length ? refs.map((ref) => loc(ref.fileName, ref.textSpan.start)).join("\n") : "No references found.",
|
|
625
|
+
details: { operation: input.operation, projectRoot: context.projectRoot, path: filePath, provider: "typescript_service", resultCount: refs.length, tsconfigPath: project.configPath }
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const quickInfo = project.service.getQuickInfoAtPosition(filePath, offset);
|
|
629
|
+
const text = quickInfo ? ts.displayPartsToString(quickInfo.displayParts ?? []) + (quickInfo.documentation?.length ? `
|
|
630
|
+
${ts.displayPartsToString(quickInfo.documentation)}` : "") : "No hover information found.";
|
|
631
|
+
return {
|
|
632
|
+
text,
|
|
633
|
+
details: { operation: input.operation, projectRoot: context.projectRoot, path: filePath, provider: "typescript_service", resultCount: quickInfo ? 1 : 0, tsconfigPath: project.configPath }
|
|
634
|
+
};
|
|
635
|
+
} catch (error) {
|
|
636
|
+
const fallback = await lightweight.run(input, context);
|
|
637
|
+
return {
|
|
638
|
+
...fallback,
|
|
639
|
+
details: {
|
|
640
|
+
...fallback.details,
|
|
641
|
+
provider: "lightweight_fallback",
|
|
642
|
+
fallbackReason: error instanceof Error ? error.message : String(error)
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function createCodeIntelTool2(options) {
|
|
650
|
+
return createCodeIntelTool({
|
|
651
|
+
...options,
|
|
652
|
+
provider: options.provider ?? createTypeScriptCodeIntelProvider()
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/capabilities/index.ts
|
|
657
|
+
function createCliHostCapabilities(cwd, options = {}) {
|
|
658
|
+
const bashTool = createBashTool(cwd);
|
|
659
|
+
const webSearchTool = createSharedWebSearchTool({
|
|
660
|
+
getModel: options.getModel ?? (() => void 0),
|
|
661
|
+
getApiKey: options.getApiKey ?? (() => void 0),
|
|
662
|
+
getMode: options.getWebSearchMode,
|
|
663
|
+
resolveToolModel: options.resolveToolModel,
|
|
664
|
+
resolveToolModelCandidates: options.resolveToolModelCandidates
|
|
665
|
+
});
|
|
666
|
+
const webFetchTool = createSharedWebFetchTool({
|
|
667
|
+
getModel: options.getModel,
|
|
668
|
+
getApiKey: options.getApiKey
|
|
669
|
+
});
|
|
670
|
+
const codeIntelTool = createCodeIntelTool2({ projectRoot: cwd });
|
|
671
|
+
const capabilities = [
|
|
672
|
+
{
|
|
673
|
+
id: "bash",
|
|
674
|
+
description: "Node/TUI host capability for executing shell commands in the local workspace.",
|
|
675
|
+
tools: [bashTool]
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
id: "web_search",
|
|
679
|
+
description: "Provider-native web search for current information outside the local workspace.",
|
|
680
|
+
tools: [webSearchTool]
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
id: "web_fetch",
|
|
684
|
+
description: "Fetch and read a specific public web URL after a source has been found.",
|
|
685
|
+
tools: [webFetchTool]
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
id: "code_intel",
|
|
689
|
+
description: "Read-only JS/TS code intelligence for diagnostics, definitions, references, symbols, and hover.",
|
|
690
|
+
tools: [codeIntelTool]
|
|
691
|
+
}
|
|
692
|
+
];
|
|
693
|
+
return {
|
|
694
|
+
capabilities,
|
|
695
|
+
tools: capabilities.flatMap((capability) => capability.tools)
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export {
|
|
700
|
+
createCliHostCapabilities
|
|
701
|
+
};
|