@wrongstack/tools 0.148.0 → 0.236.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.js +21 -1
- package/dist/audit.js.map +1 -1
- package/dist/{background-indexer-C2014mH0.d.ts → background-indexer-CtbgPExj.d.ts} +1 -0
- package/dist/bash.js +116 -24
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +702 -209
- package/dist/builtin.js.map +1 -1
- package/dist/circuit-breaker.d.ts +9 -2
- package/dist/circuit-breaker.js +11 -2
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/codebase-index/index.d.ts +9 -2
- package/dist/codebase-index/index.js +50 -18
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/diff.js +1 -1
- package/dist/diff.js.map +1 -1
- package/dist/document.js +1 -1
- package/dist/document.js.map +1 -1
- package/dist/edit.js +1 -1
- package/dist/edit.js.map +1 -1
- package/dist/exec.js +60 -11
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +21 -1
- package/dist/format.js.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/glob.js +1 -1
- package/dist/glob.js.map +1 -1
- package/dist/grep.js +1 -1
- package/dist/grep.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +711 -217
- package/dist/index.js.map +1 -1
- package/dist/install.js +65 -14
- package/dist/install.js.map +1 -1
- package/dist/lint.js +21 -1
- package/dist/lint.js.map +1 -1
- package/dist/logs.js +1 -1
- package/dist/logs.js.map +1 -1
- package/dist/outdated.js +1 -1
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +702 -209
- package/dist/pack.js.map +1 -1
- package/dist/patch.js +1 -1
- package/dist/patch.js.map +1 -1
- package/dist/process-registry.d.ts +21 -16
- package/dist/process-registry.js +48 -10
- package/dist/process-registry.js.map +1 -1
- package/dist/read.js +1 -1
- package/dist/read.js.map +1 -1
- package/dist/replace.js +1 -1
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js +1 -1
- package/dist/scaffold.js.map +1 -1
- package/dist/search.js +19 -16
- package/dist/search.js.map +1 -1
- package/dist/test.js +21 -1
- package/dist/test.js.map +1 -1
- package/dist/todo.js +44 -0
- package/dist/todo.js.map +1 -1
- package/dist/tree.js +1 -1
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +21 -1
- package/dist/typecheck.js.map +1 -1
- package/dist/write.js +1 -1
- package/dist/write.js.map +1 -1
- package/package.json +5 -5
package/dist/builtin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { spawn, execFileSync, spawnSync } from 'node:child_process';
|
|
2
2
|
import * as Core from '@wrongstack/core';
|
|
3
|
-
import { buildChildEnv, expectDefined, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob,
|
|
3
|
+
import { buildChildEnv, expectDefined, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, recordPackageAction, detectPackageEcosystem, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, loadTasks, emptyTaskFile, saveTasks, formatTaskList, formatPlan, mutateTasks, loadPlan, emptyPlan, savePlan, computeTaskItemProgress, resolveWstackPaths } from '@wrongstack/core';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import { statSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
6
6
|
import * as path2 from 'node:path';
|
|
7
|
-
import { resolve, sep, dirname } from 'node:path';
|
|
7
|
+
import { resolve, sep, dirname, join } from 'node:path';
|
|
8
8
|
import * as fs13 from 'node:fs/promises';
|
|
9
9
|
import * as os from 'node:os';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
@@ -12,6 +12,7 @@ import * as ts from 'typescript';
|
|
|
12
12
|
import * as dns from 'node:dns/promises';
|
|
13
13
|
import * as net from 'node:net';
|
|
14
14
|
import { Agent } from 'undici';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
15
16
|
|
|
16
17
|
// src/_spawn-stream.ts
|
|
17
18
|
function resolveWin32Command(cmd) {
|
|
@@ -39,6 +40,7 @@ function resolveWin32Command(cmd) {
|
|
|
39
40
|
async function* spawnStream(opts) {
|
|
40
41
|
const max = opts.maxBytes ?? 2e5;
|
|
41
42
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
43
|
+
const maxQueue = opts.maxQueueSize ?? 500;
|
|
42
44
|
let stdout = "";
|
|
43
45
|
let stderr = "";
|
|
44
46
|
let pending = "";
|
|
@@ -54,6 +56,7 @@ async function* spawnStream(opts) {
|
|
|
54
56
|
});
|
|
55
57
|
const queue = [];
|
|
56
58
|
let waiter;
|
|
59
|
+
let paused = false;
|
|
57
60
|
const wake = () => {
|
|
58
61
|
if (waiter) {
|
|
59
62
|
const w = waiter;
|
|
@@ -61,17 +64,34 @@ async function* spawnStream(opts) {
|
|
|
61
64
|
w();
|
|
62
65
|
}
|
|
63
66
|
};
|
|
67
|
+
const resume = () => {
|
|
68
|
+
if (paused && queue.length < maxQueue) {
|
|
69
|
+
paused = false;
|
|
70
|
+
child.stdout?.resume();
|
|
71
|
+
child.stderr?.resume();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
64
74
|
child.stdout?.on("data", (c) => {
|
|
65
75
|
const s = c.toString();
|
|
66
76
|
if (stdout.length < max) stdout += s;
|
|
67
77
|
queue.push({ kind: "out", data: s });
|
|
68
78
|
wake();
|
|
79
|
+
if (!paused && queue.length >= maxQueue) {
|
|
80
|
+
paused = true;
|
|
81
|
+
child.stdout?.pause();
|
|
82
|
+
child.stderr?.pause();
|
|
83
|
+
}
|
|
69
84
|
});
|
|
70
85
|
child.stderr?.on("data", (c) => {
|
|
71
86
|
const s = c.toString();
|
|
72
87
|
if (stderr.length < max) stderr += s;
|
|
73
88
|
queue.push({ kind: "err", data: s });
|
|
74
89
|
wake();
|
|
90
|
+
if (!paused && queue.length >= maxQueue) {
|
|
91
|
+
paused = true;
|
|
92
|
+
child.stdout?.pause();
|
|
93
|
+
child.stderr?.pause();
|
|
94
|
+
}
|
|
75
95
|
});
|
|
76
96
|
child.on("error", (e) => {
|
|
77
97
|
error = e.message;
|
|
@@ -91,6 +111,7 @@ async function* spawnStream(opts) {
|
|
|
91
111
|
});
|
|
92
112
|
}
|
|
93
113
|
const chunk = queue.shift();
|
|
114
|
+
resume();
|
|
94
115
|
if (chunk.kind === "close") {
|
|
95
116
|
if (!spawnFailed) exitCode = chunk.code ?? 0;
|
|
96
117
|
break;
|
|
@@ -132,7 +153,7 @@ async function detectPackageManager(cwd) {
|
|
|
132
153
|
return "npm";
|
|
133
154
|
}
|
|
134
155
|
function resolvePath(input, ctx) {
|
|
135
|
-
return path2.isAbsolute(input) ? path2.normalize(input) : path2.resolve(ctx.cwd, input);
|
|
156
|
+
return path2.isAbsolute(input) ? path2.normalize(input) : path2.resolve(ctx.workingDir ?? ctx.cwd, input);
|
|
136
157
|
}
|
|
137
158
|
function ensureInsideRoot(absPath, ctx) {
|
|
138
159
|
const root = path2.resolve(ctx.projectRoot);
|
|
@@ -423,8 +444,13 @@ var CircuitBreaker = class {
|
|
|
423
444
|
* Call this BEFORE spawning a bash/exec process.
|
|
424
445
|
* Returns true if the call is allowed; false if the breaker is open.
|
|
425
446
|
* When false, callers MUST NOT spawn a process.
|
|
447
|
+
*
|
|
448
|
+
* @param bypass - If true, skip the circuit breaker check entirely.
|
|
449
|
+
* Use for background/fire-and-forget processes that should
|
|
450
|
+
* not affect breaker state.
|
|
426
451
|
*/
|
|
427
|
-
beforeCall() {
|
|
452
|
+
beforeCall(bypass = false) {
|
|
453
|
+
if (bypass) return true;
|
|
428
454
|
this._checkStateTransition();
|
|
429
455
|
if (this.state === "open") return false;
|
|
430
456
|
return true;
|
|
@@ -434,8 +460,12 @@ var CircuitBreaker = class {
|
|
|
434
460
|
* `durationMs` is the wall-clock time the process ran.
|
|
435
461
|
* `failed` is true when the process returned a non-zero exit code or
|
|
436
462
|
* threw an exception before spawning.
|
|
463
|
+
*
|
|
464
|
+
* @param bypass - If true, do not update breaker state.
|
|
465
|
+
* Use for background/fire-and-forget processes.
|
|
437
466
|
*/
|
|
438
|
-
afterCall(durationMs, failed) {
|
|
467
|
+
afterCall(durationMs, failed, bypass = false) {
|
|
468
|
+
if (bypass) return;
|
|
439
469
|
const now = Date.now();
|
|
440
470
|
if (this.state === "half-open") {
|
|
441
471
|
if (failed) {
|
|
@@ -534,6 +564,17 @@ function redactCommand(cmd) {
|
|
|
534
564
|
return result;
|
|
535
565
|
}
|
|
536
566
|
var DEFAULT_GRACE_MS = 2e3;
|
|
567
|
+
function killWin32Tree(pid) {
|
|
568
|
+
try {
|
|
569
|
+
spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
570
|
+
stdio: "ignore",
|
|
571
|
+
windowsHide: true
|
|
572
|
+
}).unref();
|
|
573
|
+
return true;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
537
578
|
var ProcessRegistryImpl = class {
|
|
538
579
|
processes = /* @__PURE__ */ new Map();
|
|
539
580
|
breaker;
|
|
@@ -591,16 +632,20 @@ var ProcessRegistryImpl = class {
|
|
|
591
632
|
/**
|
|
592
633
|
* Called before spawning a process. Returns true if allowed; false if
|
|
593
634
|
* the circuit breaker is open.
|
|
635
|
+
*
|
|
636
|
+
* @param bypass - If true, skip circuit breaker check (for background processes).
|
|
594
637
|
*/
|
|
595
|
-
beforeCall() {
|
|
596
|
-
return this.breaker.beforeCall();
|
|
638
|
+
beforeCall(bypass = false) {
|
|
639
|
+
return this.breaker.beforeCall(bypass);
|
|
597
640
|
}
|
|
598
641
|
/**
|
|
599
642
|
* Called after a process finishes. `durationMs` is wall-clock time;
|
|
600
643
|
* `failed` is true for non-zero exit codes.
|
|
644
|
+
*
|
|
645
|
+
* @param bypass - If true, do not update circuit breaker state (for background processes).
|
|
601
646
|
*/
|
|
602
|
-
afterCall(durationMs, failed) {
|
|
603
|
-
this.breaker.afterCall(durationMs, failed);
|
|
647
|
+
afterCall(durationMs, failed, bypass = false) {
|
|
648
|
+
this.breaker.afterCall(durationMs, failed, bypass);
|
|
604
649
|
}
|
|
605
650
|
/** Force-open the circuit breaker (Ctrl+C, /kill force). */
|
|
606
651
|
forceBreakerOpen() {
|
|
@@ -631,9 +676,22 @@ var ProcessRegistryImpl = class {
|
|
|
631
676
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
632
677
|
const isWin = os.platform() === "win32";
|
|
633
678
|
if (isWin) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
679
|
+
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
680
|
+
if (liveRealChild && killWin32Tree(pid)) {
|
|
681
|
+
const fallback = setTimeout(() => {
|
|
682
|
+
if (p.child.exitCode === null) {
|
|
683
|
+
try {
|
|
684
|
+
p.child.kill("SIGKILL");
|
|
685
|
+
} catch {
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}, graceMs);
|
|
689
|
+
fallback.unref?.();
|
|
690
|
+
} else {
|
|
691
|
+
try {
|
|
692
|
+
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
637
695
|
}
|
|
638
696
|
p.killed = true;
|
|
639
697
|
return true;
|
|
@@ -709,6 +767,7 @@ var MAX_OUTPUT = 32768;
|
|
|
709
767
|
var DEFAULT_TIMEOUT_MS = 3e5;
|
|
710
768
|
var STREAM_FLUSH_INTERVAL_MS = 200;
|
|
711
769
|
var STREAM_FLUSH_BYTES = 4 * 1024;
|
|
770
|
+
var MAX_QUEUE_CHUNKS = 500;
|
|
712
771
|
var bashTool = {
|
|
713
772
|
name: "bash",
|
|
714
773
|
category: "Shell",
|
|
@@ -756,7 +815,8 @@ var bashTool = {
|
|
|
756
815
|
async *executeStream(input, ctx, opts) {
|
|
757
816
|
if (!input?.command) throw new Error("bash: command is required");
|
|
758
817
|
const registry = getProcessRegistry();
|
|
759
|
-
|
|
818
|
+
const bypassBreaker = !!input.background;
|
|
819
|
+
if (!registry.beforeCall(bypassBreaker)) {
|
|
760
820
|
yield {
|
|
761
821
|
type: "final",
|
|
762
822
|
output: {
|
|
@@ -769,6 +829,17 @@ var bashTool = {
|
|
|
769
829
|
};
|
|
770
830
|
return;
|
|
771
831
|
}
|
|
832
|
+
const PIPE_TO_SHELL_PATTERN = /\|\s*(sh|bash|ksh|zsh|fish|cmd|powershell|pwsh)/i;
|
|
833
|
+
if (PIPE_TO_SHELL_PATTERN.test(input.command)) {
|
|
834
|
+
console.warn(JSON.stringify({
|
|
835
|
+
level: "warn",
|
|
836
|
+
event: "bash.pipe_to_shell_detected",
|
|
837
|
+
message: "Detected pipe-to-shell pattern. Consider reviewing the full command before confirming.",
|
|
838
|
+
command_prefix: input.command.slice(0, 100),
|
|
839
|
+
// Log first 100 chars for review
|
|
840
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
841
|
+
}));
|
|
842
|
+
}
|
|
772
843
|
const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
|
|
773
844
|
const isWin = os.platform() === "win32";
|
|
774
845
|
const shell = (() => {
|
|
@@ -827,7 +898,7 @@ var bashTool = {
|
|
|
827
898
|
}
|
|
828
899
|
});
|
|
829
900
|
child2.on("close", () => {
|
|
830
|
-
registry.afterCall(Date.now() - startedAt, false);
|
|
901
|
+
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
831
902
|
});
|
|
832
903
|
if (typeof pid2 === "number") child2.unref();
|
|
833
904
|
yield {
|
|
@@ -846,7 +917,7 @@ var bashTool = {
|
|
|
846
917
|
env,
|
|
847
918
|
stdio: ["ignore", "pipe", "pipe"],
|
|
848
919
|
detached,
|
|
849
|
-
signal: opts.signal
|
|
920
|
+
...isWin ? {} : { signal: opts.signal }
|
|
850
921
|
});
|
|
851
922
|
const pid = child.pid;
|
|
852
923
|
if (typeof pid === "number") {
|
|
@@ -865,9 +936,22 @@ var bashTool = {
|
|
|
865
936
|
const timers = [];
|
|
866
937
|
function killWithTimeout(child2, timeoutMs2) {
|
|
867
938
|
if (isWin) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
939
|
+
if (typeof child2.pid === "number" && child2.exitCode === null && killWin32Tree(child2.pid)) {
|
|
940
|
+
const fallback = setTimeout(() => {
|
|
941
|
+
if (child2.exitCode === null) {
|
|
942
|
+
try {
|
|
943
|
+
child2.kill();
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}, 2e3);
|
|
948
|
+
timers.push(fallback);
|
|
949
|
+
fallback.unref?.();
|
|
950
|
+
} else {
|
|
951
|
+
try {
|
|
952
|
+
child2.kill();
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
871
955
|
}
|
|
872
956
|
return;
|
|
873
957
|
}
|
|
@@ -906,6 +990,11 @@ var bashTool = {
|
|
|
906
990
|
}, timeoutMs);
|
|
907
991
|
timers.push(timer);
|
|
908
992
|
timer.unref?.();
|
|
993
|
+
const onAbort = () => killWithTimeout(child, 2e3);
|
|
994
|
+
if (isWin) {
|
|
995
|
+
if (opts.signal.aborted) onAbort();
|
|
996
|
+
else opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
997
|
+
}
|
|
909
998
|
const queue = [];
|
|
910
999
|
let resolveNext = null;
|
|
911
1000
|
const push = (c) => {
|
|
@@ -930,18 +1019,32 @@ var bashTool = {
|
|
|
930
1019
|
lastFlush = Date.now();
|
|
931
1020
|
return text;
|
|
932
1021
|
};
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1022
|
+
let paused = false;
|
|
1023
|
+
const pauseIfFlooded = () => {
|
|
1024
|
+
if (!paused && queue.length >= MAX_QUEUE_CHUNKS) {
|
|
1025
|
+
paused = true;
|
|
1026
|
+
child.stdout?.pause();
|
|
1027
|
+
child.stderr?.pause();
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
const resumeIfDrained = () => {
|
|
1031
|
+
if (paused && queue.length < MAX_QUEUE_CHUNKS) {
|
|
1032
|
+
paused = false;
|
|
1033
|
+
child.stdout?.resume();
|
|
1034
|
+
child.stderr?.resume();
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
const onData = (chunk) => {
|
|
940
1038
|
const text = chunk.toString();
|
|
941
|
-
buf
|
|
1039
|
+
if (buf.length < MAX_OUTPUT) {
|
|
1040
|
+
buf += text.slice(0, MAX_OUTPUT - buf.length);
|
|
1041
|
+
}
|
|
942
1042
|
pending += text;
|
|
943
1043
|
push({ kind: "data", text });
|
|
944
|
-
|
|
1044
|
+
pauseIfFlooded();
|
|
1045
|
+
};
|
|
1046
|
+
child.stdout?.on("data", onData);
|
|
1047
|
+
child.stderr?.on("data", onData);
|
|
945
1048
|
child.on("error", (err) => {
|
|
946
1049
|
for (const t of timers) clearTimeout(t);
|
|
947
1050
|
registry.afterCall(Date.now() - startedAt, true);
|
|
@@ -956,6 +1059,7 @@ var bashTool = {
|
|
|
956
1059
|
try {
|
|
957
1060
|
while (true) {
|
|
958
1061
|
const c = await next();
|
|
1062
|
+
resumeIfDrained();
|
|
959
1063
|
if (c.kind === "error") throw c.err;
|
|
960
1064
|
if (c.kind === "end") {
|
|
961
1065
|
const remainder = flush();
|
|
@@ -980,6 +1084,15 @@ var bashTool = {
|
|
|
980
1084
|
}
|
|
981
1085
|
} finally {
|
|
982
1086
|
for (const t of timers) clearTimeout(t);
|
|
1087
|
+
if (isWin) opts.signal.removeEventListener("abort", onAbort);
|
|
1088
|
+
child.stdout?.off("data", onData);
|
|
1089
|
+
child.stderr?.off("data", onData);
|
|
1090
|
+
child.stdout?.destroy();
|
|
1091
|
+
child.stderr?.destroy();
|
|
1092
|
+
if (child.exitCode === null && !child.killed) {
|
|
1093
|
+
if (typeof pid === "number") registry.kill(pid, { force: true });
|
|
1094
|
+
else killWithTimeout(child, 2e3);
|
|
1095
|
+
}
|
|
983
1096
|
}
|
|
984
1097
|
}
|
|
985
1098
|
};
|
|
@@ -2748,6 +2861,16 @@ var YIELD_EVERY_N = 50;
|
|
|
2748
2861
|
function yieldEventLoop() {
|
|
2749
2862
|
return new Promise((resolve7) => setImmediate(resolve7));
|
|
2750
2863
|
}
|
|
2864
|
+
function throwIfAborted(signal) {
|
|
2865
|
+
if (!signal?.aborted) return;
|
|
2866
|
+
if (signal.reason instanceof Error) throw signal.reason;
|
|
2867
|
+
throw new Error(
|
|
2868
|
+
typeof signal.reason === "string" ? signal.reason : "Indexing cancelled"
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
function isAbortError(err) {
|
|
2872
|
+
return err instanceof DOMException && err.name === "AbortError";
|
|
2873
|
+
}
|
|
2751
2874
|
var DEFAULT_IGNORE = [
|
|
2752
2875
|
"node_modules",
|
|
2753
2876
|
".git",
|
|
@@ -2759,7 +2882,7 @@ var DEFAULT_IGNORE = [
|
|
|
2759
2882
|
"__snapshots__",
|
|
2760
2883
|
".nyc_output"
|
|
2761
2884
|
];
|
|
2762
|
-
async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
|
|
2885
|
+
async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
2763
2886
|
const results = [];
|
|
2764
2887
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
2765
2888
|
const globs = [
|
|
@@ -2774,13 +2897,20 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
|
|
|
2774
2897
|
{ ext: ".yaml", pat: compileGlob("**/*.yaml") },
|
|
2775
2898
|
{ ext: ".yml", pat: compileGlob("**/*.yml") }
|
|
2776
2899
|
];
|
|
2900
|
+
let dirCount = 0;
|
|
2777
2901
|
const walk = async (dir) => {
|
|
2902
|
+
throwIfAborted(signal);
|
|
2903
|
+
if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
|
|
2904
|
+
await yieldEventLoop();
|
|
2905
|
+
throwIfAborted(signal);
|
|
2906
|
+
}
|
|
2778
2907
|
let entries;
|
|
2779
2908
|
try {
|
|
2780
2909
|
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
2781
2910
|
} catch {
|
|
2782
2911
|
return;
|
|
2783
2912
|
}
|
|
2913
|
+
dirCount++;
|
|
2784
2914
|
for (const e of entries) {
|
|
2785
2915
|
if (ignoreSet.has(e.name)) continue;
|
|
2786
2916
|
const full = path2.join(dir, e.name);
|
|
@@ -2825,8 +2955,18 @@ async function parseFile(file, content, lang) {
|
|
|
2825
2955
|
}
|
|
2826
2956
|
}
|
|
2827
2957
|
async function runIndexer(_ctx, opts) {
|
|
2828
|
-
const
|
|
2829
|
-
|
|
2958
|
+
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
2959
|
+
try {
|
|
2960
|
+
return await runIndexerWithStore(store, opts);
|
|
2961
|
+
} finally {
|
|
2962
|
+
try {
|
|
2963
|
+
store.close();
|
|
2964
|
+
} catch {
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
async function runIndexerWithStore(store, opts) {
|
|
2969
|
+
const { projectRoot, force = false, langs, ignore = [], signal } = opts;
|
|
2830
2970
|
const startMs = Date.now();
|
|
2831
2971
|
const errors = [];
|
|
2832
2972
|
const langStats = {};
|
|
@@ -2837,7 +2977,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2837
2977
|
if (opts.files && opts.files.length > 0) {
|
|
2838
2978
|
files = opts.files.map((f) => path2.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path2.relative(projectRoot, f).replace(/\\/g, "/"), false));
|
|
2839
2979
|
} else {
|
|
2840
|
-
files = await findSourceFiles(projectRoot, ignore, isGitIgnored);
|
|
2980
|
+
files = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
2841
2981
|
}
|
|
2842
2982
|
if (langs && langs.length > 0) {
|
|
2843
2983
|
const langSet = new Set(langs);
|
|
@@ -2856,11 +2996,14 @@ async function runIndexer(_ctx, opts) {
|
|
|
2856
2996
|
_setIndexProgress(fi + 1, files.length);
|
|
2857
2997
|
if (fi > 0 && fi % YIELD_EVERY_N === 0) {
|
|
2858
2998
|
await yieldEventLoop();
|
|
2999
|
+
throwIfAborted(signal);
|
|
2859
3000
|
}
|
|
2860
3001
|
let stat10;
|
|
2861
3002
|
try {
|
|
2862
|
-
|
|
2863
|
-
|
|
3003
|
+
const statOpts = signal ? { signal } : {};
|
|
3004
|
+
stat10 = await fs13.stat(file, statOpts);
|
|
3005
|
+
} catch (e) {
|
|
3006
|
+
if (isAbortError(e)) throw e;
|
|
2864
3007
|
store.deleteFile(file);
|
|
2865
3008
|
continue;
|
|
2866
3009
|
}
|
|
@@ -2878,8 +3021,9 @@ async function runIndexer(_ctx, opts) {
|
|
|
2878
3021
|
store.deleteSymbolsForFile(file);
|
|
2879
3022
|
let content;
|
|
2880
3023
|
try {
|
|
2881
|
-
content = await fs13.readFile(file, "utf8");
|
|
3024
|
+
content = await fs13.readFile(file, { encoding: "utf8", signal });
|
|
2882
3025
|
} catch (e) {
|
|
3026
|
+
if (isAbortError(e)) throw e;
|
|
2883
3027
|
errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2884
3028
|
continue;
|
|
2885
3029
|
}
|
|
@@ -2935,7 +3079,6 @@ async function runIndexer(_ctx, opts) {
|
|
|
2935
3079
|
}
|
|
2936
3080
|
const durationMs = Date.now() - startMs;
|
|
2937
3081
|
store.setLastIndexed(Date.now());
|
|
2938
|
-
store.close();
|
|
2939
3082
|
return {
|
|
2940
3083
|
filesIndexed,
|
|
2941
3084
|
symbolsIndexed,
|
|
@@ -2969,12 +3112,13 @@ var codebaseIndexTool = {
|
|
|
2969
3112
|
}
|
|
2970
3113
|
}
|
|
2971
3114
|
},
|
|
2972
|
-
async execute(input, ctx) {
|
|
3115
|
+
async execute(input, ctx, execOpts) {
|
|
2973
3116
|
const result = await runIndexer(ctx, {
|
|
2974
3117
|
projectRoot: ctx.projectRoot,
|
|
2975
3118
|
force: input.force ?? false,
|
|
2976
3119
|
langs: input.langs,
|
|
2977
|
-
indexDir: codebaseIndexDirOverride(ctx)
|
|
3120
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
3121
|
+
signal: execOpts?.signal
|
|
2978
3122
|
});
|
|
2979
3123
|
setIndexReady();
|
|
2980
3124
|
return result;
|
|
@@ -3856,12 +4000,13 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3856
4000
|
let killed = false;
|
|
3857
4001
|
const startedAt = Date.now();
|
|
3858
4002
|
const resolved = resolveWin32Command(cmd);
|
|
3859
|
-
const
|
|
4003
|
+
const isWin = process.platform === "win32";
|
|
4004
|
+
const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
3860
4005
|
const child = spawn(resolved, args, {
|
|
3861
4006
|
cwd,
|
|
3862
|
-
signal,
|
|
3863
4007
|
env: buildChildEnv(sessionId),
|
|
3864
4008
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4009
|
+
...isWin ? {} : { signal },
|
|
3865
4010
|
...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
|
|
3866
4011
|
});
|
|
3867
4012
|
const registry = getProcessRegistry();
|
|
@@ -3875,6 +4020,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3875
4020
|
if (typeof pid === "number") registry.kill(pid);
|
|
3876
4021
|
else child.kill("SIGTERM");
|
|
3877
4022
|
}, timeout);
|
|
4023
|
+
const onAbort = () => {
|
|
4024
|
+
killed = true;
|
|
4025
|
+
if (typeof pid === "number") registry.kill(pid, { force: true });
|
|
4026
|
+
else child.kill("SIGTERM");
|
|
4027
|
+
};
|
|
4028
|
+
if (isWin) {
|
|
4029
|
+
if (signal.aborted) onAbort();
|
|
4030
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
4031
|
+
}
|
|
3878
4032
|
child.stdout?.on("data", (chunk) => {
|
|
3879
4033
|
if (stdout.length < MAX_OUTPUT2) stdout += chunk.toString();
|
|
3880
4034
|
});
|
|
@@ -3883,6 +4037,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3883
4037
|
});
|
|
3884
4038
|
child.on("close", (code) => {
|
|
3885
4039
|
clearTimeout(timer);
|
|
4040
|
+
if (isWin) signal.removeEventListener("abort", onAbort);
|
|
3886
4041
|
if (typeof pid === "number") registry.unregister(pid);
|
|
3887
4042
|
const durationMs = Date.now() - startedAt;
|
|
3888
4043
|
const exitCode = killed ? 124 : code ?? 1;
|
|
@@ -3899,6 +4054,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3899
4054
|
});
|
|
3900
4055
|
child.on("error", (err) => {
|
|
3901
4056
|
clearTimeout(timer);
|
|
4057
|
+
if (isWin) signal.removeEventListener("abort", onAbort);
|
|
3902
4058
|
if (typeof pid === "number") registry.unregister(pid);
|
|
3903
4059
|
registry.afterCall(Date.now() - startedAt, true);
|
|
3904
4060
|
resolve7({
|
|
@@ -5020,8 +5176,6 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
5020
5176
|
used: "native"
|
|
5021
5177
|
};
|
|
5022
5178
|
}
|
|
5023
|
-
|
|
5024
|
-
// src/install.ts
|
|
5025
5179
|
var installTool = {
|
|
5026
5180
|
name: "install",
|
|
5027
5181
|
category: "Package Management",
|
|
@@ -5116,18 +5270,48 @@ var installTool = {
|
|
|
5116
5270
|
signal: opts.signal,
|
|
5117
5271
|
maxBytes: 1e5
|
|
5118
5272
|
});
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
dry_run: args.includes("--dry-run"),
|
|
5126
|
-
truncated: result.truncated
|
|
5127
|
-
}
|
|
5273
|
+
const output = {
|
|
5274
|
+
packages: pkgList,
|
|
5275
|
+
exit_code: result.exitCode,
|
|
5276
|
+
output: normalizeCommandOutput(result.stdout || result.stderr || result.error || ""),
|
|
5277
|
+
dry_run: args.includes("--dry-run"),
|
|
5278
|
+
truncated: result.truncated
|
|
5128
5279
|
};
|
|
5280
|
+
const isSuccess = result.exitCode === 0 && !output.dry_run && !input.global;
|
|
5281
|
+
if (isSuccess && pkgList.length > 0) {
|
|
5282
|
+
const trackerOpts = ctx.meta?.["packageTrackerOpts"];
|
|
5283
|
+
if (trackerOpts) {
|
|
5284
|
+
const manifestPath = resolveManifestPath(cwd, pkgManager);
|
|
5285
|
+
for (const pkg of pkgList) {
|
|
5286
|
+
try {
|
|
5287
|
+
await recordPackageAction(trackerOpts, {
|
|
5288
|
+
manifestPath,
|
|
5289
|
+
packageName: pkg,
|
|
5290
|
+
versionSpec: "latest",
|
|
5291
|
+
// exact version resolved by package manager at install time
|
|
5292
|
+
ecosystem: detectPackageEcosystem(manifestPath),
|
|
5293
|
+
agentId: ctx.agentId,
|
|
5294
|
+
agentName: ctx.agentName,
|
|
5295
|
+
sessionId: ctx.session.id
|
|
5296
|
+
});
|
|
5297
|
+
} catch {
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
yield { type: "final", output };
|
|
5129
5303
|
}
|
|
5130
5304
|
};
|
|
5305
|
+
function resolveManifestPath(cwd, pkgManager) {
|
|
5306
|
+
switch (pkgManager) {
|
|
5307
|
+
case "pnpm":
|
|
5308
|
+
case "yarn":
|
|
5309
|
+
case "npm":
|
|
5310
|
+
return join(cwd, "package.json");
|
|
5311
|
+
default:
|
|
5312
|
+
return join(cwd, "package.json");
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5131
5315
|
var jsonTool = {
|
|
5132
5316
|
name: "json",
|
|
5133
5317
|
category: "Data",
|
|
@@ -5757,7 +5941,7 @@ var planTool = {
|
|
|
5757
5941
|
name: "plan",
|
|
5758
5942
|
category: "Session",
|
|
5759
5943
|
description: "Manage a persistent strategic plan for the current session. Unlike todos, plans are meant for higher-level, multi-phase approaches and survive across conversation resumptions. Use this to outline big-picture work, then promote concrete items into the todo list when ready to execute.",
|
|
5760
|
-
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- 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 or even multiple sessions.',
|
|
5944
|
+
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 or even multiple sessions.',
|
|
5761
5945
|
permission: "confirm",
|
|
5762
5946
|
mutating: true,
|
|
5763
5947
|
capabilities: ["fs.write"],
|
|
@@ -5774,9 +5958,9 @@ var planTool = {
|
|
|
5774
5958
|
"done",
|
|
5775
5959
|
"remove",
|
|
5776
5960
|
"promote",
|
|
5777
|
-
"derive",
|
|
5778
5961
|
"template_use",
|
|
5779
|
-
"clear"
|
|
5962
|
+
"clear",
|
|
5963
|
+
"taskify"
|
|
5780
5964
|
],
|
|
5781
5965
|
description: "The operation to perform on the plan board."
|
|
5782
5966
|
},
|
|
@@ -5795,7 +5979,7 @@ var planTool = {
|
|
|
5795
5979
|
subtasks: {
|
|
5796
5980
|
type: "array",
|
|
5797
5981
|
items: { type: "string" },
|
|
5798
|
-
description: "List of subtask titles. Used with promote
|
|
5982
|
+
description: "List of subtask titles. Used with promote to break a plan item into multiple todos."
|
|
5799
5983
|
},
|
|
5800
5984
|
template: {
|
|
5801
5985
|
type: "string",
|
|
@@ -5816,92 +6000,151 @@ var planTool = {
|
|
|
5816
6000
|
};
|
|
5817
6001
|
}
|
|
5818
6002
|
const sessionId = ctx.session?.id ?? "unknown";
|
|
5819
|
-
let
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
if (!input.target) {
|
|
5835
|
-
return mkResult(plan, false, `${input.action} requires \`target\` (id|index|substring).`);
|
|
6003
|
+
let early = null;
|
|
6004
|
+
const taskifyMeta = { title: "", details: "" };
|
|
6005
|
+
let didTaskify = false;
|
|
6006
|
+
const plan = await mutatePlan(planPath, sessionId, async (p) => {
|
|
6007
|
+
switch (input.action) {
|
|
6008
|
+
case "show":
|
|
6009
|
+
break;
|
|
6010
|
+
case "add": {
|
|
6011
|
+
const title = input.title?.trim();
|
|
6012
|
+
if (!title) {
|
|
6013
|
+
early = mkResult(p, false, "add requires `title`.");
|
|
6014
|
+
return p;
|
|
6015
|
+
}
|
|
6016
|
+
const { plan: updated } = addPlanItem(p, title, input.details?.trim() || void 0);
|
|
6017
|
+
return updated;
|
|
5836
6018
|
}
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
input.target
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
6019
|
+
case "start":
|
|
6020
|
+
case "done": {
|
|
6021
|
+
if (!input.target) {
|
|
6022
|
+
early = mkResult(p, false, `${input.action} requires \`target\` (id|index|substring).`);
|
|
6023
|
+
return p;
|
|
6024
|
+
}
|
|
6025
|
+
const next = setPlanItemStatus(
|
|
6026
|
+
p,
|
|
6027
|
+
input.target,
|
|
6028
|
+
input.action === "start" ? "in_progress" : "done"
|
|
6029
|
+
);
|
|
6030
|
+
if (next === p) {
|
|
6031
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
6032
|
+
return p;
|
|
6033
|
+
}
|
|
6034
|
+
return next;
|
|
5844
6035
|
}
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
6036
|
+
case "remove": {
|
|
6037
|
+
if (!input.target) {
|
|
6038
|
+
early = mkResult(p, false, "remove requires `target` (id|index|substring).");
|
|
6039
|
+
return p;
|
|
6040
|
+
}
|
|
6041
|
+
const next = removePlanItem(p, input.target);
|
|
6042
|
+
if (next === p) {
|
|
6043
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
6044
|
+
return p;
|
|
6045
|
+
}
|
|
6046
|
+
return next;
|
|
5852
6047
|
}
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
6048
|
+
case "promote": {
|
|
6049
|
+
if (!input.target) {
|
|
6050
|
+
early = mkResult(p, false, `${input.action} requires \`target\` (id|index|substring).`);
|
|
6051
|
+
return p;
|
|
6052
|
+
}
|
|
6053
|
+
const derived = deriveTodosFromPlanItem(p, input.target, input.subtasks);
|
|
6054
|
+
if (!derived) {
|
|
6055
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
6056
|
+
return p;
|
|
6057
|
+
}
|
|
6058
|
+
ctx.state.replaceTodos(derived.todos);
|
|
6059
|
+
early = mkResult(
|
|
6060
|
+
derived.plan,
|
|
6061
|
+
true,
|
|
6062
|
+
`${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`,
|
|
6063
|
+
derived.todos
|
|
6064
|
+
);
|
|
6065
|
+
return derived.plan;
|
|
5856
6066
|
}
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
6067
|
+
case "template_use": {
|
|
6068
|
+
const templateName = input.template?.trim();
|
|
6069
|
+
if (!templateName) {
|
|
6070
|
+
early = mkResult(p, false, "template_use requires `template` name.");
|
|
6071
|
+
return p;
|
|
6072
|
+
}
|
|
6073
|
+
const template = getPlanTemplate(templateName);
|
|
6074
|
+
if (!template) {
|
|
6075
|
+
early = mkResult(p, false, `Unknown template "${templateName}".`);
|
|
6076
|
+
return p;
|
|
6077
|
+
}
|
|
6078
|
+
let updated = p;
|
|
6079
|
+
for (const item of template.items) {
|
|
6080
|
+
({ plan: updated } = addPlanItem(updated, item.title, item.details));
|
|
6081
|
+
}
|
|
6082
|
+
early = mkResult(
|
|
6083
|
+
updated,
|
|
6084
|
+
true,
|
|
6085
|
+
`Applied template "${template.name}" \u2014 ${template.items.length} items added.`
|
|
6086
|
+
);
|
|
6087
|
+
return updated;
|
|
5865
6088
|
}
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
6089
|
+
case "clear":
|
|
6090
|
+
return clearPlan(p);
|
|
6091
|
+
case "taskify": {
|
|
6092
|
+
if (!input.target) {
|
|
6093
|
+
early = mkResult(p, false, "taskify requires `target` (plan item id|index|substring).");
|
|
6094
|
+
return p;
|
|
6095
|
+
}
|
|
6096
|
+
let itemIdx = -1;
|
|
6097
|
+
const asNum = Number.parseInt(input.target, 10);
|
|
6098
|
+
if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= p.items.length) {
|
|
6099
|
+
itemIdx = asNum - 1;
|
|
6100
|
+
} else {
|
|
6101
|
+
itemIdx = p.items.findIndex((it) => it.id === input.target);
|
|
6102
|
+
if (itemIdx === -1) {
|
|
6103
|
+
const lower = input.target.toLowerCase();
|
|
6104
|
+
itemIdx = p.items.findIndex((it) => it.title.toLowerCase().includes(lower));
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
if (itemIdx === -1 || !p.items[itemIdx]) {
|
|
6108
|
+
early = mkResult(p, false, `No plan item matched "${input.target}".`);
|
|
6109
|
+
return p;
|
|
6110
|
+
}
|
|
6111
|
+
const item = p.items[itemIdx];
|
|
6112
|
+
taskifyMeta.title = item.title;
|
|
6113
|
+
taskifyMeta.details = item.details ?? "";
|
|
6114
|
+
didTaskify = true;
|
|
6115
|
+
break;
|
|
5869
6116
|
}
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
return mkResult(
|
|
5874
|
-
plan,
|
|
5875
|
-
true,
|
|
5876
|
-
`${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`,
|
|
5877
|
-
derived.todos
|
|
5878
|
-
);
|
|
6117
|
+
default:
|
|
6118
|
+
early = mkResult(p, false, `Unknown action "${input.action}".`);
|
|
6119
|
+
return p;
|
|
5879
6120
|
}
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
return mkResult(plan, false, `Unknown template "${templateName}".`);
|
|
5888
|
-
}
|
|
5889
|
-
for (const item of template.items) {
|
|
5890
|
-
({ plan } = addPlanItem(plan, item.title, item.details));
|
|
5891
|
-
}
|
|
5892
|
-
await savePlan(planPath, plan);
|
|
5893
|
-
return mkResult(
|
|
5894
|
-
plan,
|
|
5895
|
-
true,
|
|
5896
|
-
`Applied template "${template.name}" \u2014 ${template.items.length} items added.`
|
|
5897
|
-
);
|
|
6121
|
+
return p;
|
|
6122
|
+
});
|
|
6123
|
+
if (early) return early;
|
|
6124
|
+
if (didTaskify) {
|
|
6125
|
+
const taskPath = ctx.meta["task.path"];
|
|
6126
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
6127
|
+
return mkResult(plan, false, "Task storage path not configured \u2014 cannot taskify.");
|
|
5898
6128
|
}
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
6129
|
+
const taskFile = await loadTasks(taskPath) ?? emptyTaskFile(sessionId);
|
|
6130
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6131
|
+
taskFile.tasks.push({
|
|
6132
|
+
id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
6133
|
+
title: taskifyMeta.title,
|
|
6134
|
+
description: taskifyMeta.details || void 0,
|
|
6135
|
+
type: "feature",
|
|
6136
|
+
priority: "medium",
|
|
6137
|
+
status: "pending",
|
|
6138
|
+
createdAt: now,
|
|
6139
|
+
updatedAt: now
|
|
6140
|
+
});
|
|
6141
|
+
await saveTasks(taskPath, taskFile);
|
|
6142
|
+
return mkResult(
|
|
6143
|
+
plan,
|
|
6144
|
+
true,
|
|
6145
|
+
`taskify ok \u2014 added "${taskifyMeta.title}" to tasks.
|
|
6146
|
+
${formatTaskList(taskFile.tasks)}`
|
|
6147
|
+
);
|
|
5905
6148
|
}
|
|
5906
6149
|
return mkResult(plan, true, `Plan ${input.action} ok.`);
|
|
5907
6150
|
}
|
|
@@ -6447,13 +6690,24 @@ var searchTool = {
|
|
|
6447
6690
|
async function duckduckgoSearch(query2, num, signal) {
|
|
6448
6691
|
const encoded = encodeURIComponent(query2);
|
|
6449
6692
|
const url = `https://lite.duckduckgo.com/lite/?q=${encoded}&kd=-1&kl=wt-wt`;
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
results,
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6693
|
+
try {
|
|
6694
|
+
const response = await fetchWithTimeout(url, signal, TIMEOUT_MS3);
|
|
6695
|
+
const html = await response.text();
|
|
6696
|
+
const results = parseDuckDuckGo(html, num);
|
|
6697
|
+
return {
|
|
6698
|
+
query: query2,
|
|
6699
|
+
results,
|
|
6700
|
+
source: "duckduckgo",
|
|
6701
|
+
truncated: results.length >= num
|
|
6702
|
+
};
|
|
6703
|
+
} catch {
|
|
6704
|
+
return {
|
|
6705
|
+
query: query2,
|
|
6706
|
+
results: [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }],
|
|
6707
|
+
source: "duckduckgo",
|
|
6708
|
+
truncated: false
|
|
6709
|
+
};
|
|
6710
|
+
}
|
|
6457
6711
|
}
|
|
6458
6712
|
function takeFrom(iter, max) {
|
|
6459
6713
|
const out = [];
|
|
@@ -6572,34 +6826,92 @@ async function fetchWithTimeout(url, signal, timeoutMs) {
|
|
|
6572
6826
|
}
|
|
6573
6827
|
}
|
|
6574
6828
|
function anySignal(...signals) {
|
|
6575
|
-
|
|
6576
|
-
for (const s of signals) {
|
|
6577
|
-
if (s.aborted) {
|
|
6578
|
-
controller.abort();
|
|
6579
|
-
break;
|
|
6580
|
-
}
|
|
6581
|
-
s.addEventListener("abort", () => controller.abort());
|
|
6582
|
-
}
|
|
6583
|
-
return controller.signal;
|
|
6829
|
+
return AbortSignal.any(signals);
|
|
6584
6830
|
}
|
|
6585
6831
|
function stripTags2(html) {
|
|
6586
6832
|
return html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").trim();
|
|
6587
6833
|
}
|
|
6834
|
+
var setWorkingDirTool = {
|
|
6835
|
+
name: "set_working_dir",
|
|
6836
|
+
category: "Context",
|
|
6837
|
+
description: "Change the current working directory for all subsequent file operations. The new directory must be inside the project root. Use this to navigate between subdirectories when working on files in different parts of the project.",
|
|
6838
|
+
usageHint: "Change the working directory so relative paths in subsequent tool calls resolve from a different directory. Pass `path` to set a new directory, or omit to query the current one. The directory must exist and be inside the project root.",
|
|
6839
|
+
permission: "confirm",
|
|
6840
|
+
mutating: true,
|
|
6841
|
+
capabilities: ["fs.read"],
|
|
6842
|
+
timeoutMs: 5e3,
|
|
6843
|
+
inputSchema: {
|
|
6844
|
+
type: "object",
|
|
6845
|
+
properties: {
|
|
6846
|
+
path: {
|
|
6847
|
+
type: "string",
|
|
6848
|
+
description: "Directory to navigate to. Can be relative (to projectRoot) or absolute. If omitted, returns the current working directory without changing it."
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
},
|
|
6852
|
+
async execute(input, ctx, _opts) {
|
|
6853
|
+
if (!input.path) {
|
|
6854
|
+
return {
|
|
6855
|
+
current: ctx.workingDir,
|
|
6856
|
+
message: `Current working directory is ${ctx.workingDir}`
|
|
6857
|
+
};
|
|
6858
|
+
}
|
|
6859
|
+
const previous = ctx.workingDir;
|
|
6860
|
+
let resolved;
|
|
6861
|
+
try {
|
|
6862
|
+
resolved = ctx.setWorkingDir(input.path);
|
|
6863
|
+
} catch (err) {
|
|
6864
|
+
return {
|
|
6865
|
+
current: ctx.workingDir,
|
|
6866
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6867
|
+
};
|
|
6868
|
+
}
|
|
6869
|
+
try {
|
|
6870
|
+
await fs13.access(resolved);
|
|
6871
|
+
} catch {
|
|
6872
|
+
try {
|
|
6873
|
+
ctx.setWorkingDir(previous);
|
|
6874
|
+
} catch {
|
|
6875
|
+
}
|
|
6876
|
+
return {
|
|
6877
|
+
current: ctx.workingDir,
|
|
6878
|
+
error: `Directory does not exist: ${resolved}`
|
|
6879
|
+
};
|
|
6880
|
+
}
|
|
6881
|
+
return {
|
|
6882
|
+
current: resolved,
|
|
6883
|
+
previous,
|
|
6884
|
+
message: `Working directory changed to ${resolved}`
|
|
6885
|
+
};
|
|
6886
|
+
}
|
|
6887
|
+
};
|
|
6888
|
+
function findTaskIndex(tasks, query2) {
|
|
6889
|
+
const asNum = Number.parseInt(query2, 10);
|
|
6890
|
+
if (!Number.isNaN(asNum)) {
|
|
6891
|
+
const idx = asNum - 1;
|
|
6892
|
+
if (tasks[idx]) return idx;
|
|
6893
|
+
}
|
|
6894
|
+
const byId = tasks.findIndex((t) => t.id === query2);
|
|
6895
|
+
if (byId >= 0) return byId;
|
|
6896
|
+
const lower = query2.toLowerCase();
|
|
6897
|
+
return tasks.findIndex((t) => t.title.toLowerCase().includes(lower));
|
|
6898
|
+
}
|
|
6588
6899
|
var taskTool = {
|
|
6589
6900
|
name: "task",
|
|
6590
6901
|
category: "Session",
|
|
6591
6902
|
description: "Manage structured work items with dependencies, types, and priorities. Use this for complex, multi-step work where tasks have ordering constraints. Unlike `todo` (flat, tactical), `task` supports typed work (feature/bugfix/refactor/etc.), dependencies between items, priority ranking, and agent assignment. The task list persists across session resumes.",
|
|
6592
|
-
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\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',
|
|
6593
|
-
permission: "
|
|
6594
|
-
mutating:
|
|
6903
|
+
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',
|
|
6904
|
+
permission: "confirm",
|
|
6905
|
+
mutating: true,
|
|
6906
|
+
capabilities: ["fs.write"],
|
|
6595
6907
|
timeoutMs: 2e3,
|
|
6596
6908
|
inputSchema: {
|
|
6597
6909
|
type: "object",
|
|
6598
6910
|
properties: {
|
|
6599
6911
|
action: {
|
|
6600
6912
|
type: "string",
|
|
6601
|
-
enum: ["replace", "add", "status", "show"],
|
|
6602
|
-
description: "replace = set full list, add = append, status = update task status, show = view only."
|
|
6913
|
+
enum: ["replace", "add", "status", "show", "promote", "planify"],
|
|
6914
|
+
description: "replace = set full list, add = append, status = update task status, show = view only, promote = convert task to todos, planify = convert task to plan item."
|
|
6603
6915
|
},
|
|
6604
6916
|
tasks: {
|
|
6605
6917
|
type: "array",
|
|
@@ -6643,11 +6955,20 @@ var taskTool = {
|
|
|
6643
6955
|
required: ["title", "type", "priority"],
|
|
6644
6956
|
description: "Single task to append (id/createdAt/updatedAt auto-generated)."
|
|
6645
6957
|
},
|
|
6646
|
-
id: { type: "string", description: "Task id for action=status." },
|
|
6958
|
+
id: { type: "string", description: "Task id for action=status or target for action=promote." },
|
|
6647
6959
|
status: {
|
|
6648
6960
|
type: "string",
|
|
6649
6961
|
enum: ["pending", "in_progress", "blocked", "failed", "review", "completed"],
|
|
6650
6962
|
description: "New status for action=status."
|
|
6963
|
+
},
|
|
6964
|
+
target: {
|
|
6965
|
+
type: "string",
|
|
6966
|
+
description: "Target task identifier (id, 1-based index, or title substring) for action=promote."
|
|
6967
|
+
},
|
|
6968
|
+
subtasks: {
|
|
6969
|
+
type: "array",
|
|
6970
|
+
items: { type: "string" },
|
|
6971
|
+
description: "Optional subtask titles for action=promote. Each becomes a pending todo."
|
|
6651
6972
|
}
|
|
6652
6973
|
},
|
|
6653
6974
|
required: ["action"]
|
|
@@ -6658,65 +6979,196 @@ var taskTool = {
|
|
|
6658
6979
|
return { ok: false, message: "Task storage path not configured.", count: 0, completed: 0, inProgress: 0 };
|
|
6659
6980
|
}
|
|
6660
6981
|
const sessionId = ctx.session?.id ?? "unknown";
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6982
|
+
let early = null;
|
|
6983
|
+
const promoteMeta = { count: 0, title: "" };
|
|
6984
|
+
const planifyMeta = { title: "", details: "" };
|
|
6985
|
+
let didPlanify = false;
|
|
6986
|
+
const file = await mutateTasks(taskPath, sessionId, async (f) => {
|
|
6987
|
+
switch (input.action) {
|
|
6988
|
+
case "show":
|
|
6989
|
+
break;
|
|
6990
|
+
case "replace": {
|
|
6991
|
+
if (!Array.isArray(input.tasks)) {
|
|
6992
|
+
early = { ok: false, message: "action=replace requires `tasks` array.", count: 0, completed: 0, inProgress: 0 };
|
|
6993
|
+
return f;
|
|
6994
|
+
}
|
|
6995
|
+
const newIds = new Set(input.tasks.map((t) => t.id));
|
|
6996
|
+
for (const t of input.tasks) {
|
|
6997
|
+
if (t.dependsOn && t.dependsOn.length > 0) {
|
|
6998
|
+
const missing = t.dependsOn.filter((d) => !newIds.has(d));
|
|
6999
|
+
if (missing.length > 0) {
|
|
7000
|
+
early = {
|
|
7001
|
+
ok: false,
|
|
7002
|
+
message: `dependsOn validation failed: task "${t.id}" references unknown IDs: ${missing.join(", ")}`,
|
|
7003
|
+
count: 0,
|
|
7004
|
+
completed: 0,
|
|
7005
|
+
inProgress: 0
|
|
7006
|
+
};
|
|
7007
|
+
return f;
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
7010
|
+
}
|
|
7011
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7012
|
+
f.tasks = input.tasks.map((t) => ({
|
|
7013
|
+
...t,
|
|
7014
|
+
createdAt: t.createdAt || now,
|
|
7015
|
+
updatedAt: now
|
|
7016
|
+
}));
|
|
7017
|
+
break;
|
|
6668
7018
|
}
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
7019
|
+
case "add": {
|
|
7020
|
+
const t = input.task;
|
|
7021
|
+
if (!t || !t.title) {
|
|
7022
|
+
early = { ok: false, message: "action=add requires `task` with at least `title`.", count: 0, completed: 0, inProgress: 0 };
|
|
7023
|
+
return f;
|
|
7024
|
+
}
|
|
7025
|
+
if (t.dependsOn && t.dependsOn.length > 0) {
|
|
7026
|
+
const existingIds = new Set(f.tasks.map((e) => e.id));
|
|
7027
|
+
const missing = t.dependsOn.filter((d) => !existingIds.has(d));
|
|
7028
|
+
if (missing.length > 0) {
|
|
7029
|
+
early = {
|
|
7030
|
+
ok: false,
|
|
7031
|
+
message: `dependsOn validation failed: unknown task IDs: ${missing.join(", ")}`,
|
|
7032
|
+
count: 0,
|
|
7033
|
+
completed: 0,
|
|
7034
|
+
inProgress: 0
|
|
7035
|
+
};
|
|
7036
|
+
return f;
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7040
|
+
const newTask = {
|
|
7041
|
+
id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
7042
|
+
title: t.title,
|
|
7043
|
+
description: t.description,
|
|
7044
|
+
type: t.type || "feature",
|
|
7045
|
+
priority: t.priority || "medium",
|
|
7046
|
+
status: t.status || "pending",
|
|
7047
|
+
dependsOn: t.dependsOn,
|
|
7048
|
+
assignee: t.assignee,
|
|
7049
|
+
estimateHours: t.estimateHours,
|
|
7050
|
+
tags: t.tags,
|
|
7051
|
+
createdAt: now,
|
|
7052
|
+
updatedAt: now
|
|
7053
|
+
};
|
|
7054
|
+
f.tasks.push(newTask);
|
|
7055
|
+
break;
|
|
6682
7056
|
}
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
updatedAt: now
|
|
6697
|
-
};
|
|
6698
|
-
file.tasks.push(newTask);
|
|
6699
|
-
await saveTasks(taskPath, file);
|
|
6700
|
-
break;
|
|
6701
|
-
}
|
|
6702
|
-
case "status": {
|
|
6703
|
-
if (!input.id || !input.status) {
|
|
6704
|
-
return { ok: false, message: "action=status requires `id` and `status`.", count: 0, completed: 0, inProgress: 0 };
|
|
7057
|
+
case "status": {
|
|
7058
|
+
if (!input.id || !input.status) {
|
|
7059
|
+
early = { ok: false, message: "action=status requires `id` and `status`.", count: 0, completed: 0, inProgress: 0 };
|
|
7060
|
+
return f;
|
|
7061
|
+
}
|
|
7062
|
+
const task = f.tasks.find((t) => t.id === input.id);
|
|
7063
|
+
if (!task) {
|
|
7064
|
+
early = { ok: false, message: `Task "${input.id}" not found.`, count: 0, completed: 0, inProgress: 0 };
|
|
7065
|
+
return f;
|
|
7066
|
+
}
|
|
7067
|
+
task.status = input.status;
|
|
7068
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7069
|
+
break;
|
|
6705
7070
|
}
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
7071
|
+
case "promote": {
|
|
7072
|
+
const target = input.target?.trim();
|
|
7073
|
+
if (!target) {
|
|
7074
|
+
early = { ok: false, message: "action=promote requires `target` (task id, index, or title substring).", count: 0, completed: 0, inProgress: 0 };
|
|
7075
|
+
return f;
|
|
7076
|
+
}
|
|
7077
|
+
const idx = findTaskIndex(f.tasks, target);
|
|
7078
|
+
if (idx === -1) {
|
|
7079
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
7080
|
+
return f;
|
|
7081
|
+
}
|
|
7082
|
+
const match = f.tasks[idx];
|
|
7083
|
+
if (!match) {
|
|
7084
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
7085
|
+
return f;
|
|
7086
|
+
}
|
|
7087
|
+
if (match.status !== "completed" && match.status !== "failed") {
|
|
7088
|
+
match.status = "in_progress";
|
|
7089
|
+
match.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7090
|
+
}
|
|
7091
|
+
const todos = [];
|
|
7092
|
+
const ts2 = Date.now();
|
|
7093
|
+
todos.push({
|
|
7094
|
+
id: `todo_${ts2}_task`,
|
|
7095
|
+
content: match.title,
|
|
7096
|
+
status: "in_progress",
|
|
7097
|
+
activeForm: match.title,
|
|
7098
|
+
promotedFromTask: match.id
|
|
7099
|
+
});
|
|
7100
|
+
if (match.description) {
|
|
7101
|
+
todos.push({
|
|
7102
|
+
id: `todo_${ts2}_${randomUUID().slice(0, 6)}`,
|
|
7103
|
+
content: match.description.slice(0, 200),
|
|
7104
|
+
status: "pending",
|
|
7105
|
+
promotedFromTask: match.id
|
|
7106
|
+
});
|
|
7107
|
+
}
|
|
7108
|
+
if (input.subtasks && input.subtasks.length > 0) {
|
|
7109
|
+
for (const st of input.subtasks) {
|
|
7110
|
+
todos.push({
|
|
7111
|
+
id: `todo_${ts2}_${randomUUID().slice(0, 6)}`,
|
|
7112
|
+
content: st,
|
|
7113
|
+
status: "pending",
|
|
7114
|
+
promotedFromTask: match.id
|
|
7115
|
+
});
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
ctx.state.replaceTodos(todos);
|
|
7119
|
+
promoteMeta.count = todos.length;
|
|
7120
|
+
promoteMeta.title = match.title;
|
|
7121
|
+
break;
|
|
6709
7122
|
}
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
7123
|
+
case "planify": {
|
|
7124
|
+
const target = input.target?.trim();
|
|
7125
|
+
if (!target) {
|
|
7126
|
+
early = { ok: false, message: "action=planify requires `target` (task id, index, or title substring).", count: 0, completed: 0, inProgress: 0 };
|
|
7127
|
+
return f;
|
|
7128
|
+
}
|
|
7129
|
+
const idx = findTaskIndex(f.tasks, target);
|
|
7130
|
+
if (idx === -1) {
|
|
7131
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
7132
|
+
return f;
|
|
7133
|
+
}
|
|
7134
|
+
const match = f.tasks[idx];
|
|
7135
|
+
if (!match) {
|
|
7136
|
+
early = { ok: false, message: `No task matched "${target}".`, count: 0, completed: 0, inProgress: 0 };
|
|
7137
|
+
return f;
|
|
7138
|
+
}
|
|
7139
|
+
planifyMeta.title = match.title;
|
|
7140
|
+
planifyMeta.details = match.description ?? "";
|
|
7141
|
+
didPlanify = true;
|
|
7142
|
+
break;
|
|
7143
|
+
}
|
|
7144
|
+
default:
|
|
7145
|
+
early = { ok: false, message: `Unknown action "${input.action}". Use replace | add | status | show | promote | planify.`, count: 0, completed: 0, inProgress: 0 };
|
|
7146
|
+
return f;
|
|
6714
7147
|
}
|
|
6715
|
-
|
|
6716
|
-
|
|
7148
|
+
return f;
|
|
7149
|
+
});
|
|
7150
|
+
if (early) return early;
|
|
7151
|
+
if (didPlanify) {
|
|
7152
|
+
const { title, details } = planifyMeta;
|
|
7153
|
+
const planPath = ctx.meta["plan.path"];
|
|
7154
|
+
if (typeof planPath === "string" && planPath) {
|
|
7155
|
+
const planCfg = await loadPlan(planPath) ?? emptyPlan(sessionId);
|
|
7156
|
+
const { plan: updated } = addPlanItem(planCfg, title, details || void 0);
|
|
7157
|
+
await savePlan(planPath, updated);
|
|
7158
|
+
return {
|
|
7159
|
+
ok: true,
|
|
7160
|
+
message: `planify ok \u2014 added "${title}" to plan.
|
|
7161
|
+
${formatPlan(updated)}`,
|
|
7162
|
+
count: file.tasks.length,
|
|
7163
|
+
completed: computeTaskItemProgress(file.tasks).completed,
|
|
7164
|
+
inProgress: computeTaskItemProgress(file.tasks).inProgress
|
|
7165
|
+
};
|
|
7166
|
+
}
|
|
7167
|
+
return { ok: false, message: "Plan storage path not configured \u2014 cannot planify.", count: 0, completed: 0, inProgress: 0 };
|
|
6717
7168
|
}
|
|
6718
7169
|
const p = computeTaskItemProgress(file.tasks);
|
|
6719
|
-
const summary =
|
|
7170
|
+
const summary = promoteMeta.count > 0 ? `promote ok \u2014 ${promoteMeta.count} todo(s) created from "${promoteMeta.title}".
|
|
7171
|
+
${formatTaskList(file.tasks)}` : file.tasks.length > 0 ? formatTaskList(file.tasks) : "No tasks.";
|
|
6720
7172
|
return {
|
|
6721
7173
|
ok: true,
|
|
6722
7174
|
message: summary,
|
|
@@ -6874,8 +7326,6 @@ function parseResult(runner, result, duration) {
|
|
|
6874
7326
|
truncated: result.truncated
|
|
6875
7327
|
};
|
|
6876
7328
|
}
|
|
6877
|
-
|
|
6878
|
-
// src/todo.ts
|
|
6879
7329
|
var todoTool = {
|
|
6880
7330
|
name: "todo",
|
|
6881
7331
|
category: "Session",
|
|
@@ -6934,6 +7384,48 @@ var todoTool = {
|
|
|
6934
7384
|
}
|
|
6935
7385
|
}
|
|
6936
7386
|
ctx.state.replaceTodos(items);
|
|
7387
|
+
const completedPlanIds = /* @__PURE__ */ new Set();
|
|
7388
|
+
const completedTaskIds = /* @__PURE__ */ new Set();
|
|
7389
|
+
const pendingPlanIds = /* @__PURE__ */ new Set();
|
|
7390
|
+
const pendingTaskIds = /* @__PURE__ */ new Set();
|
|
7391
|
+
for (const item of items) {
|
|
7392
|
+
if (item.promotedFromPlan) {
|
|
7393
|
+
(item.status === "completed" ? completedPlanIds : pendingPlanIds).add(item.promotedFromPlan);
|
|
7394
|
+
}
|
|
7395
|
+
if (item.promotedFromTask) {
|
|
7396
|
+
(item.status === "completed" ? completedTaskIds : pendingTaskIds).add(item.promotedFromTask);
|
|
7397
|
+
}
|
|
7398
|
+
}
|
|
7399
|
+
for (const planId of completedPlanIds) {
|
|
7400
|
+
if (pendingPlanIds.has(planId)) continue;
|
|
7401
|
+
const planPath = ctx.meta["plan.path"];
|
|
7402
|
+
if (typeof planPath !== "string" || !planPath) continue;
|
|
7403
|
+
try {
|
|
7404
|
+
const plan = await loadPlan(planPath);
|
|
7405
|
+
if (plan) {
|
|
7406
|
+
const updated = setPlanItemStatus(plan, planId, "done");
|
|
7407
|
+
await savePlan(planPath, updated);
|
|
7408
|
+
}
|
|
7409
|
+
} catch {
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
for (const taskId of completedTaskIds) {
|
|
7413
|
+
if (pendingTaskIds.has(taskId)) continue;
|
|
7414
|
+
const taskPath = ctx.meta["task.path"];
|
|
7415
|
+
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
7416
|
+
try {
|
|
7417
|
+
const file = await loadTasks(taskPath);
|
|
7418
|
+
if (file) {
|
|
7419
|
+
const task = file.tasks.find((t) => t.id === taskId);
|
|
7420
|
+
if (task && task.status !== "completed") {
|
|
7421
|
+
task.status = "completed";
|
|
7422
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7423
|
+
await saveTasks(taskPath, file);
|
|
7424
|
+
}
|
|
7425
|
+
}
|
|
7426
|
+
} catch {
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
6937
7429
|
return {
|
|
6938
7430
|
count: items.length,
|
|
6939
7431
|
in_progress: items.filter((t) => t.status === "in_progress").length
|
|
@@ -7569,7 +8061,8 @@ var builtinTools = [
|
|
|
7569
8061
|
toolHelpTool,
|
|
7570
8062
|
codebaseIndexTool,
|
|
7571
8063
|
codebaseSearchTool,
|
|
7572
|
-
codebaseStatsTool
|
|
8064
|
+
codebaseStatsTool,
|
|
8065
|
+
setWorkingDirTool
|
|
7573
8066
|
];
|
|
7574
8067
|
|
|
7575
8068
|
export { builtinTools };
|