@wrongstack/tools 0.264.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 -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 +661 -325
- 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.js +21 -15
- 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.js +1 -0
- 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 +819 -325
- 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 +16 -8
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +630 -324
- 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.js +21 -16
- 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/install.js
CHANGED
|
@@ -132,6 +132,23 @@ var CircuitBreaker = class {
|
|
|
132
132
|
lastSlowAt = null;
|
|
133
133
|
/** Timestamp when the breaker was opened (for cooldown calculation). */
|
|
134
134
|
openedAt = null;
|
|
135
|
+
/**
|
|
136
|
+
* Master enable flag. When false the breaker is bypassed: `beforeCall`
|
|
137
|
+
* always returns true and `afterCall` records nothing. The class itself
|
|
138
|
+
* defaults to enabled (so the standalone unit tests exercise tripping); the
|
|
139
|
+
* ProcessRegistry flips this off until the user opts in via `/settings`.
|
|
140
|
+
*/
|
|
141
|
+
enabled = true;
|
|
142
|
+
/**
|
|
143
|
+
* Fired (best-effort) when the breaker transitions into the `open` state.
|
|
144
|
+
* The registry uses this to arm its auto kill/reset countdown.
|
|
145
|
+
*/
|
|
146
|
+
onTrip;
|
|
147
|
+
/**
|
|
148
|
+
* Fired (best-effort) when the breaker returns to `closed` after having been
|
|
149
|
+
* open/half-open. The registry uses this to cancel a pending kill/reset.
|
|
150
|
+
*/
|
|
151
|
+
onReset;
|
|
135
152
|
constructor(config = {}) {
|
|
136
153
|
this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
|
|
137
154
|
this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;
|
|
@@ -140,12 +157,22 @@ var CircuitBreaker = class {
|
|
|
140
157
|
this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;
|
|
141
158
|
this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
142
159
|
}
|
|
160
|
+
/** Toggle the master enable. Disabling resets to a clean `closed` state. */
|
|
161
|
+
setEnabled(enabled) {
|
|
162
|
+
if (this.enabled === enabled) return;
|
|
163
|
+
this.enabled = enabled;
|
|
164
|
+
if (!enabled) this._reset();
|
|
165
|
+
}
|
|
166
|
+
get isEnabled() {
|
|
167
|
+
return this.enabled;
|
|
168
|
+
}
|
|
143
169
|
/**
|
|
144
170
|
* Returns true if the circuit allows a new call to proceed.
|
|
145
171
|
* When false, callers should abort the tool call and return a
|
|
146
172
|
* circuit-breaker error instead of spawning a process.
|
|
147
173
|
*/
|
|
148
174
|
get canProceed() {
|
|
175
|
+
if (!this.enabled) return true;
|
|
149
176
|
this._checkStateTransition();
|
|
150
177
|
return this.state !== "open";
|
|
151
178
|
}
|
|
@@ -181,7 +208,7 @@ var CircuitBreaker = class {
|
|
|
181
208
|
* not affect breaker state.
|
|
182
209
|
*/
|
|
183
210
|
beforeCall(bypass = false) {
|
|
184
|
-
if (bypass) return true;
|
|
211
|
+
if (bypass || !this.enabled) return true;
|
|
185
212
|
this._checkStateTransition();
|
|
186
213
|
if (this.state === "open") return false;
|
|
187
214
|
return true;
|
|
@@ -196,7 +223,7 @@ var CircuitBreaker = class {
|
|
|
196
223
|
* Use for background/fire-and-forget processes.
|
|
197
224
|
*/
|
|
198
225
|
afterCall(durationMs, failed, bypass = false) {
|
|
199
|
-
if (bypass) return;
|
|
226
|
+
if (bypass || !this.enabled) return;
|
|
200
227
|
const now = Date.now();
|
|
201
228
|
if (this.state === "half-open") {
|
|
202
229
|
if (failed) {
|
|
@@ -242,12 +269,23 @@ var CircuitBreaker = class {
|
|
|
242
269
|
if (this.state === "open") return;
|
|
243
270
|
this.state = "open";
|
|
244
271
|
this.openedAt = Date.now();
|
|
272
|
+
try {
|
|
273
|
+
this.onTrip?.();
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
245
276
|
}
|
|
246
277
|
_reset() {
|
|
278
|
+
const wasRecovering = this.state !== "closed";
|
|
247
279
|
this.state = "closed";
|
|
248
280
|
this.consecutiveFailures = 0;
|
|
249
281
|
this.window = [];
|
|
250
282
|
this.openedAt = null;
|
|
283
|
+
if (wasRecovering) {
|
|
284
|
+
try {
|
|
285
|
+
this.onReset?.();
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
}
|
|
251
289
|
}
|
|
252
290
|
/** Transition from open → half-open when cooldown elapses. */
|
|
253
291
|
_checkStateTransition() {
|
|
@@ -309,8 +347,21 @@ function killWin32Tree(pid) {
|
|
|
309
347
|
var ProcessRegistryImpl = class {
|
|
310
348
|
processes = /* @__PURE__ */ new Map();
|
|
311
349
|
breaker;
|
|
350
|
+
/**
|
|
351
|
+
* Auto kill/reset config. When the breaker trips and `autoKillResetMs > 0`,
|
|
352
|
+
* a countdown is armed; on expiry all tracked processes are killed and the
|
|
353
|
+
* breaker is reset to closed (forced recovery). Zero means manual recovery
|
|
354
|
+
* only (`/kill reset`).
|
|
355
|
+
*/
|
|
356
|
+
autoKillResetMs = 0;
|
|
357
|
+
autoKillTimer = null;
|
|
358
|
+
autoKillArmedAt = null;
|
|
359
|
+
breakerCountdownListeners = [];
|
|
312
360
|
constructor(breakerConfig) {
|
|
313
361
|
this.breaker = new CircuitBreaker(breakerConfig);
|
|
362
|
+
this.breaker.onTrip = () => this._armAutoKillReset();
|
|
363
|
+
this.breaker.onReset = () => this._cancelAutoKillReset();
|
|
364
|
+
this.breaker.setEnabled(false);
|
|
314
365
|
}
|
|
315
366
|
register(info) {
|
|
316
367
|
this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });
|
|
@@ -386,6 +437,90 @@ var ProcessRegistryImpl = class {
|
|
|
386
437
|
forceBreakerReset() {
|
|
387
438
|
this.breaker.forceReset();
|
|
388
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Configure circuit-breaker protection at runtime. Called from `/settings`
|
|
442
|
+
* (instant, all modes) and on TUI mount (applies persisted config).
|
|
443
|
+
*
|
|
444
|
+
* - `enabled` toggles whether the breaker gates `bash`/`exec`.
|
|
445
|
+
* - `autoKillResetMs` arms the auto kill/reset countdown when the breaker
|
|
446
|
+
* trips (0 = manual recovery only).
|
|
447
|
+
*
|
|
448
|
+
* Re-applies cleanly on every call: cancels a pending countdown when the
|
|
449
|
+
* timeout is cleared or protection disabled, and re-arms if the breaker is
|
|
450
|
+
* currently open under the new settings.
|
|
451
|
+
*/
|
|
452
|
+
setBreakerConfig(cfg) {
|
|
453
|
+
if (cfg.enabled !== void 0) this.breaker.setEnabled(cfg.enabled);
|
|
454
|
+
if (cfg.autoKillResetMs !== void 0) this.autoKillResetMs = Math.max(0, cfg.autoKillResetMs);
|
|
455
|
+
if (this.autoKillResetMs <= 0) {
|
|
456
|
+
this._cancelAutoKillReset();
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (this.breaker.isEnabled && this.breaker.snapshot().state === "open") {
|
|
460
|
+
this._armAutoKillReset();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Live countdown to the next auto kill/reset, or null when nothing is armed.
|
|
465
|
+
* The TUI polls this on a 1s tick while armed so the statusline decrements.
|
|
466
|
+
*/
|
|
467
|
+
getBreakerCountdown() {
|
|
468
|
+
if (this.autoKillArmedAt === null || this.autoKillResetMs <= 0) return null;
|
|
469
|
+
const elapsed = Date.now() - this.autoKillArmedAt;
|
|
470
|
+
return { remainingMs: Math.max(0, this.autoKillResetMs - elapsed), totalMs: this.autoKillResetMs };
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Subscribe to countdown arm/cancel events. Returns an unsubscribe function.
|
|
474
|
+
* Use {@link getBreakerCountdown} for the live ticking value between events.
|
|
475
|
+
*/
|
|
476
|
+
onBreakerCountdownChange(listener) {
|
|
477
|
+
this.breakerCountdownListeners.push(listener);
|
|
478
|
+
return () => {
|
|
479
|
+
this.breakerCountdownListeners = this.breakerCountdownListeners.filter((l) => l !== listener);
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
_emitBreakerCountdown() {
|
|
483
|
+
const snap = this.getBreakerCountdown();
|
|
484
|
+
for (const l of this.breakerCountdownListeners) {
|
|
485
|
+
try {
|
|
486
|
+
l(snap);
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Arm the auto kill/reset countdown. Idempotent: re-arming resets the window
|
|
493
|
+
* (a fresh trip after a failed half-open probe restarts the clock). No-op
|
|
494
|
+
* when protection is off or no timeout is configured.
|
|
495
|
+
*/
|
|
496
|
+
_armAutoKillReset() {
|
|
497
|
+
if (this.autoKillResetMs <= 0 || !this.breaker.isEnabled) return;
|
|
498
|
+
this._clearAutoKillTimer();
|
|
499
|
+
this.autoKillArmedAt = Date.now();
|
|
500
|
+
this.autoKillTimer = setTimeout(() => {
|
|
501
|
+
this.autoKillTimer = null;
|
|
502
|
+
this.autoKillArmedAt = null;
|
|
503
|
+
this.killAll({ force: false });
|
|
504
|
+
this.breaker.forceReset();
|
|
505
|
+
this._emitBreakerCountdown();
|
|
506
|
+
}, this.autoKillResetMs);
|
|
507
|
+
this.autoKillTimer.unref?.();
|
|
508
|
+
this._emitBreakerCountdown();
|
|
509
|
+
}
|
|
510
|
+
_cancelAutoKillReset() {
|
|
511
|
+
const wasArmed = this.autoKillArmedAt !== null;
|
|
512
|
+
this._clearAutoKillTimer();
|
|
513
|
+
if (wasArmed) {
|
|
514
|
+
this.autoKillArmedAt = null;
|
|
515
|
+
this._emitBreakerCountdown();
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
_clearAutoKillTimer() {
|
|
519
|
+
if (this.autoKillTimer !== null) {
|
|
520
|
+
clearTimeout(this.autoKillTimer);
|
|
521
|
+
this.autoKillTimer = null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
389
524
|
/** Kill a single process by PID.
|
|
390
525
|
*
|
|
391
526
|
* On POSIX: sends SIGTERM to the *process group* (-pid) so that
|
|
@@ -524,8 +659,9 @@ async function* spawnStream(opts) {
|
|
|
524
659
|
let pending = "";
|
|
525
660
|
let error;
|
|
526
661
|
const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
|
|
527
|
-
const
|
|
528
|
-
const needsShell = isWin && (
|
|
662
|
+
const resolved = resolveWin32Command(opts.cmd);
|
|
663
|
+
const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
664
|
+
const cmd = needsShell ? opts.cmd : resolved;
|
|
529
665
|
const child = spawn(cmd, opts.args, {
|
|
530
666
|
cwd: opts.cwd,
|
|
531
667
|
env: buildChildEnv(),
|
|
@@ -689,15 +825,20 @@ async function detectPackageManager(cwd) {
|
|
|
689
825
|
function resolvePath(input, ctx) {
|
|
690
826
|
return path3.isAbsolute(input) ? path3.normalize(input) : path3.resolve(ctx.workingDir ?? ctx.cwd, input);
|
|
691
827
|
}
|
|
828
|
+
function allowedRoots(ctx) {
|
|
829
|
+
return [path3.resolve(ctx.projectRoot), path3.resolve(Core.wstackGlobalRoot())];
|
|
830
|
+
}
|
|
831
|
+
function isInsideAny(target, roots) {
|
|
832
|
+
return roots.some((root) => {
|
|
833
|
+
const rel = path3.relative(root, target);
|
|
834
|
+
return rel === "" || !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
835
|
+
});
|
|
836
|
+
}
|
|
692
837
|
function ensureInsideRoot(absPath, ctx) {
|
|
693
|
-
if (ctx.allowOutsideProjectRoot) return path3.resolve(absPath);
|
|
694
|
-
const root = path3.resolve(ctx.projectRoot);
|
|
695
838
|
const target = path3.resolve(absPath);
|
|
696
|
-
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
return target;
|
|
839
|
+
if (ctx.allowOutsideProjectRoot) return target;
|
|
840
|
+
if (isInsideAny(target, allowedRoots(ctx))) return target;
|
|
841
|
+
throw new Error(`Path "${absPath}" is outside project root "${path3.resolve(ctx.projectRoot)}"`);
|
|
701
842
|
}
|
|
702
843
|
function safeResolve(input, ctx) {
|
|
703
844
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
@@ -782,6 +923,7 @@ var installTool = {
|
|
|
782
923
|
permission: "confirm",
|
|
783
924
|
mutating: true,
|
|
784
925
|
riskTier: "standard",
|
|
926
|
+
icon: "package",
|
|
785
927
|
timeoutMs: 12e4,
|
|
786
928
|
capabilities: ["package.install", "shell.restricted"],
|
|
787
929
|
inputSchema: {
|