@wrongstack/tools 0.300.0 → 0.302.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.js +1 -1
- package/dist/batch-tool-use.js +3 -1
- package/dist/browser/index.js +1 -1
- package/dist/builtin.d.ts +3 -2
- package/dist/builtin.js +311 -84
- package/dist/codebase-index/bm25.d.ts +7 -1
- package/dist/codebase-index/index.js +23 -13
- package/dist/codebase-index/project-server.js +23 -13
- package/dist/codebase-index/worker.js +20 -12
- package/dist/format.js +1 -1
- package/dist/git.js +2 -5
- package/dist/glob.js +2 -2
- package/dist/grep.js +118 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +423 -91
- package/dist/install.js +1 -1
- package/dist/json.js +81 -0
- package/dist/languages/index.js +1 -1
- package/dist/lint.js +1 -1
- package/dist/logs.js +81 -0
- package/dist/next-steps-tool.d.ts +26 -0
- package/dist/outdated.js +1 -1
- package/dist/pack.js +311 -84
- package/dist/patch.js +206 -45
- package/dist/read.js +23 -13
- package/dist/replace.js +81 -0
- package/dist/skill.js +51 -2
- package/dist/test.js +1 -1
- package/dist/tool-help.js +2 -2
- package/dist/tool-search.js +1 -1
- package/dist/tool-tier.d.ts +1 -1
- package/dist/tool-tier.js +312 -101
- package/dist/tool-use.js +1 -1
- package/dist/tree.js +13 -3
- package/dist/typecheck.js +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -851,7 +851,7 @@ import {
|
|
|
851
851
|
} from "@wrongstack/core/observability";
|
|
852
852
|
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
853
853
|
async function* spawnStream(opts) {
|
|
854
|
-
const max = opts.maxBytes ??
|
|
854
|
+
const max = opts.maxBytes ?? 999999999;
|
|
855
855
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
856
856
|
const maxQueue = opts.maxQueueSize ?? 500;
|
|
857
857
|
const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
|
|
@@ -5927,16 +5927,23 @@ function looksBinary(content) {
|
|
|
5927
5927
|
}
|
|
5928
5928
|
return bad / sample.length > 0.1;
|
|
5929
5929
|
}
|
|
5930
|
-
function
|
|
5931
|
-
|
|
5932
|
-
let
|
|
5933
|
-
|
|
5934
|
-
if (content.charCodeAt(i) === 10) {
|
|
5935
|
-
line++;
|
|
5936
|
-
lastNl = i;
|
|
5937
|
-
}
|
|
5930
|
+
function newlineOffsets2(content) {
|
|
5931
|
+
const offsets = [];
|
|
5932
|
+
for (let i = 0; i < content.length; i++) {
|
|
5933
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
5938
5934
|
}
|
|
5939
|
-
return
|
|
5935
|
+
return offsets;
|
|
5936
|
+
}
|
|
5937
|
+
function lineColAt(offsets, index) {
|
|
5938
|
+
let low = 0;
|
|
5939
|
+
let high = offsets.length;
|
|
5940
|
+
while (low < high) {
|
|
5941
|
+
const mid = low + high >>> 1;
|
|
5942
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
5943
|
+
else high = mid;
|
|
5944
|
+
}
|
|
5945
|
+
const lastNl = low > 0 ? offsets[low - 1] : -1;
|
|
5946
|
+
return { line: low + 1, col: index - lastNl };
|
|
5940
5947
|
}
|
|
5941
5948
|
function parseGeneric2(opts) {
|
|
5942
5949
|
const { file, lang } = opts;
|
|
@@ -5949,6 +5956,7 @@ function parseGeneric2(opts) {
|
|
|
5949
5956
|
const patterns = patternsFor(lang);
|
|
5950
5957
|
const symbols = [];
|
|
5951
5958
|
const seen = /* @__PURE__ */ new Set();
|
|
5959
|
+
const nlOffsets = newlineOffsets2(content);
|
|
5952
5960
|
for (const pattern of patterns) {
|
|
5953
5961
|
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
5954
5962
|
re.lastIndex = 0;
|
|
@@ -5962,7 +5970,7 @@ function parseGeneric2(opts) {
|
|
|
5962
5970
|
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
5963
5971
|
continue;
|
|
5964
5972
|
}
|
|
5965
|
-
const { line, col } = lineColAt(
|
|
5973
|
+
const { line, col } = lineColAt(nlOffsets, match.index ?? 0);
|
|
5966
5974
|
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
5967
5975
|
if (seen.has(key)) continue;
|
|
5968
5976
|
seen.add(key);
|
|
@@ -9115,7 +9123,9 @@ async function executeSingle(call, ctx, governedExecute) {
|
|
|
9115
9123
|
executionMs: Date.now() - start
|
|
9116
9124
|
};
|
|
9117
9125
|
}
|
|
9118
|
-
const tool = ctx.tools.find(
|
|
9126
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find(
|
|
9127
|
+
(candidate) => candidate.name === call.tool
|
|
9128
|
+
);
|
|
9119
9129
|
if (!tool) {
|
|
9120
9130
|
return {
|
|
9121
9131
|
tool: call.tool,
|
|
@@ -9337,7 +9347,7 @@ function parsePrivateOriginAllowlist(raw) {
|
|
|
9337
9347
|
if (!raw?.trim()) return [];
|
|
9338
9348
|
const origins = /* @__PURE__ */ new Set();
|
|
9339
9349
|
for (const entry of raw.split(",")) {
|
|
9340
|
-
const candidate = entry.trim();
|
|
9350
|
+
const candidate = entry.trim().replace(/^["']+|["']+$/gu, "");
|
|
9341
9351
|
if (!candidate) continue;
|
|
9342
9352
|
const url = parseBrowserUrl(candidate, true);
|
|
9343
9353
|
if (url.pathname !== "/" || url.search || url.hash) {
|
|
@@ -10581,9 +10591,9 @@ import * as path16 from "node:path";
|
|
|
10581
10591
|
// src/codebase-index/bm25.ts
|
|
10582
10592
|
var K1 = 1.5;
|
|
10583
10593
|
var B = 0.75;
|
|
10594
|
+
var TOKENISE_RE = new RegExp("[^\\p{L}\\p{N}$']", "gu");
|
|
10584
10595
|
function tokenise(text) {
|
|
10585
|
-
|
|
10586
|
-
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
10596
|
+
return text.replace(TOKENISE_RE, " ").toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
10587
10597
|
}
|
|
10588
10598
|
function splitName(name) {
|
|
10589
10599
|
return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([\p{L}])(\d)/gu, "$1 $2").replace(/(\d)([\p{L}])/gu, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
@@ -13069,7 +13079,9 @@ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
|
13069
13079
|
var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
|
|
13070
13080
|
var buildIdCache;
|
|
13071
13081
|
function projectIndexServerBuildId(entrypoint) {
|
|
13072
|
-
const
|
|
13082
|
+
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
13083
|
+
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
13084
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath(cleanHref) : path17.resolve(cleanHref);
|
|
13073
13085
|
try {
|
|
13074
13086
|
const stat19 = fs12.statSync(file);
|
|
13075
13087
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat19.mtimeMs && buildIdCache.size === stat19.size) {
|
|
@@ -19888,11 +19900,8 @@ function buildArgs(input) {
|
|
|
19888
19900
|
...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]
|
|
19889
19901
|
] : ["branch"];
|
|
19890
19902
|
case "checkout":
|
|
19891
|
-
return [
|
|
19892
|
-
|
|
19893
|
-
...input.branch ? ["--", input.branch] : [],
|
|
19894
|
-
...files.length ? ["--", ...files] : []
|
|
19895
|
-
];
|
|
19903
|
+
if (files.length) return ["checkout", "--", ...files];
|
|
19904
|
+
return input.branch ? ["checkout", input.branch, "--"] : ["checkout"];
|
|
19896
19905
|
case "stash":
|
|
19897
19906
|
return input.message ? ["stash", "push", "-m", input.message] : ["stash", "push"];
|
|
19898
19907
|
case "push":
|
|
@@ -19997,7 +20006,7 @@ async function mapWithConcurrency2(items, limit, fn) {
|
|
|
19997
20006
|
|
|
19998
20007
|
// src/glob.ts
|
|
19999
20008
|
init_util();
|
|
20000
|
-
var DEFAULT_IGNORE2 = DEFAULT_WALK_IGNORE_DIRS2;
|
|
20009
|
+
var DEFAULT_IGNORE2 = new Set(DEFAULT_WALK_IGNORE_DIRS2);
|
|
20001
20010
|
var WALK_CONCURRENCY = 16;
|
|
20002
20011
|
var globTool = {
|
|
20003
20012
|
name: "glob",
|
|
@@ -20076,7 +20085,7 @@ var globTool = {
|
|
|
20076
20085
|
const matchedFiles = [];
|
|
20077
20086
|
for (const e of entries) {
|
|
20078
20087
|
const name = e.name;
|
|
20079
|
-
if (DEFAULT_IGNORE2.
|
|
20088
|
+
if (DEFAULT_IGNORE2.has(name)) continue;
|
|
20080
20089
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
20081
20090
|
const full = path30.join(dir, name);
|
|
20082
20091
|
if (e.isDirectory()) {
|
|
@@ -20150,6 +20159,81 @@ var DANGEROUS_PATTERNS = [
|
|
|
20150
20159
|
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
20151
20160
|
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
20152
20161
|
];
|
|
20162
|
+
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
20163
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
20164
|
+
if (pattern[i] !== "(") continue;
|
|
20165
|
+
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
20166
|
+
let depth = 0;
|
|
20167
|
+
let inClass = false;
|
|
20168
|
+
let j = i;
|
|
20169
|
+
for (; j < pattern.length; j++) {
|
|
20170
|
+
const ch = pattern[j];
|
|
20171
|
+
if (ch === "\\") {
|
|
20172
|
+
j++;
|
|
20173
|
+
continue;
|
|
20174
|
+
}
|
|
20175
|
+
if (inClass) {
|
|
20176
|
+
if (ch === "]") inClass = false;
|
|
20177
|
+
continue;
|
|
20178
|
+
}
|
|
20179
|
+
if (ch === "[") {
|
|
20180
|
+
inClass = true;
|
|
20181
|
+
continue;
|
|
20182
|
+
}
|
|
20183
|
+
if (ch === "(") depth++;
|
|
20184
|
+
else if (ch === ")") {
|
|
20185
|
+
depth--;
|
|
20186
|
+
if (depth === 0) break;
|
|
20187
|
+
}
|
|
20188
|
+
}
|
|
20189
|
+
if (j >= pattern.length) return false;
|
|
20190
|
+
const next = pattern[j + 1];
|
|
20191
|
+
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
20192
|
+
let inner = pattern.slice(i + 1, j);
|
|
20193
|
+
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
20194
|
+
const branches = [];
|
|
20195
|
+
let current = "";
|
|
20196
|
+
let d = 0;
|
|
20197
|
+
let cls = false;
|
|
20198
|
+
for (let k = 0; k < inner.length; k++) {
|
|
20199
|
+
const ch = inner[k];
|
|
20200
|
+
if (ch === "\\") {
|
|
20201
|
+
current += ch + (inner[k + 1] ?? "");
|
|
20202
|
+
k++;
|
|
20203
|
+
continue;
|
|
20204
|
+
}
|
|
20205
|
+
if (cls) {
|
|
20206
|
+
if (ch === "]") cls = false;
|
|
20207
|
+
current += ch;
|
|
20208
|
+
continue;
|
|
20209
|
+
}
|
|
20210
|
+
if (ch === "[") {
|
|
20211
|
+
cls = true;
|
|
20212
|
+
current += ch;
|
|
20213
|
+
continue;
|
|
20214
|
+
}
|
|
20215
|
+
if (ch === "(") d++;
|
|
20216
|
+
if (ch === ")") d--;
|
|
20217
|
+
if (ch === "|" && d === 0) {
|
|
20218
|
+
branches.push(current);
|
|
20219
|
+
current = "";
|
|
20220
|
+
continue;
|
|
20221
|
+
}
|
|
20222
|
+
current += ch;
|
|
20223
|
+
}
|
|
20224
|
+
branches.push(current);
|
|
20225
|
+
if (branches.length < 2) continue;
|
|
20226
|
+
for (let a = 0; a < branches.length; a++) {
|
|
20227
|
+
for (let b = a + 1; b < branches.length; b++) {
|
|
20228
|
+
const x = branches[a];
|
|
20229
|
+
const y = branches[b];
|
|
20230
|
+
if (x === "" || y === "") return true;
|
|
20231
|
+
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
20232
|
+
}
|
|
20233
|
+
}
|
|
20234
|
+
}
|
|
20235
|
+
return false;
|
|
20236
|
+
}
|
|
20153
20237
|
function compileUserRegex(pattern, flags) {
|
|
20154
20238
|
if (typeof pattern !== "string") {
|
|
20155
20239
|
return { ok: false, reason: "pattern must be a string" };
|
|
@@ -20168,6 +20252,12 @@ function compileUserRegex(pattern, flags) {
|
|
|
20168
20252
|
};
|
|
20169
20253
|
}
|
|
20170
20254
|
}
|
|
20255
|
+
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
20256
|
+
return {
|
|
20257
|
+
ok: false,
|
|
20258
|
+
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
20259
|
+
};
|
|
20260
|
+
}
|
|
20171
20261
|
try {
|
|
20172
20262
|
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
20173
20263
|
} catch (err) {
|
|
@@ -20184,7 +20274,7 @@ function capSubject(line) {
|
|
|
20184
20274
|
|
|
20185
20275
|
// src/grep.ts
|
|
20186
20276
|
init_util();
|
|
20187
|
-
var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
|
|
20277
|
+
var DEFAULT_IGNORE3 = new Set(DEFAULT_WALK_IGNORE_DIRS3);
|
|
20188
20278
|
var NATIVE_SCAN_CONCURRENCY = 32;
|
|
20189
20279
|
var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
|
|
20190
20280
|
var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
@@ -20257,7 +20347,7 @@ var grepTool = {
|
|
|
20257
20347
|
field: "pattern"
|
|
20258
20348
|
});
|
|
20259
20349
|
}
|
|
20260
|
-
const base = input.path ?
|
|
20350
|
+
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
20261
20351
|
const mode = input.output_mode ?? "content";
|
|
20262
20352
|
const limit = Math.max(1, Math.min(input.limit ?? 200, 2e3));
|
|
20263
20353
|
const validation = compileUserRegex(input.pattern, input.case_insensitive ? "i" : "");
|
|
@@ -20582,7 +20672,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
20582
20672
|
const subdirs = [];
|
|
20583
20673
|
for (const e of entries) {
|
|
20584
20674
|
if (stopped) return;
|
|
20585
|
-
if (DEFAULT_IGNORE3.
|
|
20675
|
+
if (DEFAULT_IGNORE3.has(e.name)) continue;
|
|
20586
20676
|
if (e.isSymbolicLink()) continue;
|
|
20587
20677
|
const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
|
|
20588
20678
|
const full = path31.join(dir, e.name);
|
|
@@ -24057,12 +24147,12 @@ import { spawn as spawn13 } from "node:child_process";
|
|
|
24057
24147
|
import * as fs27 from "node:fs/promises";
|
|
24058
24148
|
import * as os9 from "node:os";
|
|
24059
24149
|
import * as path32 from "node:path";
|
|
24060
|
-
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core/utils";
|
|
24150
|
+
import { buildChildEnv as buildChildEnv8, toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
24061
24151
|
var patchTool = {
|
|
24062
24152
|
name: "patch",
|
|
24063
24153
|
category: "Filesystem",
|
|
24064
24154
|
description: "Apply a unified diff (patch) to the project. This is the correct tool when you have a diff that needs to be applied precisely, including handling of rejects.",
|
|
24065
|
-
usageHint: "Best used when you already have a diff (from generation, external source, or previous step).\n- Use `dry_run: true` to see what would happen without modifying files.\n-
|
|
24155
|
+
usageHint: "Best used when you already have a diff (from generation, external source, or previous step).\n- Use `dry_run: true` to see what would happen without modifying files.\n- Applied with `--merge`: a conflicting hunk writes git-style conflict\n markers (<<<<<<< / ======= / >>>>>>>) INTO the file and reports failure.\n It does NOT create .rej/.orig files. `files` lists what changed on disk\n even when the patch failed, so read those back before retrying.\nOften cleaner than many small `edit` operations for larger changes.",
|
|
24066
24156
|
selection: {
|
|
24067
24157
|
doNotUseWhen: "you do not already have a unified diff or only need one precise replacement.",
|
|
24068
24158
|
useInstead: ["edit"]
|
|
@@ -24088,31 +24178,50 @@ var patchTool = {
|
|
|
24088
24178
|
},
|
|
24089
24179
|
async execute(input, ctx, opts) {
|
|
24090
24180
|
if (!input?.patch) throw new Error("patch: patch content is required");
|
|
24091
|
-
const dir = input.directory ? safeResolve(input.directory, ctx) : ctx.cwd;
|
|
24092
24181
|
const strip = Math.max(1, input.strip ?? 1);
|
|
24093
24182
|
const dryRun = input.dry_run ?? false;
|
|
24183
|
+
const refuse = (message) => ({
|
|
24184
|
+
applied: 0,
|
|
24185
|
+
rejected: 1,
|
|
24186
|
+
files: [],
|
|
24187
|
+
dry_run: dryRun,
|
|
24188
|
+
message
|
|
24189
|
+
});
|
|
24190
|
+
let dir;
|
|
24191
|
+
try {
|
|
24192
|
+
dir = input.directory ? await safeResolveReal(input.directory, ctx) : ctx.cwd;
|
|
24193
|
+
} catch (err) {
|
|
24194
|
+
return refuse(`patch refused: ${toErrorMessage4(err)}`);
|
|
24195
|
+
}
|
|
24196
|
+
const realRoot = await fs27.realpath(ctx.projectRoot).catch(() => path32.resolve(ctx.projectRoot));
|
|
24094
24197
|
const targets = extractDiffTargets(input.patch);
|
|
24095
24198
|
const resolvedTargets = [];
|
|
24096
24199
|
for (const t of targets) {
|
|
24097
|
-
const stripped = stripPathComponents(t, strip);
|
|
24200
|
+
const stripped = stripPathComponents(t.raw, strip);
|
|
24098
24201
|
if (!stripped) continue;
|
|
24202
|
+
if (path32.isAbsolute(stripped)) {
|
|
24203
|
+
return refuse(`patch refused: target "${t.raw}" strips to absolute path`);
|
|
24204
|
+
}
|
|
24099
24205
|
const candidate = path32.resolve(dir, stripped);
|
|
24100
|
-
|
|
24206
|
+
let real;
|
|
24207
|
+
try {
|
|
24208
|
+
real = await resolveRealInsideRoot(candidate, ctx);
|
|
24209
|
+
} catch (err) {
|
|
24210
|
+
return refuse(`patch refused: target "${t.raw}" ${toErrorMessage4(err)}`);
|
|
24211
|
+
}
|
|
24212
|
+
const rel = path32.relative(realRoot, real);
|
|
24101
24213
|
if (rel.startsWith("..") || path32.isAbsolute(rel)) {
|
|
24102
|
-
return {
|
|
24103
|
-
applied: 0,
|
|
24104
|
-
rejected: 1,
|
|
24105
|
-
files: [],
|
|
24106
|
-
dry_run: dryRun,
|
|
24107
|
-
message: `patch refused: target "${t}" resolves outside project root`
|
|
24108
|
-
};
|
|
24214
|
+
return refuse(`patch refused: target "${t.raw}" resolves outside project root`);
|
|
24109
24215
|
}
|
|
24110
|
-
resolvedTargets.push(
|
|
24216
|
+
resolvedTargets.push({ raw: t.raw, deleted: t.deleted, abs: real });
|
|
24111
24217
|
}
|
|
24112
24218
|
const beforeContents = /* @__PURE__ */ new Map();
|
|
24219
|
+
const beforeExisted = /* @__PURE__ */ new Set();
|
|
24113
24220
|
if (!dryRun) {
|
|
24114
24221
|
for (const target of resolvedTargets) {
|
|
24115
|
-
|
|
24222
|
+
const existed = (await fs27.stat(target.abs).catch(() => null))?.isFile() ?? false;
|
|
24223
|
+
if (existed) beforeExisted.add(target.abs);
|
|
24224
|
+
beforeContents.set(target.abs, await readTextForTracking(target.abs));
|
|
24116
24225
|
}
|
|
24117
24226
|
}
|
|
24118
24227
|
const tmpDir = await fs27.mkdtemp(path32.join(os9.tmpdir(), ".wstack_patch_"));
|
|
@@ -24122,32 +24231,70 @@ var patchTool = {
|
|
|
24122
24231
|
const patchFile = path32.join(tmpDir, "in.diff");
|
|
24123
24232
|
await fs27.writeFile(patchFile, input.patch, { mode: 384 });
|
|
24124
24233
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
24125
|
-
const result = await runPatch(args, dir, opts.signal
|
|
24126
|
-
|
|
24127
|
-
|
|
24128
|
-
|
|
24129
|
-
|
|
24130
|
-
|
|
24131
|
-
dry_run: dryRun,
|
|
24132
|
-
message: `patch failed: ${result.stderr || result.stdout}`
|
|
24133
|
-
};
|
|
24134
|
-
}
|
|
24135
|
-
const patched = extractPatchedFiles(result.stdout);
|
|
24234
|
+
const result = await runPatch(args, dir, opts.signal, {
|
|
24235
|
+
patchFile,
|
|
24236
|
+
strip,
|
|
24237
|
+
dryRun
|
|
24238
|
+
});
|
|
24239
|
+
const touched = [];
|
|
24136
24240
|
if (!dryRun) {
|
|
24137
24241
|
for (const target of resolvedTargets) {
|
|
24138
|
-
const
|
|
24139
|
-
const
|
|
24242
|
+
const abs = target.abs;
|
|
24243
|
+
const before = beforeContents.get(abs) ?? null;
|
|
24244
|
+
const stat19 = await fs27.stat(abs).catch(() => null);
|
|
24245
|
+
if (!stat19?.isFile()) {
|
|
24246
|
+
if (beforeExisted.has(abs)) {
|
|
24247
|
+
touched.push(abs);
|
|
24248
|
+
ctx.session?.recordFileChange?.({
|
|
24249
|
+
path: abs,
|
|
24250
|
+
action: "deleted",
|
|
24251
|
+
before,
|
|
24252
|
+
after: null
|
|
24253
|
+
});
|
|
24254
|
+
}
|
|
24255
|
+
continue;
|
|
24256
|
+
}
|
|
24257
|
+
const after = await readTextForTracking(abs);
|
|
24140
24258
|
if (after === null || after === before) continue;
|
|
24141
|
-
|
|
24142
|
-
|
|
24259
|
+
touched.push(abs);
|
|
24260
|
+
ctx.recordRead?.(abs, stat19.mtimeMs, "write", sha256hex(after));
|
|
24143
24261
|
ctx.session?.recordFileChange?.({
|
|
24144
|
-
path:
|
|
24262
|
+
path: abs,
|
|
24145
24263
|
action: before === null ? "created" : "modified",
|
|
24146
24264
|
before,
|
|
24147
24265
|
after
|
|
24148
24266
|
});
|
|
24149
24267
|
}
|
|
24150
24268
|
}
|
|
24269
|
+
if (result.exitCode !== 0) {
|
|
24270
|
+
if (!dryRun) {
|
|
24271
|
+
const partial = touched.length > 0 ? ` ${touched.length} file(s) were still modified on disk and have been recorded for rewind: ${touched.map((p) => path32.relative(realRoot, p) || p).join(", ")}.` : "";
|
|
24272
|
+
return {
|
|
24273
|
+
applied: touched.length,
|
|
24274
|
+
rejected: 1,
|
|
24275
|
+
// Normalize to relative-to-realRoot for API consistency with the
|
|
24276
|
+
// success path (which returns GNU patch's dir-relative names).
|
|
24277
|
+
// `touched` entries are realpaths from resolveRealInsideRoot, and
|
|
24278
|
+
// realRoot is also a realpath, so path.relative is like-for-like.
|
|
24279
|
+
files: touched.map((p) => path32.relative(realRoot, p) || p),
|
|
24280
|
+
dry_run: dryRun,
|
|
24281
|
+
message: `patch failed: ${result.stderr || result.stdout}${partial}`
|
|
24282
|
+
};
|
|
24283
|
+
}
|
|
24284
|
+
const wouldPatch = extractPatchedFiles(result.stdout);
|
|
24285
|
+
return {
|
|
24286
|
+
applied: wouldPatch.length,
|
|
24287
|
+
rejected: 1,
|
|
24288
|
+
files: wouldPatch,
|
|
24289
|
+
dry_run: dryRun,
|
|
24290
|
+
message: `patch preview: would conflict \u2014 ${result.stderr || result.stdout}`
|
|
24291
|
+
};
|
|
24292
|
+
}
|
|
24293
|
+
const patched = result.engine === "git" ? [
|
|
24294
|
+
...new Set(
|
|
24295
|
+
resolvedTargets.map((target) => path32.relative(dir, target.abs) || target.abs)
|
|
24296
|
+
)
|
|
24297
|
+
] : extractPatchedFiles(result.stdout);
|
|
24151
24298
|
return {
|
|
24152
24299
|
applied: patched.length,
|
|
24153
24300
|
rejected: 0,
|
|
@@ -24175,27 +24322,86 @@ async function readTextForTracking(absPath) {
|
|
|
24175
24322
|
}
|
|
24176
24323
|
function extractDiffTargets(patch) {
|
|
24177
24324
|
const out = [];
|
|
24178
|
-
const
|
|
24179
|
-
|
|
24180
|
-
|
|
24181
|
-
|
|
24182
|
-
|
|
24183
|
-
|
|
24184
|
-
|
|
24325
|
+
const clean = (raw) => {
|
|
24326
|
+
if (!raw) return "";
|
|
24327
|
+
return (raw.length > 4096 ? raw.slice(0, 4096) : raw).trim();
|
|
24328
|
+
};
|
|
24329
|
+
let lastOld;
|
|
24330
|
+
let inHunk = false;
|
|
24331
|
+
let oldLinesLeft = 0;
|
|
24332
|
+
let newLinesLeft = 0;
|
|
24333
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
24334
|
+
const hunkMatch = /^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/.exec(line);
|
|
24335
|
+
if (hunkMatch) {
|
|
24336
|
+
inHunk = true;
|
|
24337
|
+
oldLinesLeft = hunkMatch[1] ? Number(hunkMatch[1]) : 1;
|
|
24338
|
+
newLinesLeft = hunkMatch[2] ? Number(hunkMatch[2]) : 1;
|
|
24339
|
+
lastOld = void 0;
|
|
24340
|
+
continue;
|
|
24341
|
+
}
|
|
24342
|
+
if (inHunk) {
|
|
24343
|
+
const ch = line[0];
|
|
24344
|
+
if (ch === "-") oldLinesLeft--;
|
|
24345
|
+
else if (ch === "+") newLinesLeft--;
|
|
24346
|
+
else if (ch === " " || ch === void 0) {
|
|
24347
|
+
oldLinesLeft--;
|
|
24348
|
+
newLinesLeft--;
|
|
24349
|
+
}
|
|
24350
|
+
if (oldLinesLeft <= 0 && newLinesLeft <= 0) inHunk = false;
|
|
24351
|
+
continue;
|
|
24352
|
+
}
|
|
24353
|
+
const oldMatch = /^---\s+([^\t\r\n]+)/.exec(line);
|
|
24354
|
+
if (oldMatch) {
|
|
24355
|
+
lastOld = clean(oldMatch[1]);
|
|
24356
|
+
continue;
|
|
24357
|
+
}
|
|
24358
|
+
const newMatch = /^\+\+\+\s+([^\t\r\n]+)/.exec(line);
|
|
24359
|
+
if (!newMatch) continue;
|
|
24360
|
+
const newTarget = clean(newMatch[1]);
|
|
24361
|
+
if (newTarget && newTarget !== "/dev/null") {
|
|
24362
|
+
out.push({ raw: newTarget, deleted: false });
|
|
24363
|
+
} else if (lastOld && lastOld !== "/dev/null") {
|
|
24364
|
+
out.push({ raw: lastOld, deleted: true });
|
|
24365
|
+
}
|
|
24366
|
+
lastOld = void 0;
|
|
24185
24367
|
}
|
|
24186
24368
|
return out;
|
|
24187
24369
|
}
|
|
24188
24370
|
function stripPathComponents(p, strip) {
|
|
24189
|
-
const
|
|
24190
|
-
|
|
24191
|
-
|
|
24371
|
+
const s = p.replace(/\\/g, "/");
|
|
24372
|
+
let idx = 0;
|
|
24373
|
+
for (let i = 0; i < strip; i++) {
|
|
24374
|
+
while (idx < s.length && s[idx] !== "/") idx++;
|
|
24375
|
+
let hadSlash = false;
|
|
24376
|
+
while (idx < s.length && s[idx] === "/") {
|
|
24377
|
+
idx++;
|
|
24378
|
+
hadSlash = true;
|
|
24379
|
+
}
|
|
24380
|
+
if (!hadSlash) return void 0;
|
|
24381
|
+
}
|
|
24382
|
+
return s.slice(idx) || void 0;
|
|
24383
|
+
}
|
|
24384
|
+
function runPatch(args, cwd, signal, fallback) {
|
|
24385
|
+
return runPatchProcess("patch", args, cwd, signal).then(async (result) => {
|
|
24386
|
+
if (!result.unavailable) return { ...result, engine: "patch" };
|
|
24387
|
+
const gitArgs = [
|
|
24388
|
+
"apply",
|
|
24389
|
+
"--unsafe-paths",
|
|
24390
|
+
`-p${fallback.strip}`,
|
|
24391
|
+
"--verbose",
|
|
24392
|
+
...fallback.dryRun ? ["--check"] : [],
|
|
24393
|
+
fallback.patchFile
|
|
24394
|
+
];
|
|
24395
|
+
const gitResult = await runPatchProcess("git", gitArgs, cwd, signal);
|
|
24396
|
+
return { ...gitResult, engine: "git" };
|
|
24397
|
+
});
|
|
24192
24398
|
}
|
|
24193
|
-
function
|
|
24399
|
+
function runPatchProcess(command, args, cwd, signal) {
|
|
24194
24400
|
return new Promise((resolve17) => {
|
|
24195
24401
|
let stdout = "";
|
|
24196
24402
|
let stderr = "";
|
|
24197
24403
|
const env = { ...buildChildEnv8(), LANG: "C", LC_ALL: "C" };
|
|
24198
|
-
const child = spawn13(
|
|
24404
|
+
const child = spawn13(command, args, {
|
|
24199
24405
|
cwd,
|
|
24200
24406
|
signal,
|
|
24201
24407
|
env,
|
|
@@ -24208,13 +24414,24 @@ function runPatch(args, cwd, signal) {
|
|
|
24208
24414
|
child.stderr?.on("data", (c) => {
|
|
24209
24415
|
stderr += c.toString();
|
|
24210
24416
|
});
|
|
24211
|
-
child.on(
|
|
24212
|
-
|
|
24417
|
+
child.on(
|
|
24418
|
+
"close",
|
|
24419
|
+
(code) => resolve17({ exitCode: code ?? 1, stdout, stderr, unavailable: false })
|
|
24420
|
+
);
|
|
24421
|
+
child.on(
|
|
24422
|
+
"error",
|
|
24423
|
+
(e) => resolve17({
|
|
24424
|
+
exitCode: 1,
|
|
24425
|
+
stdout: "",
|
|
24426
|
+
stderr: e.message,
|
|
24427
|
+
unavailable: e.code === "ENOENT"
|
|
24428
|
+
})
|
|
24429
|
+
);
|
|
24213
24430
|
});
|
|
24214
24431
|
}
|
|
24215
24432
|
function extractPatchedFiles(output) {
|
|
24216
24433
|
const files = [];
|
|
24217
|
-
const re = /patching file (.+)/gi;
|
|
24434
|
+
const re = /(?:patching|checking) file (.+)/gi;
|
|
24218
24435
|
for (const m of output.matchAll(re)) {
|
|
24219
24436
|
if (m[1]) files.push(m[1]);
|
|
24220
24437
|
}
|
|
@@ -24523,7 +24740,7 @@ function mkResult(plan, ok, message, todos) {
|
|
|
24523
24740
|
init_util();
|
|
24524
24741
|
import * as fs28 from "node:fs/promises";
|
|
24525
24742
|
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
24526
|
-
import { toErrorMessage as
|
|
24743
|
+
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
24527
24744
|
var ADVANCED_MODE_META_KEY = "tools.read.advancedMode";
|
|
24528
24745
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
24529
24746
|
var readTool = {
|
|
@@ -24591,7 +24808,7 @@ var readTool = {
|
|
|
24591
24808
|
});
|
|
24592
24809
|
}
|
|
24593
24810
|
throw new FsError({
|
|
24594
|
-
message: `read: failed to stat "${input.path}": ${
|
|
24811
|
+
message: `read: failed to stat "${input.path}": ${toErrorMessage5(err)}`,
|
|
24595
24812
|
code: "FS_READ_FAILED",
|
|
24596
24813
|
path: absPath,
|
|
24597
24814
|
context: { errno: code },
|
|
@@ -25250,7 +25467,7 @@ function substituteVars(content, name, vars) {
|
|
|
25250
25467
|
// src/search.ts
|
|
25251
25468
|
import { FetchError as FetchError3, ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
25252
25469
|
import { expectDefined as expectDefined9 } from "@wrongstack/core/utils";
|
|
25253
|
-
import { toErrorMessage as
|
|
25470
|
+
import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
25254
25471
|
var DEFAULT_NUM = 10;
|
|
25255
25472
|
var MAX_RESULTS = 50;
|
|
25256
25473
|
var TIMEOUT_MS3 = 15e3;
|
|
@@ -25454,7 +25671,7 @@ async function duckduckgoSearch(query, num, signal) {
|
|
|
25454
25671
|
return parseDuckDuckGo(html, num);
|
|
25455
25672
|
} catch (err) {
|
|
25456
25673
|
console.log(
|
|
25457
|
-
JSON.stringify({ level: "debug", event: "search_failed", query, error:
|
|
25674
|
+
JSON.stringify({ level: "debug", event: "search_failed", query, error: toErrorMessage6(err) })
|
|
25458
25675
|
);
|
|
25459
25676
|
return [{ title: "Search unavailable", url: "https://duckduckgo.com/unavailable", snippet: "Could not reach DuckDuckGo", score: 0 }];
|
|
25460
25677
|
}
|
|
@@ -25638,7 +25855,7 @@ function decodeHtmlEntities(text) {
|
|
|
25638
25855
|
|
|
25639
25856
|
// src/set-working-dir.ts
|
|
25640
25857
|
import * as fs31 from "node:fs/promises";
|
|
25641
|
-
import { toErrorMessage as
|
|
25858
|
+
import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
|
|
25642
25859
|
var setWorkingDirTool = {
|
|
25643
25860
|
name: "set_working_dir",
|
|
25644
25861
|
category: "Context",
|
|
@@ -25672,7 +25889,7 @@ var setWorkingDirTool = {
|
|
|
25672
25889
|
} catch (err) {
|
|
25673
25890
|
return {
|
|
25674
25891
|
current: ctx.workingDir,
|
|
25675
|
-
error:
|
|
25892
|
+
error: toErrorMessage7(err)
|
|
25676
25893
|
};
|
|
25677
25894
|
}
|
|
25678
25895
|
try {
|
|
@@ -26407,7 +26624,7 @@ var toolHelpTool = {
|
|
|
26407
26624
|
const format = input.format ?? "short";
|
|
26408
26625
|
const includeExamples = input.include_examples ?? false;
|
|
26409
26626
|
if (input.tool) {
|
|
26410
|
-
const tool = ctx.tools.find((t) => t.name === input.tool);
|
|
26627
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find((t) => t.name === input.tool);
|
|
26411
26628
|
if (!tool) {
|
|
26412
26629
|
return {
|
|
26413
26630
|
tool: input.tool,
|
|
@@ -26432,7 +26649,7 @@ var toolHelpTool = {
|
|
|
26432
26649
|
total: 1
|
|
26433
26650
|
};
|
|
26434
26651
|
}
|
|
26435
|
-
const allTools = ctx.tools.map((t) => ({
|
|
26652
|
+
const allTools = (ctx.catalogTools ?? ctx.tools).map((t) => ({
|
|
26436
26653
|
name: t.name,
|
|
26437
26654
|
description: t.description,
|
|
26438
26655
|
usageHint: t.usageHint ?? "",
|
|
@@ -26540,7 +26757,7 @@ var toolSearchTool = {
|
|
|
26540
26757
|
},
|
|
26541
26758
|
async execute(input, ctx) {
|
|
26542
26759
|
const limit = Math.min(input.limit ?? 20, 100);
|
|
26543
|
-
const tools = ctx.tools;
|
|
26760
|
+
const tools = ctx.catalogTools ?? ctx.tools;
|
|
26544
26761
|
const query = input.query?.toLowerCase() ?? "";
|
|
26545
26762
|
const filtered = tools.filter((t) => {
|
|
26546
26763
|
if (query && !t.name.toLowerCase().includes(query) && !t.description.toLowerCase().includes(query)) {
|
|
@@ -26620,7 +26837,7 @@ var toolUseTool = {
|
|
|
26620
26837
|
executionMs: 0
|
|
26621
26838
|
};
|
|
26622
26839
|
}
|
|
26623
|
-
const tool = ctx.tools.find((t) => t.name === input.tool);
|
|
26840
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find((t) => t.name === input.tool);
|
|
26624
26841
|
if (!tool) {
|
|
26625
26842
|
return {
|
|
26626
26843
|
tool: input.tool,
|
|
@@ -26678,7 +26895,13 @@ init_util();
|
|
|
26678
26895
|
import * as fs32 from "node:fs/promises";
|
|
26679
26896
|
import * as path36 from "node:path";
|
|
26680
26897
|
import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
26681
|
-
var DEFAULT_IGNORE5 =
|
|
26898
|
+
var DEFAULT_IGNORE5 = /* @__PURE__ */ new Set([
|
|
26899
|
+
...DEFAULT_WALK_IGNORE_DIRS4,
|
|
26900
|
+
".wrongstack",
|
|
26901
|
+
".ssh",
|
|
26902
|
+
".gnupg",
|
|
26903
|
+
".aws"
|
|
26904
|
+
]);
|
|
26682
26905
|
var DEFAULT_MAX_ENTRIES2 = 5e3;
|
|
26683
26906
|
var MAX_TREE_OUTPUT_BYTES = 256 * 1024;
|
|
26684
26907
|
var treeTool = {
|
|
@@ -26839,8 +27062,12 @@ async function walkDir(dir, depth, opts) {
|
|
|
26839
27062
|
return true;
|
|
26840
27063
|
});
|
|
26841
27064
|
if (depth > 0) {
|
|
26842
|
-
|
|
26843
|
-
|
|
27065
|
+
let dirCount = 0;
|
|
27066
|
+
let fileCount = 0;
|
|
27067
|
+
for (const e of filtered) {
|
|
27068
|
+
if (e.isDirectory()) dirCount++;
|
|
27069
|
+
else if (e.isFile()) fileCount++;
|
|
27070
|
+
}
|
|
26844
27071
|
opts.totalDirs.value += dirCount;
|
|
26845
27072
|
opts.totalFiles.value += fileCount;
|
|
26846
27073
|
opts.onProgress?.();
|
|
@@ -27521,6 +27748,62 @@ ${mode.description}`
|
|
|
27521
27748
|
};
|
|
27522
27749
|
}
|
|
27523
27750
|
|
|
27751
|
+
// src/next-steps-tool.ts
|
|
27752
|
+
import {
|
|
27753
|
+
MAX_PENDING_NEXT_STEPS,
|
|
27754
|
+
writePendingNextSteps
|
|
27755
|
+
} from "@wrongstack/core/agent";
|
|
27756
|
+
var nextStepsTool = {
|
|
27757
|
+
name: "nextsteps",
|
|
27758
|
+
category: "Session",
|
|
27759
|
+
description: "Record the after-task follow-on suggestions for this turn. Equivalent to ending your final message with a <nextsteps> block \u2014 use whichever you prefer. The list is fully replaced on every call (not appended).",
|
|
27760
|
+
usageHint: 'Call this at most once, on the turn you are finishing the work \u2014 not mid-task.\n- Each `text` is the **exact prompt message** that gets submitted back to you when the user picks it. Write agent-directed work ("Run the parser tests and fix any failures"), never a chore for the user to do by hand.\n- Order by priority; 1-4 items. Do not pad with filler or invent work to fill the list.\n- `auto: true` is honored on the first item only. Set it when that prompt is safe to run unattended \u2014 YOLO+auto executes it verbatim, so it must be complete and self-contained.\n- Omit the call entirely while any todo is still `pending` or `in_progress`; suggestions recorded in that state are discarded.\n- If you also write a <nextsteps> block in your message, that block wins.',
|
|
27761
|
+
permission: "auto",
|
|
27762
|
+
mutating: false,
|
|
27763
|
+
// mutates only turn-scoped conversation state — no confirmation needed
|
|
27764
|
+
timeoutMs: 5e3,
|
|
27765
|
+
capabilities: ["session.nextsteps"],
|
|
27766
|
+
icon: "todo",
|
|
27767
|
+
inputSchema: {
|
|
27768
|
+
type: "object",
|
|
27769
|
+
properties: {
|
|
27770
|
+
steps: {
|
|
27771
|
+
type: "array",
|
|
27772
|
+
minItems: 1,
|
|
27773
|
+
maxItems: MAX_PENDING_NEXT_STEPS,
|
|
27774
|
+
items: {
|
|
27775
|
+
type: "object",
|
|
27776
|
+
properties: {
|
|
27777
|
+
text: {
|
|
27778
|
+
type: "string",
|
|
27779
|
+
description: "The exact natural-language prompt message to submit back to the agent when the user selects this item."
|
|
27780
|
+
},
|
|
27781
|
+
auto: {
|
|
27782
|
+
type: "boolean",
|
|
27783
|
+
description: "Safe to run unattended. Honored on the first item only; ignored elsewhere."
|
|
27784
|
+
}
|
|
27785
|
+
},
|
|
27786
|
+
required: ["text"]
|
|
27787
|
+
},
|
|
27788
|
+
description: `The complete suggestion list (1-${MAX_PENDING_NEXT_STEPS} items), highest priority first. Replaces any previous list for this turn.`
|
|
27789
|
+
}
|
|
27790
|
+
},
|
|
27791
|
+
required: ["steps"]
|
|
27792
|
+
},
|
|
27793
|
+
async execute(input, ctx) {
|
|
27794
|
+
if (!Array.isArray(input?.steps)) {
|
|
27795
|
+
throw new Error("nextsteps: steps must be an array");
|
|
27796
|
+
}
|
|
27797
|
+
const steps = input.steps.filter((s) => typeof s?.text === "string").map((s) => s.auto === true ? { text: s.text, auto: true } : { text: s.text });
|
|
27798
|
+
if (steps.length === 0) {
|
|
27799
|
+
throw new Error("nextsteps: steps must contain at least one item with a non-empty text");
|
|
27800
|
+
}
|
|
27801
|
+
writePendingNextSteps(ctx, steps);
|
|
27802
|
+
const accepted = Math.min(steps.length, MAX_PENDING_NEXT_STEPS);
|
|
27803
|
+
return { accepted, auto: steps[0]?.auto === true };
|
|
27804
|
+
}
|
|
27805
|
+
};
|
|
27806
|
+
|
|
27524
27807
|
// src/pack.ts
|
|
27525
27808
|
var builtinToolsPack = {
|
|
27526
27809
|
name: "builtin-tools",
|
|
@@ -28121,6 +28404,11 @@ function createGlobalPsSlashCommand() {
|
|
|
28121
28404
|
// src/skill.ts
|
|
28122
28405
|
import * as fs34 from "node:fs/promises";
|
|
28123
28406
|
import * as path38 from "node:path";
|
|
28407
|
+
import {
|
|
28408
|
+
missingRequiredRuntimeTools,
|
|
28409
|
+
missingRuntimeCapabilities,
|
|
28410
|
+
runtimeToolReferencesFromText
|
|
28411
|
+
} from "@wrongstack/core/agent-catalog";
|
|
28124
28412
|
import { SKILL_LIMITS, stripFrontmatter } from "@wrongstack/core/skills";
|
|
28125
28413
|
import { ToolValidationError as ToolValidationError10 } from "@wrongstack/core/types";
|
|
28126
28414
|
var MAX_BODY_CHARS = SKILL_LIMITS.MAX_SKILL_BODY_CHARS;
|
|
@@ -28164,12 +28452,37 @@ function makeSkillTool(skillLoader) {
|
|
|
28164
28452
|
field: "name"
|
|
28165
28453
|
});
|
|
28166
28454
|
}
|
|
28455
|
+
const availableToolNames = (ctx?.catalogTools ?? ctx?.tools ?? []).map((tool) => tool.name);
|
|
28456
|
+
const missingCapabilities = missingRuntimeCapabilities(
|
|
28457
|
+
manifest.requiredCapabilities,
|
|
28458
|
+
availableToolNames
|
|
28459
|
+
);
|
|
28460
|
+
const missingTools = missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames);
|
|
28461
|
+
if (missingCapabilities.length > 0 || missingTools.length > 0) {
|
|
28462
|
+
throw new ToolValidationError10({
|
|
28463
|
+
message: `skill "${name}" is unavailable in this runtime; ` + [
|
|
28464
|
+
missingCapabilities.length > 0 ? `missing capabilities: ${missingCapabilities.join(", ")}` : "",
|
|
28465
|
+
missingTools.length > 0 ? `missing tools: ${missingTools.join(", ")}` : ""
|
|
28466
|
+
].filter(Boolean).join("; "),
|
|
28467
|
+
field: "name"
|
|
28468
|
+
});
|
|
28469
|
+
}
|
|
28167
28470
|
const dir = path38.dirname(manifest.path);
|
|
28168
28471
|
let loadedResource;
|
|
28169
28472
|
if (input.resource?.trim()) {
|
|
28170
28473
|
loadedResource = await loadResource(dir, input.resource.trim());
|
|
28171
28474
|
}
|
|
28172
28475
|
const raw = await skillLoader.readBody(name);
|
|
28476
|
+
const missingBodyTools = missingRequiredRuntimeTools(
|
|
28477
|
+
runtimeToolReferencesFromText(raw),
|
|
28478
|
+
availableToolNames
|
|
28479
|
+
);
|
|
28480
|
+
if (missingBodyTools.length > 0) {
|
|
28481
|
+
throw new ToolValidationError10({
|
|
28482
|
+
message: `skill "${name}" references unregistered tools: ${missingBodyTools.join(", ")}`,
|
|
28483
|
+
field: "name"
|
|
28484
|
+
});
|
|
28485
|
+
}
|
|
28173
28486
|
const body = stripFrontmatter(raw).trim().slice(0, MAX_BODY_CHARS);
|
|
28174
28487
|
const resources = loadedResource ? [] : await listResources(dir);
|
|
28175
28488
|
try {
|
|
@@ -28228,9 +28541,26 @@ async function loadResource(skillDir, rel) {
|
|
|
28228
28541
|
field: "resource"
|
|
28229
28542
|
});
|
|
28230
28543
|
}
|
|
28544
|
+
let realPath;
|
|
28545
|
+
let realRoot;
|
|
28546
|
+
try {
|
|
28547
|
+
realRoot = await fs34.realpath(root);
|
|
28548
|
+
realPath = await fs34.realpath(absPath);
|
|
28549
|
+
} catch {
|
|
28550
|
+
throw new ToolValidationError10({
|
|
28551
|
+
message: `skill: resource "${rel}" not readable`,
|
|
28552
|
+
field: "resource"
|
|
28553
|
+
});
|
|
28554
|
+
}
|
|
28555
|
+
if (realPath !== realRoot && !realPath.startsWith(realRoot + path38.sep)) {
|
|
28556
|
+
throw new ToolValidationError10({
|
|
28557
|
+
message: `skill: resource "${rel}" resolves outside the skill directory`,
|
|
28558
|
+
field: "resource"
|
|
28559
|
+
});
|
|
28560
|
+
}
|
|
28231
28561
|
let buf;
|
|
28232
28562
|
try {
|
|
28233
|
-
buf = await fs34.readFile(
|
|
28563
|
+
buf = await fs34.readFile(realPath);
|
|
28234
28564
|
} catch {
|
|
28235
28565
|
throw new ToolValidationError10({
|
|
28236
28566
|
message: `skill: resource "${rel}" not readable`,
|
|
@@ -28241,7 +28571,9 @@ async function loadResource(skillDir, rel) {
|
|
|
28241
28571
|
const truncated = raw.length > MAX_RESOURCE_CHARS;
|
|
28242
28572
|
return {
|
|
28243
28573
|
rel: norm,
|
|
28244
|
-
|
|
28574
|
+
// The canonical path — the one actually opened, and the one a follow-up
|
|
28575
|
+
// `bash` invocation should use.
|
|
28576
|
+
absPath: realPath,
|
|
28245
28577
|
content: truncated ? raw.slice(0, MAX_RESOURCE_CHARS) : raw,
|
|
28246
28578
|
bytes: buf.length,
|
|
28247
28579
|
truncated
|
|
@@ -28349,6 +28681,9 @@ var TOOL_ICON_MAP = {
|
|
|
28349
28681
|
// Task management
|
|
28350
28682
|
todo: "todo",
|
|
28351
28683
|
todos: "todo",
|
|
28684
|
+
// After-task suggestions — same family as the todo board, so it reuses the
|
|
28685
|
+
// icon rather than widening ToolIconId (every UI maps that union by hand).
|
|
28686
|
+
nextsteps: "todo",
|
|
28352
28687
|
// Planning
|
|
28353
28688
|
plan: "plan",
|
|
28354
28689
|
planning: "plan",
|
|
@@ -28475,11 +28810,7 @@ function selectBuiltinToolsForTier(tier, allTools) {
|
|
|
28475
28810
|
}
|
|
28476
28811
|
case "aggressive": {
|
|
28477
28812
|
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
28478
|
-
|
|
28479
|
-
const tier3Names = toolNameSet(TIER3_TOOLS);
|
|
28480
|
-
return allTools.filter(
|
|
28481
|
-
(tool) => tier1Names.has(tool.name) || tier2Names.has(tool.name) && tool.name !== "task" || tier3Names.has(tool.name) && tool.name !== "set_working_dir"
|
|
28482
|
-
);
|
|
28813
|
+
return allTools.filter((tool) => tier1Names.has(tool.name));
|
|
28483
28814
|
}
|
|
28484
28815
|
}
|
|
28485
28816
|
}
|
|
@@ -28606,6 +28937,7 @@ export {
|
|
|
28606
28937
|
mirrorSessionPlanToKanban,
|
|
28607
28938
|
mirrorSessionTasksToKanban,
|
|
28608
28939
|
mirrorSessionTodosToKanban,
|
|
28940
|
+
nextStepsTool,
|
|
28609
28941
|
normalizeShell,
|
|
28610
28942
|
onIndexStateChange,
|
|
28611
28943
|
outdatedTool,
|