@wrongstack/tools 0.277.2 → 0.280.1
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/dist/bash.js +2 -2
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +314 -15
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.js +12 -11
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +12 -11
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/exec-Ca3fnpUh.d.ts +121 -0
- package/dist/exec.d.ts +2 -39
- package/dist/exec.js +322 -11
- package/dist/exec.js.map +1 -1
- package/dist/index.d.ts +46 -3
- package/dist/index.js +629 -153
- package/dist/index.js.map +1 -1
- package/dist/install.js.map +1 -1
- package/dist/json.js +0 -1
- package/dist/json.js.map +1 -1
- package/dist/logs.js.map +1 -1
- package/dist/pack.js +314 -15
- package/dist/pack.js.map +1 -1
- package/dist/scaffold.js.map +1 -1
- package/dist/tool-icons.js +6 -6
- package/dist/tool-icons.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs7 from 'node:fs/promises';
|
|
2
2
|
import * as Core from '@wrongstack/core';
|
|
3
|
-
import { ToolValidationError, FsError, toErrorMessage, atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, ToolError, FetchError, isPrivateIPv4, isPrivateIPv6, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, mutateTasks, formatTaskList, formatPlan, assessCommitSafety, deepMerge, recordPackageAction, detectPackageEcosystem, getDesignKitLoader, isDesignStack, loadActiveKit, applyTokenOverrides, setActiveKit, recordKitChoice, recordOverrides, setDesignOverrides, materializeTokens, runDesignVerify, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
|
|
3
|
+
import { SKILL_LIMITS, ToolValidationError, FsError, toErrorMessage, atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, ToolError, FetchError, isPrivateIPv4, isPrivateIPv6, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, mutateTasks, formatTaskList, formatPlan, assessCommitSafety, deepMerge, recordPackageAction, detectPackageEcosystem, getDesignKitLoader, isDesignStack, loadActiveKit, applyTokenOverrides, setActiveKit, recordKitChoice, recordOverrides, setDesignOverrides, materializeTokens, runDesignVerify, stripFrontmatter, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { resolve, sep, dirname, join } from 'node:path';
|
|
6
6
|
import { spawn, execFileSync } from 'node:child_process';
|
|
@@ -21,14 +21,14 @@ import * as ts from 'typescript';
|
|
|
21
21
|
|
|
22
22
|
// src/read.ts
|
|
23
23
|
async function detectPackageManager(cwd) {
|
|
24
|
-
const { stat:
|
|
24
|
+
const { stat: stat12 } = await import('node:fs/promises');
|
|
25
25
|
try {
|
|
26
|
-
await
|
|
26
|
+
await stat12(`${cwd}/pnpm-lock.yaml`);
|
|
27
27
|
return "pnpm";
|
|
28
28
|
} catch {
|
|
29
29
|
}
|
|
30
30
|
try {
|
|
31
|
-
await
|
|
31
|
+
await stat12(`${cwd}/yarn.lock`);
|
|
32
32
|
return "yarn";
|
|
33
33
|
} catch {
|
|
34
34
|
}
|
|
@@ -214,9 +214,9 @@ var readTool = {
|
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
216
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
217
|
-
let
|
|
217
|
+
let stat12;
|
|
218
218
|
try {
|
|
219
|
-
|
|
219
|
+
stat12 = await fs7.stat(absPath);
|
|
220
220
|
} catch (err) {
|
|
221
221
|
const code = err.code;
|
|
222
222
|
if (code === "ENOENT") {
|
|
@@ -235,7 +235,7 @@ var readTool = {
|
|
|
235
235
|
cause: err
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
|
-
if (!
|
|
238
|
+
if (!stat12.isFile()) {
|
|
239
239
|
throw new FsError({
|
|
240
240
|
message: `read: "${input.path}" is not a regular file`,
|
|
241
241
|
code: "FS_READ_FAILED",
|
|
@@ -243,22 +243,22 @@ var readTool = {
|
|
|
243
243
|
context: { reason: "not-a-regular-file" }
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
|
-
if (
|
|
246
|
+
if (stat12.size > MAX_BYTES) {
|
|
247
247
|
throw new FsError({
|
|
248
|
-
message: `read: file too large (${
|
|
248
|
+
message: `read: file too large (${stat12.size} bytes, limit ${MAX_BYTES})`,
|
|
249
249
|
code: "FS_READ_FAILED",
|
|
250
250
|
path: absPath,
|
|
251
|
-
context: { size:
|
|
251
|
+
context: { size: stat12.size, limit: MAX_BYTES, reason: "too-large" }
|
|
252
252
|
});
|
|
253
253
|
}
|
|
254
254
|
const offset = Math.max(1, input.offset ?? 1);
|
|
255
255
|
const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
|
|
256
256
|
const prior = getReadRangeRecord(ctx, absPath);
|
|
257
257
|
const requestedEnd = prior ? Math.min(offset + limit - 1, prior.totalLines) : offset + limit - 1;
|
|
258
|
-
if (input.mode !== "summary" && limit > 0 && prior && coversRange(prior,
|
|
259
|
-
ctx.recordRead(absPath,
|
|
258
|
+
if (input.mode !== "summary" && limit > 0 && prior && coversRange(prior, stat12.mtimeMs, offset, requestedEnd)) {
|
|
259
|
+
ctx.recordRead(absPath, stat12.mtimeMs);
|
|
260
260
|
return {
|
|
261
|
-
text: `[unchanged since previous read: "${input.path}" mtime=${Math.round(
|
|
261
|
+
text: `[unchanged since previous read: "${input.path}" mtime=${Math.round(stat12.mtimeMs)}; requested lines ${offset}-${requestedEnd} were already shown. Use offset/limit for a new range if needed.]`,
|
|
262
262
|
total_lines: prior.totalLines,
|
|
263
263
|
encoding: "utf8",
|
|
264
264
|
truncated: requestedEnd < prior.totalLines,
|
|
@@ -274,10 +274,10 @@ var readTool = {
|
|
|
274
274
|
const allLines = text.split(/\r\n|\r|\n/);
|
|
275
275
|
const total = allLines.length;
|
|
276
276
|
if (input.mode === "summary") {
|
|
277
|
-
ctx.recordRead(absPath,
|
|
278
|
-
rememberReadRange(ctx, absPath,
|
|
277
|
+
ctx.recordRead(absPath, stat12.mtimeMs);
|
|
278
|
+
rememberReadRange(ctx, absPath, stat12.mtimeMs, total, 1, Math.min(total, 200));
|
|
279
279
|
return {
|
|
280
|
-
text: summarizeFile(input.path,
|
|
280
|
+
text: summarizeFile(input.path, stat12.size, allLines),
|
|
281
281
|
total_lines: total,
|
|
282
282
|
encoding: "utf8",
|
|
283
283
|
truncated: total > 200,
|
|
@@ -285,13 +285,13 @@ var readTool = {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
if (limit === 0) {
|
|
288
|
-
ctx.recordRead(absPath,
|
|
289
|
-
rememberReadRange(ctx, absPath,
|
|
288
|
+
ctx.recordRead(absPath, stat12.mtimeMs);
|
|
289
|
+
rememberReadRange(ctx, absPath, stat12.mtimeMs, total, 1, 0);
|
|
290
290
|
return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
|
|
291
291
|
}
|
|
292
292
|
if (offset > total) {
|
|
293
|
-
ctx.recordRead(absPath,
|
|
294
|
-
rememberReadRange(ctx, absPath,
|
|
293
|
+
ctx.recordRead(absPath, stat12.mtimeMs);
|
|
294
|
+
rememberReadRange(ctx, absPath, stat12.mtimeMs, total, total + 1, total + 1);
|
|
295
295
|
return {
|
|
296
296
|
text: `[offset ${offset} is past end of file "${input.path}" \u2014 file has ${total} line(s). Do not retry this offset.]`,
|
|
297
297
|
total_lines: total,
|
|
@@ -303,8 +303,8 @@ var readTool = {
|
|
|
303
303
|
const truncated = offset - 1 + slice.length < total;
|
|
304
304
|
const width = String(offset + slice.length - 1).length;
|
|
305
305
|
const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
|
|
306
|
-
ctx.recordRead(absPath,
|
|
307
|
-
rememberReadRange(ctx, absPath,
|
|
306
|
+
ctx.recordRead(absPath, stat12.mtimeMs);
|
|
307
|
+
rememberReadRange(ctx, absPath, stat12.mtimeMs, total, offset, offset + slice.length - 1);
|
|
308
308
|
return {
|
|
309
309
|
text: numbered,
|
|
310
310
|
total_lines: total,
|
|
@@ -410,12 +410,12 @@ var writeTool = {
|
|
|
410
410
|
let existed = false;
|
|
411
411
|
let prev = "";
|
|
412
412
|
try {
|
|
413
|
-
const
|
|
414
|
-
existed =
|
|
413
|
+
const stat13 = await fs7.stat(absPath);
|
|
414
|
+
existed = stat13.isFile();
|
|
415
415
|
if (existed) {
|
|
416
416
|
if (!ctx.hasRead(absPath)) {
|
|
417
417
|
prev = await fs7.readFile(absPath, "utf8");
|
|
418
|
-
ctx.recordRead(absPath,
|
|
418
|
+
ctx.recordRead(absPath, stat13.mtimeMs, "write");
|
|
419
419
|
} else {
|
|
420
420
|
prev = await fs7.readFile(absPath, "utf8");
|
|
421
421
|
}
|
|
@@ -428,8 +428,8 @@ var writeTool = {
|
|
|
428
428
|
await atomicWrite(absPath, input.content);
|
|
429
429
|
const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
|
|
430
430
|
+ (new file, ${input.content.split("\n").length} lines)`;
|
|
431
|
-
const
|
|
432
|
-
ctx.recordRead(absPath,
|
|
431
|
+
const stat12 = await fs7.stat(absPath);
|
|
432
|
+
ctx.recordRead(absPath, stat12.mtimeMs, "write");
|
|
433
433
|
ctx.session.recordFileChange({
|
|
434
434
|
path: absPath,
|
|
435
435
|
action: existed ? "modified" : "created",
|
|
@@ -487,7 +487,7 @@ var editTool = {
|
|
|
487
487
|
});
|
|
488
488
|
}
|
|
489
489
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
490
|
-
const
|
|
490
|
+
const stat12 = await fs7.stat(absPath).catch((err) => {
|
|
491
491
|
if (err.code === "ENOENT") {
|
|
492
492
|
throw new ToolValidationError({
|
|
493
493
|
message: `edit: file "${input.path}" does not exist. Use \`write\` instead.`,
|
|
@@ -497,7 +497,7 @@ var editTool = {
|
|
|
497
497
|
}
|
|
498
498
|
throw err;
|
|
499
499
|
});
|
|
500
|
-
if (!
|
|
500
|
+
if (!stat12.isFile()) {
|
|
501
501
|
throw new ToolValidationError({
|
|
502
502
|
message: `edit: "${input.path}" is not a regular file`,
|
|
503
503
|
field: "path"
|
|
@@ -515,7 +515,7 @@ var editTool = {
|
|
|
515
515
|
context: { reason: "external_modification" }
|
|
516
516
|
});
|
|
517
517
|
}
|
|
518
|
-
if (autoRead && updated.mtimeMs >
|
|
518
|
+
if (autoRead && updated.mtimeMs > stat12.mtimeMs + mtimeTolerance) {
|
|
519
519
|
throw new ToolValidationError({
|
|
520
520
|
message: `edit: file "${input.path}" changed while being auto-read. Retry the edit.`,
|
|
521
521
|
field: "path",
|
|
@@ -732,8 +732,8 @@ var replaceTool = {
|
|
|
732
732
|
}
|
|
733
733
|
const rel = path.relative(realRoot, realPath);
|
|
734
734
|
if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
|
|
735
|
-
const
|
|
736
|
-
if (!
|
|
735
|
+
const stat12 = await fs7.stat(realPath).catch(() => null);
|
|
736
|
+
if (!stat12?.isFile()) continue;
|
|
737
737
|
let content;
|
|
738
738
|
try {
|
|
739
739
|
const buf = await fs7.readFile(realPath);
|
|
@@ -758,7 +758,7 @@ var replaceTool = {
|
|
|
758
758
|
totalReplacements += count;
|
|
759
759
|
if (!dryRun) {
|
|
760
760
|
const newContent = toStyle(newContentLf, style);
|
|
761
|
-
await atomicWrite(realPath, newContent, { mode:
|
|
761
|
+
await atomicWrite(realPath, newContent, { mode: stat12.mode & 511 });
|
|
762
762
|
}
|
|
763
763
|
const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
|
|
764
764
|
fromFile: absPath,
|
|
@@ -788,8 +788,8 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
|
|
|
788
788
|
const resolved = [];
|
|
789
789
|
for (const p of parts) {
|
|
790
790
|
const absPath = safeResolve(p, ctx);
|
|
791
|
-
const
|
|
792
|
-
if (
|
|
791
|
+
const stat12 = await fs7.stat(absPath).catch(() => null);
|
|
792
|
+
if (stat12?.isFile()) {
|
|
793
793
|
resolved.push(absPath);
|
|
794
794
|
}
|
|
795
795
|
}
|
|
@@ -807,13 +807,13 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
807
807
|
return await globNative(pattern, base, extraGlob);
|
|
808
808
|
}
|
|
809
809
|
function checkRg() {
|
|
810
|
-
return new Promise((
|
|
810
|
+
return new Promise((resolve8) => {
|
|
811
811
|
try {
|
|
812
812
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", windowsHide: true });
|
|
813
|
-
p.on("error", () =>
|
|
814
|
-
p.on("close", (code) =>
|
|
813
|
+
p.on("error", () => resolve8(false));
|
|
814
|
+
p.on("close", (code) => resolve8(code === 0));
|
|
815
815
|
} catch {
|
|
816
|
-
|
|
816
|
+
resolve8(false);
|
|
817
817
|
}
|
|
818
818
|
});
|
|
819
819
|
}
|
|
@@ -830,10 +830,10 @@ function spawnRgFind(pattern, base) {
|
|
|
830
830
|
buf += chunk.toString();
|
|
831
831
|
});
|
|
832
832
|
return {
|
|
833
|
-
promise: new Promise((
|
|
833
|
+
promise: new Promise((resolve8, reject) => {
|
|
834
834
|
child.on("error", reject);
|
|
835
835
|
child.on("close", () => {
|
|
836
|
-
|
|
836
|
+
resolve8(buf.split("\n").filter(Boolean));
|
|
837
837
|
});
|
|
838
838
|
})
|
|
839
839
|
};
|
|
@@ -841,7 +841,7 @@ function spawnRgFind(pattern, base) {
|
|
|
841
841
|
async function globNative(pattern, base, extraGlob) {
|
|
842
842
|
const results = [];
|
|
843
843
|
const globRe = compileGlob(pattern);
|
|
844
|
-
const
|
|
844
|
+
const walk2 = async (dir) => {
|
|
845
845
|
let entries;
|
|
846
846
|
try {
|
|
847
847
|
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
@@ -852,13 +852,13 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
852
852
|
if (DEFAULT_IGNORE.includes(e.name)) continue;
|
|
853
853
|
const full = path.join(dir, e.name);
|
|
854
854
|
try {
|
|
855
|
-
const
|
|
856
|
-
if (
|
|
855
|
+
const stat12 = await fs7.lstat(full);
|
|
856
|
+
if (stat12.isSymbolicLink()) continue;
|
|
857
857
|
} catch {
|
|
858
858
|
continue;
|
|
859
859
|
}
|
|
860
860
|
if (e.isDirectory()) {
|
|
861
|
-
await
|
|
861
|
+
await walk2(full);
|
|
862
862
|
} else if (e.isFile()) {
|
|
863
863
|
const name = e.name;
|
|
864
864
|
if (globRe.test(name) || globRe.test(full)) {
|
|
@@ -870,7 +870,7 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
870
870
|
}
|
|
871
871
|
}
|
|
872
872
|
};
|
|
873
|
-
await
|
|
873
|
+
await walk2(base);
|
|
874
874
|
return results;
|
|
875
875
|
}
|
|
876
876
|
|
|
@@ -947,7 +947,7 @@ var globTool = {
|
|
|
947
947
|
} catch {
|
|
948
948
|
}
|
|
949
949
|
};
|
|
950
|
-
const
|
|
950
|
+
const walk2 = async (dir, relPrefix) => {
|
|
951
951
|
if (results.length >= limit) {
|
|
952
952
|
truncated = true;
|
|
953
953
|
return;
|
|
@@ -1000,9 +1000,9 @@ var globTool = {
|
|
|
1000
1000
|
await mapWithConcurrency(matchedFiles, WALK_CONCURRENCY, pushResult);
|
|
1001
1001
|
if (truncated) return;
|
|
1002
1002
|
const remainingSubdirs = truncated ? [] : subdirs;
|
|
1003
|
-
await mapWithConcurrency(remainingSubdirs, WALK_CONCURRENCY, ({ full, rel }) =>
|
|
1003
|
+
await mapWithConcurrency(remainingSubdirs, WALK_CONCURRENCY, ({ full, rel }) => walk2(full, rel));
|
|
1004
1004
|
};
|
|
1005
|
-
await
|
|
1005
|
+
await walk2(base, "");
|
|
1006
1006
|
results.sort((a, b) => b.mtime - a.mtime);
|
|
1007
1007
|
return { files: results.map((r) => r.rel), truncated };
|
|
1008
1008
|
}
|
|
@@ -1106,13 +1106,13 @@ var grepTool = {
|
|
|
1106
1106
|
}
|
|
1107
1107
|
};
|
|
1108
1108
|
async function detectRg(signal) {
|
|
1109
|
-
return new Promise((
|
|
1109
|
+
return new Promise((resolve8) => {
|
|
1110
1110
|
try {
|
|
1111
1111
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", signal, windowsHide: true });
|
|
1112
|
-
p.on("error", () =>
|
|
1113
|
-
p.on("close", (code) =>
|
|
1112
|
+
p.on("error", () => resolve8(false));
|
|
1113
|
+
p.on("close", (code) => resolve8(code === 0));
|
|
1114
1114
|
} catch {
|
|
1115
|
-
|
|
1115
|
+
resolve8(false);
|
|
1116
1116
|
}
|
|
1117
1117
|
});
|
|
1118
1118
|
}
|
|
@@ -1263,8 +1263,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1263
1263
|
if (globRe && !globRe.test(name) && !globRe.test(full)) return;
|
|
1264
1264
|
if (globRe) globRe.lastIndex = 0;
|
|
1265
1265
|
try {
|
|
1266
|
-
const
|
|
1267
|
-
if (!
|
|
1266
|
+
const stat12 = await fs7.stat(full);
|
|
1267
|
+
if (!stat12.isFile() || stat12.size > maxBytes || stopped || signal.aborted) return;
|
|
1268
1268
|
const file = await fs7.open(full, "r");
|
|
1269
1269
|
try {
|
|
1270
1270
|
let bytesReadTotal = 0;
|
|
@@ -1340,7 +1340,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1340
1340
|
} catch {
|
|
1341
1341
|
}
|
|
1342
1342
|
};
|
|
1343
|
-
const
|
|
1343
|
+
const walk2 = async (dir) => {
|
|
1344
1344
|
if (stopped || signal.aborted) return;
|
|
1345
1345
|
let entries;
|
|
1346
1346
|
try {
|
|
@@ -1362,9 +1362,9 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1362
1362
|
}
|
|
1363
1363
|
}
|
|
1364
1364
|
await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
|
|
1365
|
-
await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY),
|
|
1365
|
+
await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY), walk2);
|
|
1366
1366
|
};
|
|
1367
|
-
await
|
|
1367
|
+
await walk2(base);
|
|
1368
1368
|
return {
|
|
1369
1369
|
matches,
|
|
1370
1370
|
count: total,
|
|
@@ -2643,7 +2643,7 @@ function looksLikePowerShell(command) {
|
|
|
2643
2643
|
if (/&\s+\$/.test(trimmed)) return true;
|
|
2644
2644
|
if (/(^|\s)@\s*\(/.test(trimmed)) return true;
|
|
2645
2645
|
if (/(^|\s)@\{/.test(trimmed)) return true;
|
|
2646
|
-
if (/(?:^|[\s
|
|
2646
|
+
if (/(?:^|[\s[({,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\s\])},;])/i.test(trimmed)) {
|
|
2647
2647
|
return true;
|
|
2648
2648
|
}
|
|
2649
2649
|
if (PS_VERB_RE.test(trimmed)) return true;
|
|
@@ -2670,7 +2670,7 @@ function looksLikePowerShellExtended(command) {
|
|
|
2670
2670
|
return true;
|
|
2671
2671
|
}
|
|
2672
2672
|
if (/^\s*<#|#>\s*$/m.test(trimmed)) return true;
|
|
2673
|
-
if (/(?:^|\s)[
|
|
2673
|
+
if (/(?:^|\s)[-//](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\s|=|$)/i.test(trimmed)) {
|
|
2674
2674
|
return true;
|
|
2675
2675
|
}
|
|
2676
2676
|
return false;
|
|
@@ -3093,10 +3093,10 @@ var bashTool = {
|
|
|
3093
3093
|
queue.push(c);
|
|
3094
3094
|
}
|
|
3095
3095
|
};
|
|
3096
|
-
const next = () => new Promise((
|
|
3096
|
+
const next = () => new Promise((resolve8) => {
|
|
3097
3097
|
const c = queue.shift();
|
|
3098
|
-
if (c)
|
|
3099
|
-
else resolveNext =
|
|
3098
|
+
if (c) resolve8(c);
|
|
3099
|
+
else resolveNext = resolve8;
|
|
3100
3100
|
});
|
|
3101
3101
|
let lastFlush = Date.now();
|
|
3102
3102
|
const flush = () => {
|
|
@@ -3257,6 +3257,294 @@ function ensureSessionShell(opts = {}) {
|
|
|
3257
3257
|
env["WRONGSTACK_SHELL"] = chosen;
|
|
3258
3258
|
return chosen;
|
|
3259
3259
|
}
|
|
3260
|
+
|
|
3261
|
+
// src/_danger-detect.ts
|
|
3262
|
+
var argHas = (args, value) => args.includes(value);
|
|
3263
|
+
var argMatches = (args, re) => args.some((a) => re.test(a));
|
|
3264
|
+
var hasShortFlags = (args, letters) => {
|
|
3265
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3266
|
+
for (const a of args) {
|
|
3267
|
+
if (!a.startsWith("-") || a.startsWith("--")) continue;
|
|
3268
|
+
for (const ch of a.replace(/^-+/, "")) seen.add(ch);
|
|
3269
|
+
}
|
|
3270
|
+
return letters.split("").every((l) => seen.has(l));
|
|
3271
|
+
};
|
|
3272
|
+
var RULES = [
|
|
3273
|
+
// ----- rm / rmdir: recursive force delete (any path) -----
|
|
3274
|
+
// Note: BLOCKED_ARG_PATTERNS already hard-denies root/home/glob paths,
|
|
3275
|
+
// but `rm -rf ./build` is a normal dev workflow that the user might
|
|
3276
|
+
// want to do intentionally. We downgrade it to 'destructive' so the
|
|
3277
|
+
// confirm prompt can approve.
|
|
3278
|
+
{
|
|
3279
|
+
id: "rm-recursive",
|
|
3280
|
+
level: "destructive",
|
|
3281
|
+
test: (cmd, args) => (cmd === "rm" || cmd === "rmdir") && hasShortFlags(args, "rf"),
|
|
3282
|
+
reason: "recursive force-delete"
|
|
3283
|
+
},
|
|
3284
|
+
// ----- Windows PowerShell Remove-Item: -Recurse -Force -----
|
|
3285
|
+
{
|
|
3286
|
+
id: "powershell-remove-item-recursive-force",
|
|
3287
|
+
level: "destructive",
|
|
3288
|
+
test: (cmd, args) => {
|
|
3289
|
+
if (cmd !== "powershell" && cmd !== "pwsh") return false;
|
|
3290
|
+
const hasRecurse = argMatches(args, /^-(?:R|Recurse|Recurse\s)/);
|
|
3291
|
+
const hasForce = argHas(args, "-Force") || argHas(args, "-F");
|
|
3292
|
+
if (argHas(args, "-WhatIf")) return false;
|
|
3293
|
+
return hasRecurse && hasForce;
|
|
3294
|
+
},
|
|
3295
|
+
reason: "Remove-Item with -Recurse -Force"
|
|
3296
|
+
},
|
|
3297
|
+
// ----- find -exec / -ok / -execdir -----
|
|
3298
|
+
{
|
|
3299
|
+
id: "find-exec",
|
|
3300
|
+
level: "destructive",
|
|
3301
|
+
test: (cmd, args) => {
|
|
3302
|
+
if (cmd !== "find") return false;
|
|
3303
|
+
return args.some(
|
|
3304
|
+
(a) => a === "-exec" || a === "-exec;" || a === "-ok" || a === "-ok;" || a === "-execdir" || a === "-execdir;" || a.startsWith("-exec=") || a.startsWith("-ok=") || a.startsWith("-execdir=")
|
|
3305
|
+
);
|
|
3306
|
+
},
|
|
3307
|
+
reason: "find with -exec/-ok (executes arbitrary command on matches)"
|
|
3308
|
+
},
|
|
3309
|
+
// ----- git --exec= / --upload-pack= / --receive-pack= -----
|
|
3310
|
+
// These run arbitrary commands via the git transport layer.
|
|
3311
|
+
{
|
|
3312
|
+
id: "git-exec",
|
|
3313
|
+
level: "destructive",
|
|
3314
|
+
test: (cmd, args) => cmd === "git" && args.some(
|
|
3315
|
+
(a) => a.startsWith("--exec=") || a.startsWith("--upload-pack=") || a.startsWith("--receive-pack=") || a === "--exec" || a === "--upload-pack" || a === "--receive-pack"
|
|
3316
|
+
),
|
|
3317
|
+
reason: "git with --exec/--upload-pack/--receive-pack (runs arbitrary code)"
|
|
3318
|
+
},
|
|
3319
|
+
// ----- Windows: format / diskpart / bcdedit -----
|
|
3320
|
+
{
|
|
3321
|
+
id: "win32-format",
|
|
3322
|
+
level: "destructive",
|
|
3323
|
+
test: (cmd) => cmd === "format" || cmd === "format.exe",
|
|
3324
|
+
reason: "format (Windows disk format)"
|
|
3325
|
+
},
|
|
3326
|
+
{
|
|
3327
|
+
id: "win32-diskpart",
|
|
3328
|
+
level: "destructive",
|
|
3329
|
+
test: (cmd) => cmd === "diskpart" || cmd === "diskpart.exe",
|
|
3330
|
+
reason: "diskpart (Windows partition editor)"
|
|
3331
|
+
},
|
|
3332
|
+
{
|
|
3333
|
+
id: "win32-bcdedit",
|
|
3334
|
+
level: "destructive",
|
|
3335
|
+
test: (cmd) => cmd === "bcdedit" || cmd === "bcdedit.exe",
|
|
3336
|
+
reason: "bcdedit (Windows boot config editor)"
|
|
3337
|
+
},
|
|
3338
|
+
// ----- mkfs family -----
|
|
3339
|
+
{
|
|
3340
|
+
id: "mkfs",
|
|
3341
|
+
level: "destructive",
|
|
3342
|
+
test: (cmd) => /^mkfs(\.[a-z0-9]+)?$/.test(cmd) || cmd === "mkswap",
|
|
3343
|
+
reason: "mkfs (filesystem creation \u2014 destroys existing data)"
|
|
3344
|
+
},
|
|
3345
|
+
// ----- dd writing to a block device -----
|
|
3346
|
+
{
|
|
3347
|
+
id: "dd-to-block-device",
|
|
3348
|
+
level: "destructive",
|
|
3349
|
+
test: (cmd, args) => {
|
|
3350
|
+
if (cmd !== "dd") return false;
|
|
3351
|
+
return args.some((a) => /of=\/dev\/(sd|hd|nvme|vd|mmcblk|xvd|loop|disk)/.test(a));
|
|
3352
|
+
},
|
|
3353
|
+
reason: "dd writing to a block device"
|
|
3354
|
+
},
|
|
3355
|
+
// ----- Secure-erase tools -----
|
|
3356
|
+
{
|
|
3357
|
+
id: "shred",
|
|
3358
|
+
level: "destructive",
|
|
3359
|
+
test: (cmd) => cmd === "shred" || cmd === "shred.exe",
|
|
3360
|
+
reason: "shred (secure file delete)"
|
|
3361
|
+
},
|
|
3362
|
+
{
|
|
3363
|
+
id: "wipefs",
|
|
3364
|
+
level: "destructive",
|
|
3365
|
+
test: (cmd) => cmd === "wipefs" || cmd === "wipefs.exe",
|
|
3366
|
+
reason: "wipefs (signature wipe \u2014 destroys filesystem headers)"
|
|
3367
|
+
},
|
|
3368
|
+
{
|
|
3369
|
+
id: "sdelete",
|
|
3370
|
+
level: "destructive",
|
|
3371
|
+
test: (cmd) => cmd === "sdelete" || cmd === "sdelete.exe",
|
|
3372
|
+
reason: "sdelete (Sysinternals secure delete)"
|
|
3373
|
+
},
|
|
3374
|
+
// ----- VCS history rewrite (destructive) -----
|
|
3375
|
+
// `git push --force` / `-f` rewrites remote history. `--force-with-lease`
|
|
3376
|
+
// is the safer variant (checks remote hasn't moved) but still rewrites.
|
|
3377
|
+
{
|
|
3378
|
+
id: "git-push-force",
|
|
3379
|
+
level: "destructive",
|
|
3380
|
+
test: (cmd, args) => {
|
|
3381
|
+
if (cmd !== "git") return false;
|
|
3382
|
+
const pushIdx = args.indexOf("push");
|
|
3383
|
+
if (pushIdx < 0) return false;
|
|
3384
|
+
for (let i = pushIdx + 1; i < args.length; i++) {
|
|
3385
|
+
const a = args[i];
|
|
3386
|
+
if (a === "--force" || a === "-f" || a === "--force-with-lease") return true;
|
|
3387
|
+
if (!a.startsWith("-") && !a.includes("=")) continue;
|
|
3388
|
+
if (a.startsWith("--force")) return true;
|
|
3389
|
+
}
|
|
3390
|
+
return false;
|
|
3391
|
+
},
|
|
3392
|
+
reason: "git push with --force / -f (rewrites remote history)"
|
|
3393
|
+
},
|
|
3394
|
+
// ----- git reset --hard (destructive) -----
|
|
3395
|
+
{
|
|
3396
|
+
id: "git-reset-hard",
|
|
3397
|
+
level: "destructive",
|
|
3398
|
+
test: (cmd, args) => cmd === "git" && args.some((a) => a === "--hard" || a.startsWith("--hard=")),
|
|
3399
|
+
reason: "git reset --hard (discards working tree + index)"
|
|
3400
|
+
},
|
|
3401
|
+
// ----- git clean -f / -fd (destructive) -----
|
|
3402
|
+
{
|
|
3403
|
+
id: "git-clean-force",
|
|
3404
|
+
level: "destructive",
|
|
3405
|
+
test: (cmd, args) => {
|
|
3406
|
+
if (cmd !== "git") return false;
|
|
3407
|
+
const cleanIdx = args.indexOf("clean");
|
|
3408
|
+
if (cleanIdx < 0) return false;
|
|
3409
|
+
return args.slice(cleanIdx + 1).some(
|
|
3410
|
+
(a) => a === "-f" || a === "--force" || a.startsWith("-f") || a.startsWith("--force=")
|
|
3411
|
+
);
|
|
3412
|
+
},
|
|
3413
|
+
reason: "git clean -f (deletes untracked files)"
|
|
3414
|
+
},
|
|
3415
|
+
// ----- package publish (destructive — public, irreversible) -----
|
|
3416
|
+
{
|
|
3417
|
+
id: "npm-publish",
|
|
3418
|
+
level: "destructive",
|
|
3419
|
+
test: (cmd, args) => {
|
|
3420
|
+
if (!["npm", "pnpm", "yarn", "bun", "cargo"].includes(cmd)) return false;
|
|
3421
|
+
return args.includes("publish") || cmd === "cargo" && args.includes("yank");
|
|
3422
|
+
},
|
|
3423
|
+
reason: "publishing to a public package registry (hard to reverse)"
|
|
3424
|
+
},
|
|
3425
|
+
// ----- k8s cluster-wide destructive ops (destructive) -----
|
|
3426
|
+
{
|
|
3427
|
+
id: "kubectl-delete-namespace",
|
|
3428
|
+
level: "destructive",
|
|
3429
|
+
test: (cmd, args) => {
|
|
3430
|
+
if (cmd !== "kubectl") return false;
|
|
3431
|
+
const delIdx = args.indexOf("delete");
|
|
3432
|
+
if (delIdx < 0) return false;
|
|
3433
|
+
const after = args.slice(delIdx + 1);
|
|
3434
|
+
return after[0] === "namespace" || after[0] === "ns";
|
|
3435
|
+
},
|
|
3436
|
+
reason: "kubectl delete namespace (deletes all resources in the namespace)"
|
|
3437
|
+
},
|
|
3438
|
+
{
|
|
3439
|
+
id: "kubectl-drain",
|
|
3440
|
+
level: "destructive",
|
|
3441
|
+
test: (cmd, args) => cmd === "kubectl" && args.includes("drain"),
|
|
3442
|
+
reason: "kubectl drain (evicts pods, marks node unschedulable)"
|
|
3443
|
+
},
|
|
3444
|
+
// ----- inline code evaluation (caution — high false-positive) -----
|
|
3445
|
+
// Common in scripts: `python -c "..."`, `node -e "..."`, `bash -c "..."`.
|
|
3446
|
+
// We tag 'caution' rather than 'destructive' because these are used in
|
|
3447
|
+
// many legitimate one-liners (e.g. `python -c "print(1)"`).
|
|
3448
|
+
{
|
|
3449
|
+
id: "inline-eval",
|
|
3450
|
+
level: "caution",
|
|
3451
|
+
test: (cmd, args) => {
|
|
3452
|
+
if (![
|
|
3453
|
+
"python",
|
|
3454
|
+
"python3",
|
|
3455
|
+
"python2",
|
|
3456
|
+
"node",
|
|
3457
|
+
"bash",
|
|
3458
|
+
"sh",
|
|
3459
|
+
"zsh",
|
|
3460
|
+
"ruby",
|
|
3461
|
+
"perl",
|
|
3462
|
+
"lua"
|
|
3463
|
+
].includes(cmd)) {
|
|
3464
|
+
return false;
|
|
3465
|
+
}
|
|
3466
|
+
return args.some(
|
|
3467
|
+
(a) => a === "-c" || a === "-e" || a === "--eval" || a === "-eval" || a === "-E"
|
|
3468
|
+
);
|
|
3469
|
+
},
|
|
3470
|
+
reason: "inline script evaluation (-c / -e / --eval)"
|
|
3471
|
+
},
|
|
3472
|
+
// ----- pipe-to-shell (caution — well-known exfil pattern) -----
|
|
3473
|
+
// The classic `curl https://... | sh` download-and-run vector. Detected by
|
|
3474
|
+
// looking for a known fetcher followed by a shell sink. We use a simple
|
|
3475
|
+
// substring scan; false positives are limited because both tokens must
|
|
3476
|
+
// appear in the same argv.
|
|
3477
|
+
{
|
|
3478
|
+
id: "pipe-to-shell",
|
|
3479
|
+
level: "caution",
|
|
3480
|
+
test: (_cmd, args) => {
|
|
3481
|
+
const hasFetcher = args.some(
|
|
3482
|
+
(a) => /^(curl|wget|fetch|httpie|http)$/i.test(a) || a.startsWith("curl") || a.startsWith("wget")
|
|
3483
|
+
);
|
|
3484
|
+
const hasShellSink = args.some(
|
|
3485
|
+
(a) => a === "sh" || a === "bash" || a === "zsh" || a === "fish" || a === "pwsh" || a === "powershell" || a.endsWith("/sh") || a.endsWith("/bash") || a.endsWith("/zsh") || a.endsWith("/pwsh")
|
|
3486
|
+
);
|
|
3487
|
+
return hasFetcher && hasShellSink;
|
|
3488
|
+
},
|
|
3489
|
+
reason: "network fetch piped to a shell (download-and-run pattern)"
|
|
3490
|
+
},
|
|
3491
|
+
// ----- privilege escalation (caution) -----
|
|
3492
|
+
{
|
|
3493
|
+
id: "sudo",
|
|
3494
|
+
level: "caution",
|
|
3495
|
+
test: (cmd) => cmd === "sudo" || cmd === "doas",
|
|
3496
|
+
reason: "privilege escalation (sudo / doas)"
|
|
3497
|
+
},
|
|
3498
|
+
{
|
|
3499
|
+
id: "runas",
|
|
3500
|
+
level: "caution",
|
|
3501
|
+
test: (cmd) => cmd === "runas" || cmd === "runas.exe",
|
|
3502
|
+
reason: "Windows runas (run as different user)"
|
|
3503
|
+
},
|
|
3504
|
+
// ----- world-writable permissions (caution) -----
|
|
3505
|
+
// `chmod 777` is rarely correct. `chmod -R 777` is almost always wrong.
|
|
3506
|
+
// We only flag octal modes; symbolic modes like `chmod o+w` are
|
|
3507
|
+
// left to the operator's discretion.
|
|
3508
|
+
{
|
|
3509
|
+
id: "chmod-world-writable",
|
|
3510
|
+
level: "caution",
|
|
3511
|
+
test: (cmd, args) => {
|
|
3512
|
+
if (cmd !== "chmod") return false;
|
|
3513
|
+
return args.some((a) => /^[0-7]{3,4}$/.test(a) && /7/.test(a));
|
|
3514
|
+
},
|
|
3515
|
+
reason: "chmod with world-writable octal mode (e.g. 777)"
|
|
3516
|
+
}
|
|
3517
|
+
];
|
|
3518
|
+
function detectDanger(cmd, args, bypass) {
|
|
3519
|
+
const reasons = [];
|
|
3520
|
+
let level = "safe";
|
|
3521
|
+
let matchedRule;
|
|
3522
|
+
for (const rule of RULES) {
|
|
3523
|
+
if (bypass?.has(rule.id)) continue;
|
|
3524
|
+
if (!rule.test(cmd, args)) continue;
|
|
3525
|
+
reasons.push(rule.reason);
|
|
3526
|
+
matchedRule = rule.id;
|
|
3527
|
+
if (levelRank(rule.level) > levelRank(level)) {
|
|
3528
|
+
level = rule.level;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
if (level === "safe") return { level: "safe", reasons: [] };
|
|
3532
|
+
const result = { level, reasons };
|
|
3533
|
+
if (matchedRule !== void 0) result.matchedRule = matchedRule;
|
|
3534
|
+
return result;
|
|
3535
|
+
}
|
|
3536
|
+
function levelRank(level) {
|
|
3537
|
+
switch (level) {
|
|
3538
|
+
case "safe":
|
|
3539
|
+
return 0;
|
|
3540
|
+
case "caution":
|
|
3541
|
+
return 1;
|
|
3542
|
+
case "destructive":
|
|
3543
|
+
return 2;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
// src/exec.ts
|
|
3260
3548
|
var isWin2 = process.platform === "win32";
|
|
3261
3549
|
var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
3262
3550
|
// JS / TS toolchain
|
|
@@ -3876,6 +4164,21 @@ function configureExecPolicy(opts = {}) {
|
|
|
3876
4164
|
function resetExecPolicy() {
|
|
3877
4165
|
allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
3878
4166
|
}
|
|
4167
|
+
var dangerBypass = /* @__PURE__ */ new Set();
|
|
4168
|
+
function configureDangerBypass(opts = {}) {
|
|
4169
|
+
const next = /* @__PURE__ */ new Set();
|
|
4170
|
+
for (const id of opts.bypass ?? []) {
|
|
4171
|
+
const trimmed = id.trim();
|
|
4172
|
+
if (trimmed) next.add(trimmed);
|
|
4173
|
+
}
|
|
4174
|
+
dangerBypass = next;
|
|
4175
|
+
}
|
|
4176
|
+
function resetDangerBypass() {
|
|
4177
|
+
dangerBypass = /* @__PURE__ */ new Set();
|
|
4178
|
+
}
|
|
4179
|
+
function getDangerBypass() {
|
|
4180
|
+
return dangerBypass;
|
|
4181
|
+
}
|
|
3879
4182
|
function isExecCommandAllowed(cmd) {
|
|
3880
4183
|
return allowedCommands.has(normalizeCmd(cmd));
|
|
3881
4184
|
}
|
|
@@ -3967,6 +4270,7 @@ function validateArgs(cmd, args) {
|
|
|
3967
4270
|
}
|
|
3968
4271
|
return null;
|
|
3969
4272
|
}
|
|
4273
|
+
var SAFE_DANGER = { level: "safe", reasons: [] };
|
|
3970
4274
|
var execTool = {
|
|
3971
4275
|
name: "exec",
|
|
3972
4276
|
category: "Shell",
|
|
@@ -4011,7 +4315,8 @@ var execTool = {
|
|
|
4011
4315
|
stderr: "Circuit breaker is open \u2014 too many consecutive failures. Use /kill reset to recover.",
|
|
4012
4316
|
exitCode: 1,
|
|
4013
4317
|
truncated: false,
|
|
4014
|
-
allowed: false
|
|
4318
|
+
allowed: false,
|
|
4319
|
+
danger: SAFE_DANGER
|
|
4015
4320
|
};
|
|
4016
4321
|
}
|
|
4017
4322
|
const cmd = input.command.trim();
|
|
@@ -4023,7 +4328,8 @@ var execTool = {
|
|
|
4023
4328
|
stderr: "Empty command",
|
|
4024
4329
|
exitCode: 1,
|
|
4025
4330
|
truncated: false,
|
|
4026
|
-
allowed: false
|
|
4331
|
+
allowed: false,
|
|
4332
|
+
danger: SAFE_DANGER
|
|
4027
4333
|
};
|
|
4028
4334
|
if (!isExecCommandAllowed(cmd)) {
|
|
4029
4335
|
return {
|
|
@@ -4033,11 +4339,13 @@ var execTool = {
|
|
|
4033
4339
|
stderr: `Command "${cmd}" not in allowlist. Add it to your ~/.wrongstack/config.json under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
|
|
4034
4340
|
exitCode: 1,
|
|
4035
4341
|
truncated: false,
|
|
4036
|
-
allowed: false
|
|
4342
|
+
allowed: false,
|
|
4343
|
+
danger: SAFE_DANGER
|
|
4037
4344
|
};
|
|
4038
4345
|
}
|
|
4039
4346
|
const args = (input.args ?? []).slice(0, MAX_ARGS);
|
|
4040
4347
|
const timeout = Math.max(1, Math.min(input.timeout ?? DEFAULT_TIMEOUT_MS2, DEFAULT_TIMEOUT_MS2));
|
|
4348
|
+
const danger = detectDanger(cmd, args, dangerBypass);
|
|
4041
4349
|
const argError = validateArgs(cmd, args);
|
|
4042
4350
|
if (argError) {
|
|
4043
4351
|
return {
|
|
@@ -4047,7 +4355,8 @@ var execTool = {
|
|
|
4047
4355
|
stderr: argError,
|
|
4048
4356
|
exitCode: 1,
|
|
4049
4357
|
truncated: false,
|
|
4050
|
-
allowed: false
|
|
4358
|
+
allowed: false,
|
|
4359
|
+
danger
|
|
4051
4360
|
};
|
|
4052
4361
|
}
|
|
4053
4362
|
let cwd;
|
|
@@ -4061,15 +4370,16 @@ var execTool = {
|
|
|
4061
4370
|
stderr: `cwd "${input.cwd ?? ctx.cwd}" resolves outside project root`,
|
|
4062
4371
|
exitCode: 1,
|
|
4063
4372
|
truncated: false,
|
|
4064
|
-
allowed: false
|
|
4373
|
+
allowed: false,
|
|
4374
|
+
danger
|
|
4065
4375
|
};
|
|
4066
4376
|
}
|
|
4067
4377
|
const signal = opts.signal;
|
|
4068
|
-
return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id);
|
|
4378
|
+
return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id, danger);
|
|
4069
4379
|
}
|
|
4070
4380
|
};
|
|
4071
|
-
function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
4072
|
-
return new Promise((
|
|
4381
|
+
function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
4382
|
+
return new Promise((resolve8) => {
|
|
4073
4383
|
let stdout = "";
|
|
4074
4384
|
let stderr = "";
|
|
4075
4385
|
let killed = false;
|
|
@@ -4077,7 +4387,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
4077
4387
|
const finish = (result) => {
|
|
4078
4388
|
if (resolvedOnce.value) return;
|
|
4079
4389
|
resolvedOnce.value = true;
|
|
4080
|
-
|
|
4390
|
+
resolve8(result);
|
|
4081
4391
|
};
|
|
4082
4392
|
const startedAt = Date.now();
|
|
4083
4393
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
@@ -4105,7 +4415,8 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
4105
4415
|
stderr: `spawn failed: ${toErrorMessage$1(err)}`,
|
|
4106
4416
|
exitCode: 1,
|
|
4107
4417
|
truncated: false,
|
|
4108
|
-
allowed: true
|
|
4418
|
+
allowed: true,
|
|
4419
|
+
danger
|
|
4109
4420
|
});
|
|
4110
4421
|
return;
|
|
4111
4422
|
}
|
|
@@ -4124,7 +4435,8 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
4124
4435
|
stderr: stderrText,
|
|
4125
4436
|
exitCode: isAbort ? 124 : 1,
|
|
4126
4437
|
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
4127
|
-
allowed: true
|
|
4438
|
+
allowed: true,
|
|
4439
|
+
danger
|
|
4128
4440
|
});
|
|
4129
4441
|
});
|
|
4130
4442
|
const registry = getProcessRegistry();
|
|
@@ -4172,7 +4484,8 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
4172
4484
|
stderr: normalizeCommandOutput(stderr),
|
|
4173
4485
|
exitCode,
|
|
4174
4486
|
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES || Buffer.byteLength(stderr, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
4175
|
-
allowed: true
|
|
4487
|
+
allowed: true,
|
|
4488
|
+
danger
|
|
4176
4489
|
});
|
|
4177
4490
|
});
|
|
4178
4491
|
});
|
|
@@ -5436,8 +5749,8 @@ function findGitDir(cwd, projectRoot) {
|
|
|
5436
5749
|
let dir = cwd;
|
|
5437
5750
|
for (let i = 0; i < 20; i++) {
|
|
5438
5751
|
try {
|
|
5439
|
-
const
|
|
5440
|
-
if (
|
|
5752
|
+
const stat12 = statSync(`${dir}/.git`);
|
|
5753
|
+
if (stat12.isDirectory() || stat12.isFile()) return dir;
|
|
5441
5754
|
} catch {
|
|
5442
5755
|
}
|
|
5443
5756
|
if (dir === root) break;
|
|
@@ -5518,7 +5831,7 @@ function buildArgs(input) {
|
|
|
5518
5831
|
}
|
|
5519
5832
|
}
|
|
5520
5833
|
function runGit(args, cwd, signal) {
|
|
5521
|
-
return new Promise((
|
|
5834
|
+
return new Promise((resolve8) => {
|
|
5522
5835
|
let stdout = "";
|
|
5523
5836
|
let stderr = "";
|
|
5524
5837
|
const child = spawn("git", args, {
|
|
@@ -5539,7 +5852,7 @@ function runGit(args, cwd, signal) {
|
|
|
5539
5852
|
}
|
|
5540
5853
|
});
|
|
5541
5854
|
child.on("error", (err) => {
|
|
5542
|
-
|
|
5855
|
+
resolve8({
|
|
5543
5856
|
command: args[0],
|
|
5544
5857
|
stdout: normalizeCommandOutput(stdout),
|
|
5545
5858
|
stderr: err.message,
|
|
@@ -5548,7 +5861,7 @@ function runGit(args, cwd, signal) {
|
|
|
5548
5861
|
});
|
|
5549
5862
|
});
|
|
5550
5863
|
child.on("close", (code) => {
|
|
5551
|
-
|
|
5864
|
+
resolve8({
|
|
5552
5865
|
command: args[0],
|
|
5553
5866
|
stdout: normalizeCommandOutput(stdout),
|
|
5554
5867
|
stderr: normalizeCommandOutput(stderr),
|
|
@@ -5648,7 +5961,7 @@ function stripPathComponents(p, strip) {
|
|
|
5648
5961
|
return parts.slice(strip).join("/");
|
|
5649
5962
|
}
|
|
5650
5963
|
function runPatch(args, cwd, signal) {
|
|
5651
|
-
return new Promise((
|
|
5964
|
+
return new Promise((resolve8) => {
|
|
5652
5965
|
let stdout = "";
|
|
5653
5966
|
let stderr = "";
|
|
5654
5967
|
const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
|
|
@@ -5659,8 +5972,8 @@ function runPatch(args, cwd, signal) {
|
|
|
5659
5972
|
child.stderr?.on("data", (c) => {
|
|
5660
5973
|
stderr += c.toString();
|
|
5661
5974
|
});
|
|
5662
|
-
child.on("close", (code) =>
|
|
5663
|
-
child.on("error", (e) =>
|
|
5975
|
+
child.on("close", (code) => resolve8({ exitCode: code ?? 1, stdout, stderr }));
|
|
5976
|
+
child.on("error", (e) => resolve8({ exitCode: 1, stdout: "", stderr: e.message }));
|
|
5664
5977
|
});
|
|
5665
5978
|
}
|
|
5666
5979
|
function extractPatchedFiles(output) {
|
|
@@ -5733,7 +6046,6 @@ var jsonTool = {
|
|
|
5733
6046
|
return executeTransform(input, ctx);
|
|
5734
6047
|
case "merge":
|
|
5735
6048
|
return executeMerge(input);
|
|
5736
|
-
case "parse":
|
|
5737
6049
|
default:
|
|
5738
6050
|
return executeParse(input, ctx);
|
|
5739
6051
|
}
|
|
@@ -6049,56 +6361,56 @@ function jmespathSearch(data, query) {
|
|
|
6049
6361
|
}
|
|
6050
6362
|
function validateJsonSchema(data, schema) {
|
|
6051
6363
|
const errors = [];
|
|
6052
|
-
function check(value, s,
|
|
6364
|
+
function check(value, s, path23) {
|
|
6053
6365
|
if (s["type"]) {
|
|
6054
6366
|
const expectedType = s["type"];
|
|
6055
6367
|
const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
6056
6368
|
if (expectedType === "integer") {
|
|
6057
|
-
if (!Number.isInteger(value)) errors.push(`${
|
|
6369
|
+
if (!Number.isInteger(value)) errors.push(`${path23}: expected integer, got ${actualType}`);
|
|
6058
6370
|
} else if (expectedType !== actualType) {
|
|
6059
|
-
errors.push(`${
|
|
6371
|
+
errors.push(`${path23}: expected ${expectedType}, got ${actualType}`);
|
|
6060
6372
|
}
|
|
6061
6373
|
}
|
|
6062
6374
|
if (typeof value === "string" && s["format"] === "uri" && value) {
|
|
6063
6375
|
try {
|
|
6064
6376
|
new URL(value);
|
|
6065
6377
|
} catch {
|
|
6066
|
-
errors.push(`${
|
|
6378
|
+
errors.push(`${path23}: not a valid URI`);
|
|
6067
6379
|
}
|
|
6068
6380
|
}
|
|
6069
6381
|
if (typeof value === "string" && s["pattern"]) {
|
|
6070
6382
|
const re = new RegExp(s["pattern"]);
|
|
6071
|
-
if (!re.test(value)) errors.push(`${
|
|
6383
|
+
if (!re.test(value)) errors.push(`${path23}: does not match pattern ${s["pattern"]}`);
|
|
6072
6384
|
}
|
|
6073
6385
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
6074
|
-
errors.push(`${
|
|
6386
|
+
errors.push(`${path23}: string too short (min ${s["minLength"]})`);
|
|
6075
6387
|
}
|
|
6076
6388
|
if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
|
|
6077
|
-
errors.push(`${
|
|
6389
|
+
errors.push(`${path23}: string too long (max ${s["maxLength"]})`);
|
|
6078
6390
|
}
|
|
6079
6391
|
if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
|
|
6080
|
-
errors.push(`${
|
|
6392
|
+
errors.push(`${path23}: below minimum ${s["minimum"]}`);
|
|
6081
6393
|
}
|
|
6082
6394
|
if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
|
|
6083
|
-
errors.push(`${
|
|
6395
|
+
errors.push(`${path23}: above maximum ${s["maximum"]}`);
|
|
6084
6396
|
}
|
|
6085
6397
|
if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
|
|
6086
6398
|
for (let i = 0; i < value.length; i++) {
|
|
6087
|
-
check(value[i], s["items"], `${
|
|
6399
|
+
check(value[i], s["items"], `${path23}[${i}]`);
|
|
6088
6400
|
}
|
|
6089
6401
|
}
|
|
6090
6402
|
if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
|
|
6091
6403
|
const props = s["properties"];
|
|
6092
6404
|
for (const [k, propSchema] of Object.entries(props)) {
|
|
6093
|
-
check(value[k], propSchema, `${
|
|
6405
|
+
check(value[k], propSchema, `${path23}.${k}`);
|
|
6094
6406
|
}
|
|
6095
6407
|
}
|
|
6096
6408
|
}
|
|
6097
6409
|
check(data, schema, "$");
|
|
6098
6410
|
return { valid: errors.length === 0, errors };
|
|
6099
6411
|
}
|
|
6100
|
-
function simpleQuery(data,
|
|
6101
|
-
const parts =
|
|
6412
|
+
function simpleQuery(data, path23) {
|
|
6413
|
+
const parts = path23.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
6102
6414
|
let current = data;
|
|
6103
6415
|
for (const part of parts) {
|
|
6104
6416
|
if (current === null || current === void 0) return void 0;
|
|
@@ -6228,8 +6540,8 @@ function findGitDir2(cwd) {
|
|
|
6228
6540
|
let dir = cwd;
|
|
6229
6541
|
for (let i = 0; i < 20; i++) {
|
|
6230
6542
|
try {
|
|
6231
|
-
const
|
|
6232
|
-
if (
|
|
6543
|
+
const stat12 = statSync(path.join(dir, ".git"));
|
|
6544
|
+
if (stat12.isDirectory()) return dir;
|
|
6233
6545
|
} catch {
|
|
6234
6546
|
}
|
|
6235
6547
|
const parent = path.dirname(dir);
|
|
@@ -6239,7 +6551,7 @@ function findGitDir2(cwd) {
|
|
|
6239
6551
|
return null;
|
|
6240
6552
|
}
|
|
6241
6553
|
function runGit2(args, cwd, signal) {
|
|
6242
|
-
return new Promise((
|
|
6554
|
+
return new Promise((resolve8) => {
|
|
6243
6555
|
let stdout = "";
|
|
6244
6556
|
let stderr = "";
|
|
6245
6557
|
const child = spawn("git", args, {
|
|
@@ -6255,8 +6567,8 @@ function runGit2(args, cwd, signal) {
|
|
|
6255
6567
|
child.stderr?.on("data", (c) => {
|
|
6256
6568
|
stderr += c.toString();
|
|
6257
6569
|
});
|
|
6258
|
-
child.on("close", (code) =>
|
|
6259
|
-
child.on("error", (e) =>
|
|
6570
|
+
child.on("close", (code) => resolve8({ stdout, stderr, exitCode: code ?? 0 }));
|
|
6571
|
+
child.on("error", (e) => resolve8({ stdout: "", stderr: e.message, exitCode: 1 }));
|
|
6260
6572
|
});
|
|
6261
6573
|
}
|
|
6262
6574
|
async function fileDiff(input, ctx, _signal) {
|
|
@@ -6273,8 +6585,8 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
6273
6585
|
const results = [];
|
|
6274
6586
|
for (const file of files) {
|
|
6275
6587
|
const absPath = safeResolve(file, ctx);
|
|
6276
|
-
const
|
|
6277
|
-
if (!
|
|
6588
|
+
const stat12 = await fs7.stat(absPath).catch(() => null);
|
|
6589
|
+
if (!stat12?.isFile()) continue;
|
|
6278
6590
|
const content = await fs7.readFile(absPath, "utf8");
|
|
6279
6591
|
const lines = content.split(/\r?\n/);
|
|
6280
6592
|
results.push(formatWithLineNumbers(file, lines));
|
|
@@ -6578,8 +6890,8 @@ async function* spawnStream(opts) {
|
|
|
6578
6890
|
try {
|
|
6579
6891
|
for (; ; ) {
|
|
6580
6892
|
while (queue.length === 0) {
|
|
6581
|
-
await new Promise((
|
|
6582
|
-
waiter =
|
|
6893
|
+
await new Promise((resolve8) => {
|
|
6894
|
+
waiter = resolve8;
|
|
6583
6895
|
});
|
|
6584
6896
|
}
|
|
6585
6897
|
const chunk = queue.shift();
|
|
@@ -6716,11 +7028,11 @@ var lintTool = {
|
|
|
6716
7028
|
}
|
|
6717
7029
|
};
|
|
6718
7030
|
async function detectLinter(cwd) {
|
|
6719
|
-
const { stat:
|
|
7031
|
+
const { stat: stat12 } = await import('node:fs/promises');
|
|
6720
7032
|
const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
|
|
6721
7033
|
for (const f of checks) {
|
|
6722
7034
|
try {
|
|
6723
|
-
await
|
|
7035
|
+
await stat12(`${cwd}/${f}`);
|
|
6724
7036
|
if (f.includes("biome")) return "biome";
|
|
6725
7037
|
if (f.includes("eslint")) return "eslint";
|
|
6726
7038
|
if (f.includes("tslint")) return "tslint";
|
|
@@ -6819,13 +7131,13 @@ var formatTool = {
|
|
|
6819
7131
|
}
|
|
6820
7132
|
};
|
|
6821
7133
|
async function detectFixer(cwd) {
|
|
6822
|
-
const { stat:
|
|
7134
|
+
const { stat: stat12 } = await import('node:fs/promises');
|
|
6823
7135
|
try {
|
|
6824
|
-
await
|
|
7136
|
+
await stat12(`${cwd}/biome.json`);
|
|
6825
7137
|
return "biome";
|
|
6826
7138
|
} catch {
|
|
6827
7139
|
try {
|
|
6828
|
-
await
|
|
7140
|
+
await stat12(`${cwd}/.prettierrc`);
|
|
6829
7141
|
return "prettier";
|
|
6830
7142
|
} catch {
|
|
6831
7143
|
return "biome";
|
|
@@ -6910,11 +7222,11 @@ var typecheckTool = {
|
|
|
6910
7222
|
}
|
|
6911
7223
|
};
|
|
6912
7224
|
async function findTsConfig(cwd) {
|
|
6913
|
-
const { stat:
|
|
7225
|
+
const { stat: stat12 } = await import('node:fs/promises');
|
|
6914
7226
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
6915
7227
|
for (const f of candidates) {
|
|
6916
7228
|
try {
|
|
6917
|
-
const s = await
|
|
7229
|
+
const s = await stat12(path.join(cwd, f));
|
|
6918
7230
|
if (s.isFile()) return path.join(cwd, f);
|
|
6919
7231
|
} catch {
|
|
6920
7232
|
}
|
|
@@ -6999,11 +7311,11 @@ var testTool = {
|
|
|
6999
7311
|
}
|
|
7000
7312
|
};
|
|
7001
7313
|
async function detectRunner(cwd) {
|
|
7002
|
-
const { stat:
|
|
7314
|
+
const { stat: stat12 } = await import('node:fs/promises');
|
|
7003
7315
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
7004
7316
|
for (const f of candidates) {
|
|
7005
7317
|
try {
|
|
7006
|
-
await
|
|
7318
|
+
await stat12(path.join(cwd, f));
|
|
7007
7319
|
if (f.includes("vitest")) return "vitest";
|
|
7008
7320
|
if (f.includes("jest")) return "jest";
|
|
7009
7321
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -7385,7 +7697,7 @@ var outdatedTool = {
|
|
|
7385
7697
|
}
|
|
7386
7698
|
};
|
|
7387
7699
|
function runOutdated(manager, args, cwd, signal) {
|
|
7388
|
-
return new Promise((
|
|
7700
|
+
return new Promise((resolve8) => {
|
|
7389
7701
|
let stdout = "";
|
|
7390
7702
|
let stderr = "";
|
|
7391
7703
|
const MAX = 1e5;
|
|
@@ -7403,10 +7715,10 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
7403
7715
|
});
|
|
7404
7716
|
child.on("close", (code) => {
|
|
7405
7717
|
const result = parseOutdatedOutput(stdout, code ?? 0);
|
|
7406
|
-
|
|
7718
|
+
resolve8(result);
|
|
7407
7719
|
});
|
|
7408
7720
|
child.on("error", (e) => {
|
|
7409
|
-
|
|
7721
|
+
resolve8({
|
|
7410
7722
|
exit_code: 1,
|
|
7411
7723
|
packages: [],
|
|
7412
7724
|
total: 0,
|
|
@@ -7532,7 +7844,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
7532
7844
|
};
|
|
7533
7845
|
}
|
|
7534
7846
|
args.push("--timestamps", service);
|
|
7535
|
-
return new Promise((
|
|
7847
|
+
return new Promise((resolve8) => {
|
|
7536
7848
|
let stdout = "";
|
|
7537
7849
|
let stderr = "";
|
|
7538
7850
|
const MAX = 2e5;
|
|
@@ -7548,7 +7860,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
7548
7860
|
if (settled) return;
|
|
7549
7861
|
settled = true;
|
|
7550
7862
|
clearTimeout(timer);
|
|
7551
|
-
|
|
7863
|
+
resolve8(result);
|
|
7552
7864
|
};
|
|
7553
7865
|
const child = spawn("docker", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
7554
7866
|
const timer = setTimeout(() => {
|
|
@@ -7583,7 +7895,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
7583
7895
|
}
|
|
7584
7896
|
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
7585
7897
|
var MAX_TAIL_LINES = 1e5;
|
|
7586
|
-
async function fileLogs(
|
|
7898
|
+
async function fileLogs(path23, lines, filterRe, stream) {
|
|
7587
7899
|
const { createInterface } = await import('node:readline');
|
|
7588
7900
|
const { createReadStream } = await import('node:fs');
|
|
7589
7901
|
const entries = [];
|
|
@@ -7592,7 +7904,7 @@ async function fileLogs(path22, lines, filterRe, stream) {
|
|
|
7592
7904
|
let writeIdx = 0;
|
|
7593
7905
|
let totalLines = 0;
|
|
7594
7906
|
const rl = createInterface({
|
|
7595
|
-
input: createReadStream(
|
|
7907
|
+
input: createReadStream(path23),
|
|
7596
7908
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
7597
7909
|
});
|
|
7598
7910
|
for await (const line of rl) {
|
|
@@ -7613,7 +7925,7 @@ async function fileLogs(path22, lines, filterRe, stream) {
|
|
|
7613
7925
|
if (parsed) entries.push(parsed);
|
|
7614
7926
|
}
|
|
7615
7927
|
return {
|
|
7616
|
-
source:
|
|
7928
|
+
source: path23,
|
|
7617
7929
|
entries,
|
|
7618
7930
|
total: entries.length,
|
|
7619
7931
|
truncated: totalLines > effLines,
|
|
@@ -7734,8 +8046,8 @@ async function resolveFiles2(filesInput, cwd) {
|
|
|
7734
8046
|
for (const f of files) {
|
|
7735
8047
|
const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
|
|
7736
8048
|
try {
|
|
7737
|
-
const
|
|
7738
|
-
if (
|
|
8049
|
+
const stat12 = await fs7.stat(absPath);
|
|
8050
|
+
if (stat12.isFile()) resolved.push(absPath);
|
|
7739
8051
|
} catch {
|
|
7740
8052
|
}
|
|
7741
8053
|
}
|
|
@@ -8789,6 +9101,169 @@ function relatedMemoryTool(memory) {
|
|
|
8789
9101
|
}
|
|
8790
9102
|
};
|
|
8791
9103
|
}
|
|
9104
|
+
var MAX_BODY_CHARS = SKILL_LIMITS.MAX_SKILL_BODY_CHARS;
|
|
9105
|
+
var MAX_RESOURCE_CHARS = SKILL_LIMITS.MAX_RESOURCE_CHARS;
|
|
9106
|
+
var MAX_LISTED_RESOURCES = SKILL_LIMITS.MAX_LISTED_RESOURCES;
|
|
9107
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build"]);
|
|
9108
|
+
function makeSkillTool(skillLoader) {
|
|
9109
|
+
return {
|
|
9110
|
+
name: "skill",
|
|
9111
|
+
category: "Skills",
|
|
9112
|
+
description: "Load a skill's instructions or a bundled resource on demand (agentskills.io progressive disclosure). With only `name`: returns the SKILL.md body + a list of bundled resource files. With `name` + `resource` (a relative path like `references/REF.md` or `scripts/extract.py`): returns that file content. Prefer this over the read tool for skill files \u2014 it reaches foreign skills outside the project root.",
|
|
9113
|
+
usageHint: 'Load a skill body or one of its bundled resources (progressive disclosure).\n\nWHEN TO USE:\n- A task matches a skill trigger \u2192 load the body: skill({ name })\n- You need a reference/template/script bundled with the skill \u2192 skill({ name, resource: "references/REF.md" })\n\nThe body call lists every bundled resource; load the ones you need. Run scripts via bash using the returned abs path.',
|
|
9114
|
+
permission: "auto",
|
|
9115
|
+
mutating: false,
|
|
9116
|
+
capabilities: ["fs.read"],
|
|
9117
|
+
icon: "document",
|
|
9118
|
+
timeoutMs: 5e3,
|
|
9119
|
+
inputSchema: {
|
|
9120
|
+
type: "object",
|
|
9121
|
+
properties: {
|
|
9122
|
+
name: {
|
|
9123
|
+
type: "string",
|
|
9124
|
+
description: "Exact skill name (as shown in the available-skills list)."
|
|
9125
|
+
},
|
|
9126
|
+
resource: {
|
|
9127
|
+
type: "string",
|
|
9128
|
+
description: "Optional relative path of a bundled resource to load (e.g. references/REF.md, scripts/extract.py, assets/template.html). Omit to list resources."
|
|
9129
|
+
}
|
|
9130
|
+
},
|
|
9131
|
+
required: ["name"]
|
|
9132
|
+
},
|
|
9133
|
+
async execute(input, ctx) {
|
|
9134
|
+
const name = input?.name?.trim();
|
|
9135
|
+
if (!name) {
|
|
9136
|
+
throw new ToolValidationError({ message: "skill: name is required", field: "name" });
|
|
9137
|
+
}
|
|
9138
|
+
const manifest = await skillLoader.find(name);
|
|
9139
|
+
if (!manifest) {
|
|
9140
|
+
throw new ToolValidationError({
|
|
9141
|
+
message: `skill "${name}" not found \u2014 use /skill to list available skills`,
|
|
9142
|
+
field: "name"
|
|
9143
|
+
});
|
|
9144
|
+
}
|
|
9145
|
+
const dir = path.dirname(manifest.path);
|
|
9146
|
+
let loadedResource;
|
|
9147
|
+
if (input.resource?.trim()) {
|
|
9148
|
+
loadedResource = await loadResource(dir, input.resource.trim());
|
|
9149
|
+
}
|
|
9150
|
+
const raw = await skillLoader.readBody(name);
|
|
9151
|
+
const body = stripFrontmatter(raw).trim().slice(0, MAX_BODY_CHARS);
|
|
9152
|
+
const resources = loadedResource ? [] : await listResources(dir);
|
|
9153
|
+
try {
|
|
9154
|
+
await ctx?.session?.append({
|
|
9155
|
+
type: "skill_activated",
|
|
9156
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9157
|
+
skillName: manifest.name
|
|
9158
|
+
});
|
|
9159
|
+
} catch {
|
|
9160
|
+
}
|
|
9161
|
+
return {
|
|
9162
|
+
name: manifest.name,
|
|
9163
|
+
description: manifest.description,
|
|
9164
|
+
body,
|
|
9165
|
+
resources,
|
|
9166
|
+
dir,
|
|
9167
|
+
loadedResource
|
|
9168
|
+
};
|
|
9169
|
+
},
|
|
9170
|
+
serialize(output) {
|
|
9171
|
+
if (output.loadedResource) {
|
|
9172
|
+
const lr = output.loadedResource;
|
|
9173
|
+
const note = lr.truncated ? ` (truncated to ${lr.content.length} chars of ${lr.bytes} B)` : "";
|
|
9174
|
+
return `# Resource: ${output.name}/${lr.rel}
|
|
9175
|
+
(abs path: ${lr.absPath})${note}
|
|
9176
|
+
|
|
9177
|
+
${lr.content}`;
|
|
9178
|
+
}
|
|
9179
|
+
const head = `# Skill: ${output.name}
|
|
9180
|
+
${output.description}
|
|
9181
|
+
|
|
9182
|
+
${output.body}`;
|
|
9183
|
+
if (output.resources.length === 0) return head;
|
|
9184
|
+
const listing = output.resources.map((r) => `- ${r.path} (${r.bytes} B)`).join("\n");
|
|
9185
|
+
return `${head}
|
|
9186
|
+
|
|
9187
|
+
## Bundled resources (load on demand)
|
|
9188
|
+
Load any with: \`skill({ name: "${output.name}", resource: "<path>" })\`. Run scripts via bash using their abs path under ${output.dir}.
|
|
9189
|
+
${listing}`;
|
|
9190
|
+
}
|
|
9191
|
+
};
|
|
9192
|
+
}
|
|
9193
|
+
async function loadResource(skillDir, rel) {
|
|
9194
|
+
const norm = rel.replace(/\\/g, "/");
|
|
9195
|
+
if (path.isAbsolute(rel) || norm.split("/").some((seg) => seg === "..")) {
|
|
9196
|
+
throw new ToolValidationError({
|
|
9197
|
+
message: `skill: invalid resource path "${rel}"`,
|
|
9198
|
+
field: "resource"
|
|
9199
|
+
});
|
|
9200
|
+
}
|
|
9201
|
+
const absPath = path.resolve(skillDir, rel);
|
|
9202
|
+
const root = path.resolve(skillDir);
|
|
9203
|
+
if (absPath !== root && !absPath.startsWith(root + path.sep)) {
|
|
9204
|
+
throw new ToolValidationError({
|
|
9205
|
+
message: `skill: resource "${rel}" escapes the skill directory`,
|
|
9206
|
+
field: "resource"
|
|
9207
|
+
});
|
|
9208
|
+
}
|
|
9209
|
+
let buf;
|
|
9210
|
+
try {
|
|
9211
|
+
buf = await fs7.readFile(absPath);
|
|
9212
|
+
} catch {
|
|
9213
|
+
throw new ToolValidationError({
|
|
9214
|
+
message: `skill: resource "${rel}" not readable`,
|
|
9215
|
+
field: "resource"
|
|
9216
|
+
});
|
|
9217
|
+
}
|
|
9218
|
+
const raw = buf.toString("utf8");
|
|
9219
|
+
const truncated = raw.length > MAX_RESOURCE_CHARS;
|
|
9220
|
+
return {
|
|
9221
|
+
rel: norm,
|
|
9222
|
+
absPath,
|
|
9223
|
+
content: truncated ? raw.slice(0, MAX_RESOURCE_CHARS) : raw,
|
|
9224
|
+
bytes: buf.length,
|
|
9225
|
+
truncated
|
|
9226
|
+
};
|
|
9227
|
+
}
|
|
9228
|
+
async function listResources(skillDir) {
|
|
9229
|
+
const out = [];
|
|
9230
|
+
await walk(skillDir, skillDir, out);
|
|
9231
|
+
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
9232
|
+
return out.slice(0, MAX_LISTED_RESOURCES);
|
|
9233
|
+
}
|
|
9234
|
+
async function walk(root, dir, out) {
|
|
9235
|
+
if (out.length >= MAX_LISTED_RESOURCES) return;
|
|
9236
|
+
let entries;
|
|
9237
|
+
try {
|
|
9238
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
9239
|
+
} catch {
|
|
9240
|
+
return;
|
|
9241
|
+
}
|
|
9242
|
+
for (const e of entries) {
|
|
9243
|
+
if (out.length >= MAX_LISTED_RESOURCES) return;
|
|
9244
|
+
const fullPath = path.join(dir, e.name);
|
|
9245
|
+
let isDir = e.isDirectory();
|
|
9246
|
+
if (e.isSymbolicLink()) {
|
|
9247
|
+
try {
|
|
9248
|
+
isDir = (await fs7.stat(fullPath)).isDirectory();
|
|
9249
|
+
} catch {
|
|
9250
|
+
continue;
|
|
9251
|
+
}
|
|
9252
|
+
}
|
|
9253
|
+
if (isDir) {
|
|
9254
|
+
if (SKIP_DIRS.has(e.name) || e.name.startsWith(".")) continue;
|
|
9255
|
+
await walk(root, fullPath, out);
|
|
9256
|
+
} else if (e.isFile()) {
|
|
9257
|
+
if (e.name === "SKILL.md" || e.name === "SKILL.save.md") continue;
|
|
9258
|
+
try {
|
|
9259
|
+
const stat12 = await fs7.stat(fullPath);
|
|
9260
|
+
const rel = path.relative(root, fullPath).split(path.sep).join("/");
|
|
9261
|
+
out.push({ path: rel, bytes: stat12.size });
|
|
9262
|
+
} catch {
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
8792
9267
|
|
|
8793
9268
|
// src/mode.ts
|
|
8794
9269
|
function createModeTool(modeStore) {
|
|
@@ -10734,9 +11209,8 @@ func formatType(t ast.Expr) string {
|
|
|
10734
11209
|
}
|
|
10735
11210
|
`;
|
|
10736
11211
|
async function syncGoParse(filePath, content, lang) {
|
|
10737
|
-
const tmpDir = path.join(os2.tmpdir(), "ws-go-parse");
|
|
11212
|
+
const tmpDir = await fs7.mkdtemp(path.join(os2.tmpdir(), "ws-go-parse-"));
|
|
10738
11213
|
try {
|
|
10739
|
-
await fs7.mkdir(tmpDir, { recursive: true });
|
|
10740
11214
|
const scriptPath = path.join(tmpDir, "parse.go");
|
|
10741
11215
|
await fs7.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
10742
11216
|
const proc = spawn("go", ["run", scriptPath], {
|
|
@@ -10750,8 +11224,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
10750
11224
|
proc.stdin?.write(content);
|
|
10751
11225
|
proc.stdin?.end();
|
|
10752
11226
|
const { code } = await Promise.race([
|
|
10753
|
-
new Promise((
|
|
10754
|
-
proc.on("close", (c) =>
|
|
11227
|
+
new Promise((resolve8) => {
|
|
11228
|
+
proc.on("close", (c) => resolve8({ code: c }));
|
|
10755
11229
|
}),
|
|
10756
11230
|
new Promise(
|
|
10757
11231
|
(_, reject) => setTimeout(() => {
|
|
@@ -10780,6 +11254,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
10780
11254
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
10781
11255
|
} catch {
|
|
10782
11256
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
11257
|
+
} finally {
|
|
11258
|
+
await fs7.rm(tmpDir, { recursive: true, force: true });
|
|
10783
11259
|
}
|
|
10784
11260
|
}
|
|
10785
11261
|
async function parseSymbols3(opts) {
|
|
@@ -11013,8 +11489,8 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
11013
11489
|
stdout += chunk.toString();
|
|
11014
11490
|
});
|
|
11015
11491
|
const { code } = await Promise.race([
|
|
11016
|
-
new Promise((
|
|
11017
|
-
proc.on("close", (c) =>
|
|
11492
|
+
new Promise((resolve8) => {
|
|
11493
|
+
proc.on("close", (c) => resolve8({ code: c }));
|
|
11018
11494
|
}),
|
|
11019
11495
|
new Promise(
|
|
11020
11496
|
(_, reject) => setTimeout(() => {
|
|
@@ -11099,8 +11575,8 @@ async function tryNativeParse(file, content) {
|
|
|
11099
11575
|
stdout += chunk.toString();
|
|
11100
11576
|
});
|
|
11101
11577
|
const { code } = await Promise.race([
|
|
11102
|
-
new Promise((
|
|
11103
|
-
proc.on("close", (c) =>
|
|
11578
|
+
new Promise((resolve8) => {
|
|
11579
|
+
proc.on("close", (c) => resolve8({ code: c }));
|
|
11104
11580
|
}),
|
|
11105
11581
|
new Promise(
|
|
11106
11582
|
(_, reject) => setTimeout(() => {
|
|
@@ -11590,7 +12066,7 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
11590
12066
|
var YIELD_EVERY_N = 50;
|
|
11591
12067
|
var PARALLEL_BATCH = 20;
|
|
11592
12068
|
function yieldEventLoop() {
|
|
11593
|
-
return new Promise((
|
|
12069
|
+
return new Promise((resolve8) => setImmediate(resolve8));
|
|
11594
12070
|
}
|
|
11595
12071
|
function throwIfAborted(signal) {
|
|
11596
12072
|
if (!signal?.aborted) return;
|
|
@@ -11629,7 +12105,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
11629
12105
|
{ ext: ".yml", pat: compileGlob("**/*.yml") }
|
|
11630
12106
|
];
|
|
11631
12107
|
let dirCount = 0;
|
|
11632
|
-
const
|
|
12108
|
+
const walk2 = async (dir) => {
|
|
11633
12109
|
throwIfAborted(signal);
|
|
11634
12110
|
if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
|
|
11635
12111
|
await yieldEventLoop();
|
|
@@ -11648,7 +12124,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
11648
12124
|
const rel = path.relative(projectRoot, full).replace(/\\/g, "/");
|
|
11649
12125
|
if (e.isDirectory()) {
|
|
11650
12126
|
if (isGitIgnored(rel, true)) continue;
|
|
11651
|
-
await
|
|
12127
|
+
await walk2(full);
|
|
11652
12128
|
} else if (e.isFile()) {
|
|
11653
12129
|
if (isGitIgnored(rel, false)) continue;
|
|
11654
12130
|
const ext = path.extname(e.name);
|
|
@@ -11661,7 +12137,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
11661
12137
|
}
|
|
11662
12138
|
}
|
|
11663
12139
|
};
|
|
11664
|
-
await
|
|
12140
|
+
await walk2(projectRoot);
|
|
11665
12141
|
return results;
|
|
11666
12142
|
}
|
|
11667
12143
|
async function parseFile(file, content, lang) {
|
|
@@ -11735,34 +12211,34 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11735
12211
|
const statOpts = signal ? { signal } : {};
|
|
11736
12212
|
const statReadParse = await Promise.allSettled(
|
|
11737
12213
|
batchFiles.map(async (file) => {
|
|
11738
|
-
let
|
|
12214
|
+
let stat12;
|
|
11739
12215
|
try {
|
|
11740
|
-
|
|
12216
|
+
stat12 = await fs7.stat(file, statOpts);
|
|
11741
12217
|
} catch (e) {
|
|
11742
12218
|
if (isAbortError(e)) throw e;
|
|
11743
12219
|
return { file, stat: null, lang: "", parsed: null, error: `stat error: ${e instanceof Error ? e.message : String(e)}` };
|
|
11744
12220
|
}
|
|
11745
|
-
if (!
|
|
12221
|
+
if (!stat12.isFile()) return { file, stat: stat12, lang: "", parsed: null };
|
|
11746
12222
|
const lang = detectLang(file);
|
|
11747
|
-
if (!lang) return { file, stat:
|
|
12223
|
+
if (!lang) return { file, stat: stat12, lang: "", parsed: null };
|
|
11748
12224
|
const meta = existingMeta.get(file);
|
|
11749
|
-
if (!force && meta && meta.mtimeMs === Math.floor(
|
|
11750
|
-
return { file, stat:
|
|
12225
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat12.mtimeMs)) {
|
|
12226
|
+
return { file, stat: stat12, lang, parsed: null, skippedMeta: meta };
|
|
11751
12227
|
}
|
|
11752
12228
|
let content;
|
|
11753
12229
|
try {
|
|
11754
12230
|
content = await fs7.readFile(file, { encoding: "utf8", signal });
|
|
11755
12231
|
} catch (e) {
|
|
11756
12232
|
if (isAbortError(e)) throw e;
|
|
11757
|
-
return { file, stat:
|
|
12233
|
+
return { file, stat: stat12, lang, parsed: null, error: `read error: ${e instanceof Error ? e.message : String(e)}` };
|
|
11758
12234
|
}
|
|
11759
12235
|
let parsed;
|
|
11760
12236
|
try {
|
|
11761
12237
|
parsed = await parseFile(file, content, lang);
|
|
11762
12238
|
} catch (e) {
|
|
11763
|
-
return { file, stat:
|
|
12239
|
+
return { file, stat: stat12, lang, parsed: null, error: `parse error: ${e instanceof Error ? e.message : String(e)}` };
|
|
11764
12240
|
}
|
|
11765
|
-
return { file, stat:
|
|
12241
|
+
return { file, stat: stat12, lang, parsed, content };
|
|
11766
12242
|
})
|
|
11767
12243
|
);
|
|
11768
12244
|
const batchEntries = [];
|
|
@@ -11782,7 +12258,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11782
12258
|
if (result.error.includes("error:")) errors.push(result.error);
|
|
11783
12259
|
continue;
|
|
11784
12260
|
}
|
|
11785
|
-
const { stat:
|
|
12261
|
+
const { stat: stat12, lang, parsed } = result;
|
|
11786
12262
|
if (result.skippedMeta) {
|
|
11787
12263
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
11788
12264
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
@@ -11794,7 +12270,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11794
12270
|
store.upsertFile({
|
|
11795
12271
|
file,
|
|
11796
12272
|
lang,
|
|
11797
|
-
mtimeMs: Math.floor(
|
|
12273
|
+
mtimeMs: Math.floor(stat12.mtimeMs),
|
|
11798
12274
|
symbolCount: 0,
|
|
11799
12275
|
lastIndexed: Date.now()
|
|
11800
12276
|
});
|
|
@@ -11806,7 +12282,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11806
12282
|
store.upsertFile({
|
|
11807
12283
|
file,
|
|
11808
12284
|
lang,
|
|
11809
|
-
mtimeMs: Math.floor(
|
|
12285
|
+
mtimeMs: Math.floor(stat12.mtimeMs),
|
|
11810
12286
|
symbolCount: 0,
|
|
11811
12287
|
lastIndexed: Date.now()
|
|
11812
12288
|
});
|
|
@@ -11822,7 +12298,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11822
12298
|
lang,
|
|
11823
12299
|
symbols: parsed.symbols,
|
|
11824
12300
|
refs,
|
|
11825
|
-
mtimeMs: Math.floor(
|
|
12301
|
+
mtimeMs: Math.floor(stat12.mtimeMs),
|
|
11826
12302
|
symbolCount: parsed.symbols.length
|
|
11827
12303
|
});
|
|
11828
12304
|
deleteForFiles.push(file);
|
|
@@ -12083,7 +12559,7 @@ function shutdownCodebaseIndexHost() {
|
|
|
12083
12559
|
function callIndexOp(op, args, opts) {
|
|
12084
12560
|
const w = ensureWorker();
|
|
12085
12561
|
if (!w) return callInline(op, args, opts);
|
|
12086
|
-
return new Promise((
|
|
12562
|
+
return new Promise((resolve8, reject) => {
|
|
12087
12563
|
const id = nextRpcId++;
|
|
12088
12564
|
const timer = setTimeout(() => {
|
|
12089
12565
|
pending.delete(id);
|
|
@@ -12106,7 +12582,7 @@ function callIndexOp(op, args, opts) {
|
|
|
12106
12582
|
pending.set(id, {
|
|
12107
12583
|
resolve: (v) => {
|
|
12108
12584
|
cleanup();
|
|
12109
|
-
|
|
12585
|
+
resolve8(v);
|
|
12110
12586
|
},
|
|
12111
12587
|
reject: (e) => {
|
|
12112
12588
|
cleanup();
|
|
@@ -13168,6 +13644,6 @@ var TOOL_ICON_CONFIG = {
|
|
|
13168
13644
|
};
|
|
13169
13645
|
var FALLBACK_ICON = "fallback";
|
|
13170
13646
|
|
|
13171
|
-
export { CircuitBreaker, CircuitOpenError, FALLBACK_ICON, IndexCircuitBreaker, IndexTimeoutError, OPTIONAL_TOOLS, TIER1_TOOLS, TIER2_TOOLS, TIER3_TOOLS, TOOL_ICON_CONFIG, TOOL_ICON_MAP, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexStats, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, configureExecPolicy, createGlobalPsSlashCommand, createModeTool, designTool, diffTool, documentTool, editTool, enqueueReindex, ensureSessionShell, execTool, fetchTool, forgetTool, formatGlobalStatus, formatInstanceList, formatInstanceSummary, formatTool, getExecAllowlist, getIndexState, getInstanceCount, getPersistentProcessRegistry, getProcessGuardian, getProcessRegistry, getToolIcon, gitTool, globTool, grepTool, indexCircuitBreaker, installTool, isExecCommandAllowed, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, listInstances, logsTool, normalizeShell, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, relatedMemoryTool, rememberTool, replaceTool, resetExecPolicy, resetIndexCircuitBreaker, resetPersistentProcessRegistry, resolveSessionShell, runStartupIndex, scaffoldTool, searchCodebaseIndex, searchMemoryTool, searchTool, shutdownCodebaseIndexHost, startProcessGuardian, stopProcessGuardian, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
|
|
13647
|
+
export { CircuitBreaker, CircuitOpenError, FALLBACK_ICON, IndexCircuitBreaker, IndexTimeoutError, OPTIONAL_TOOLS, TIER1_TOOLS, TIER2_TOOLS, TIER3_TOOLS, TOOL_ICON_CONFIG, TOOL_ICON_MAP, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexStats, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, configureDangerBypass, configureExecPolicy, createGlobalPsSlashCommand, createModeTool, designTool, detectDanger, diffTool, documentTool, editTool, enqueueReindex, ensureSessionShell, execTool, fetchTool, forgetTool, formatGlobalStatus, formatInstanceList, formatInstanceSummary, formatTool, getDangerBypass, getExecAllowlist, getIndexState, getInstanceCount, getPersistentProcessRegistry, getProcessGuardian, getProcessRegistry, getToolIcon, gitTool, globTool, grepTool, indexCircuitBreaker, installTool, isExecCommandAllowed, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, listInstances, logsTool, makeSkillTool, normalizeShell, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, relatedMemoryTool, rememberTool, replaceTool, resetDangerBypass, resetExecPolicy, resetIndexCircuitBreaker, resetPersistentProcessRegistry, resolveSessionShell, runStartupIndex, scaffoldTool, searchCodebaseIndex, searchMemoryTool, searchTool, shutdownCodebaseIndexHost, startProcessGuardian, stopProcessGuardian, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
|
|
13172
13648
|
//# sourceMappingURL=index.js.map
|
|
13173
13649
|
//# sourceMappingURL=index.js.map
|