@wrongstack/tools 0.260.0 → 0.265.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.js +154 -10
- package/dist/audit.js.map +1 -1
- package/dist/bash.js +138 -2
- package/dist/bash.js.map +1 -1
- package/dist/batch-tool-use.js +1 -0
- package/dist/batch-tool-use.js.map +1 -1
- package/dist/builtin.d.ts +20 -1
- package/dist/builtin.js +674 -333
- package/dist/builtin.js.map +1 -1
- package/dist/circuit-breaker.d.ts +20 -0
- package/dist/circuit-breaker.js +40 -2
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/codebase-index/index.d.ts +16 -0
- package/dist/codebase-index/index.js +62 -27
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +59 -27
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/diff.js +14 -6
- package/dist/diff.js.map +1 -1
- package/dist/document.js +18 -11
- package/dist/document.js.map +1 -1
- package/dist/edit.js +22 -14
- package/dist/edit.js.map +1 -1
- package/dist/exec.js +140 -3
- package/dist/exec.js.map +1 -1
- package/dist/fetch.d.ts +19 -1
- package/dist/fetch.js +2 -1
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +153 -10
- package/dist/format.js.map +1 -1
- package/dist/git.js +1 -0
- package/dist/git.js.map +1 -1
- package/dist/glob.js +14 -6
- package/dist/glob.js.map +1 -1
- package/dist/grep.js +14 -6
- package/dist/grep.js.map +1 -1
- package/dist/index.d.ts +55 -3
- package/dist/index.js +833 -336
- package/dist/index.js.map +1 -1
- package/dist/install.js +154 -10
- package/dist/install.js.map +1 -1
- package/dist/json.js +2 -0
- package/dist/json.js.map +1 -1
- package/dist/lint.js +153 -10
- package/dist/lint.js.map +1 -1
- package/dist/logs.js +14 -6
- package/dist/logs.js.map +1 -1
- package/dist/memory.js +1 -0
- package/dist/memory.js.map +1 -1
- package/dist/mode.js +1 -0
- package/dist/mode.js.map +1 -1
- package/dist/outdated.js +16 -7
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +643 -332
- package/dist/pack.js.map +1 -1
- package/dist/patch.js +14 -6
- package/dist/patch.js.map +1 -1
- package/dist/process-registry.d.ts +56 -2
- package/dist/process-registry.js +138 -3
- package/dist/process-registry.js.map +1 -1
- package/dist/read.js +24 -18
- package/dist/read.js.map +1 -1
- package/dist/replace.js +14 -6
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js +14 -6
- package/dist/scaffold.js.map +1 -1
- package/dist/search.js +3 -3
- package/dist/search.js.map +1 -1
- package/dist/test.js +153 -10
- package/dist/test.js.map +1 -1
- package/dist/todo.js +1 -0
- package/dist/todo.js.map +1 -1
- package/dist/tool-help.js +1 -0
- package/dist/tool-help.js.map +1 -1
- package/dist/tool-icons.d.ts +20 -0
- package/dist/tool-icons.js +130 -0
- package/dist/tool-icons.js.map +1 -0
- package/dist/tool-search.js +1 -0
- package/dist/tool-search.js.map +1 -1
- package/dist/tool-use.js +1 -0
- package/dist/tool-use.js.map +1 -1
- package/dist/tree.js +14 -6
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +153 -10
- package/dist/typecheck.js.map +1 -1
- package/dist/write.js +22 -14
- package/dist/write.js.map +1 -1
- package/package.json +6 -2
package/dist/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
|
|
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"],
|