@wrongstack/tools 0.305.1 → 0.306.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/_shell-pick.d.ts +4 -5
- package/dist/_util.d.ts +22 -5
- package/dist/audit.d.ts +0 -1
- package/dist/audit.js +135 -46
- package/dist/bash.js +61 -37
- package/dist/browser/index.js +29 -9
- package/dist/browser/types.d.ts +7 -1
- package/dist/builtin.js +1279 -726
- package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
- package/dist/codebase-index/index.js +223 -152
- package/dist/diff.d.ts +5 -0
- package/dist/diff.js +78 -12
- package/dist/document.js +18 -6
- package/dist/edit.js +69 -16
- package/dist/exec.js +44 -22
- package/dist/fetch.js +13 -1
- package/dist/format.d.ts +4 -2
- package/dist/format.js +81 -31
- package/dist/glob.js +12 -4
- package/dist/grep.d.ts +2 -0
- package/dist/grep.js +15 -4
- package/dist/index.js +1342 -761
- package/dist/install.js +96 -37
- package/dist/kanban-tool-types.d.ts +6 -1
- package/dist/kanban.js +60 -0
- package/dist/languages/index.js +28 -13
- package/dist/lint.js +28 -13
- package/dist/logs.d.ts +0 -1
- package/dist/logs.js +44 -13
- package/dist/memory.d.ts +8 -0
- package/dist/memory.js +23 -3
- package/dist/mode.d.ts +1 -1
- package/dist/mode.js +3 -0
- package/dist/next-steps.d.ts +2 -3
- package/dist/next-steps.js +3 -3
- package/dist/outdated.d.ts +0 -3
- package/dist/outdated.js +89 -48
- package/dist/pack.js +1279 -726
- package/dist/plan.js +76 -3
- package/dist/process-registry.d.ts +8 -2
- package/dist/process-registry.js +28 -13
- package/dist/ps-slash.js +22 -12
- package/dist/read.js +10 -3
- package/dist/replace.d.ts +4 -0
- package/dist/replace.js +104 -7
- package/dist/search.d.ts +6 -0
- package/dist/search.js +47 -26
- package/dist/skill.d.ts +6 -0
- package/dist/skill.js +9 -10
- package/dist/task.js +66 -2
- package/dist/test.js +28 -13
- package/dist/todo.js +64 -2
- package/dist/tool-icons.js +4 -2
- package/dist/tool-summary.d.ts +1 -1
- package/dist/tool-summary.js +76 -1
- package/dist/tool-tier.js +1279 -726
- package/dist/tree.js +9 -10
- package/dist/typecheck.d.ts +0 -2
- package/dist/typecheck.js +98 -31
- package/dist/write.js +58 -10
- package/package.json +4 -4
package/dist/search.js
CHANGED
|
@@ -102,6 +102,10 @@ async function guardedFetch(url, maxRedirects, signal, headers = {
|
|
|
102
102
|
if (res.status < 300 || res.status > 399) {
|
|
103
103
|
return res;
|
|
104
104
|
}
|
|
105
|
+
try {
|
|
106
|
+
await res.body?.cancel();
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
105
109
|
redirectCount++;
|
|
106
110
|
if (redirectCount > maxRedirects) {
|
|
107
111
|
throw new FetchError({
|
|
@@ -167,6 +171,7 @@ async function assertNotPrivate(hostname) {
|
|
|
167
171
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
168
172
|
var DEFAULT_NUM = 10;
|
|
169
173
|
var MAX_RESULTS = 50;
|
|
174
|
+
var MAX_SNIPPET_CHARS = 300;
|
|
170
175
|
var TIMEOUT_MS = 15e3;
|
|
171
176
|
var CACHE_TTL_MS = 3e5;
|
|
172
177
|
var CACHE_MAX_ENTRIES = 200;
|
|
@@ -174,7 +179,7 @@ var cache = /* @__PURE__ */ new Map();
|
|
|
174
179
|
var searchTool = {
|
|
175
180
|
name: "search",
|
|
176
181
|
category: "Search",
|
|
177
|
-
description: "Perform a web search and return results with title, URL, and snippet. Use this when you need up-to-date external information that is not in the local codebase. Results are cached (5 min TTL) and deduplicated by URL.",
|
|
182
|
+
description: "Perform a web search and return results with title, URL, and snippet. Use this when you need up-to-date external information that is not in the local codebase. Results are cached (5 min TTL) and deduplicated by URL. google and bing are best-effort HTML scrapes that fall back to duckduckgo when they return nothing usable.",
|
|
178
183
|
usageHint: "Good for: API documentation, error messages, library usage examples, current best practices.\n\n- Prefer specific queries over very broad ones.\n- Results go through the guarded fetch system (same protections as the `fetch` tool).\n- Supports duckduckgo (default), google, and bing sources.\n- Set `skip_cache: true` to force a fresh search.\n- This is often better than the model trying to recall outdated knowledge.",
|
|
179
184
|
permission: "auto",
|
|
180
185
|
mutating: false,
|
|
@@ -248,7 +253,7 @@ var searchTool = {
|
|
|
248
253
|
query: input.query,
|
|
249
254
|
results: results.slice(0, num),
|
|
250
255
|
source: entry.source,
|
|
251
|
-
truncated: results.length
|
|
256
|
+
truncated: results.length > num,
|
|
252
257
|
cached: true
|
|
253
258
|
}
|
|
254
259
|
};
|
|
@@ -260,17 +265,17 @@ var searchTool = {
|
|
|
260
265
|
text: `Querying ${source} for "${input.query}"\u2026`,
|
|
261
266
|
data: { source, query: input.query, cached: false }
|
|
262
267
|
};
|
|
263
|
-
let
|
|
268
|
+
let engine;
|
|
264
269
|
let effectiveSource = source;
|
|
265
270
|
switch (source) {
|
|
266
271
|
case "duckduckgo":
|
|
267
|
-
|
|
272
|
+
engine = await duckduckgoSearch(input.query, opts.signal);
|
|
268
273
|
break;
|
|
269
274
|
case "google":
|
|
270
|
-
|
|
275
|
+
engine = await googleSearch(input.query, opts.signal);
|
|
271
276
|
break;
|
|
272
277
|
case "bing":
|
|
273
|
-
|
|
278
|
+
engine = await bingSearch(input.query, opts.signal);
|
|
274
279
|
break;
|
|
275
280
|
default:
|
|
276
281
|
throw new ToolValidationError2({
|
|
@@ -278,23 +283,27 @@ var searchTool = {
|
|
|
278
283
|
field: "source"
|
|
279
284
|
});
|
|
280
285
|
}
|
|
281
|
-
let ranked = rankSearchResults(
|
|
286
|
+
let ranked = rankSearchResults(engine.results, input.query);
|
|
287
|
+
let engineError = engine.error;
|
|
282
288
|
if (source !== "duckduckgo" && shouldFallbackToDuckDuckGo(ranked, input.query)) {
|
|
283
289
|
yield {
|
|
284
290
|
type: "log",
|
|
285
291
|
text: `${source} returned no relevant static results; falling back to duckduckgo`,
|
|
286
292
|
data: { source, fallback: "duckduckgo", query: input.query }
|
|
287
293
|
};
|
|
288
|
-
|
|
289
|
-
ranked = rankSearchResults(
|
|
294
|
+
const fallback = await duckduckgoSearch(input.query, opts.signal);
|
|
295
|
+
ranked = rankSearchResults(fallback.results, input.query);
|
|
296
|
+
engineError = fallback.error;
|
|
290
297
|
effectiveSource = "duckduckgo";
|
|
291
298
|
}
|
|
292
299
|
const finalResults = ranked.slice(0, num);
|
|
293
|
-
|
|
294
|
-
|
|
300
|
+
if (!engineError) {
|
|
301
|
+
cache.set(cacheKey, { results: ranked, source: effectiveSource, timestamp: Date.now() });
|
|
302
|
+
pruneCacheEntries();
|
|
303
|
+
}
|
|
295
304
|
yield {
|
|
296
305
|
type: "partial_output",
|
|
297
|
-
text: `${finalResults.length} results from ${effectiveSource}`,
|
|
306
|
+
text: engineError ? `search failed: ${engineError}` : `${finalResults.length} results from ${effectiveSource}`,
|
|
298
307
|
data: { count: finalResults.length, cached: false, source: effectiveSource }
|
|
299
308
|
};
|
|
300
309
|
yield {
|
|
@@ -307,8 +316,9 @@ var searchTool = {
|
|
|
307
316
|
snippet: r.snippet
|
|
308
317
|
})),
|
|
309
318
|
source: effectiveSource,
|
|
310
|
-
truncated:
|
|
311
|
-
cached: false
|
|
319
|
+
truncated: ranked.length > num,
|
|
320
|
+
cached: false,
|
|
321
|
+
...engineError ? { error: engineError } : {}
|
|
312
322
|
}
|
|
313
323
|
};
|
|
314
324
|
}
|
|
@@ -362,18 +372,18 @@ function shouldFallbackToDuckDuckGo(results, query) {
|
|
|
362
372
|
return terms.some((term) => haystack.includes(term));
|
|
363
373
|
});
|
|
364
374
|
}
|
|
365
|
-
async function duckduckgoSearch(query,
|
|
375
|
+
async function duckduckgoSearch(query, signal) {
|
|
366
376
|
const encoded = encodeURIComponent(query);
|
|
367
377
|
const url = `https://lite.duckduckgo.com/lite/?q=${encoded}&kd=-1&kl=wt-wt`;
|
|
368
378
|
try {
|
|
369
379
|
const response = await fetchWithTimeout(url, signal, TIMEOUT_MS);
|
|
370
380
|
const html = await response.text();
|
|
371
|
-
return parseDuckDuckGo(html,
|
|
381
|
+
return { results: parseDuckDuckGo(html, MAX_RESULTS) };
|
|
372
382
|
} catch (err) {
|
|
373
383
|
console.log(
|
|
374
384
|
JSON.stringify({ level: "debug", event: "search_failed", query, error: toErrorMessage(err) })
|
|
375
385
|
);
|
|
376
|
-
return
|
|
386
|
+
return { results: [], error: `duckduckgo unreachable: ${toErrorMessage(err)}` };
|
|
377
387
|
}
|
|
378
388
|
}
|
|
379
389
|
function takeFrom(iter, max) {
|
|
@@ -407,7 +417,7 @@ function parseDuckDuckGo(html, num) {
|
|
|
407
417
|
results.push({
|
|
408
418
|
title: entry.title ?? "",
|
|
409
419
|
url: entry.url ?? "",
|
|
410
|
-
snippet: snippetMatches[i] ?? "",
|
|
420
|
+
snippet: capSnippet(snippetMatches[i] ?? ""),
|
|
411
421
|
score: 1
|
|
412
422
|
});
|
|
413
423
|
}
|
|
@@ -432,11 +442,15 @@ function normalizeDuckDuckGoUrl(raw) {
|
|
|
432
442
|
return raw;
|
|
433
443
|
}
|
|
434
444
|
}
|
|
435
|
-
async function googleSearch(query,
|
|
445
|
+
async function googleSearch(query, signal) {
|
|
436
446
|
const encoded = encodeURIComponent(query);
|
|
437
447
|
const url = `https://www.google.com/search?q=${encoded}&hl=en`;
|
|
438
|
-
|
|
439
|
-
|
|
448
|
+
try {
|
|
449
|
+
const html = await fetchWithTimeout(url, signal, TIMEOUT_MS).then((r) => r.text());
|
|
450
|
+
return { results: parseGoogleResults(html, MAX_RESULTS) };
|
|
451
|
+
} catch (err) {
|
|
452
|
+
return { results: [], error: `google unreachable: ${toErrorMessage(err)}` };
|
|
453
|
+
}
|
|
440
454
|
}
|
|
441
455
|
function parseGoogleResults(html, num) {
|
|
442
456
|
const results = [];
|
|
@@ -459,17 +473,21 @@ function parseGoogleResults(html, num) {
|
|
|
459
473
|
results.push({
|
|
460
474
|
title: titles[i] ?? "",
|
|
461
475
|
url: urls[i] ?? "",
|
|
462
|
-
snippet: snippets[i] ?? "",
|
|
476
|
+
snippet: capSnippet(snippets[i] ?? ""),
|
|
463
477
|
score: 1
|
|
464
478
|
});
|
|
465
479
|
}
|
|
466
480
|
return results;
|
|
467
481
|
}
|
|
468
|
-
async function bingSearch(query,
|
|
482
|
+
async function bingSearch(query, signal) {
|
|
469
483
|
const encoded = encodeURIComponent(query);
|
|
470
484
|
const url = `https://www.bing.com/search?q=${encoded}`;
|
|
471
|
-
|
|
472
|
-
|
|
485
|
+
try {
|
|
486
|
+
const html = await fetchWithTimeout(url, signal, TIMEOUT_MS).then((r) => r.text());
|
|
487
|
+
return { results: parseBingResults(html, MAX_RESULTS) };
|
|
488
|
+
} catch (err) {
|
|
489
|
+
return { results: [], error: `bing unreachable: ${toErrorMessage(err)}` };
|
|
490
|
+
}
|
|
473
491
|
}
|
|
474
492
|
function parseBingResults(html, num) {
|
|
475
493
|
const results = [];
|
|
@@ -482,7 +500,7 @@ function parseBingResults(html, num) {
|
|
|
482
500
|
const title = stripTags(expectDefined(titleMatch[2]));
|
|
483
501
|
if (!href || !title) return [];
|
|
484
502
|
const snippetMatch = /<p\b[^>]*class=(["'])[^"']*\b(?:b_paractl|b_lineclamp\d*)\b[^"']*\1[^>]*>([\s\S]*?)<\/p>/i.exec(block) ?? /<p\b[^>]*>([\s\S]*?)<\/p>/i.exec(block);
|
|
485
|
-
const snippet = snippetMatch ? stripTags(expectDefined(snippetMatch.at(-1))) : "";
|
|
503
|
+
const snippet = snippetMatch ? capSnippet(stripTags(expectDefined(snippetMatch.at(-1)))) : "";
|
|
486
504
|
return [{ url: normalizeBingUrl(href), title, snippet, score: 1 }];
|
|
487
505
|
}), num);
|
|
488
506
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -549,6 +567,9 @@ function anySignal(...signals) {
|
|
|
549
567
|
function stripTags(html) {
|
|
550
568
|
return decodeHtmlEntities(html.replace(/<[^>]+>/g, "")).trim();
|
|
551
569
|
}
|
|
570
|
+
function capSnippet(snippet) {
|
|
571
|
+
return snippet.length > MAX_SNIPPET_CHARS ? `${snippet.slice(0, MAX_SNIPPET_CHARS - 1)}\u2026` : snippet;
|
|
572
|
+
}
|
|
552
573
|
function decodeHtmlEntities(text) {
|
|
553
574
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
554
575
|
}
|
package/dist/skill.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ interface SkillToolOutput {
|
|
|
27
27
|
dir: string;
|
|
28
28
|
/** When `resource` was requested: the loaded file. */
|
|
29
29
|
loadedResource?: LoadedResource | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Soft advisory: prose in the skill body mentions tools not registered in
|
|
32
|
+
* this runtime (e.g. hidden by a token-saving tier). The skill still loads —
|
|
33
|
+
* only `manifest.requiredTools` hard-fails.
|
|
34
|
+
*/
|
|
35
|
+
warning?: string | undefined;
|
|
30
36
|
}
|
|
31
37
|
/**
|
|
32
38
|
* Skill tool — the agentskills.io progressive-disclosure primitive.
|
package/dist/skill.js
CHANGED
|
@@ -74,12 +74,7 @@ function makeSkillTool(skillLoader) {
|
|
|
74
74
|
runtimeToolReferencesFromText(raw),
|
|
75
75
|
availableToolNames
|
|
76
76
|
);
|
|
77
|
-
|
|
78
|
-
throw new ToolValidationError({
|
|
79
|
-
message: `skill "${name}" references unregistered tools: ${missingBodyTools.join(", ")}`,
|
|
80
|
-
field: "name"
|
|
81
|
-
});
|
|
82
|
-
}
|
|
77
|
+
const warning = missingBodyTools.length > 0 ? `Warning: skill "${name}" references tools not registered in this runtime: ${missingBodyTools.join(", ")}. Steps that call them may be unavailable.` : void 0;
|
|
83
78
|
const body = stripFrontmatter(raw).trim().slice(0, MAX_BODY_CHARS);
|
|
84
79
|
const resources = loadedResource ? [] : await listResources(dir);
|
|
85
80
|
try {
|
|
@@ -96,29 +91,33 @@ function makeSkillTool(skillLoader) {
|
|
|
96
91
|
body,
|
|
97
92
|
resources,
|
|
98
93
|
dir,
|
|
99
|
-
loadedResource
|
|
94
|
+
loadedResource,
|
|
95
|
+
warning
|
|
100
96
|
};
|
|
101
97
|
},
|
|
102
98
|
serialize(output) {
|
|
99
|
+
const warningLine = output.warning ? `
|
|
100
|
+
|
|
101
|
+
${output.warning}` : "";
|
|
103
102
|
if (output.loadedResource) {
|
|
104
103
|
const lr = output.loadedResource;
|
|
105
104
|
const note = lr.truncated ? ` (truncated to ${lr.content.length} chars of ${lr.bytes} B)` : "";
|
|
106
105
|
return `# Resource: ${output.name}/${lr.rel}
|
|
107
106
|
(abs path: ${lr.absPath})${note}
|
|
108
107
|
|
|
109
|
-
${lr.content}`;
|
|
108
|
+
${lr.content}${warningLine}`;
|
|
110
109
|
}
|
|
111
110
|
const head = `# Skill: ${output.name}
|
|
112
111
|
${output.description}
|
|
113
112
|
|
|
114
113
|
${output.body}`;
|
|
115
|
-
if (output.resources.length === 0) return head
|
|
114
|
+
if (output.resources.length === 0) return `${head}${warningLine}`;
|
|
116
115
|
const listing = output.resources.map((r) => `- ${r.path} (${r.bytes} B)`).join("\n");
|
|
117
116
|
return `${head}
|
|
118
117
|
|
|
119
118
|
## Bundled resources (load on demand)
|
|
120
119
|
Load any with: \`skill({ name: "${output.name}", resource: "<path>" })\`. Run scripts via bash using their abs path under ${output.dir}.
|
|
121
|
-
${listing}`;
|
|
120
|
+
${listing}${warningLine}`;
|
|
122
121
|
}
|
|
123
122
|
};
|
|
124
123
|
}
|
package/dist/task.js
CHANGED
|
@@ -1350,6 +1350,18 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
1350
1350
|
},
|
|
1351
1351
|
transitionAction: { type: "string" },
|
|
1352
1352
|
transitionComment: { type: "string" },
|
|
1353
|
+
tickChecks: {
|
|
1354
|
+
type: "array",
|
|
1355
|
+
items: {
|
|
1356
|
+
type: "object",
|
|
1357
|
+
properties: {
|
|
1358
|
+
checkId: { type: "string" },
|
|
1359
|
+
checkStatus: { type: "string", enum: ["passed", "failed", "skipped"] }
|
|
1360
|
+
},
|
|
1361
|
+
required: ["checkId", "checkStatus"]
|
|
1362
|
+
},
|
|
1363
|
+
description: "`transition_task` (to=done only): flip one or more manual criteria to `passed` before the gate fires. Read ids from kanban get_task. Non-manual criteria are refused."
|
|
1364
|
+
},
|
|
1353
1365
|
attachmentUrl: { type: "string" },
|
|
1354
1366
|
attachmentTitle: { type: "string" },
|
|
1355
1367
|
attachmentType: {
|
|
@@ -1537,6 +1549,9 @@ var kanbanTool = {
|
|
|
1537
1549
|
description: KANBAN_TOOL_DESCRIPTION,
|
|
1538
1550
|
usageHint: KANBAN_TOOL_USAGE_HINT,
|
|
1539
1551
|
permission: "confirm",
|
|
1552
|
+
// WS-046: gives permission decisions something to key on.
|
|
1553
|
+
// The action performed; kanban has no single file or path subject.
|
|
1554
|
+
subjectKey: "action",
|
|
1540
1555
|
mutating: true,
|
|
1541
1556
|
capabilities: ["fs.write"],
|
|
1542
1557
|
icon: "task",
|
|
@@ -1978,6 +1993,7 @@ var kanbanTool = {
|
|
|
1978
1993
|
actor: input.author,
|
|
1979
1994
|
comment: input.transitionComment,
|
|
1980
1995
|
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
1996
|
+
...input.tickChecks !== void 0 ? { tickChecks: input.tickChecks } : {},
|
|
1981
1997
|
...input.attachmentUrl !== void 0 ? {
|
|
1982
1998
|
attachment: {
|
|
1983
1999
|
url: input.attachmentUrl,
|
|
@@ -2370,8 +2386,52 @@ var kanbanTool = {
|
|
|
2370
2386
|
} catch (err) {
|
|
2371
2387
|
return fail(stripLifecycleIssues(err instanceof Error ? err.message : String(err)));
|
|
2372
2388
|
}
|
|
2389
|
+
},
|
|
2390
|
+
serialize(output, input) {
|
|
2391
|
+
return serializeKanbanOutput(output, input);
|
|
2373
2392
|
}
|
|
2374
2393
|
};
|
|
2394
|
+
var KANBAN_BOARD_TRANSCRIPT_BYTE_CAP = 16384;
|
|
2395
|
+
var KANBAN_FULL_BOARD_ACTIONS = /* @__PURE__ */ new Set([
|
|
2396
|
+
"get_board",
|
|
2397
|
+
"export_markdown",
|
|
2398
|
+
"export_task_graph"
|
|
2399
|
+
]);
|
|
2400
|
+
function serializeKanbanOutput(output, input) {
|
|
2401
|
+
const action = input && typeof input === "object" ? input.action : void 0;
|
|
2402
|
+
const board = output.board;
|
|
2403
|
+
if (board) {
|
|
2404
|
+
const keepFull = typeof action === "string" && KANBAN_FULL_BOARD_ACTIONS.has(action);
|
|
2405
|
+
let boardBytes = 0;
|
|
2406
|
+
if (!keepFull) {
|
|
2407
|
+
try {
|
|
2408
|
+
boardBytes = Buffer.byteLength(JSON.stringify(board), "utf8");
|
|
2409
|
+
} catch {
|
|
2410
|
+
boardBytes = 0;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
if (!keepFull && boardBytes > KANBAN_BOARD_TRANSCRIPT_BYTE_CAP) {
|
|
2414
|
+
const columns = {};
|
|
2415
|
+
for (const column of board.columns) {
|
|
2416
|
+
columns[column.title || column.id] = board.tasks.filter(
|
|
2417
|
+
(task) => task.columnId === column.id
|
|
2418
|
+
).length;
|
|
2419
|
+
}
|
|
2420
|
+
const compact = {
|
|
2421
|
+
...output,
|
|
2422
|
+
board: {
|
|
2423
|
+
id: board.id,
|
|
2424
|
+
title: board.title,
|
|
2425
|
+
columns,
|
|
2426
|
+
totalTasks: board.tasks.length,
|
|
2427
|
+
note: `Full board (${boardBytes} bytes) omitted from the transcript; use get_board to load it.`
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
return JSON.stringify(compact, null, 2);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
return JSON.stringify(output, null, 2);
|
|
2434
|
+
}
|
|
2375
2435
|
|
|
2376
2436
|
// src/todo.ts
|
|
2377
2437
|
function normalizedTitle(value) {
|
|
@@ -2710,7 +2770,8 @@ var todoTool = {
|
|
|
2710
2770
|
}
|
|
2711
2771
|
for (const planId of completedPlanIds) {
|
|
2712
2772
|
if (pendingPlanIds.has(planId)) continue;
|
|
2713
|
-
const
|
|
2773
|
+
const meta = ctx.meta;
|
|
2774
|
+
const planPath = meta["plan.path.resolved"] ?? meta["plan.path"];
|
|
2714
2775
|
if (typeof planPath !== "string" || !planPath) continue;
|
|
2715
2776
|
try {
|
|
2716
2777
|
const plan = await loadPlan2(planPath);
|
|
@@ -2723,7 +2784,8 @@ var todoTool = {
|
|
|
2723
2784
|
}
|
|
2724
2785
|
for (const taskId of completedTaskIds) {
|
|
2725
2786
|
if (pendingTaskIds.has(taskId)) continue;
|
|
2726
|
-
const
|
|
2787
|
+
const meta = ctx.meta;
|
|
2788
|
+
const taskPath = meta["task.path.resolved"] ?? meta["task.path"];
|
|
2727
2789
|
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
2728
2790
|
try {
|
|
2729
2791
|
const file = await loadTasks3(taskPath);
|
|
@@ -3204,6 +3266,7 @@ var taskTool = {
|
|
|
3204
3266
|
inProgress: 0
|
|
3205
3267
|
};
|
|
3206
3268
|
}
|
|
3269
|
+
ctx.meta["task.path.resolved"] = taskPath;
|
|
3207
3270
|
if (todosToReplace) {
|
|
3208
3271
|
await todoTool.execute({ todos: todosToReplace }, ctx, {
|
|
3209
3272
|
signal: AbortSignal.timeout(3e4)
|
|
@@ -3228,6 +3291,7 @@ var taskTool = {
|
|
|
3228
3291
|
formatted = formatPlan(updated);
|
|
3229
3292
|
return updated;
|
|
3230
3293
|
});
|
|
3294
|
+
ctx.meta["plan.path.resolved"] = planPath;
|
|
3231
3295
|
} catch (err) {
|
|
3232
3296
|
return {
|
|
3233
3297
|
ok: false,
|
package/dist/test.js
CHANGED
|
@@ -367,8 +367,11 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
367
367
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
368
368
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
369
369
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
370
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
371
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
372
|
+
// redacted, not just the first.
|
|
370
373
|
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
371
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]
|
|
374
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
372
375
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
373
376
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
374
377
|
// redaction function. Synced with core copy.
|
|
@@ -376,8 +379,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
376
379
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
377
380
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
378
381
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
379
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
380
|
-
|
|
382
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
383
|
+
// every such flag in the command line is redacted, not just the first.
|
|
384
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
381
385
|
];
|
|
382
386
|
function redactCommand(cmd) {
|
|
383
387
|
let result = cmd;
|
|
@@ -466,11 +470,15 @@ var ProcessRegistryImpl = class {
|
|
|
466
470
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
467
471
|
}
|
|
468
472
|
_canSignalProcessGroup(p) {
|
|
469
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
473
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
470
474
|
}
|
|
471
475
|
_killChildDirect(p, signal) {
|
|
472
476
|
try {
|
|
473
|
-
p.child
|
|
477
|
+
if (p.child) {
|
|
478
|
+
p.child.kill(signal);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
474
482
|
} catch {
|
|
475
483
|
}
|
|
476
484
|
}
|
|
@@ -668,15 +676,15 @@ var ProcessRegistryImpl = class {
|
|
|
668
676
|
this._pruneStale(pid);
|
|
669
677
|
const p = this.processes.get(pid);
|
|
670
678
|
if (!p) return false;
|
|
671
|
-
if (p.killed) return true;
|
|
679
|
+
if (p.killed && opts.force !== true) return true;
|
|
672
680
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
673
681
|
if (opts.preserveBackground && p.background) return false;
|
|
674
682
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
675
683
|
const isWin2 = os.platform() === "win32";
|
|
676
684
|
if (isWin2) {
|
|
677
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
685
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
678
686
|
const directFallback = () => {
|
|
679
|
-
if (p.child.exitCode === null) {
|
|
687
|
+
if (p.child && p.child.exitCode === null) {
|
|
680
688
|
try {
|
|
681
689
|
p.child.kill("SIGKILL");
|
|
682
690
|
} catch {
|
|
@@ -688,10 +696,7 @@ var ProcessRegistryImpl = class {
|
|
|
688
696
|
onSettled: directFallback
|
|
689
697
|
})) {
|
|
690
698
|
} else {
|
|
691
|
-
|
|
692
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
693
|
-
} catch {
|
|
694
|
-
}
|
|
699
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
695
700
|
}
|
|
696
701
|
p.killed = true;
|
|
697
702
|
return true;
|
|
@@ -702,7 +707,7 @@ var ProcessRegistryImpl = class {
|
|
|
702
707
|
} else {
|
|
703
708
|
this._killPosix(p, "SIGTERM");
|
|
704
709
|
const timer = setTimeout(() => {
|
|
705
|
-
if (this.processes.has(pid) && !p.child
|
|
710
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
706
711
|
this._killPosix(p, "SIGKILL");
|
|
707
712
|
}
|
|
708
713
|
}, graceMs);
|
|
@@ -757,6 +762,16 @@ var ProcessRegistryImpl = class {
|
|
|
757
762
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
758
763
|
*/
|
|
759
764
|
_isStaleEntry(entry) {
|
|
765
|
+
if (entry.child === null) {
|
|
766
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
767
|
+
if (os.platform() === "win32") return false;
|
|
768
|
+
try {
|
|
769
|
+
process.kill(entry.pid, 0);
|
|
770
|
+
return false;
|
|
771
|
+
} catch (err) {
|
|
772
|
+
return err.code !== "EPERM";
|
|
773
|
+
}
|
|
774
|
+
}
|
|
760
775
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
761
776
|
}
|
|
762
777
|
/**
|
package/dist/todo.js
CHANGED
|
@@ -870,6 +870,18 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
870
870
|
},
|
|
871
871
|
transitionAction: { type: "string" },
|
|
872
872
|
transitionComment: { type: "string" },
|
|
873
|
+
tickChecks: {
|
|
874
|
+
type: "array",
|
|
875
|
+
items: {
|
|
876
|
+
type: "object",
|
|
877
|
+
properties: {
|
|
878
|
+
checkId: { type: "string" },
|
|
879
|
+
checkStatus: { type: "string", enum: ["passed", "failed", "skipped"] }
|
|
880
|
+
},
|
|
881
|
+
required: ["checkId", "checkStatus"]
|
|
882
|
+
},
|
|
883
|
+
description: "`transition_task` (to=done only): flip one or more manual criteria to `passed` before the gate fires. Read ids from kanban get_task. Non-manual criteria are refused."
|
|
884
|
+
},
|
|
873
885
|
attachmentUrl: { type: "string" },
|
|
874
886
|
attachmentTitle: { type: "string" },
|
|
875
887
|
attachmentType: {
|
|
@@ -1524,6 +1536,9 @@ var kanbanTool = {
|
|
|
1524
1536
|
description: KANBAN_TOOL_DESCRIPTION,
|
|
1525
1537
|
usageHint: KANBAN_TOOL_USAGE_HINT,
|
|
1526
1538
|
permission: "confirm",
|
|
1539
|
+
// WS-046: gives permission decisions something to key on.
|
|
1540
|
+
// The action performed; kanban has no single file or path subject.
|
|
1541
|
+
subjectKey: "action",
|
|
1527
1542
|
mutating: true,
|
|
1528
1543
|
capabilities: ["fs.write"],
|
|
1529
1544
|
icon: "task",
|
|
@@ -1965,6 +1980,7 @@ var kanbanTool = {
|
|
|
1965
1980
|
actor: input.author,
|
|
1966
1981
|
comment: input.transitionComment,
|
|
1967
1982
|
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
1983
|
+
...input.tickChecks !== void 0 ? { tickChecks: input.tickChecks } : {},
|
|
1968
1984
|
...input.attachmentUrl !== void 0 ? {
|
|
1969
1985
|
attachment: {
|
|
1970
1986
|
url: input.attachmentUrl,
|
|
@@ -2357,8 +2373,52 @@ var kanbanTool = {
|
|
|
2357
2373
|
} catch (err) {
|
|
2358
2374
|
return fail(stripLifecycleIssues(err instanceof Error ? err.message : String(err)));
|
|
2359
2375
|
}
|
|
2376
|
+
},
|
|
2377
|
+
serialize(output, input) {
|
|
2378
|
+
return serializeKanbanOutput(output, input);
|
|
2360
2379
|
}
|
|
2361
2380
|
};
|
|
2381
|
+
var KANBAN_BOARD_TRANSCRIPT_BYTE_CAP = 16384;
|
|
2382
|
+
var KANBAN_FULL_BOARD_ACTIONS = /* @__PURE__ */ new Set([
|
|
2383
|
+
"get_board",
|
|
2384
|
+
"export_markdown",
|
|
2385
|
+
"export_task_graph"
|
|
2386
|
+
]);
|
|
2387
|
+
function serializeKanbanOutput(output, input) {
|
|
2388
|
+
const action = input && typeof input === "object" ? input.action : void 0;
|
|
2389
|
+
const board = output.board;
|
|
2390
|
+
if (board) {
|
|
2391
|
+
const keepFull = typeof action === "string" && KANBAN_FULL_BOARD_ACTIONS.has(action);
|
|
2392
|
+
let boardBytes = 0;
|
|
2393
|
+
if (!keepFull) {
|
|
2394
|
+
try {
|
|
2395
|
+
boardBytes = Buffer.byteLength(JSON.stringify(board), "utf8");
|
|
2396
|
+
} catch {
|
|
2397
|
+
boardBytes = 0;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
if (!keepFull && boardBytes > KANBAN_BOARD_TRANSCRIPT_BYTE_CAP) {
|
|
2401
|
+
const columns = {};
|
|
2402
|
+
for (const column of board.columns) {
|
|
2403
|
+
columns[column.title || column.id] = board.tasks.filter(
|
|
2404
|
+
(task) => task.columnId === column.id
|
|
2405
|
+
).length;
|
|
2406
|
+
}
|
|
2407
|
+
const compact = {
|
|
2408
|
+
...output,
|
|
2409
|
+
board: {
|
|
2410
|
+
id: board.id,
|
|
2411
|
+
title: board.title,
|
|
2412
|
+
columns,
|
|
2413
|
+
totalTasks: board.tasks.length,
|
|
2414
|
+
note: `Full board (${boardBytes} bytes) omitted from the transcript; use get_board to load it.`
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
return JSON.stringify(compact, null, 2);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return JSON.stringify(output, null, 2);
|
|
2421
|
+
}
|
|
2362
2422
|
|
|
2363
2423
|
// src/todo.ts
|
|
2364
2424
|
function normalizedTitle(value) {
|
|
@@ -2697,7 +2757,8 @@ var todoTool = {
|
|
|
2697
2757
|
}
|
|
2698
2758
|
for (const planId of completedPlanIds) {
|
|
2699
2759
|
if (pendingPlanIds.has(planId)) continue;
|
|
2700
|
-
const
|
|
2760
|
+
const meta = ctx.meta;
|
|
2761
|
+
const planPath = meta["plan.path.resolved"] ?? meta["plan.path"];
|
|
2701
2762
|
if (typeof planPath !== "string" || !planPath) continue;
|
|
2702
2763
|
try {
|
|
2703
2764
|
const plan = await loadPlan2(planPath);
|
|
@@ -2710,7 +2771,8 @@ var todoTool = {
|
|
|
2710
2771
|
}
|
|
2711
2772
|
for (const taskId of completedTaskIds) {
|
|
2712
2773
|
if (pendingTaskIds.has(taskId)) continue;
|
|
2713
|
-
const
|
|
2774
|
+
const meta = ctx.meta;
|
|
2775
|
+
const taskPath = meta["task.path.resolved"] ?? meta["task.path"];
|
|
2714
2776
|
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
2715
2777
|
try {
|
|
2716
2778
|
const file = await loadTasks3(taskPath);
|
package/dist/tool-icons.js
CHANGED
|
@@ -60,13 +60,15 @@ var TOOL_ICON_MAP = {
|
|
|
60
60
|
replace: "edit",
|
|
61
61
|
str_replace: "edit",
|
|
62
62
|
multi_edit: "edit",
|
|
63
|
-
|
|
63
|
+
// Matches patchTool's own `icon: 'edit'` (tool-icon-map.ts agrees).
|
|
64
|
+
patch: "edit",
|
|
64
65
|
// ── search ──
|
|
65
66
|
grep: "search",
|
|
66
67
|
search: "search",
|
|
67
68
|
rg: "search",
|
|
68
69
|
ripgrep: "search",
|
|
69
|
-
|
|
70
|
+
// Matches globTool's own `icon: 'folder'` (tool-icon-map.ts agrees).
|
|
71
|
+
glob: "folder",
|
|
70
72
|
find: "search",
|
|
71
73
|
// ── navigation ──
|
|
72
74
|
folder: "folder",
|
package/dist/tool-summary.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Head-field lookup order for the generic fallback. */
|
|
2
|
-
export declare const FALLBACK_HEAD_FIELDS: readonly ['path', 'file_path', 'pattern', 'command', 'cmd', 'url', 'query', 'description', 'content'];
|
|
2
|
+
export declare const FALLBACK_HEAD_FIELDS: readonly ['path', 'file_path', 'pattern', 'symbol', 'command', 'cmd', 'url', 'query', 'description', 'content'];
|
|
3
3
|
/**
|
|
4
4
|
* One-line, tool-aware summary of a tool call's input.
|
|
5
5
|
*
|