@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.
Files changed (87) hide show
  1. package/dist/audit.js +154 -11
  2. package/dist/audit.js.map +1 -1
  3. package/dist/bash.js +138 -2
  4. package/dist/bash.js.map +1 -1
  5. package/dist/batch-tool-use.js +1 -0
  6. package/dist/batch-tool-use.js.map +1 -1
  7. package/dist/builtin.d.ts +20 -1
  8. package/dist/builtin.js +661 -325
  9. package/dist/builtin.js.map +1 -1
  10. package/dist/circuit-breaker.d.ts +20 -0
  11. package/dist/circuit-breaker.js +40 -2
  12. package/dist/circuit-breaker.js.map +1 -1
  13. package/dist/codebase-index/index.d.ts +16 -0
  14. package/dist/codebase-index/index.js +59 -25
  15. package/dist/codebase-index/index.js.map +1 -1
  16. package/dist/codebase-index/worker.js +56 -25
  17. package/dist/codebase-index/worker.js.map +1 -1
  18. package/dist/diff.js +14 -7
  19. package/dist/diff.js.map +1 -1
  20. package/dist/document.js +14 -8
  21. package/dist/document.js.map +1 -1
  22. package/dist/edit.js +21 -15
  23. package/dist/edit.js.map +1 -1
  24. package/dist/exec.js +140 -3
  25. package/dist/exec.js.map +1 -1
  26. package/dist/fetch.js +1 -0
  27. package/dist/fetch.js.map +1 -1
  28. package/dist/format.js +153 -11
  29. package/dist/format.js.map +1 -1
  30. package/dist/git.js +1 -0
  31. package/dist/git.js.map +1 -1
  32. package/dist/glob.js +14 -7
  33. package/dist/glob.js.map +1 -1
  34. package/dist/grep.js +14 -7
  35. package/dist/grep.js.map +1 -1
  36. package/dist/index.d.ts +55 -3
  37. package/dist/index.js +819 -325
  38. package/dist/index.js.map +1 -1
  39. package/dist/install.js +153 -11
  40. package/dist/install.js.map +1 -1
  41. package/dist/json.js +1 -0
  42. package/dist/json.js.map +1 -1
  43. package/dist/lint.js +153 -11
  44. package/dist/lint.js.map +1 -1
  45. package/dist/logs.js +14 -7
  46. package/dist/logs.js.map +1 -1
  47. package/dist/memory.js +1 -0
  48. package/dist/memory.js.map +1 -1
  49. package/dist/mode.js +1 -0
  50. package/dist/mode.js.map +1 -1
  51. package/dist/outdated.js +16 -8
  52. package/dist/outdated.js.map +1 -1
  53. package/dist/pack.js +630 -324
  54. package/dist/pack.js.map +1 -1
  55. package/dist/patch.js +14 -7
  56. package/dist/patch.js.map +1 -1
  57. package/dist/process-registry.d.ts +56 -2
  58. package/dist/process-registry.js +138 -3
  59. package/dist/process-registry.js.map +1 -1
  60. package/dist/read.js +21 -16
  61. package/dist/read.js.map +1 -1
  62. package/dist/replace.js +14 -7
  63. package/dist/replace.js.map +1 -1
  64. package/dist/scaffold.js +14 -7
  65. package/dist/scaffold.js.map +1 -1
  66. package/dist/search.js +1 -0
  67. package/dist/search.js.map +1 -1
  68. package/dist/test.js +153 -11
  69. package/dist/test.js.map +1 -1
  70. package/dist/todo.js +1 -0
  71. package/dist/todo.js.map +1 -1
  72. package/dist/tool-help.js +1 -0
  73. package/dist/tool-help.js.map +1 -1
  74. package/dist/tool-icons.d.ts +20 -0
  75. package/dist/tool-icons.js +130 -0
  76. package/dist/tool-icons.js.map +1 -0
  77. package/dist/tool-search.js +1 -0
  78. package/dist/tool-search.js.map +1 -1
  79. package/dist/tool-use.js +1 -0
  80. package/dist/tool-use.js.map +1 -1
  81. package/dist/tree.js +14 -7
  82. package/dist/tree.js.map +1 -1
  83. package/dist/typecheck.js +153 -11
  84. package/dist/typecheck.js.map +1 -1
  85. package/dist/write.js +21 -15
  86. package/dist/write.js.map +1 -1
  87. package/package.json +6 -2
