@wrongstack/tools 0.260.0 → 0.265.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/audit.js +154 -10
- package/dist/audit.js.map +1 -1
- package/dist/bash.js +138 -2
- package/dist/bash.js.map +1 -1
- package/dist/batch-tool-use.js +1 -0
- package/dist/batch-tool-use.js.map +1 -1
- package/dist/builtin.d.ts +20 -1
- package/dist/builtin.js +674 -333
- package/dist/builtin.js.map +1 -1
- package/dist/circuit-breaker.d.ts +20 -0
- package/dist/circuit-breaker.js +40 -2
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/codebase-index/index.d.ts +16 -0
- package/dist/codebase-index/index.js +62 -27
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +59 -27
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/diff.js +14 -6
- package/dist/diff.js.map +1 -1
- package/dist/document.js +18 -11
- package/dist/document.js.map +1 -1
- package/dist/edit.js +22 -14
- package/dist/edit.js.map +1 -1
- package/dist/exec.js +140 -3
- package/dist/exec.js.map +1 -1
- package/dist/fetch.d.ts +19 -1
- package/dist/fetch.js +2 -1
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +153 -10
- package/dist/format.js.map +1 -1
- package/dist/git.js +1 -0
- package/dist/git.js.map +1 -1
- package/dist/glob.js +14 -6
- package/dist/glob.js.map +1 -1
- package/dist/grep.js +14 -6
- package/dist/grep.js.map +1 -1
- package/dist/index.d.ts +55 -3
- package/dist/index.js +833 -336
- package/dist/index.js.map +1 -1
- package/dist/install.js +154 -10
- package/dist/install.js.map +1 -1
- package/dist/json.js +2 -0
- package/dist/json.js.map +1 -1
- package/dist/lint.js +153 -10
- package/dist/lint.js.map +1 -1
- package/dist/logs.js +14 -6
- package/dist/logs.js.map +1 -1
- package/dist/memory.js +1 -0
- package/dist/memory.js.map +1 -1
- package/dist/mode.js +1 -0
- package/dist/mode.js.map +1 -1
- package/dist/outdated.js +16 -7
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +643 -332
- package/dist/pack.js.map +1 -1
- package/dist/patch.js +14 -6
- package/dist/patch.js.map +1 -1
- package/dist/process-registry.d.ts +56 -2
- package/dist/process-registry.js +138 -3
- package/dist/process-registry.js.map +1 -1
- package/dist/read.js +24 -18
- package/dist/read.js.map +1 -1
- package/dist/replace.js +14 -6
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js +14 -6
- package/dist/scaffold.js.map +1 -1
- package/dist/search.js +3 -3
- package/dist/search.js.map +1 -1
- package/dist/test.js +153 -10
- package/dist/test.js.map +1 -1
- package/dist/todo.js +1 -0
- package/dist/todo.js.map +1 -1
- package/dist/tool-help.js +1 -0
- package/dist/tool-help.js.map +1 -1
- package/dist/tool-icons.d.ts +20 -0
- package/dist/tool-icons.js +130 -0
- package/dist/tool-icons.js.map +1 -0
- package/dist/tool-search.js +1 -0
- package/dist/tool-search.js.map +1 -1
- package/dist/tool-use.js +1 -0
- package/dist/tool-use.js.map +1 -1
- package/dist/tree.js +14 -6
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +153 -10
- package/dist/typecheck.js.map +1 -1
- package/dist/write.js +22 -14
- package/dist/write.js.map +1 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@ import * as fs4 from 'node:fs/promises';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { resolve, sep, dirname, join } from 'node:path';
|
|
4
4
|
import * as Core from '@wrongstack/core';
|
|
5
|
-
import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, isPrivateIPv4, isPrivateIPv6, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem,
|
|
5
|
+
import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, isPrivateIPv4, isPrivateIPv6, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, mutateTasks, formatTaskList, formatPlan, recordPackageAction, detectPackageEcosystem, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
|
|
6
|
+
import { toErrorMessage } from '@wrongstack/core/utils';
|
|
6
7
|
import { spawn, execFileSync, spawnSync } from 'node:child_process';
|
|
7
8
|
import * as os from 'node:os';
|
|
8
9
|
import * as fs7 from 'node:fs';
|
|
@@ -11,11 +12,11 @@ import * as dns from 'node:dns/promises';
|
|
|
11
12
|
import * as net from 'node:net';
|
|
12
13
|
import { Agent } from 'undici';
|
|
13
14
|
import TurndownService from 'turndown';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
14
16
|
import { createRequire } from 'node:module';
|
|
15
17
|
import { fileURLToPath } from 'node:url';
|
|
16
18
|
import { Worker } from 'node:worker_threads';
|
|
17
19
|
import * as ts from 'typescript';
|
|
18
|
-
import { randomUUID } from 'node:crypto';
|
|
19
20
|
|
|
20
21
|
// src/read.ts
|
|
21
22
|
async function detectPackageManager(cwd) {
|
|
@@ -35,20 +36,29 @@ async function detectPackageManager(cwd) {
|
|
|
35
36
|
function resolvePath(input, ctx) {
|
|
36
37
|
return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
|
|
37
38
|
}
|
|
39
|
+
function allowedRoots(ctx) {
|
|
40
|
+
return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];
|
|
41
|
+
}
|
|
42
|
+
function isInsideAny(target, roots) {
|
|
43
|
+
return roots.some((root) => {
|
|
44
|
+
const rel = path.relative(root, target);
|
|
45
|
+
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
38
48
|
function ensureInsideRoot(absPath, ctx) {
|
|
39
|
-
const root = path.resolve(ctx.projectRoot);
|
|
40
49
|
const target = path.resolve(absPath);
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
return target;
|
|
50
|
+
if (ctx.allowOutsideProjectRoot) return target;
|
|
51
|
+
if (isInsideAny(target, allowedRoots(ctx))) return target;
|
|
52
|
+
throw new Error(`Path "${absPath}" is outside project root "${path.resolve(ctx.projectRoot)}"`);
|
|
46
53
|
}
|
|
47
54
|
function safeResolve(input, ctx) {
|
|
48
55
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
49
56
|
}
|
|
50
57
|
async function assertRealInsideRoot(absPath, ctx) {
|
|
51
|
-
|
|
58
|
+
if (ctx.allowOutsideProjectRoot) return;
|
|
59
|
+
const realRoots = await Promise.all(
|
|
60
|
+
allowedRoots(ctx).map((r) => fs4.realpath(r).catch(() => path.resolve(r)))
|
|
61
|
+
);
|
|
52
62
|
let probe = absPath;
|
|
53
63
|
for (; ; ) {
|
|
54
64
|
let real;
|
|
@@ -63,13 +73,10 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
63
73
|
}
|
|
64
74
|
throw err;
|
|
65
75
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return;
|
|
76
|
+
if (isInsideAny(real, realRoots)) return;
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
79
|
+
);
|
|
73
80
|
}
|
|
74
81
|
}
|
|
75
82
|
async function safeResolveReal(input, ctx) {
|
|
@@ -161,8 +168,6 @@ function normalizeCommandOutput(raw, opts = {}) {
|
|
|
161
168
|
text = text.replace(/\n{3,}/g, "\n\n");
|
|
162
169
|
return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);
|
|
163
170
|
}
|
|
164
|
-
|
|
165
|
-
// src/read.ts
|
|
166
171
|
var MAX_BYTES = 5 * 1024 * 1024;
|
|
167
172
|
var readTool = {
|
|
168
173
|
name: "read",
|
|
@@ -172,6 +177,7 @@ var readTool = {
|
|
|
172
177
|
permission: "auto",
|
|
173
178
|
mutating: false,
|
|
174
179
|
capabilities: ["fs.read"],
|
|
180
|
+
icon: "file",
|
|
175
181
|
maxOutputBytes: 262144,
|
|
176
182
|
timeoutMs: 5e3,
|
|
177
183
|
inputSchema: {
|
|
@@ -202,7 +208,7 @@ var readTool = {
|
|
|
202
208
|
const code = err.code;
|
|
203
209
|
if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
|
|
204
210
|
throw new Error(
|
|
205
|
-
`read: failed to stat "${input.path}": ${
|
|
211
|
+
`read: failed to stat "${input.path}": ${toErrorMessage(err)}`
|
|
206
212
|
);
|
|
207
213
|
}
|
|
208
214
|
if (!stat11.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
|
|
@@ -244,6 +250,7 @@ var writeTool = {
|
|
|
244
250
|
mutating: true,
|
|
245
251
|
timeoutMs: 5e3,
|
|
246
252
|
capabilities: ["fs.write"],
|
|
253
|
+
icon: "file",
|
|
247
254
|
inputSchema: {
|
|
248
255
|
type: "object",
|
|
249
256
|
properties: {
|
|
@@ -307,6 +314,7 @@ var editTool = {
|
|
|
307
314
|
permission: "confirm",
|
|
308
315
|
mutating: true,
|
|
309
316
|
capabilities: ["fs.write"],
|
|
317
|
+
icon: "edit",
|
|
310
318
|
timeoutMs: 5e3,
|
|
311
319
|
inputSchema: {
|
|
312
320
|
type: "object",
|
|
@@ -474,6 +482,7 @@ var replaceTool = {
|
|
|
474
482
|
permission: "confirm",
|
|
475
483
|
mutating: true,
|
|
476
484
|
capabilities: ["fs.write"],
|
|
485
|
+
icon: "edit",
|
|
477
486
|
timeoutMs: 3e4,
|
|
478
487
|
inputSchema: {
|
|
479
488
|
type: "object",
|
|
@@ -675,6 +684,7 @@ var globTool = {
|
|
|
675
684
|
permission: "auto",
|
|
676
685
|
mutating: false,
|
|
677
686
|
capabilities: ["fs.read"],
|
|
687
|
+
icon: "folder",
|
|
678
688
|
maxOutputBytes: 65536,
|
|
679
689
|
timeoutMs: 5e3,
|
|
680
690
|
inputSchema: {
|
|
@@ -760,6 +770,7 @@ var grepTool = {
|
|
|
760
770
|
permission: "auto",
|
|
761
771
|
mutating: false,
|
|
762
772
|
capabilities: ["fs.read"],
|
|
773
|
+
icon: "search",
|
|
763
774
|
maxOutputBytes: 131072,
|
|
764
775
|
timeoutMs: 1e4,
|
|
765
776
|
inputSchema: {
|
|
@@ -1161,6 +1172,23 @@ var CircuitBreaker = class {
|
|
|
1161
1172
|
lastSlowAt = null;
|
|
1162
1173
|
/** Timestamp when the breaker was opened (for cooldown calculation). */
|
|
1163
1174
|
openedAt = null;
|
|
1175
|
+
/**
|
|
1176
|
+
* Master enable flag. When false the breaker is bypassed: `beforeCall`
|
|
1177
|
+
* always returns true and `afterCall` records nothing. The class itself
|
|
1178
|
+
* defaults to enabled (so the standalone unit tests exercise tripping); the
|
|
1179
|
+
* ProcessRegistry flips this off until the user opts in via `/settings`.
|
|
1180
|
+
*/
|
|
1181
|
+
enabled = true;
|
|
1182
|
+
/**
|
|
1183
|
+
* Fired (best-effort) when the breaker transitions into the `open` state.
|
|
1184
|
+
* The registry uses this to arm its auto kill/reset countdown.
|
|
1185
|
+
*/
|
|
1186
|
+
onTrip;
|
|
1187
|
+
/**
|
|
1188
|
+
* Fired (best-effort) when the breaker returns to `closed` after having been
|
|
1189
|
+
* open/half-open. The registry uses this to cancel a pending kill/reset.
|
|
1190
|
+
*/
|
|
1191
|
+
onReset;
|
|
1164
1192
|
constructor(config = {}) {
|
|
1165
1193
|
this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
|
|
1166
1194
|
this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;
|
|
@@ -1169,12 +1197,22 @@ var CircuitBreaker = class {
|
|
|
1169
1197
|
this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;
|
|
1170
1198
|
this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
1171
1199
|
}
|
|
1200
|
+
/** Toggle the master enable. Disabling resets to a clean `closed` state. */
|
|
1201
|
+
setEnabled(enabled) {
|
|
1202
|
+
if (this.enabled === enabled) return;
|
|
1203
|
+
this.enabled = enabled;
|
|
1204
|
+
if (!enabled) this._reset();
|
|
1205
|
+
}
|
|
1206
|
+
get isEnabled() {
|
|
1207
|
+
return this.enabled;
|
|
1208
|
+
}
|
|
1172
1209
|
/**
|
|
1173
1210
|
* Returns true if the circuit allows a new call to proceed.
|
|
1174
1211
|
* When false, callers should abort the tool call and return a
|
|
1175
1212
|
* circuit-breaker error instead of spawning a process.
|
|
1176
1213
|
*/
|
|
1177
1214
|
get canProceed() {
|
|
1215
|
+
if (!this.enabled) return true;
|
|
1178
1216
|
this._checkStateTransition();
|
|
1179
1217
|
return this.state !== "open";
|
|
1180
1218
|
}
|
|
@@ -1210,7 +1248,7 @@ var CircuitBreaker = class {
|
|
|
1210
1248
|
* not affect breaker state.
|
|
1211
1249
|
*/
|
|
1212
1250
|
beforeCall(bypass = false) {
|
|
1213
|
-
if (bypass) return true;
|
|
1251
|
+
if (bypass || !this.enabled) return true;
|
|
1214
1252
|
this._checkStateTransition();
|
|
1215
1253
|
if (this.state === "open") return false;
|
|
1216
1254
|
return true;
|
|
@@ -1225,7 +1263,7 @@ var CircuitBreaker = class {
|
|
|
1225
1263
|
* Use for background/fire-and-forget processes.
|
|
1226
1264
|
*/
|
|
1227
1265
|
afterCall(durationMs, failed, bypass = false) {
|
|
1228
|
-
if (bypass) return;
|
|
1266
|
+
if (bypass || !this.enabled) return;
|
|
1229
1267
|
const now = Date.now();
|
|
1230
1268
|
if (this.state === "half-open") {
|
|
1231
1269
|
if (failed) {
|
|
@@ -1271,12 +1309,23 @@ var CircuitBreaker = class {
|
|
|
1271
1309
|
if (this.state === "open") return;
|
|
1272
1310
|
this.state = "open";
|
|
1273
1311
|
this.openedAt = Date.now();
|
|
1312
|
+
try {
|
|
1313
|
+
this.onTrip?.();
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1274
1316
|
}
|
|
1275
1317
|
_reset() {
|
|
1318
|
+
const wasRecovering = this.state !== "closed";
|
|
1276
1319
|
this.state = "closed";
|
|
1277
1320
|
this.consecutiveFailures = 0;
|
|
1278
1321
|
this.window = [];
|
|
1279
1322
|
this.openedAt = null;
|
|
1323
|
+
if (wasRecovering) {
|
|
1324
|
+
try {
|
|
1325
|
+
this.onReset?.();
|
|
1326
|
+
} catch {
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1280
1329
|
}
|
|
1281
1330
|
/** Transition from open → half-open when cooldown elapses. */
|
|
1282
1331
|
_checkStateTransition() {
|
|
@@ -1338,8 +1387,21 @@ function killWin32Tree(pid) {
|
|
|
1338
1387
|
var ProcessRegistryImpl = class {
|
|
1339
1388
|
processes = /* @__PURE__ */ new Map();
|
|
1340
1389
|
breaker;
|
|
1390
|
+
/**
|
|
1391
|
+
* Auto kill/reset config. When the breaker trips and `autoKillResetMs > 0`,
|
|
1392
|
+
* a countdown is armed; on expiry all tracked processes are killed and the
|
|
1393
|
+
* breaker is reset to closed (forced recovery). Zero means manual recovery
|
|
1394
|
+
* only (`/kill reset`).
|
|
1395
|
+
*/
|
|
1396
|
+
autoKillResetMs = 0;
|
|
1397
|
+
autoKillTimer = null;
|
|
1398
|
+
autoKillArmedAt = null;
|
|
1399
|
+
breakerCountdownListeners = [];
|
|
1341
1400
|
constructor(breakerConfig) {
|
|
1342
1401
|
this.breaker = new CircuitBreaker(breakerConfig);
|
|
1402
|
+
this.breaker.onTrip = () => this._armAutoKillReset();
|
|
1403
|
+
this.breaker.onReset = () => this._cancelAutoKillReset();
|
|
1404
|
+
this.breaker.setEnabled(false);
|
|
1343
1405
|
}
|
|
1344
1406
|
register(info) {
|
|
1345
1407
|
this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });
|
|
@@ -1415,6 +1477,90 @@ var ProcessRegistryImpl = class {
|
|
|
1415
1477
|
forceBreakerReset() {
|
|
1416
1478
|
this.breaker.forceReset();
|
|
1417
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Configure circuit-breaker protection at runtime. Called from `/settings`
|
|
1482
|
+
* (instant, all modes) and on TUI mount (applies persisted config).
|
|
1483
|
+
*
|
|
1484
|
+
* - `enabled` toggles whether the breaker gates `bash`/`exec`.
|
|
1485
|
+
* - `autoKillResetMs` arms the auto kill/reset countdown when the breaker
|
|
1486
|
+
* trips (0 = manual recovery only).
|
|
1487
|
+
*
|
|
1488
|
+
* Re-applies cleanly on every call: cancels a pending countdown when the
|
|
1489
|
+
* timeout is cleared or protection disabled, and re-arms if the breaker is
|
|
1490
|
+
* currently open under the new settings.
|
|
1491
|
+
*/
|
|
1492
|
+
setBreakerConfig(cfg) {
|
|
1493
|
+
if (cfg.enabled !== void 0) this.breaker.setEnabled(cfg.enabled);
|
|
1494
|
+
if (cfg.autoKillResetMs !== void 0) this.autoKillResetMs = Math.max(0, cfg.autoKillResetMs);
|
|
1495
|
+
if (this.autoKillResetMs <= 0) {
|
|
1496
|
+
this._cancelAutoKillReset();
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
if (this.breaker.isEnabled && this.breaker.snapshot().state === "open") {
|
|
1500
|
+
this._armAutoKillReset();
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Live countdown to the next auto kill/reset, or null when nothing is armed.
|
|
1505
|
+
* The TUI polls this on a 1s tick while armed so the statusline decrements.
|
|
1506
|
+
*/
|
|
1507
|
+
getBreakerCountdown() {
|
|
1508
|
+
if (this.autoKillArmedAt === null || this.autoKillResetMs <= 0) return null;
|
|
1509
|
+
const elapsed = Date.now() - this.autoKillArmedAt;
|
|
1510
|
+
return { remainingMs: Math.max(0, this.autoKillResetMs - elapsed), totalMs: this.autoKillResetMs };
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Subscribe to countdown arm/cancel events. Returns an unsubscribe function.
|
|
1514
|
+
* Use {@link getBreakerCountdown} for the live ticking value between events.
|
|
1515
|
+
*/
|
|
1516
|
+
onBreakerCountdownChange(listener) {
|
|
1517
|
+
this.breakerCountdownListeners.push(listener);
|
|
1518
|
+
return () => {
|
|
1519
|
+
this.breakerCountdownListeners = this.breakerCountdownListeners.filter((l) => l !== listener);
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
_emitBreakerCountdown() {
|
|
1523
|
+
const snap = this.getBreakerCountdown();
|
|
1524
|
+
for (const l of this.breakerCountdownListeners) {
|
|
1525
|
+
try {
|
|
1526
|
+
l(snap);
|
|
1527
|
+
} catch {
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Arm the auto kill/reset countdown. Idempotent: re-arming resets the window
|
|
1533
|
+
* (a fresh trip after a failed half-open probe restarts the clock). No-op
|
|
1534
|
+
* when protection is off or no timeout is configured.
|
|
1535
|
+
*/
|
|
1536
|
+
_armAutoKillReset() {
|
|
1537
|
+
if (this.autoKillResetMs <= 0 || !this.breaker.isEnabled) return;
|
|
1538
|
+
this._clearAutoKillTimer();
|
|
1539
|
+
this.autoKillArmedAt = Date.now();
|
|
1540
|
+
this.autoKillTimer = setTimeout(() => {
|
|
1541
|
+
this.autoKillTimer = null;
|
|
1542
|
+
this.autoKillArmedAt = null;
|
|
1543
|
+
this.killAll({ force: false });
|
|
1544
|
+
this.breaker.forceReset();
|
|
1545
|
+
this._emitBreakerCountdown();
|
|
1546
|
+
}, this.autoKillResetMs);
|
|
1547
|
+
this.autoKillTimer.unref?.();
|
|
1548
|
+
this._emitBreakerCountdown();
|
|
1549
|
+
}
|
|
1550
|
+
_cancelAutoKillReset() {
|
|
1551
|
+
const wasArmed = this.autoKillArmedAt !== null;
|
|
1552
|
+
this._clearAutoKillTimer();
|
|
1553
|
+
if (wasArmed) {
|
|
1554
|
+
this.autoKillArmedAt = null;
|
|
1555
|
+
this._emitBreakerCountdown();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
_clearAutoKillTimer() {
|
|
1559
|
+
if (this.autoKillTimer !== null) {
|
|
1560
|
+
clearTimeout(this.autoKillTimer);
|
|
1561
|
+
this.autoKillTimer = null;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1418
1564
|
/** Kill a single process by PID.
|
|
1419
1565
|
*
|
|
1420
1566
|
* On POSIX: sends SIGTERM to the *process group* (-pid) so that
|
|
@@ -1539,6 +1685,7 @@ var bashTool = {
|
|
|
1539
1685
|
permission: "confirm",
|
|
1540
1686
|
mutating: true,
|
|
1541
1687
|
riskTier: "destructive",
|
|
1688
|
+
icon: "terminal",
|
|
1542
1689
|
// Trust rules match on the literal `command` string. Without subjectKey
|
|
1543
1690
|
// the policy heuristic would have done the same here, but declaring it
|
|
1544
1691
|
// explicitly removes the implicit cross-tool aliasing.
|
|
@@ -2001,6 +2148,7 @@ var execTool = {
|
|
|
2001
2148
|
riskTier: "standard",
|
|
2002
2149
|
timeoutMs: DEFAULT_TIMEOUT_MS2,
|
|
2003
2150
|
capabilities: ["shell.restricted"],
|
|
2151
|
+
icon: "terminal",
|
|
2004
2152
|
inputSchema: {
|
|
2005
2153
|
type: "object",
|
|
2006
2154
|
properties: {
|
|
@@ -2100,7 +2248,8 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
2100
2248
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
2101
2249
|
const resolved = resolveWin32Command(cmd);
|
|
2102
2250
|
const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
2103
|
-
const
|
|
2251
|
+
const spawnCmd = needsShell ? cmd : resolved;
|
|
2252
|
+
const child = spawn(spawnCmd, args, {
|
|
2104
2253
|
cwd,
|
|
2105
2254
|
env: buildChildEnv(sessionId),
|
|
2106
2255
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -2287,6 +2436,7 @@ var fetchTool = {
|
|
|
2287
2436
|
permission: "confirm",
|
|
2288
2437
|
mutating: false,
|
|
2289
2438
|
capabilities: ["net.outbound"],
|
|
2439
|
+
icon: "web",
|
|
2290
2440
|
// Trust rules for fetch match on the literal URL — declare it explicitly
|
|
2291
2441
|
// so a user can trust `https://api.example.com/*` without accidentally
|
|
2292
2442
|
// matching that pattern on any other tool that happens to have a `url`
|
|
@@ -2437,6 +2587,7 @@ var searchTool = {
|
|
|
2437
2587
|
permission: "confirm",
|
|
2438
2588
|
mutating: false,
|
|
2439
2589
|
capabilities: ["net.outbound"],
|
|
2590
|
+
icon: "search",
|
|
2440
2591
|
timeoutMs: TIMEOUT_MS2,
|
|
2441
2592
|
inputSchema: {
|
|
2442
2593
|
type: "object",
|
|
@@ -2511,7 +2662,7 @@ async function duckduckgoSearch(query2, num, signal) {
|
|
|
2511
2662
|
truncated: results.length >= num
|
|
2512
2663
|
};
|
|
2513
2664
|
} catch (err) {
|
|
2514
|
-
console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error:
|
|
2665
|
+
console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: toErrorMessage(err) }));
|
|
2515
2666
|
return {
|
|
2516
2667
|
query: query2,
|
|
2517
2668
|
results: [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }],
|
|
@@ -2652,6 +2803,7 @@ var todoTool = {
|
|
|
2652
2803
|
// mutates only conversation state (ctx.todos), not external state — no confirmation needed
|
|
2653
2804
|
timeoutMs: 1e3,
|
|
2654
2805
|
capabilities: ["session.todo"],
|
|
2806
|
+
icon: "todo",
|
|
2655
2807
|
inputSchema: {
|
|
2656
2808
|
type: "object",
|
|
2657
2809
|
properties: {
|
|
@@ -2752,11 +2904,12 @@ var todoTool = {
|
|
|
2752
2904
|
var planTool = {
|
|
2753
2905
|
name: "plan",
|
|
2754
2906
|
category: "Session",
|
|
2755
|
-
description:
|
|
2756
|
-
usageHint: 'RECOMMENDED FOR COMPLEX, MULTI-PHASE WORK:\n\n- Start by creating a high-level plan with `action: "add"` or using templates (`template_use`).\n- Use `promote` to turn a plan item into actionable todos.\n- Use `taskify` to convert a plan item into a structured task (with type/priority/deps).\n- Keep plans at the "why and what" level, and todos at the "how and next step" level.\n- Common templates: "new-feature", "bug-fix", "refactor", "release", "security-audit".\n\nThis tool is excellent for maintaining long-term direction across many turns
|
|
2907
|
+
description: 'Manage a session-persistent strategic plan. The plan is written to disk and survives conversation resumptions within the same session, but is isolated to this session \u2014 other sessions have their own separate plans. Unlike todos (which are per-turn and lost on restart), a plan tracks high-level progress across multiple turns. Use this to outline big-picture work, then promote concrete items into the todo list when ready to execute. By default plans are isolated to this session; use `scope: "project"` to store the plan in a shared project-level file visible to all sessions.',
|
|
2908
|
+
usageHint: 'RECOMMENDED FOR COMPLEX, MULTI-PHASE WORK:\n\n- Start by creating a high-level plan with `action: "add"` or using templates (`template_use`).\n- Use `promote` to turn a plan item into actionable todos.\n- Use `taskify` to convert a plan item into a structured task (with type/priority/deps).\n- Keep plans at the "why and what" level, and todos at the "how and next step" level.\n- Common templates: "new-feature", "bug-fix", "refactor", "release", "security-audit".\n\nThis tool is excellent for maintaining long-term direction across many turns within a session. Plans survive resume but are not shared across separate sessions.\nUse `scope: "project"` to use a shared project-level plan file.',
|
|
2757
2909
|
permission: "confirm",
|
|
2758
2910
|
mutating: true,
|
|
2759
2911
|
capabilities: ["fs.write"],
|
|
2912
|
+
icon: "plan",
|
|
2760
2913
|
timeoutMs: 2e3,
|
|
2761
2914
|
inputSchema: {
|
|
2762
2915
|
type: "object",
|
|
@@ -2796,12 +2949,26 @@ var planTool = {
|
|
|
2796
2949
|
template: {
|
|
2797
2950
|
type: "string",
|
|
2798
2951
|
description: "Template identifier when using action=template_use. Common values: new-feature, bug-fix, refactor, release, security-audit."
|
|
2952
|
+
},
|
|
2953
|
+
scope: {
|
|
2954
|
+
type: "string",
|
|
2955
|
+
enum: ["session", "project"],
|
|
2956
|
+
description: 'Storage scope: "session" (default, isolated to this session) or "project" (shared across all sessions for this project).'
|
|
2799
2957
|
}
|
|
2800
2958
|
},
|
|
2801
2959
|
required: ["action"]
|
|
2802
2960
|
},
|
|
2803
2961
|
async execute(input, ctx) {
|
|
2804
|
-
const
|
|
2962
|
+
const sessionPlanPath = ctx.meta["plan.path"];
|
|
2963
|
+
let planPath;
|
|
2964
|
+
if (input.scope === "project") {
|
|
2965
|
+
if (typeof sessionPlanPath === "string") {
|
|
2966
|
+
const lastSep = Math.max(sessionPlanPath.lastIndexOf("/"), sessionPlanPath.lastIndexOf("\\"));
|
|
2967
|
+
planPath = lastSep >= 0 ? sessionPlanPath.slice(0, lastSep + 1) + "backlog.plan.json" : "backlog.plan.json";
|
|
2968
|
+
}
|
|
2969
|
+
} else {
|
|
2970
|
+
planPath = sessionPlanPath;
|
|
2971
|
+
}
|
|
2805
2972
|
if (typeof planPath !== "string" || !planPath) {
|
|
2806
2973
|
return {
|
|
2807
2974
|
ok: false,
|
|
@@ -2815,148 +2982,169 @@ var planTool = {
|
|
|
2815
2982
|
let early = null;
|
|
2816
2983
|
const taskifyMeta = { title: "", details: "" };
|
|
2817
2984
|
let didTaskify = false;
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
case "done": {
|
|
2833
|
-
if (!input.target) {
|
|
2834
|
-
early = mkResult(p, false, `${input.action} requires \`target\` (id|index|substring).`);
|
|
2835
|
-
return p;
|
|
2836
|
-
}
|
|
2837
|
-
const next = setPlanItemStatus(
|
|
2838
|
-
p,
|
|
2839
|
-
input.target,
|
|
2840
|
-
input.action === "start" ? "in_progress" : "done"
|
|
2841
|
-
);
|
|
2842
|
-
if (next === p) {
|
|
2843
|
-
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
2844
|
-
return p;
|
|
2845
|
-
}
|
|
2846
|
-
return next;
|
|
2847
|
-
}
|
|
2848
|
-
case "remove": {
|
|
2849
|
-
if (!input.target) {
|
|
2850
|
-
early = mkResult(p, false, "remove requires `target` (id|index|substring).");
|
|
2851
|
-
return p;
|
|
2852
|
-
}
|
|
2853
|
-
const next = removePlanItem(p, input.target);
|
|
2854
|
-
if (next === p) {
|
|
2855
|
-
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
2856
|
-
return p;
|
|
2857
|
-
}
|
|
2858
|
-
return next;
|
|
2859
|
-
}
|
|
2860
|
-
case "promote": {
|
|
2861
|
-
if (!input.target) {
|
|
2862
|
-
early = mkResult(p, false, `${input.action} requires \`target\` (id|index|substring).`);
|
|
2863
|
-
return p;
|
|
2864
|
-
}
|
|
2865
|
-
const derived = deriveTodosFromPlanItem(p, input.target, input.subtasks);
|
|
2866
|
-
if (!derived) {
|
|
2867
|
-
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
2868
|
-
return p;
|
|
2985
|
+
let plan;
|
|
2986
|
+
try {
|
|
2987
|
+
plan = await mutatePlan(planPath, sessionId, async (p) => {
|
|
2988
|
+
switch (input.action) {
|
|
2989
|
+
case "show":
|
|
2990
|
+
break;
|
|
2991
|
+
case "add": {
|
|
2992
|
+
const title = input.title?.trim();
|
|
2993
|
+
if (!title) {
|
|
2994
|
+
early = mkResult(p, false, "add requires `title`.");
|
|
2995
|
+
return p;
|
|
2996
|
+
}
|
|
2997
|
+
const { plan: updated } = addPlanItem(p, title, input.details?.trim() || void 0);
|
|
2998
|
+
return updated;
|
|
2869
2999
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
3000
|
+
case "start":
|
|
3001
|
+
case "done": {
|
|
3002
|
+
if (!input.target) {
|
|
3003
|
+
early = mkResult(p, false, `${input.action} requires \`target\` (id|index|substring).`);
|
|
3004
|
+
return p;
|
|
3005
|
+
}
|
|
3006
|
+
const next = setPlanItemStatus(
|
|
3007
|
+
p,
|
|
3008
|
+
input.target,
|
|
3009
|
+
input.action === "start" ? "in_progress" : "done"
|
|
3010
|
+
);
|
|
3011
|
+
if (next === p) {
|
|
3012
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
3013
|
+
return p;
|
|
3014
|
+
}
|
|
3015
|
+
return next;
|
|
2884
3016
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
3017
|
+
case "remove": {
|
|
3018
|
+
if (!input.target) {
|
|
3019
|
+
early = mkResult(p, false, "remove requires `target` (id|index|substring).");
|
|
3020
|
+
return p;
|
|
3021
|
+
}
|
|
3022
|
+
const next = removePlanItem(p, input.target);
|
|
3023
|
+
if (next === p) {
|
|
3024
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
3025
|
+
return p;
|
|
3026
|
+
}
|
|
3027
|
+
return next;
|
|
2889
3028
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
3029
|
+
case "promote": {
|
|
3030
|
+
if (!input.target) {
|
|
3031
|
+
early = mkResult(p, false, `${input.action} requires \`target\` (id|index|substring).`);
|
|
3032
|
+
return p;
|
|
3033
|
+
}
|
|
3034
|
+
const derived = deriveTodosFromPlanItem(p, input.target, input.subtasks);
|
|
3035
|
+
if (!derived) {
|
|
3036
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
3037
|
+
return p;
|
|
3038
|
+
}
|
|
3039
|
+
ctx.state.replaceTodos(derived.todos);
|
|
3040
|
+
early = mkResult(
|
|
3041
|
+
derived.plan,
|
|
3042
|
+
true,
|
|
3043
|
+
`${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`,
|
|
3044
|
+
derived.todos
|
|
3045
|
+
);
|
|
3046
|
+
return derived.plan;
|
|
2893
3047
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3048
|
+
case "template_use": {
|
|
3049
|
+
const templateName = input.template?.trim();
|
|
3050
|
+
if (!templateName) {
|
|
3051
|
+
early = mkResult(p, false, "template_use requires `template` name.");
|
|
3052
|
+
return p;
|
|
3053
|
+
}
|
|
3054
|
+
const template = getPlanTemplate(templateName);
|
|
3055
|
+
if (!template) {
|
|
3056
|
+
early = mkResult(p, false, `Unknown template "${templateName}".`);
|
|
3057
|
+
return p;
|
|
3058
|
+
}
|
|
3059
|
+
let updated = p;
|
|
3060
|
+
for (const item of template.items) {
|
|
3061
|
+
({ plan: updated } = addPlanItem(updated, item.title, item.details));
|
|
3062
|
+
}
|
|
3063
|
+
early = mkResult(
|
|
3064
|
+
updated,
|
|
3065
|
+
true,
|
|
3066
|
+
`Applied template "${template.name}" \u2014 ${template.items.length} items added.`
|
|
3067
|
+
);
|
|
3068
|
+
return updated;
|
|
2907
3069
|
}
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
3070
|
+
case "clear":
|
|
3071
|
+
return clearPlan(p);
|
|
3072
|
+
case "taskify": {
|
|
3073
|
+
if (!input.target) {
|
|
3074
|
+
early = mkResult(p, false, "taskify requires `target` (plan item id|index|substring).");
|
|
3075
|
+
return p;
|
|
3076
|
+
}
|
|
3077
|
+
let itemIdx = -1;
|
|
3078
|
+
const asNum = Number.parseInt(input.target, 10);
|
|
3079
|
+
if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= p.items.length) {
|
|
3080
|
+
itemIdx = asNum - 1;
|
|
3081
|
+
} else {
|
|
3082
|
+
itemIdx = p.items.findIndex((it) => it.id === input.target);
|
|
3083
|
+
if (itemIdx === -1) {
|
|
3084
|
+
const lower = input.target.toLowerCase();
|
|
3085
|
+
itemIdx = p.items.findIndex((it) => it.title.toLowerCase().includes(lower));
|
|
3086
|
+
}
|
|
2917
3087
|
}
|
|
3088
|
+
if (itemIdx === -1 || !p.items[itemIdx]) {
|
|
3089
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
3090
|
+
return p;
|
|
3091
|
+
}
|
|
3092
|
+
const item = p.items[itemIdx];
|
|
3093
|
+
taskifyMeta.title = item.title;
|
|
3094
|
+
taskifyMeta.details = item.details ?? "";
|
|
3095
|
+
didTaskify = true;
|
|
3096
|
+
break;
|
|
2918
3097
|
}
|
|
2919
|
-
|
|
2920
|
-
early = mkResult(p, false, `
|
|
3098
|
+
default:
|
|
3099
|
+
early = mkResult(p, false, `Unknown action "${input.action}".`);
|
|
2921
3100
|
return p;
|
|
2922
|
-
}
|
|
2923
|
-
const item = p.items[itemIdx];
|
|
2924
|
-
taskifyMeta.title = item.title;
|
|
2925
|
-
taskifyMeta.details = item.details ?? "";
|
|
2926
|
-
didTaskify = true;
|
|
2927
|
-
break;
|
|
2928
3101
|
}
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3102
|
+
return p;
|
|
3103
|
+
});
|
|
3104
|
+
} catch (err) {
|
|
3105
|
+
return {
|
|
3106
|
+
ok: false,
|
|
3107
|
+
message: `Plan change not saved \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
3108
|
+
plan: "",
|
|
3109
|
+
count: 0,
|
|
3110
|
+
open: 0
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
2935
3113
|
if (early) return early;
|
|
2936
3114
|
if (didTaskify) {
|
|
2937
|
-
const
|
|
2938
|
-
if (typeof
|
|
3115
|
+
const taskPathRaw = ctx.meta["task.path"];
|
|
3116
|
+
if (typeof taskPathRaw !== "string" || !taskPathRaw) {
|
|
2939
3117
|
return mkResult(plan, false, "Task storage path not configured \u2014 cannot taskify.");
|
|
2940
3118
|
}
|
|
2941
|
-
|
|
3119
|
+
let taskPath = taskPathRaw;
|
|
3120
|
+
if (input.scope === "project") {
|
|
3121
|
+
const lastSep = Math.max(taskPath.lastIndexOf("/"), taskPath.lastIndexOf("\\"));
|
|
3122
|
+
taskPath = lastSep >= 0 ? taskPath.slice(0, lastSep + 1) + "backlog.tasks.json" : "backlog.tasks.json";
|
|
3123
|
+
}
|
|
2942
3124
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3125
|
+
try {
|
|
3126
|
+
const taskFile = await mutateTasks(taskPath, sessionId, (f) => {
|
|
3127
|
+
f.tasks.push({
|
|
3128
|
+
id: `task_${randomUUID()}`,
|
|
3129
|
+
title: taskifyMeta.title,
|
|
3130
|
+
description: taskifyMeta.details || void 0,
|
|
3131
|
+
type: "feature",
|
|
3132
|
+
priority: "medium",
|
|
3133
|
+
status: "pending",
|
|
3134
|
+
createdAt: now,
|
|
3135
|
+
updatedAt: now
|
|
3136
|
+
});
|
|
3137
|
+
return f;
|
|
3138
|
+
});
|
|
3139
|
+
return mkResult(
|
|
3140
|
+
plan,
|
|
3141
|
+
true,
|
|
3142
|
+
`taskify ok \u2014 added "${taskifyMeta.title}" to tasks.
|
|
2958
3143
|
${formatTaskList(taskFile.tasks)}`
|
|
2959
|
-
|
|
3144
|
+
);
|
|
3145
|
+
} catch (err) {
|
|
3146
|
+
return mkResult(plan, false, `taskify: task not saved \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
3147
|
+
}
|
|
2960
3148
|
}
|
|
2961
3149
|
return mkResult(plan, true, `Plan ${input.action} ok.`);
|
|
2962
3150
|
}
|
|
@@ -2981,6 +3169,7 @@ var gitTool = {
|
|
|
2981
3169
|
description: "Safe wrapper around common git operations. Supports status, log, diff, commit, branch, checkout, stash, push, pull, fetch, reset, worktree, etc. This is the preferred way to interact with git instead of using the raw `bash` or `exec` tools.",
|
|
2982
3170
|
usageHint: "ALWAYS prefer this tool over raw shell git commands.\n\nKey fields:\n- `command`: one of the supported subcommands (status, log, diff, commit, etc.)\n- Use `message` only for commit operations.\n- Use `files` array for operations that take paths (status, diff, add, etc.).\n- Non-mutating commands (status, log, diff, branch, fetch) are still permission:confirm for safety.\nNever pass raw git flags through `args` for dangerous operations \u2014 use the structured fields.",
|
|
2983
3171
|
permission: "confirm",
|
|
3172
|
+
icon: "git",
|
|
2984
3173
|
// Conservative: any of these may mutate. The non-mutating commands
|
|
2985
3174
|
// (status/log/diff/branch/fetch) are still gated on `permission: 'confirm'`
|
|
2986
3175
|
// and `MUTATING_SUBCOMMANDS` is consulted at runtime for per-call checks.
|
|
@@ -3239,6 +3428,7 @@ var patchTool = {
|
|
|
3239
3428
|
permission: "confirm",
|
|
3240
3429
|
mutating: true,
|
|
3241
3430
|
capabilities: ["fs.write"],
|
|
3431
|
+
icon: "edit",
|
|
3242
3432
|
timeoutMs: 3e4,
|
|
3243
3433
|
inputSchema: {
|
|
3244
3434
|
type: "object",
|
|
@@ -3352,6 +3542,7 @@ var jsonTool = {
|
|
|
3352
3542
|
mutating: false,
|
|
3353
3543
|
timeoutMs: 5e3,
|
|
3354
3544
|
capabilities: ["fs.read"],
|
|
3545
|
+
icon: "json",
|
|
3355
3546
|
inputSchema: {
|
|
3356
3547
|
type: "object",
|
|
3357
3548
|
properties: {
|
|
@@ -3394,6 +3585,7 @@ var jsonTool = {
|
|
|
3394
3585
|
data: null,
|
|
3395
3586
|
formatted: "",
|
|
3396
3587
|
type: "unknown",
|
|
3588
|
+
/* v8 ignore next -- JSON.parse only throws SyntaxError (an Error); the String(e) side is defensive. */
|
|
3397
3589
|
error: `Parse failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3398
3590
|
};
|
|
3399
3591
|
}
|
|
@@ -3473,6 +3665,7 @@ var diffTool = {
|
|
|
3473
3665
|
permission: "auto",
|
|
3474
3666
|
mutating: false,
|
|
3475
3667
|
capabilities: ["fs.read"],
|
|
3668
|
+
icon: "diff",
|
|
3476
3669
|
timeoutMs: 1e4,
|
|
3477
3670
|
inputSchema: {
|
|
3478
3671
|
type: "object",
|
|
@@ -3631,6 +3824,7 @@ var treeTool = {
|
|
|
3631
3824
|
permission: "auto",
|
|
3632
3825
|
mutating: false,
|
|
3633
3826
|
capabilities: ["fs.read"],
|
|
3827
|
+
icon: "tree",
|
|
3634
3828
|
timeoutMs: 15e3,
|
|
3635
3829
|
inputSchema: {
|
|
3636
3830
|
type: "object",
|
|
@@ -3797,8 +3991,9 @@ async function* spawnStream(opts) {
|
|
|
3797
3991
|
let pending2 = "";
|
|
3798
3992
|
let error;
|
|
3799
3993
|
const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
|
|
3800
|
-
const
|
|
3801
|
-
const needsShell = isWin2 && (
|
|
3994
|
+
const resolved = resolveWin32Command(opts.cmd);
|
|
3995
|
+
const needsShell = isWin2 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
3996
|
+
const cmd = needsShell ? opts.cmd : resolved;
|
|
3802
3997
|
const child = spawn(cmd, opts.args, {
|
|
3803
3998
|
cwd: opts.cwd,
|
|
3804
3999
|
env: buildChildEnv(),
|
|
@@ -3956,6 +4151,7 @@ var lintTool = {
|
|
|
3956
4151
|
mutating: false,
|
|
3957
4152
|
timeoutMs: 6e4,
|
|
3958
4153
|
capabilities: ["shell.restricted"],
|
|
4154
|
+
icon: "code",
|
|
3959
4155
|
inputSchema: {
|
|
3960
4156
|
type: "object",
|
|
3961
4157
|
properties: {
|
|
@@ -4050,6 +4246,7 @@ var formatTool = {
|
|
|
4050
4246
|
permission: "confirm",
|
|
4051
4247
|
mutating: true,
|
|
4052
4248
|
capabilities: ["fs.write", "shell.exec"],
|
|
4249
|
+
icon: "code",
|
|
4053
4250
|
timeoutMs: 6e4,
|
|
4054
4251
|
inputSchema: {
|
|
4055
4252
|
type: "object",
|
|
@@ -4151,6 +4348,7 @@ var typecheckTool = {
|
|
|
4151
4348
|
mutating: false,
|
|
4152
4349
|
timeoutMs: 12e4,
|
|
4153
4350
|
capabilities: ["shell.restricted"],
|
|
4351
|
+
icon: "code",
|
|
4154
4352
|
inputSchema: {
|
|
4155
4353
|
type: "object",
|
|
4156
4354
|
properties: {
|
|
@@ -4237,6 +4435,7 @@ var testTool = {
|
|
|
4237
4435
|
usageHint: "ESSENTIAL BEFORE CONSIDERING WORK DONE:\n\n- Use `files` or `grep` to run only relevant tests during development.\n- `coverage: true` is useful when working on critical paths.\nRun tests frequently. A clean test run is usually required before the task can be considered complete.",
|
|
4238
4436
|
permission: "confirm",
|
|
4239
4437
|
mutating: false,
|
|
4438
|
+
icon: "test",
|
|
4240
4439
|
timeoutMs: 12e4,
|
|
4241
4440
|
capabilities: ["shell.restricted"],
|
|
4242
4441
|
inputSchema: {
|
|
@@ -4394,6 +4593,7 @@ var installTool = {
|
|
|
4394
4593
|
permission: "confirm",
|
|
4395
4594
|
mutating: true,
|
|
4396
4595
|
riskTier: "standard",
|
|
4596
|
+
icon: "package",
|
|
4397
4597
|
timeoutMs: 12e4,
|
|
4398
4598
|
capabilities: ["package.install", "shell.restricted"],
|
|
4399
4599
|
inputSchema: {
|
|
@@ -4518,6 +4718,7 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
4518
4718
|
case "yarn":
|
|
4519
4719
|
case "npm":
|
|
4520
4720
|
return join(cwd, "package.json");
|
|
4721
|
+
/* v8 ignore next 2 -- pkgManager is always pnpm/yarn/npm; the default is defensive. */
|
|
4521
4722
|
default:
|
|
4522
4723
|
return join(cwd, "package.json");
|
|
4523
4724
|
}
|
|
@@ -4532,6 +4733,7 @@ var auditTool = {
|
|
|
4532
4733
|
permission: "confirm",
|
|
4533
4734
|
mutating: false,
|
|
4534
4735
|
capabilities: ["shell.restricted"],
|
|
4736
|
+
icon: "package",
|
|
4535
4737
|
timeoutMs: 6e4,
|
|
4536
4738
|
inputSchema: {
|
|
4537
4739
|
type: "object",
|
|
@@ -4627,6 +4829,7 @@ var outdatedTool = {
|
|
|
4627
4829
|
description: "Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.",
|
|
4628
4830
|
usageHint: "MAINTENANCE & SECURITY TOOL:\n\n- Run periodically or before dependency-related work.\n- Helps surface packages that may need updates for security or features.\n- Hits the package registry over HTTP, so it is NOT purely local \u2014 flagged as mutating for the confirmation gate.\nUse the output to decide on upgrades. Prefer this over manual shell commands for dependency hygiene.",
|
|
4629
4831
|
permission: "confirm",
|
|
4832
|
+
icon: "package",
|
|
4630
4833
|
// Network side-effecting (registry HTTP). Pairs with `mutating: true`
|
|
4631
4834
|
// so the H7 invariant test (`no auto-permission tool declares
|
|
4632
4835
|
// mutating: true`) passes — a tool claiming `'auto'` must be purely
|
|
@@ -4678,7 +4881,8 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
4678
4881
|
const MAX = 1e5;
|
|
4679
4882
|
const resolved = resolveWin32Command(manager);
|
|
4680
4883
|
const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
4681
|
-
const
|
|
4884
|
+
const spawnCmd = needsShell ? manager : resolved;
|
|
4885
|
+
const child = spawn(spawnCmd, args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true, ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {} });
|
|
4682
4886
|
child.stdout?.on("data", (c) => {
|
|
4683
4887
|
if (stdout.length < MAX) stdout += c.toString();
|
|
4684
4888
|
});
|
|
@@ -4743,6 +4947,7 @@ var logsTool = {
|
|
|
4743
4947
|
mutating: false,
|
|
4744
4948
|
timeoutMs: 3e4,
|
|
4745
4949
|
capabilities: ["shell.restricted"],
|
|
4950
|
+
icon: "logs",
|
|
4746
4951
|
inputSchema: {
|
|
4747
4952
|
type: "object",
|
|
4748
4953
|
properties: {
|
|
@@ -4947,6 +5152,7 @@ var documentTool = {
|
|
|
4947
5152
|
mutating: false,
|
|
4948
5153
|
timeoutMs: 3e4,
|
|
4949
5154
|
capabilities: ["fs.read"],
|
|
5155
|
+
icon: "document",
|
|
4950
5156
|
inputSchema: {
|
|
4951
5157
|
type: "object",
|
|
4952
5158
|
properties: {
|
|
@@ -5025,10 +5231,10 @@ async function resolveFiles2(filesInput, cwd) {
|
|
|
5025
5231
|
}
|
|
5026
5232
|
function processFile(content, absPath, _style, _overwrite, target) {
|
|
5027
5233
|
const results = [];
|
|
5028
|
-
const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)
|
|
5029
|
-
const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s
|
|
5030
|
-
const classRegex = /class\s+(\w+)
|
|
5031
|
-
const typeRegex = /(?:type|interface)\s+(\w+)\s*[=<]
|
|
5234
|
+
const functionRegex = /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
|
|
5235
|
+
const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/g;
|
|
5236
|
+
const classRegex = /class\s+(\w+)/g;
|
|
5237
|
+
const typeRegex = /(?:type|interface)\s+(\w+)\s*[=<]/g;
|
|
5032
5238
|
const allMatches = [];
|
|
5033
5239
|
if (target === "all" || target === "function") {
|
|
5034
5240
|
for (const m of content.matchAll(functionRegex)) {
|
|
@@ -5182,6 +5388,7 @@ var scaffoldTool = {
|
|
|
5182
5388
|
permission: "confirm",
|
|
5183
5389
|
mutating: true,
|
|
5184
5390
|
capabilities: ["fs.write.outside-project", "fs.write"],
|
|
5391
|
+
icon: "scaffold",
|
|
5185
5392
|
timeoutMs: 3e4,
|
|
5186
5393
|
inputSchema: {
|
|
5187
5394
|
type: "object",
|
|
@@ -5277,6 +5484,7 @@ var toolSearchTool = {
|
|
|
5277
5484
|
mutating: false,
|
|
5278
5485
|
timeoutMs: 1e3,
|
|
5279
5486
|
capabilities: ["tool.meta"],
|
|
5487
|
+
icon: "meta",
|
|
5280
5488
|
inputSchema: {
|
|
5281
5489
|
type: "object",
|
|
5282
5490
|
properties: {
|
|
@@ -5356,6 +5564,7 @@ var toolUseTool = {
|
|
|
5356
5564
|
mutating: true,
|
|
5357
5565
|
timeoutMs: 6e4,
|
|
5358
5566
|
capabilities: ["tool.mutate.any"],
|
|
5567
|
+
icon: "meta",
|
|
5359
5568
|
inputSchema: {
|
|
5360
5569
|
type: "object",
|
|
5361
5570
|
properties: {
|
|
@@ -5426,6 +5635,7 @@ var batchToolUseTool = {
|
|
|
5426
5635
|
mutating: true,
|
|
5427
5636
|
timeoutMs: 12e4,
|
|
5428
5637
|
capabilities: ["tool.mutate.any"],
|
|
5638
|
+
icon: "meta",
|
|
5429
5639
|
inputSchema: {
|
|
5430
5640
|
type: "object",
|
|
5431
5641
|
properties: {
|
|
@@ -5531,6 +5741,7 @@ var toolHelpTool = {
|
|
|
5531
5741
|
mutating: false,
|
|
5532
5742
|
timeoutMs: 5e3,
|
|
5533
5743
|
capabilities: ["tool.meta"],
|
|
5744
|
+
icon: "meta",
|
|
5534
5745
|
inputSchema: {
|
|
5535
5746
|
type: "object",
|
|
5536
5747
|
properties: {
|
|
@@ -5655,6 +5866,7 @@ function rememberTool(memory) {
|
|
|
5655
5866
|
mutating: true,
|
|
5656
5867
|
timeoutMs: 2e3,
|
|
5657
5868
|
capabilities: ["memory.write"],
|
|
5869
|
+
icon: "settings",
|
|
5658
5870
|
inputSchema: {
|
|
5659
5871
|
type: "object",
|
|
5660
5872
|
properties: {
|
|
@@ -5829,6 +6041,7 @@ function createModeTool(modeStore) {
|
|
|
5829
6041
|
mutating: true,
|
|
5830
6042
|
timeoutMs: 5e3,
|
|
5831
6043
|
capabilities: ["session.mode"],
|
|
6044
|
+
icon: "settings",
|
|
5832
6045
|
inputSchema: {
|
|
5833
6046
|
type: "object",
|
|
5834
6047
|
properties: {
|
|
@@ -6139,7 +6352,7 @@ function loadDatabaseSync() {
|
|
|
6139
6352
|
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
6140
6353
|
} catch (err) {
|
|
6141
6354
|
throw new Error(
|
|
6142
|
-
`The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${
|
|
6355
|
+
`The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage(err)}`
|
|
6143
6356
|
);
|
|
6144
6357
|
}
|
|
6145
6358
|
return DatabaseSyncCtor;
|
|
@@ -6585,39 +6798,57 @@ var IndexStore = class {
|
|
|
6585
6798
|
}
|
|
6586
6799
|
});
|
|
6587
6800
|
}
|
|
6801
|
+
/**
|
|
6802
|
+
* Bulk-insert refs for many source symbols in a single transaction.
|
|
6803
|
+
*
|
|
6804
|
+
* Unlike {@link insertRefs} this does NOT delete per source id — the caller
|
|
6805
|
+
* (the indexer) has already cleared stale refs for the file via
|
|
6806
|
+
* {@link deleteRefsForFile}, so the per-source DELETE would be redundant work
|
|
6807
|
+
* repeated once per symbol. One transaction for the whole file instead of one
|
|
6808
|
+
* per symbol turns an O(symbols) transaction count into O(1).
|
|
6809
|
+
*
|
|
6810
|
+
* Each ref's own {@link Ref.fromId} is used; pass an empty array to no-op.
|
|
6811
|
+
*/
|
|
6812
|
+
insertRefsBatch(refs) {
|
|
6813
|
+
if (refs.length === 0) return;
|
|
6814
|
+
this.runWithRetry(() => {
|
|
6815
|
+
const stmt = this.db.prepare(
|
|
6816
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
6817
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
6818
|
+
);
|
|
6819
|
+
for (const ref of refs) {
|
|
6820
|
+
stmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
6821
|
+
}
|
|
6822
|
+
});
|
|
6823
|
+
}
|
|
6588
6824
|
/**
|
|
6589
6825
|
* Delete all refs whose source symbols are in a given file.
|
|
6590
6826
|
* Used when re-indexing a file to clear stale refs.
|
|
6591
6827
|
*/
|
|
6592
6828
|
deleteRefsForFile(file) {
|
|
6593
6829
|
this.runWithRetry(() => {
|
|
6594
|
-
|
|
6595
|
-
"SELECT id FROM symbols WHERE file = ?"
|
|
6596
|
-
).
|
|
6597
|
-
if (!ids.length) return;
|
|
6598
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
6599
|
-
this.db.prepare(`DELETE FROM refs WHERE from_id IN (${placeholders})`).run(...ids.map((r) => r.id));
|
|
6830
|
+
this.db.prepare(
|
|
6831
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6832
|
+
).run(file);
|
|
6600
6833
|
});
|
|
6601
6834
|
}
|
|
6602
6835
|
/**
|
|
6603
6836
|
* Resolve `to_name` → `to_id` for all refs that have a name but no id.
|
|
6604
6837
|
* Call this after all symbols have been inserted to fill in cross-references.
|
|
6838
|
+
*
|
|
6839
|
+
* Single statement: the `to_name IN (SELECT name FROM symbols)` guard restricts
|
|
6840
|
+
* the UPDATE to refs that will actually resolve, so `.changes` counts only refs
|
|
6841
|
+
* that found a target — matching the previous per-row loop's return value.
|
|
6605
6842
|
*/
|
|
6606
6843
|
resolveRefs() {
|
|
6607
6844
|
return this.runWithRetry(() => {
|
|
6608
|
-
const
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
if (first) {
|
|
6616
|
-
this.db.prepare("UPDATE refs SET to_id = ? WHERE id = ?").run(first.id, row.id);
|
|
6617
|
-
resolved++;
|
|
6618
|
-
}
|
|
6619
|
-
}
|
|
6620
|
-
return resolved;
|
|
6845
|
+
const result = this.db.prepare(
|
|
6846
|
+
`UPDATE refs SET to_id = (
|
|
6847
|
+
SELECT id FROM symbols WHERE name = refs.to_name LIMIT 1
|
|
6848
|
+
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
6849
|
+
AND to_name IN (SELECT name FROM symbols)`
|
|
6850
|
+
).run();
|
|
6851
|
+
return result.changes ?? 0;
|
|
6621
6852
|
});
|
|
6622
6853
|
}
|
|
6623
6854
|
/**
|
|
@@ -6724,7 +6955,7 @@ function getSignature(node, sourceFile) {
|
|
|
6724
6955
|
}
|
|
6725
6956
|
function getJsDoc(node, sourceFile) {
|
|
6726
6957
|
const fullText = sourceFile.getFullText();
|
|
6727
|
-
const nodePos = node.
|
|
6958
|
+
const nodePos = node.getFullStart();
|
|
6728
6959
|
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
6729
6960
|
if (!comments) return "";
|
|
6730
6961
|
for (const range of comments) {
|
|
@@ -8092,14 +8323,27 @@ async function runIndexerWithStore(store, opts) {
|
|
|
8092
8323
|
symbolsIndexed += count;
|
|
8093
8324
|
langStats[lang] = (langStats[lang] ?? 0) + count;
|
|
8094
8325
|
if (parsed.refs && parsed.refs.length > 0) {
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
if (
|
|
8099
|
-
|
|
8100
|
-
|
|
8326
|
+
const refsByLine = /* @__PURE__ */ new Map();
|
|
8327
|
+
for (const r of parsed.refs) {
|
|
8328
|
+
let arr = refsByLine.get(r.line);
|
|
8329
|
+
if (!arr) {
|
|
8330
|
+
arr = [];
|
|
8331
|
+
refsByLine.set(r.line, arr);
|
|
8332
|
+
}
|
|
8333
|
+
arr.push(r);
|
|
8334
|
+
}
|
|
8335
|
+
const batch = [];
|
|
8336
|
+
for (const sym of symbolsWithIds) {
|
|
8337
|
+
const symRefs = refsByLine.get(sym.line);
|
|
8338
|
+
if (symRefs) {
|
|
8339
|
+
for (const r of symRefs) {
|
|
8340
|
+
batch.push({ ...r, fromId: sym.id });
|
|
8341
|
+
}
|
|
8101
8342
|
}
|
|
8102
8343
|
}
|
|
8344
|
+
if (batch.length > 0) {
|
|
8345
|
+
store.insertRefsBatch(batch);
|
|
8346
|
+
}
|
|
8103
8347
|
}
|
|
8104
8348
|
store.upsertFile({
|
|
8105
8349
|
file,
|
|
@@ -8491,6 +8735,7 @@ async function codebaseIndexStats(args, opts = {}) {
|
|
|
8491
8735
|
var codebaseIndexTool = {
|
|
8492
8736
|
name: "codebase-index",
|
|
8493
8737
|
category: "Project",
|
|
8738
|
+
icon: "index",
|
|
8494
8739
|
description: "Build or incrementally update the project-wide symbol index. This powers fast codebase search and understanding. By default it only processes files that have changed since the last indexing run.",
|
|
8495
8740
|
usageHint: "IMPORTANT FOR LARGE CODEBASES:\n\n- First run (or after major changes): consider `force: true` for a clean rebuild.\n- Normal usage: call without arguments for fast incremental updates.\n- Use `langs` to restrict to specific languages if you only care about certain parts of the project.\nThis tool is relatively expensive \u2014 do not call it on every turn. Use it when the index is stale or before heavy codebase-search sessions.",
|
|
8496
8741
|
permission: "confirm",
|
|
@@ -8547,6 +8792,7 @@ var codebaseIndexTool = {
|
|
|
8547
8792
|
var codebaseSearchTool = {
|
|
8548
8793
|
name: "codebase-search",
|
|
8549
8794
|
category: "Project",
|
|
8795
|
+
icon: "index",
|
|
8550
8796
|
description: "Semantic/keyword search over the indexed codebase symbols (functions, classes, interfaces, etc.). Uses BM25 ranking. Much more powerful and structured than raw `grep` for finding code by name or concept.",
|
|
8551
8797
|
usageHint: "PREFERRED FOR CODE UNDERSTANDING:\n\n- Use when you need to find where something is defined or used by name.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\nThis is generally better than `grep` when you are looking for symbols rather than arbitrary text patterns.",
|
|
8552
8798
|
permission: "auto",
|
|
@@ -8635,6 +8881,7 @@ var codebaseSearchTool = {
|
|
|
8635
8881
|
var codebaseStatsTool = {
|
|
8636
8882
|
name: "codebase-stats",
|
|
8637
8883
|
category: "Project",
|
|
8884
|
+
icon: "index",
|
|
8638
8885
|
description: "Return health and statistics about the current symbol index (total symbols, files, language/kind breakdown, size, last update). Useful to decide whether to re-index.",
|
|
8639
8886
|
usageHint: "CALL BEFORE HEAVY CODEBASE-SEARCH WORK:\n\n- Use to see if the index is up-to-date or needs a refresh.\n- No arguments required.\n- Helps avoid wasting tokens on searches against a stale index.\nLightweight and safe to call frequently.",
|
|
8640
8887
|
permission: "auto",
|
|
@@ -8695,6 +8942,7 @@ var setWorkingDirTool = {
|
|
|
8695
8942
|
permission: "confirm",
|
|
8696
8943
|
mutating: true,
|
|
8697
8944
|
capabilities: ["fs.read"],
|
|
8945
|
+
icon: "settings",
|
|
8698
8946
|
timeoutMs: 5e3,
|
|
8699
8947
|
inputSchema: {
|
|
8700
8948
|
type: "object",
|
|
@@ -8719,7 +8967,7 @@ var setWorkingDirTool = {
|
|
|
8719
8967
|
} catch (err) {
|
|
8720
8968
|
return {
|
|
8721
8969
|
current: ctx.workingDir,
|
|
8722
|
-
error:
|
|
8970
|
+
error: toErrorMessage(err)
|
|
8723
8971
|
};
|
|
8724
8972
|
}
|
|
8725
8973
|
try {
|
|
@@ -8755,11 +9003,12 @@ function findTaskIndex(tasks, query2) {
|
|
|
8755
9003
|
var taskTool = {
|
|
8756
9004
|
name: "task",
|
|
8757
9005
|
category: "Session",
|
|
8758
|
-
description:
|
|
8759
|
-
usageHint: 'USE FOR STRUCTURED WORK:\n- `action: "replace"` \u2014 set the complete task list (tasks ordered by priority)\n- `action: "add"` \u2014 append a single task\n- `action: "status"` \u2014 update a task\'s status (e.g. pending\u2192in_progress, in_progress\u2192completed)\n- `action: "show"` \u2014 view current tasks without changing them\n- `action: "promote"` \u2014 convert a task into actionable todo items via `target` (id|index|substring)\n- `action: "planify"` \u2014 promote a task to a plan item (strategic level) via `target` (id|index|substring)\n\nTask fields:\n- `dependsOn`: list of task IDs this one waits for\n- `type`: "feature" | "bugfix" | "refactor" | "docs" | "test" | "chore"\n- `priority`: "critical" | "high" | "medium" | "low"\n- `assignee`: agent/subagent name (e.g. "bug-hunter", "refactor-planner")\n- `estimateHours`: rough time estimate',
|
|
9006
|
+
description: 'Manage session-persistent structured work items with dependencies, types, and priorities. Unlike `todo` (flat, tactical), `task` supports typed work (feature/bugfix/refactor/etc.), dependencies between items, priority ranking, and agent assignment. Tasks are written to disk and survive session resumes. By default they are isolated to this session; use `scope: "project"` to store tasks in a shared project-level file visible to all sessions.',
|
|
9007
|
+
usageHint: 'USE FOR STRUCTURED WORK:\n- `action: "replace"` \u2014 set the complete task list (tasks ordered by priority)\n- `action: "add"` \u2014 append a single task\n- `action: "status"` \u2014 update a task\'s status (e.g. pending\u2192in_progress, in_progress\u2192completed)\n- `action: "show"` \u2014 view current tasks without changing them\n- `action: "promote"` \u2014 convert a task into actionable todo items via `target` (id|index|substring)\n- `action: "planify"` \u2014 promote a task to a plan item (strategic level) via `target` (id|index|substring)\n\nTask fields:\n- `dependsOn`: list of task IDs this one waits for\n- `type`: "feature" | "bugfix" | "refactor" | "docs" | "test" | "chore"\n- `priority`: "critical" | "high" | "medium" | "low"\n- `assignee`: agent/subagent name (e.g. "bug-hunter", "refactor-planner")\n- `estimateHours`: rough time estimate\n- `scope`: "session" (default, isolated) or "project" (shared across sessions)',
|
|
8760
9008
|
permission: "confirm",
|
|
8761
9009
|
mutating: true,
|
|
8762
9010
|
capabilities: ["fs.write"],
|
|
9011
|
+
icon: "task",
|
|
8763
9012
|
timeoutMs: 2e3,
|
|
8764
9013
|
inputSchema: {
|
|
8765
9014
|
type: "object",
|
|
@@ -8825,12 +9074,26 @@ var taskTool = {
|
|
|
8825
9074
|
type: "array",
|
|
8826
9075
|
items: { type: "string" },
|
|
8827
9076
|
description: "Optional subtask titles for action=promote. Each becomes a pending todo."
|
|
9077
|
+
},
|
|
9078
|
+
scope: {
|
|
9079
|
+
type: "string",
|
|
9080
|
+
enum: ["session", "project"],
|
|
9081
|
+
description: 'Storage scope: "session" (default, isolated to this session) or "project" (shared across all sessions for this project).'
|
|
8828
9082
|
}
|
|
8829
9083
|
},
|
|
8830
9084
|
required: ["action"]
|
|
8831
9085
|
},
|
|
8832
9086
|
async execute(input, ctx) {
|
|
8833
|
-
const
|
|
9087
|
+
const sessionTaskPath = ctx.meta["task.path"];
|
|
9088
|
+
let taskPath;
|
|
9089
|
+
if (input.scope === "project") {
|
|
9090
|
+
if (typeof sessionTaskPath === "string") {
|
|
9091
|
+
const lastSep = Math.max(sessionTaskPath.lastIndexOf("/"), sessionTaskPath.lastIndexOf("\\"));
|
|
9092
|
+
taskPath = lastSep >= 0 ? sessionTaskPath.slice(0, lastSep + 1) + "backlog.tasks.json" : "backlog.tasks.json";
|
|
9093
|
+
}
|
|
9094
|
+
} else {
|
|
9095
|
+
taskPath = sessionTaskPath;
|
|
9096
|
+
}
|
|
8834
9097
|
if (typeof taskPath !== "string" || !taskPath) {
|
|
8835
9098
|
return { ok: false, message: "Task storage path not configured.", count: 0, completed: 0, inProgress: 0 };
|
|
8836
9099
|
}
|
|
@@ -8840,23 +9103,66 @@ var taskTool = {
|
|
|
8840
9103
|
const planifyMeta = { title: "", details: "" };
|
|
8841
9104
|
let didPlanify = false;
|
|
8842
9105
|
let todosToReplace = null;
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
9106
|
+
let file;
|
|
9107
|
+
try {
|
|
9108
|
+
file = await mutateTasks(taskPath, sessionId, async (f) => {
|
|
9109
|
+
switch (input.action) {
|
|
9110
|
+
case "show":
|
|
9111
|
+
break;
|
|
9112
|
+
case "replace": {
|
|
9113
|
+
if (!Array.isArray(input.tasks)) {
|
|
9114
|
+
early = { ok: false, message: "action=replace requires `tasks` array.", count: 0, completed: 0, inProgress: 0 };
|
|
9115
|
+
return f;
|
|
9116
|
+
}
|
|
9117
|
+
const newIds = new Set(input.tasks.map((t) => t.id));
|
|
9118
|
+
if (newIds.size !== input.tasks.length) {
|
|
9119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9120
|
+
const dupes = [...new Set(input.tasks.map((t) => t.id).filter((id) => seen.has(id) ? true : (seen.add(id), false)))];
|
|
9121
|
+
early = {
|
|
9122
|
+
ok: false,
|
|
9123
|
+
message: `action=replace has duplicate task IDs: ${dupes.join(", ")}. Each task id must be unique.`,
|
|
9124
|
+
count: 0,
|
|
9125
|
+
completed: 0,
|
|
9126
|
+
inProgress: 0
|
|
9127
|
+
};
|
|
9128
|
+
return f;
|
|
9129
|
+
}
|
|
9130
|
+
for (const t of input.tasks) {
|
|
9131
|
+
if (t.dependsOn && t.dependsOn.length > 0) {
|
|
9132
|
+
const missing = t.dependsOn.filter((d) => !newIds.has(d));
|
|
9133
|
+
if (missing.length > 0) {
|
|
9134
|
+
early = {
|
|
9135
|
+
ok: false,
|
|
9136
|
+
message: `dependsOn validation failed: task "${t.id}" references unknown IDs: ${missing.join(", ")}`,
|
|
9137
|
+
count: 0,
|
|
9138
|
+
completed: 0,
|
|
9139
|
+
inProgress: 0
|
|
9140
|
+
};
|
|
9141
|
+
return f;
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
9145
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9146
|
+
f.tasks = input.tasks.map((t) => ({
|
|
9147
|
+
...t,
|
|
9148
|
+
createdAt: t.createdAt || now,
|
|
9149
|
+
updatedAt: now
|
|
9150
|
+
}));
|
|
9151
|
+
break;
|
|
8851
9152
|
}
|
|
8852
|
-
|
|
8853
|
-
|
|
9153
|
+
case "add": {
|
|
9154
|
+
const t = input.task;
|
|
9155
|
+
if (!t || !t.title) {
|
|
9156
|
+
early = { ok: false, message: "action=add requires `task` with at least `title`.", count: 0, completed: 0, inProgress: 0 };
|
|
9157
|
+
return f;
|
|
9158
|
+
}
|
|
8854
9159
|
if (t.dependsOn && t.dependsOn.length > 0) {
|
|
8855
|
-
const
|
|
9160
|
+
const existingIds = new Set(f.tasks.map((e) => e.id));
|
|
9161
|
+
const missing = t.dependsOn.filter((d) => !existingIds.has(d));
|
|
8856
9162
|
if (missing.length > 0) {
|
|
8857
9163
|
early = {
|
|
8858
9164
|
ok: false,
|
|
8859
|
-
message: `dependsOn validation failed: task
|
|
9165
|
+
message: `dependsOn validation failed: unknown task IDs: ${missing.join(", ")}`,
|
|
8860
9166
|
count: 0,
|
|
8861
9167
|
completed: 0,
|
|
8862
9168
|
inProgress: 0
|
|
@@ -8864,165 +9170,170 @@ var taskTool = {
|
|
|
8864
9170
|
return f;
|
|
8865
9171
|
}
|
|
8866
9172
|
}
|
|
9173
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9174
|
+
const newTask = {
|
|
9175
|
+
id: `task_${Date.now()}_${randomUUID().slice(0, 8)}`,
|
|
9176
|
+
title: t.title,
|
|
9177
|
+
description: t.description,
|
|
9178
|
+
type: t.type || "feature",
|
|
9179
|
+
priority: t.priority || "medium",
|
|
9180
|
+
status: t.status || "pending",
|
|
9181
|
+
dependsOn: t.dependsOn,
|
|
9182
|
+
assignee: t.assignee,
|
|
9183
|
+
estimateHours: t.estimateHours,
|
|
9184
|
+
tags: t.tags,
|
|
9185
|
+
createdAt: now,
|
|
9186
|
+
updatedAt: now
|
|
9187
|
+
};
|
|
9188
|
+
f.tasks.push(newTask);
|
|
9189
|
+
break;
|
|
8867
9190
|
}
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
createdAt: t.createdAt || now,
|
|
8872
|
-
updatedAt: now
|
|
8873
|
-
}));
|
|
8874
|
-
break;
|
|
8875
|
-
}
|
|
8876
|
-
case "add": {
|
|
8877
|
-
const t = input.task;
|
|
8878
|
-
if (!t || !t.title) {
|
|
8879
|
-
early = { ok: false, message: "action=add requires `task` with at least `title`.", count: 0, completed: 0, inProgress: 0 };
|
|
8880
|
-
return f;
|
|
8881
|
-
}
|
|
8882
|
-
if (t.dependsOn && t.dependsOn.length > 0) {
|
|
8883
|
-
const existingIds = new Set(f.tasks.map((e) => e.id));
|
|
8884
|
-
const missing = t.dependsOn.filter((d) => !existingIds.has(d));
|
|
8885
|
-
if (missing.length > 0) {
|
|
8886
|
-
early = {
|
|
8887
|
-
ok: false,
|
|
8888
|
-
message: `dependsOn validation failed: unknown task IDs: ${missing.join(", ")}`,
|
|
8889
|
-
count: 0,
|
|
8890
|
-
completed: 0,
|
|
8891
|
-
inProgress: 0
|
|
8892
|
-
};
|
|
9191
|
+
case "status": {
|
|
9192
|
+
if (!input.id || !input.status) {
|
|
9193
|
+
early = { ok: false, message: "action=status requires `id` and `status`.", count: 0, completed: 0, inProgress: 0 };
|
|
8893
9194
|
return f;
|
|
8894
9195
|
}
|
|
9196
|
+
const task = f.tasks.find((t) => t.id === input.id);
|
|
9197
|
+
if (!task) {
|
|
9198
|
+
early = { ok: false, message: `Task "${input.id}" not found.`, count: 0, completed: 0, inProgress: 0 };
|
|
9199
|
+
return f;
|
|
9200
|
+
}
|
|
9201
|
+
task.status = input.status;
|
|
9202
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9203
|
+
break;
|
|
8895
9204
|
}
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
}
|
|
8919
|
-
const task = f.tasks.find((t) => t.id === input.id);
|
|
8920
|
-
if (!task) {
|
|
8921
|
-
early = { ok: false, message: `Task "${input.id}" not found.`, count: 0, completed: 0, inProgress: 0 };
|
|
8922
|
-
return f;
|
|
8923
|
-
}
|
|
8924
|
-
task.status = input.status;
|
|
8925
|
-
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8926
|
-
break;
|
|
8927
|
-
}
|
|
8928
|
-
case "promote": {
|
|
8929
|
-
const target = input.target?.trim();
|
|
8930
|
-
if (!target) {
|
|
8931
|
-
early = { ok: false, message: "action=promote requires `target` (task id, index, or title substring).", count: 0, completed: 0, inProgress: 0 };
|
|
8932
|
-
return f;
|
|
8933
|
-
}
|
|
8934
|
-
const idx = findTaskIndex(f.tasks, target);
|
|
8935
|
-
if (idx === -1) {
|
|
8936
|
-
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
8937
|
-
return f;
|
|
8938
|
-
}
|
|
8939
|
-
const match = f.tasks[idx];
|
|
8940
|
-
if (!match) {
|
|
8941
|
-
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
8942
|
-
return f;
|
|
8943
|
-
}
|
|
8944
|
-
if (match.status !== "completed" && match.status !== "failed") {
|
|
8945
|
-
match.status = "in_progress";
|
|
8946
|
-
match.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8947
|
-
}
|
|
8948
|
-
const todos = [];
|
|
8949
|
-
const ts2 = Date.now();
|
|
8950
|
-
todos.push({
|
|
8951
|
-
id: `todo_${ts2}_task`,
|
|
8952
|
-
content: match.title,
|
|
8953
|
-
status: "in_progress",
|
|
8954
|
-
activeForm: match.title,
|
|
8955
|
-
promotedFromTask: match.id
|
|
8956
|
-
});
|
|
8957
|
-
if (match.description) {
|
|
9205
|
+
case "promote": {
|
|
9206
|
+
const target = input.target?.trim();
|
|
9207
|
+
if (!target) {
|
|
9208
|
+
early = { ok: false, message: "action=promote requires `target` (task id, index, or title substring).", count: 0, completed: 0, inProgress: 0 };
|
|
9209
|
+
return f;
|
|
9210
|
+
}
|
|
9211
|
+
const idx = findTaskIndex(f.tasks, target);
|
|
9212
|
+
if (idx === -1) {
|
|
9213
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
9214
|
+
return f;
|
|
9215
|
+
}
|
|
9216
|
+
const match = f.tasks[idx];
|
|
9217
|
+
if (!match) {
|
|
9218
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
9219
|
+
return f;
|
|
9220
|
+
}
|
|
9221
|
+
if (match.status !== "completed" && match.status !== "failed") {
|
|
9222
|
+
match.status = "in_progress";
|
|
9223
|
+
match.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9224
|
+
}
|
|
9225
|
+
const todos = [];
|
|
9226
|
+
const ts2 = Date.now();
|
|
8958
9227
|
todos.push({
|
|
8959
|
-
id: `todo_${ts2}
|
|
8960
|
-
content: match.
|
|
8961
|
-
status: "
|
|
9228
|
+
id: `todo_${ts2}_task`,
|
|
9229
|
+
content: match.title,
|
|
9230
|
+
status: "in_progress",
|
|
9231
|
+
activeForm: match.title,
|
|
8962
9232
|
promotedFromTask: match.id
|
|
8963
9233
|
});
|
|
8964
|
-
|
|
8965
|
-
if (input.subtasks && input.subtasks.length > 0) {
|
|
8966
|
-
for (const st of input.subtasks) {
|
|
9234
|
+
if (match.description) {
|
|
8967
9235
|
todos.push({
|
|
8968
9236
|
id: `todo_${ts2}_${randomUUID().slice(0, 6)}`,
|
|
8969
|
-
content:
|
|
9237
|
+
content: match.description.slice(0, 200),
|
|
8970
9238
|
status: "pending",
|
|
8971
9239
|
promotedFromTask: match.id
|
|
8972
9240
|
});
|
|
8973
9241
|
}
|
|
9242
|
+
if (input.subtasks && input.subtasks.length > 0) {
|
|
9243
|
+
for (const st of input.subtasks) {
|
|
9244
|
+
todos.push({
|
|
9245
|
+
id: `todo_${ts2}_${randomUUID().slice(0, 6)}`,
|
|
9246
|
+
content: st,
|
|
9247
|
+
status: "pending",
|
|
9248
|
+
promotedFromTask: match.id
|
|
9249
|
+
});
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
todosToReplace = todos;
|
|
9253
|
+
promoteMeta.count = todos.length;
|
|
9254
|
+
promoteMeta.title = match.title;
|
|
9255
|
+
break;
|
|
8974
9256
|
}
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
9257
|
+
case "planify": {
|
|
9258
|
+
const target = input.target?.trim();
|
|
9259
|
+
if (!target) {
|
|
9260
|
+
early = { ok: false, message: "action=planify requires `target` (task id, index, or title substring).", count: 0, completed: 0, inProgress: 0 };
|
|
9261
|
+
return f;
|
|
9262
|
+
}
|
|
9263
|
+
const idx = findTaskIndex(f.tasks, target);
|
|
9264
|
+
if (idx === -1) {
|
|
9265
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
9266
|
+
return f;
|
|
9267
|
+
}
|
|
9268
|
+
const match = f.tasks[idx];
|
|
9269
|
+
if (!match) {
|
|
9270
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
9271
|
+
return f;
|
|
9272
|
+
}
|
|
9273
|
+
planifyMeta.title = match.title;
|
|
9274
|
+
planifyMeta.details = match.description ?? "";
|
|
9275
|
+
didPlanify = true;
|
|
9276
|
+
break;
|
|
8990
9277
|
}
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
9278
|
+
default:
|
|
9279
|
+
early = { ok: false, message: `Unknown action "${input.action}". Use replace | add | status | show | promote | planify.`, count: 0, completed: 0, inProgress: 0 };
|
|
8994
9280
|
return f;
|
|
8995
|
-
}
|
|
8996
|
-
planifyMeta.title = match.title;
|
|
8997
|
-
planifyMeta.details = match.description ?? "";
|
|
8998
|
-
didPlanify = true;
|
|
8999
|
-
break;
|
|
9000
9281
|
}
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9282
|
+
return f;
|
|
9283
|
+
});
|
|
9284
|
+
} catch (err) {
|
|
9285
|
+
return {
|
|
9286
|
+
ok: false,
|
|
9287
|
+
message: `Task change not saved \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
9288
|
+
count: 0,
|
|
9289
|
+
completed: 0,
|
|
9290
|
+
inProgress: 0
|
|
9291
|
+
};
|
|
9292
|
+
}
|
|
9007
9293
|
if (todosToReplace) ctx.state.replaceTodos(todosToReplace);
|
|
9008
9294
|
if (early) return early;
|
|
9009
9295
|
if (didPlanify) {
|
|
9010
9296
|
const { title, details } = planifyMeta;
|
|
9011
|
-
const
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9297
|
+
const planPathRaw = ctx.meta["plan.path"];
|
|
9298
|
+
const prog = computeTaskItemProgress(file.tasks);
|
|
9299
|
+
if (typeof planPathRaw === "string" && planPathRaw) {
|
|
9300
|
+
let planPath = planPathRaw;
|
|
9301
|
+
if (input.scope === "project") {
|
|
9302
|
+
const lastSep = Math.max(planPath.lastIndexOf("/"), planPath.lastIndexOf("\\"));
|
|
9303
|
+
planPath = lastSep >= 0 ? planPath.slice(0, lastSep + 1) + "backlog.plan.json" : "backlog.plan.json";
|
|
9304
|
+
}
|
|
9305
|
+
let formatted = "";
|
|
9306
|
+
try {
|
|
9307
|
+
await mutatePlan(planPath, sessionId, (pf) => {
|
|
9308
|
+
const { plan: updated } = addPlanItem(pf, title, details || void 0);
|
|
9309
|
+
formatted = formatPlan(updated);
|
|
9310
|
+
return updated;
|
|
9311
|
+
});
|
|
9312
|
+
} catch (err) {
|
|
9313
|
+
return {
|
|
9314
|
+
ok: false,
|
|
9315
|
+
message: `planify: plan not saved \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
9316
|
+
count: file.tasks.length,
|
|
9317
|
+
completed: prog.completed,
|
|
9318
|
+
inProgress: prog.inProgress
|
|
9319
|
+
};
|
|
9320
|
+
}
|
|
9016
9321
|
return {
|
|
9017
9322
|
ok: true,
|
|
9018
9323
|
message: `planify ok \u2014 added "${title}" to plan.
|
|
9019
|
-
${
|
|
9324
|
+
${formatted}`,
|
|
9020
9325
|
count: file.tasks.length,
|
|
9021
|
-
completed:
|
|
9022
|
-
inProgress:
|
|
9326
|
+
completed: prog.completed,
|
|
9327
|
+
inProgress: prog.inProgress
|
|
9023
9328
|
};
|
|
9024
9329
|
}
|
|
9025
|
-
return {
|
|
9330
|
+
return {
|
|
9331
|
+
ok: false,
|
|
9332
|
+
message: "Plan storage path not configured \u2014 cannot planify.",
|
|
9333
|
+
count: file.tasks.length,
|
|
9334
|
+
completed: prog.completed,
|
|
9335
|
+
inProgress: prog.inProgress
|
|
9336
|
+
};
|
|
9026
9337
|
}
|
|
9027
9338
|
const p = computeTaskItemProgress(file.tasks);
|
|
9028
9339
|
const summary = promoteMeta.count > 0 ? `promote ok \u2014 ${promoteMeta.count} todo(s) created from "${promoteMeta.title}".
|
|
@@ -9066,6 +9377,36 @@ var TIER1_TOOLS = [
|
|
|
9066
9377
|
jsonTool,
|
|
9067
9378
|
searchTool
|
|
9068
9379
|
];
|
|
9380
|
+
var TIER2_TOOLS = [
|
|
9381
|
+
replaceTool,
|
|
9382
|
+
execTool,
|
|
9383
|
+
fetchTool,
|
|
9384
|
+
gitTool,
|
|
9385
|
+
treeTool,
|
|
9386
|
+
lintTool,
|
|
9387
|
+
formatTool,
|
|
9388
|
+
typecheckTool,
|
|
9389
|
+
testTool,
|
|
9390
|
+
todoTool,
|
|
9391
|
+
planTool,
|
|
9392
|
+
taskTool,
|
|
9393
|
+
installTool,
|
|
9394
|
+
auditTool
|
|
9395
|
+
];
|
|
9396
|
+
var TIER3_TOOLS = [
|
|
9397
|
+
outdatedTool,
|
|
9398
|
+
logsTool,
|
|
9399
|
+
documentTool,
|
|
9400
|
+
scaffoldTool,
|
|
9401
|
+
toolSearchTool,
|
|
9402
|
+
toolUseTool,
|
|
9403
|
+
batchToolUseTool,
|
|
9404
|
+
toolHelpTool,
|
|
9405
|
+
codebaseIndexTool,
|
|
9406
|
+
codebaseSearchTool,
|
|
9407
|
+
codebaseStatsTool,
|
|
9408
|
+
setWorkingDirTool
|
|
9409
|
+
];
|
|
9069
9410
|
var builtinTools = [
|
|
9070
9411
|
readTool,
|
|
9071
9412
|
writeTool,
|
|
@@ -9112,6 +9453,162 @@ var builtinToolsPack = {
|
|
|
9112
9453
|
tools: builtinTools
|
|
9113
9454
|
};
|
|
9114
9455
|
|
|
9115
|
-
|
|
9456
|
+
// src/tool-icon-map.ts
|
|
9457
|
+
var TOOL_ICON_MAP = {
|
|
9458
|
+
// File operations
|
|
9459
|
+
read: "file",
|
|
9460
|
+
write: "file",
|
|
9461
|
+
create: "file",
|
|
9462
|
+
// File modification
|
|
9463
|
+
edit: "edit",
|
|
9464
|
+
patch: "edit",
|
|
9465
|
+
replace: "edit",
|
|
9466
|
+
// Content search
|
|
9467
|
+
grep: "search",
|
|
9468
|
+
search: "search",
|
|
9469
|
+
// File discovery
|
|
9470
|
+
glob: "folder",
|
|
9471
|
+
// Shell/command execution
|
|
9472
|
+
bash: "terminal",
|
|
9473
|
+
exec: "terminal",
|
|
9474
|
+
run: "terminal",
|
|
9475
|
+
command: "terminal",
|
|
9476
|
+
shell: "terminal",
|
|
9477
|
+
// Network
|
|
9478
|
+
fetch: "web",
|
|
9479
|
+
curl: "web",
|
|
9480
|
+
http: "web",
|
|
9481
|
+
request: "web",
|
|
9482
|
+
// Version control
|
|
9483
|
+
git: "git",
|
|
9484
|
+
// Directory structure
|
|
9485
|
+
tree: "tree",
|
|
9486
|
+
ls: "tree",
|
|
9487
|
+
list: "tree",
|
|
9488
|
+
// Code quality
|
|
9489
|
+
lint: "code",
|
|
9490
|
+
format: "code",
|
|
9491
|
+
typecheck: "code",
|
|
9492
|
+
// Testing
|
|
9493
|
+
test: "test",
|
|
9494
|
+
tests: "test",
|
|
9495
|
+
// Package management
|
|
9496
|
+
install: "package",
|
|
9497
|
+
uninstall: "package",
|
|
9498
|
+
audit: "package",
|
|
9499
|
+
outdated: "package",
|
|
9500
|
+
npm: "package",
|
|
9501
|
+
pnpm: "package",
|
|
9502
|
+
yarn: "package",
|
|
9503
|
+
// Documentation
|
|
9504
|
+
document: "document",
|
|
9505
|
+
doc: "document",
|
|
9506
|
+
jsdoc: "document",
|
|
9507
|
+
// Project scaffolding
|
|
9508
|
+
scaffold: "scaffold",
|
|
9509
|
+
generate: "scaffold",
|
|
9510
|
+
template: "scaffold",
|
|
9511
|
+
// Task management
|
|
9512
|
+
todo: "todo",
|
|
9513
|
+
todos: "todo",
|
|
9514
|
+
// Planning
|
|
9515
|
+
plan: "plan",
|
|
9516
|
+
planning: "plan",
|
|
9517
|
+
// Structured tasks
|
|
9518
|
+
task: "task",
|
|
9519
|
+
tasks: "task",
|
|
9520
|
+
// Meta/tools
|
|
9521
|
+
"tool-use": "meta",
|
|
9522
|
+
"batch-tool-use": "meta",
|
|
9523
|
+
"tool-search": "meta",
|
|
9524
|
+
"tool-help": "meta",
|
|
9525
|
+
tool_use: "meta",
|
|
9526
|
+
batch_tool_use: "meta",
|
|
9527
|
+
tool_search: "meta",
|
|
9528
|
+
tool_help: "meta",
|
|
9529
|
+
// Code indexing
|
|
9530
|
+
"codebase-index": "index",
|
|
9531
|
+
"codebase-search": "index",
|
|
9532
|
+
"codebase-stats": "index",
|
|
9533
|
+
"codebase_index": "index",
|
|
9534
|
+
"codebase_search": "index",
|
|
9535
|
+
"codebase_stats": "index",
|
|
9536
|
+
// Data
|
|
9537
|
+
json: "json",
|
|
9538
|
+
parse: "json",
|
|
9539
|
+
query: "json",
|
|
9540
|
+
// Comparison
|
|
9541
|
+
diff: "diff",
|
|
9542
|
+
compare: "diff",
|
|
9543
|
+
// Logs
|
|
9544
|
+
logs: "logs",
|
|
9545
|
+
log: "logs",
|
|
9546
|
+
// Configuration
|
|
9547
|
+
"set-working-dir": "settings",
|
|
9548
|
+
set_working_dir: "settings",
|
|
9549
|
+
cwd: "settings",
|
|
9550
|
+
cd: "settings",
|
|
9551
|
+
// AI/Agent
|
|
9552
|
+
think: "brain",
|
|
9553
|
+
reason: "brain",
|
|
9554
|
+
analyze: "brain",
|
|
9555
|
+
reasoning: "brain"
|
|
9556
|
+
};
|
|
9557
|
+
function getToolIcon(toolName) {
|
|
9558
|
+
return TOOL_ICON_MAP[toolName.toLowerCase()] ?? "fallback";
|
|
9559
|
+
}
|
|
9560
|
+
var TOOL_ICON_CONFIG = {
|
|
9561
|
+
file: { icon: "file", color: "#6366f1" },
|
|
9562
|
+
// indigo
|
|
9563
|
+
edit: { icon: "edit", color: "#f59e0b" },
|
|
9564
|
+
// amber
|
|
9565
|
+
search: { icon: "search", color: "#10b981" },
|
|
9566
|
+
// emerald
|
|
9567
|
+
folder: { icon: "folder", color: "#8b5cf6" },
|
|
9568
|
+
// violet
|
|
9569
|
+
terminal: { icon: "terminal", color: "#ef4444" },
|
|
9570
|
+
// red
|
|
9571
|
+
web: { icon: "web", color: "#06b6d4" },
|
|
9572
|
+
// cyan
|
|
9573
|
+
git: { icon: "git", color: "#f97316" },
|
|
9574
|
+
// orange
|
|
9575
|
+
tree: { icon: "tree", color: "#22c55e" },
|
|
9576
|
+
// green
|
|
9577
|
+
code: { icon: "code", color: "#3b82f6" },
|
|
9578
|
+
// blue
|
|
9579
|
+
test: { icon: "test", color: "#84cc16" },
|
|
9580
|
+
// lime
|
|
9581
|
+
package: { icon: "package", color: "#ec4899" },
|
|
9582
|
+
// pink
|
|
9583
|
+
document: { icon: "document", color: "#14b8a6" },
|
|
9584
|
+
// teal
|
|
9585
|
+
scaffold: { icon: "scaffold", color: "#f43f5e" },
|
|
9586
|
+
// rose
|
|
9587
|
+
todo: { icon: "todo", color: "#a855f7" },
|
|
9588
|
+
// purple
|
|
9589
|
+
plan: { icon: "plan", color: "#7c3aed" },
|
|
9590
|
+
// violet-dark
|
|
9591
|
+
task: { icon: "task", color: "#db2777" },
|
|
9592
|
+
// pink-dark
|
|
9593
|
+
meta: { icon: "meta", color: "#6b7280" },
|
|
9594
|
+
// gray
|
|
9595
|
+
index: { icon: "index", color: "#0ea5e9" },
|
|
9596
|
+
// sky
|
|
9597
|
+
json: { icon: "json", color: "#fbbf24" },
|
|
9598
|
+
// yellow
|
|
9599
|
+
diff: { icon: "diff", color: "#a3e635" },
|
|
9600
|
+
// lime-light
|
|
9601
|
+
logs: { icon: "logs", color: "#78716c" },
|
|
9602
|
+
// stone
|
|
9603
|
+
settings: { icon: "settings", color: "#64748b" },
|
|
9604
|
+
// slate
|
|
9605
|
+
brain: { icon: "brain", color: "#d946ef" },
|
|
9606
|
+
// fuchsia
|
|
9607
|
+
fallback: { icon: "fallback", color: "#9ca3af" }
|
|
9608
|
+
// gray-light
|
|
9609
|
+
};
|
|
9610
|
+
var FALLBACK_ICON = "fallback";
|
|
9611
|
+
|
|
9612
|
+
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, createModeTool, diffTool, documentTool, editTool, enqueueReindex, execTool, fetchTool, forgetTool, formatTool, getIndexState, getProcessRegistry, getToolIcon, gitTool, globTool, grepTool, indexCircuitBreaker, installTool, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, logsTool, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, relatedMemoryTool, rememberTool, replaceTool, resetIndexCircuitBreaker, runStartupIndex, scaffoldTool, searchCodebaseIndex, searchMemoryTool, searchTool, shutdownCodebaseIndexHost, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
|
|
9116
9613
|
//# sourceMappingURL=index.js.map
|
|
9117
9614
|
//# sourceMappingURL=index.js.map
|