@wrongstack/tools 0.264.0 → 0.267.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 +154 -11
- 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 +796 -340
- 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 +59 -25
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +56 -25
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/diff.js +14 -7
- package/dist/diff.js.map +1 -1
- package/dist/document.js +14 -8
- package/dist/document.js.map +1 -1
- package/dist/edit.d.ts +1 -0
- package/dist/edit.js +33 -22
- package/dist/edit.js.map +1 -1
- package/dist/exec.js +140 -3
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js +1 -0
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +153 -11
- package/dist/format.js.map +1 -1
- package/dist/git.d.ts +7 -0
- package/dist/git.js +20 -2
- package/dist/git.js.map +1 -1
- package/dist/glob.js +14 -7
- package/dist/glob.js.map +1 -1
- package/dist/grep.js +14 -7
- package/dist/grep.js.map +1 -1
- package/dist/index.d.ts +55 -3
- package/dist/index.js +957 -341
- package/dist/index.js.map +1 -1
- package/dist/install.js +153 -11
- package/dist/install.js.map +1 -1
- package/dist/json.js +1 -0
- package/dist/json.js.map +1 -1
- package/dist/lint.js +153 -11
- package/dist/lint.js.map +1 -1
- package/dist/logs.js +14 -7
- 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 +21 -10
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +765 -339
- package/dist/pack.js.map +1 -1
- package/dist/patch.js +14 -7
- 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.d.ts +3 -0
- package/dist/read.js +124 -22
- package/dist/read.js.map +1 -1
- package/dist/replace.js +14 -7
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js +14 -7
- package/dist/scaffold.js.map +1 -1
- package/dist/search.js +1 -0
- package/dist/search.js.map +1 -1
- package/dist/test.js +153 -11
- 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 -7
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +153 -11
- package/dist/typecheck.js.map +1 -1
- package/dist/write.js +21 -15
- package/dist/write.js.map +1 -1
- package/package.json +6 -2
package/dist/lint.js
CHANGED
|
@@ -131,6 +131,23 @@ var CircuitBreaker = class {
|
|
|
131
131
|
lastSlowAt = null;
|
|
132
132
|
/** Timestamp when the breaker was opened (for cooldown calculation). */
|
|
133
133
|
openedAt = null;
|
|
134
|
+
/**
|
|
135
|
+
* Master enable flag. When false the breaker is bypassed: `beforeCall`
|
|
136
|
+
* always returns true and `afterCall` records nothing. The class itself
|
|
137
|
+
* defaults to enabled (so the standalone unit tests exercise tripping); the
|
|
138
|
+
* ProcessRegistry flips this off until the user opts in via `/settings`.
|
|
139
|
+
*/
|
|
140
|
+
enabled = true;
|
|
141
|
+
/**
|
|
142
|
+
* Fired (best-effort) when the breaker transitions into the `open` state.
|
|
143
|
+
* The registry uses this to arm its auto kill/reset countdown.
|
|
144
|
+
*/
|
|
145
|
+
onTrip;
|
|
146
|
+
/**
|
|
147
|
+
* Fired (best-effort) when the breaker returns to `closed` after having been
|
|
148
|
+
* open/half-open. The registry uses this to cancel a pending kill/reset.
|
|
149
|
+
*/
|
|
150
|
+
onReset;
|
|
134
151
|
constructor(config = {}) {
|
|
135
152
|
this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
|
|
136
153
|
this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;
|
|
@@ -139,12 +156,22 @@ var CircuitBreaker = class {
|
|
|
139
156
|
this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;
|
|
140
157
|
this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
141
158
|
}
|
|
159
|
+
/** Toggle the master enable. Disabling resets to a clean `closed` state. */
|
|
160
|
+
setEnabled(enabled) {
|
|
161
|
+
if (this.enabled === enabled) return;
|
|
162
|
+
this.enabled = enabled;
|
|
163
|
+
if (!enabled) this._reset();
|
|
164
|
+
}
|
|
165
|
+
get isEnabled() {
|
|
166
|
+
return this.enabled;
|
|
167
|
+
}
|
|
142
168
|
/**
|
|
143
169
|
* Returns true if the circuit allows a new call to proceed.
|
|
144
170
|
* When false, callers should abort the tool call and return a
|
|
145
171
|
* circuit-breaker error instead of spawning a process.
|
|
146
172
|
*/
|
|
147
173
|
get canProceed() {
|
|
174
|
+
if (!this.enabled) return true;
|
|
148
175
|
this._checkStateTransition();
|
|
149
176
|
return this.state !== "open";
|
|
150
177
|
}
|
|
@@ -180,7 +207,7 @@ var CircuitBreaker = class {
|
|
|
180
207
|
* not affect breaker state.
|
|
181
208
|
*/
|
|
182
209
|
beforeCall(bypass = false) {
|
|
183
|
-
if (bypass) return true;
|
|
210
|
+
if (bypass || !this.enabled) return true;
|
|
184
211
|
this._checkStateTransition();
|
|
185
212
|
if (this.state === "open") return false;
|
|
186
213
|
return true;
|
|
@@ -195,7 +222,7 @@ var CircuitBreaker = class {
|
|
|
195
222
|
* Use for background/fire-and-forget processes.
|
|
196
223
|
*/
|
|
197
224
|
afterCall(durationMs, failed, bypass = false) {
|
|
198
|
-
if (bypass) return;
|
|
225
|
+
if (bypass || !this.enabled) return;
|
|
199
226
|
const now = Date.now();
|
|
200
227
|
if (this.state === "half-open") {
|
|
201
228
|
if (failed) {
|
|
@@ -241,12 +268,23 @@ var CircuitBreaker = class {
|
|
|
241
268
|
if (this.state === "open") return;
|
|
242
269
|
this.state = "open";
|
|
243
270
|
this.openedAt = Date.now();
|
|
271
|
+
try {
|
|
272
|
+
this.onTrip?.();
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
244
275
|
}
|
|
245
276
|
_reset() {
|
|
277
|
+
const wasRecovering = this.state !== "closed";
|
|
246
278
|
this.state = "closed";
|
|
247
279
|
this.consecutiveFailures = 0;
|
|
248
280
|
this.window = [];
|
|
249
281
|
this.openedAt = null;
|
|
282
|
+
if (wasRecovering) {
|
|
283
|
+
try {
|
|
284
|
+
this.onReset?.();
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
250
288
|
}
|
|
251
289
|
/** Transition from open → half-open when cooldown elapses. */
|
|
252
290
|
_checkStateTransition() {
|
|
@@ -308,8 +346,21 @@ function killWin32Tree(pid) {
|
|
|
308
346
|
var ProcessRegistryImpl = class {
|
|
309
347
|
processes = /* @__PURE__ */ new Map();
|
|
310
348
|
breaker;
|
|
349
|
+
/**
|
|
350
|
+
* Auto kill/reset config. When the breaker trips and `autoKillResetMs > 0`,
|
|
351
|
+
* a countdown is armed; on expiry all tracked processes are killed and the
|
|
352
|
+
* breaker is reset to closed (forced recovery). Zero means manual recovery
|
|
353
|
+
* only (`/kill reset`).
|
|
354
|
+
*/
|
|
355
|
+
autoKillResetMs = 0;
|
|
356
|
+
autoKillTimer = null;
|
|
357
|
+
autoKillArmedAt = null;
|
|
358
|
+
breakerCountdownListeners = [];
|
|
311
359
|
constructor(breakerConfig) {
|
|
312
360
|
this.breaker = new CircuitBreaker(breakerConfig);
|
|
361
|
+
this.breaker.onTrip = () => this._armAutoKillReset();
|
|
362
|
+
this.breaker.onReset = () => this._cancelAutoKillReset();
|
|
363
|
+
this.breaker.setEnabled(false);
|
|
313
364
|
}
|
|
314
365
|
register(info) {
|
|
315
366
|
this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });
|
|
@@ -385,6 +436,90 @@ var ProcessRegistryImpl = class {
|
|
|
385
436
|
forceBreakerReset() {
|
|
386
437
|
this.breaker.forceReset();
|
|
387
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Configure circuit-breaker protection at runtime. Called from `/settings`
|
|
441
|
+
* (instant, all modes) and on TUI mount (applies persisted config).
|
|
442
|
+
*
|
|
443
|
+
* - `enabled` toggles whether the breaker gates `bash`/`exec`.
|
|
444
|
+
* - `autoKillResetMs` arms the auto kill/reset countdown when the breaker
|
|
445
|
+
* trips (0 = manual recovery only).
|
|
446
|
+
*
|
|
447
|
+
* Re-applies cleanly on every call: cancels a pending countdown when the
|
|
448
|
+
* timeout is cleared or protection disabled, and re-arms if the breaker is
|
|
449
|
+
* currently open under the new settings.
|
|
450
|
+
*/
|
|
451
|
+
setBreakerConfig(cfg) {
|
|
452
|
+
if (cfg.enabled !== void 0) this.breaker.setEnabled(cfg.enabled);
|
|
453
|
+
if (cfg.autoKillResetMs !== void 0) this.autoKillResetMs = Math.max(0, cfg.autoKillResetMs);
|
|
454
|
+
if (this.autoKillResetMs <= 0) {
|
|
455
|
+
this._cancelAutoKillReset();
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (this.breaker.isEnabled && this.breaker.snapshot().state === "open") {
|
|
459
|
+
this._armAutoKillReset();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Live countdown to the next auto kill/reset, or null when nothing is armed.
|
|
464
|
+
* The TUI polls this on a 1s tick while armed so the statusline decrements.
|
|
465
|
+
*/
|
|
466
|
+
getBreakerCountdown() {
|
|
467
|
+
if (this.autoKillArmedAt === null || this.autoKillResetMs <= 0) return null;
|
|
468
|
+
const elapsed = Date.now() - this.autoKillArmedAt;
|
|
469
|
+
return { remainingMs: Math.max(0, this.autoKillResetMs - elapsed), totalMs: this.autoKillResetMs };
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Subscribe to countdown arm/cancel events. Returns an unsubscribe function.
|
|
473
|
+
* Use {@link getBreakerCountdown} for the live ticking value between events.
|
|
474
|
+
*/
|
|
475
|
+
onBreakerCountdownChange(listener) {
|
|
476
|
+
this.breakerCountdownListeners.push(listener);
|
|
477
|
+
return () => {
|
|
478
|
+
this.breakerCountdownListeners = this.breakerCountdownListeners.filter((l) => l !== listener);
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
_emitBreakerCountdown() {
|
|
482
|
+
const snap = this.getBreakerCountdown();
|
|
483
|
+
for (const l of this.breakerCountdownListeners) {
|
|
484
|
+
try {
|
|
485
|
+
l(snap);
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Arm the auto kill/reset countdown. Idempotent: re-arming resets the window
|
|
492
|
+
* (a fresh trip after a failed half-open probe restarts the clock). No-op
|
|
493
|
+
* when protection is off or no timeout is configured.
|
|
494
|
+
*/
|
|
495
|
+
_armAutoKillReset() {
|
|
496
|
+
if (this.autoKillResetMs <= 0 || !this.breaker.isEnabled) return;
|
|
497
|
+
this._clearAutoKillTimer();
|
|
498
|
+
this.autoKillArmedAt = Date.now();
|
|
499
|
+
this.autoKillTimer = setTimeout(() => {
|
|
500
|
+
this.autoKillTimer = null;
|
|
501
|
+
this.autoKillArmedAt = null;
|
|
502
|
+
this.killAll({ force: false });
|
|
503
|
+
this.breaker.forceReset();
|
|
504
|
+
this._emitBreakerCountdown();
|
|
505
|
+
}, this.autoKillResetMs);
|
|
506
|
+
this.autoKillTimer.unref?.();
|
|
507
|
+
this._emitBreakerCountdown();
|
|
508
|
+
}
|
|
509
|
+
_cancelAutoKillReset() {
|
|
510
|
+
const wasArmed = this.autoKillArmedAt !== null;
|
|
511
|
+
this._clearAutoKillTimer();
|
|
512
|
+
if (wasArmed) {
|
|
513
|
+
this.autoKillArmedAt = null;
|
|
514
|
+
this._emitBreakerCountdown();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
_clearAutoKillTimer() {
|
|
518
|
+
if (this.autoKillTimer !== null) {
|
|
519
|
+
clearTimeout(this.autoKillTimer);
|
|
520
|
+
this.autoKillTimer = null;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
388
523
|
/** Kill a single process by PID.
|
|
389
524
|
*
|
|
390
525
|
* On POSIX: sends SIGTERM to the *process group* (-pid) so that
|
|
@@ -523,8 +658,9 @@ async function* spawnStream(opts) {
|
|
|
523
658
|
let pending = "";
|
|
524
659
|
let error;
|
|
525
660
|
const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
|
|
526
|
-
const
|
|
527
|
-
const needsShell = isWin && (
|
|
661
|
+
const resolved = resolveWin32Command(opts.cmd);
|
|
662
|
+
const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
663
|
+
const cmd = needsShell ? opts.cmd : resolved;
|
|
528
664
|
const child = spawn(cmd, opts.args, {
|
|
529
665
|
cwd: opts.cwd,
|
|
530
666
|
env: buildChildEnv(),
|
|
@@ -674,15 +810,20 @@ async function* spawnStream(opts) {
|
|
|
674
810
|
function resolvePath(input, ctx) {
|
|
675
811
|
return path3.isAbsolute(input) ? path3.normalize(input) : path3.resolve(ctx.workingDir ?? ctx.cwd, input);
|
|
676
812
|
}
|
|
813
|
+
function allowedRoots(ctx) {
|
|
814
|
+
return [path3.resolve(ctx.projectRoot), path3.resolve(Core.wstackGlobalRoot())];
|
|
815
|
+
}
|
|
816
|
+
function isInsideAny(target, roots) {
|
|
817
|
+
return roots.some((root) => {
|
|
818
|
+
const rel = path3.relative(root, target);
|
|
819
|
+
return rel === "" || !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
820
|
+
});
|
|
821
|
+
}
|
|
677
822
|
function ensureInsideRoot(absPath, ctx) {
|
|
678
|
-
if (ctx.allowOutsideProjectRoot) return path3.resolve(absPath);
|
|
679
|
-
const root = path3.resolve(ctx.projectRoot);
|
|
680
823
|
const target = path3.resolve(absPath);
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
return target;
|
|
824
|
+
if (ctx.allowOutsideProjectRoot) return target;
|
|
825
|
+
if (isInsideAny(target, allowedRoots(ctx))) return target;
|
|
826
|
+
throw new Error(`Path "${absPath}" is outside project root "${path3.resolve(ctx.projectRoot)}"`);
|
|
686
827
|
}
|
|
687
828
|
function safeResolve(input, ctx) {
|
|
688
829
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
@@ -768,6 +909,7 @@ var lintTool = {
|
|
|
768
909
|
mutating: false,
|
|
769
910
|
timeoutMs: 6e4,
|
|
770
911
|
capabilities: ["shell.restricted"],
|
|
912
|
+
icon: "code",
|
|
771
913
|
inputSchema: {
|
|
772
914
|
type: "object",
|
|
773
915
|
properties: {
|