package/dist/exec.js CHANGED
@@ -201,6 +201,23 @@ var CircuitBreaker = class {
201
201
  lastSlowAt = null;
202
202
  /** Timestamp when the breaker was opened (for cooldown calculation). */
203
203
  openedAt = null;
204
+ /**
205
+ * Master enable flag. When false the breaker is bypassed: `beforeCall`
206
+ * always returns true and `afterCall` records nothing. The class itself
207
+ * defaults to enabled (so the standalone unit tests exercise tripping); the
208
+ * ProcessRegistry flips this off until the user opts in via `/settings`.
209
+ */
210
+ enabled = true;
211
+ /**
212
+ * Fired (best-effort) when the breaker transitions into the `open` state.
213
+ * The registry uses this to arm its auto kill/reset countdown.
214
+ */
215
+ onTrip;
216
+ /**
217
+ * Fired (best-effort) when the breaker returns to `closed` after having been
218
+ * open/half-open. The registry uses this to cancel a pending kill/reset.
219
+ */
220
+ onReset;
204
221
  constructor(config = {}) {
205
222
  this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
206
223
  this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;
@@ -209,12 +226,22 @@ var CircuitBreaker = class {
209
226
  this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;
210
227
  this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;
211
228
  }
229
+ /** Toggle the master enable. Disabling resets to a clean `closed` state. */
230
+ setEnabled(enabled) {
231
+ if (this.enabled === enabled) return;
232
+ this.enabled = enabled;
233
+ if (!enabled) this._reset();
234
+ }
235
+ get isEnabled() {
236
+ return this.enabled;
237
+ }
212
238
  /**
213
239
  * Returns true if the circuit allows a new call to proceed.
214
240
  * When false, callers should abort the tool call and return a
215
241
  * circuit-breaker error instead of spawning a process.
216
242
  */
217
243
  get canProceed() {
244
+ if (!this.enabled) return true;
218
245
  this._checkStateTransition();
219
246
  return this.state !== "open";
220
247
  }
@@ -250,7 +277,7 @@ var CircuitBreaker = class {
250
277
  * not affect breaker state.
251
278
  */
252
279
  beforeCall(bypass = false) {
253
- if (bypass) return true;
280
+ if (bypass || !this.enabled) return true;
254
281
  this._checkStateTransition();
255
282
  if (this.state === "open") return false;
256
283
  return true;
@@ -265,7 +292,7 @@ var CircuitBreaker = class {
265
292
  * Use for background/fire-and-forget processes.
266
293
  */
267
294
  afterCall(durationMs, failed, bypass = false) {
268
- if (bypass) return;
295
+ if (bypass || !this.enabled) return;
269
296
  const now = Date.now();
270
297
  if (this.state === "half-open") {
271
298
  if (failed) {
@@ -311,12 +338,23 @@ var CircuitBreaker = class {
311
338
  if (this.state === "open") return;
312
339
  this.state = "open";
313
340
  this.openedAt = Date.now();
341
+ try {
342
+ this.onTrip?.();
343
+ } catch {
344
+ }
314
345
  }
315
346
  _reset() {
347
+ const wasRecovering = this.state !== "closed";
316
348
  this.state = "closed";
317
349
  this.consecutiveFailures = 0;
318
350
  this.window = [];
319
351
  this.openedAt = null;
352
+ if (wasRecovering) {
353
+ try {
354
+ this.onReset?.();
355
+ } catch {
356
+ }
357
+ }
320
358
  }
321
359
  /** Transition from open → half-open when cooldown elapses. */
322
360
  _checkStateTransition() {
@@ -378,8 +416,21 @@ function killWin32Tree(pid) {
378
416
  var ProcessRegistryImpl = class {
379
417
  processes = /* @__PURE__ */ new Map();
380
418
  breaker;
419
+ /**
420
+ * Auto kill/reset config. When the breaker trips and `autoKillResetMs > 0`,
421
+ * a countdown is armed; on expiry all tracked processes are killed and the
422
+ * breaker is reset to closed (forced recovery). Zero means manual recovery
423
+ * only (`/kill reset`).
424
+ */
425
+ autoKillResetMs = 0;
426
+ autoKillTimer = null;
427
+ autoKillArmedAt = null;
428
+ breakerCountdownListeners = [];
381
429
  constructor(breakerConfig) {
382
430
  this.breaker = new CircuitBreaker(breakerConfig);
431
+ this.breaker.onTrip = () => this._armAutoKillReset();
432
+ this.breaker.onReset = () => this._cancelAutoKillReset();
433
+ this.breaker.setEnabled(false);
383
434
  }
384
435
  register(info) {
385
436
  this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });
@@ -455,6 +506,90 @@ var ProcessRegistryImpl = class {
455
506
  forceBreakerReset() {
456
507
  this.breaker.forceReset();
457
508
  }
509
+ /**
510
+ * Configure circuit-breaker protection at runtime. Called from `/settings`
511
+ * (instant, all modes) and on TUI mount (applies persisted config).
512
+ *
513
+ * - `enabled` toggles whether the breaker gates `bash`/`exec`.
514
+ * - `autoKillResetMs` arms the auto kill/reset countdown when the breaker
515
+ * trips (0 = manual recovery only).
516
+ *
517
+ * Re-applies cleanly on every call: cancels a pending countdown when the
518
+ * timeout is cleared or protection disabled, and re-arms if the breaker is
519
+ * currently open under the new settings.
520
+ */
521
+ setBreakerConfig(cfg) {
522
+ if (cfg.enabled !== void 0) this.breaker.setEnabled(cfg.enabled);
523
+ if (cfg.autoKillResetMs !== void 0) this.autoKillResetMs = Math.max(0, cfg.autoKillResetMs);
524
+ if (this.autoKillResetMs <= 0) {
525
+ this._cancelAutoKillReset();
526
+ return;
527
+ }
528
+ if (this.breaker.isEnabled && this.breaker.snapshot().state === "open") {
529
+ this._armAutoKillReset();
530
+ }
531
+ }
532
+ /**
533
+ * Live countdown to the next auto kill/reset, or null when nothing is armed.
534
+ * The TUI polls this on a 1s tick while armed so the statusline decrements.
535
+ */
536
+ getBreakerCountdown() {
537
+ if (this.autoKillArmedAt === null || this.autoKillResetMs <= 0) return null;
538
+ const elapsed = Date.now() - this.autoKillArmedAt;
539
+ return { remainingMs: Math.max(0, this.autoKillResetMs - elapsed), totalMs: this.autoKillResetMs };
540
+ }
541
+ /**
542
+ * Subscribe to countdown arm/cancel events. Returns an unsubscribe function.
543
+ * Use {@link getBreakerCountdown} for the live ticking value between events.
544
+ */
545
+ onBreakerCountdownChange(listener) {
546
+ this.breakerCountdownListeners.push(listener);
547
+ return () => {
548
+ this.breakerCountdownListeners = this.breakerCountdownListeners.filter((l) => l !== listener);
549
+ };
550
+ }
551
+ _emitBreakerCountdown() {
552
+ const snap = this.getBreakerCountdown();
553
+ for (const l of this.breakerCountdownListeners) {
554
+ try {
555
+ l(snap);
556
+ } catch {
557
+ }
558
+ }
559
+ }
560
+ /**
561
+ * Arm the auto kill/reset countdown. Idempotent: re-arming resets the window
562
+ * (a fresh trip after a failed half-open probe restarts the clock). No-op
563
+ * when protection is off or no timeout is configured.
564
+ */
565
+ _armAutoKillReset() {
566
+ if (this.autoKillResetMs <= 0 || !this.breaker.isEnabled) return;
567
+ this._clearAutoKillTimer();
568
+ this.autoKillArmedAt = Date.now();
569
+ this.autoKillTimer = setTimeout(() => {
570
+ this.autoKillTimer = null;
571
+ this.autoKillArmedAt = null;
572
+ this.killAll({ force: false });
573
+ this.breaker.forceReset();
574
+ this._emitBreakerCountdown();
575
+ }, this.autoKillResetMs);
576
+ this.autoKillTimer.unref?.();
577
+ this._emitBreakerCountdown();
578
+ }
579
+ _cancelAutoKillReset() {
580
+ const wasArmed = this.autoKillArmedAt !== null;
581
+ this._clearAutoKillTimer();
582
+ if (wasArmed) {
583
+ this.autoKillArmedAt = null;
584
+ this._emitBreakerCountdown();
585
+ }
586
+ }
587
+ _clearAutoKillTimer() {
588
+ if (this.autoKillTimer !== null) {
589
+ clearTimeout(this.autoKillTimer);
590
+ this.autoKillTimer = null;
591
+ }
592
+ }
458
593
  /** Kill a single process by PID.
459
594
  *
460
595
  * On POSIX: sends SIGTERM to the *process group* (-pid) so that
@@ -694,6 +829,7 @@ var execTool = {
694
829
  riskTier: "standard",
695
830
  timeoutMs: DEFAULT_TIMEOUT_MS,
696
831
  capabilities: ["shell.restricted"],
832
+ icon: "terminal",
697
833
  inputSchema: {
698
834
  type: "object",
699
835
  properties: {
@@ -793,7 +929,8 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
793
929
  const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT });
794
930
  const resolved = resolveWin32Command(cmd);
795
931
  const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
796
- const child = spawn(resolved, args, {
932
+ const spawnCmd = needsShell ? cmd : resolved;
933
+ const child = spawn(spawnCmd, args, {
797
934
  cwd,
798
935
  env: buildChildEnv(sessionId),
799
936
  stdio: ["ignore", "pipe", "pipe"],