@wrongstack/plugins 1.0.13 → 1.0.15

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/index.js CHANGED
@@ -33,7 +33,7 @@ var state = {
33
33
  hookUnregister: null
34
34
  };
35
35
  var DEFAULTS = {
36
- enabled: false,
36
+ enabled: true,
37
37
  includeExtensions: [".tsx", ".jsx", ".html", ".vue"],
38
38
  maxFindings: 50,
39
39
  severity: "warn",
@@ -1522,6 +1522,10 @@ var plugin4 = {
1522
1522
  },
1523
1523
  permission: "auto",
1524
1524
  mutating: true,
1525
+ // It rewrites project source files, so it declares the same capability
1526
+ // as the built-in writers: the agent-state write gate and capability
1527
+ // allowlists key on `fs.write`, not on `mutating`.
1528
+ capabilities: ["fs.write"],
1525
1529
  category: "Project",
1526
1530
  async execute(input) {
1527
1531
  const inp = input;
@@ -1773,7 +1777,7 @@ var state6 = {
1773
1777
  lastResult: null
1774
1778
  };
1775
1779
  var DEFAULTS5 = {
1776
- enabled: false,
1780
+ enabled: true,
1777
1781
  fileExtensions: [".tsx", ".jsx", ".vue"],
1778
1782
  minLength: 2,
1779
1783
  maxContextStrings: 10,
@@ -3317,7 +3321,7 @@ var state10 = {
3317
3321
  hookUnregister: null
3318
3322
  };
3319
3323
  var DEFAULTS9 = {
3320
- enabled: false,
3324
+ enabled: true,
3321
3325
  extensions: [".ts", ".tsx", ".js", ".jsx"],
3322
3326
  maxFiles: 50
3323
3327
  };
@@ -5088,6 +5092,7 @@ var cost_tracker_default = plugin14;
5088
5092
  import { ToolValidationError as ToolValidationError8 } from "@wrongstack/core/types";
5089
5093
  var COORDINATION_CRON_CAPABILITY = "coordination.cron";
5090
5094
  var API_VERSION9 = "^0.1.10";
5095
+ var MAX_TIMER_DELAY_MS = 2147483647;
5091
5096
  var state14 = {
5092
5097
  jobs: /* @__PURE__ */ new Map(),
5093
5098
  timers: /* @__PURE__ */ new Map(),
@@ -5233,7 +5238,9 @@ var plugin15 = {
5233
5238
  name: { type: "string", description: "Unique name for this cron job" },
5234
5239
  intervalMs: {
5235
5240
  type: "number",
5236
- description: "Interval between runs in milliseconds (minimum 1000)"
5241
+ minimum: 1e3,
5242
+ maximum: MAX_TIMER_DELAY_MS,
5243
+ description: `Interval between runs in milliseconds (1000 to ${MAX_TIMER_DELAY_MS})`
5237
5244
  },
5238
5245
  action: {
5239
5246
  type: "string",
@@ -5265,6 +5272,12 @@ var plugin15 = {
5265
5272
  field: "intervalMs"
5266
5273
  });
5267
5274
  }
5275
+ if (intervalMs > MAX_TIMER_DELAY_MS) {
5276
+ throw new ToolValidationError8({
5277
+ message: `intervalMs must be <= ${MAX_TIMER_DELAY_MS} (about 24.8 days)`,
5278
+ field: "intervalMs"
5279
+ });
5280
+ }
5268
5281
  if (typeof action !== "string" || action.trim() === "") {
5269
5282
  throw new ToolValidationError8({
5270
5283
  message: "action is required and must be a non-empty string",
@@ -5390,7 +5403,7 @@ var state15 = {
5390
5403
  hookUnregister: null
5391
5404
  };
5392
5405
  var DEFAULTS13 = {
5393
- enabled: false,
5406
+ enabled: true,
5394
5407
  extensions: [".ts", ".tsx", ".js", ".jsx"],
5395
5408
  defaultDepth: 3,
5396
5409
  maxDepth: 10,
@@ -5546,7 +5559,7 @@ var plugin16 = {
5546
5559
  properties: {
5547
5560
  enabled: {
5548
5561
  type: "boolean",
5549
- default: false,
5562
+ default: true,
5550
5563
  description: "Master switch."
5551
5564
  },
5552
5565
  extensions: {
@@ -7103,7 +7116,7 @@ var hookIndexBudgets = {
7103
7116
  maxFileBytes: 2 * 1024 * 1024
7104
7117
  };
7105
7118
  var DEFAULTS18 = {
7106
- enabled: false,
7119
+ enabled: true,
7107
7120
  minLines: 8,
7108
7121
  threshold: 0.8,
7109
7122
  extensions: [".ts", ".tsx", ".js", ".jsx"],
@@ -7319,7 +7332,7 @@ var plugin21 = {
7319
7332
  configSchema: {
7320
7333
  type: "object",
7321
7334
  properties: {
7322
- enabled: { type: "boolean", default: false, description: "Master switch." },
7335
+ enabled: { type: "boolean", default: true, description: "Master switch." },
7323
7336
  minLines: {
7324
7337
  type: "number",
7325
7338
  minimum: 2,
@@ -7915,7 +7928,7 @@ var DEFAULT_PATTERNS2 = [
7915
7928
  String.raw`flags(?:\.|\?\.)([A-Za-z_$][A-Za-z0-9_$]*)`
7916
7929
  ];
7917
7930
  var DEFAULTS20 = {
7918
- enabled: false,
7931
+ enabled: true,
7919
7932
  extensions: [".ts", ".tsx", ".js", ".jsx"],
7920
7933
  patterns: [],
7921
7934
  maxFindings: 50
@@ -9430,8 +9443,9 @@ var plugin26 = {
9430
9443
  try {
9431
9444
  commitScope = await stageFiles(files);
9432
9445
  } catch (err) {
9446
+ const detail = err instanceof Error ? err.message : String(err);
9433
9447
  throw new Error(
9434
- `Failed to stage files: ${err instanceof Error ? err.message : String(err)}`,
9448
+ detail.startsWith("Failed to stage files") ? detail : `Failed to stage files: ${detail}`,
9435
9449
  { cause: err }
9436
9450
  );
9437
9451
  }
@@ -10747,7 +10761,7 @@ var state27 = {
10747
10761
  hookUnregister: null
10748
10762
  };
10749
10763
  var DEFAULTS25 = {
10750
- enabled: false,
10764
+ enabled: true,
10751
10765
  extensions: [".ts", ".tsx"],
10752
10766
  maxFindings: 50,
10753
10767
  maxFiles: 2e3
@@ -12465,29 +12479,70 @@ var llm_cache_default = plugin34;
12465
12479
  // src/loop-breaker/index.ts
12466
12480
  import { execFile as execFile8 } from "node:child_process";
12467
12481
  import { isAbsolute as isAbsolute15, relative as relative15 } from "node:path";
12468
- var state32 = {
12469
- lastFingerprint: null,
12470
- streak: 0,
12471
- recent: [],
12472
- pendingBlockReason: null,
12473
- lastDiffFingerprint: null,
12474
- noDiffStreak: 0,
12475
- lastErrorFingerprint: null,
12476
- repeatedErrorStreak: 0,
12477
- invocations: 0,
12478
- postInvocations: 0,
12479
- warnings: 0,
12480
- blocks: 0,
12481
- oscillationsDetected: 0,
12482
- stepBudgetBlocks: 0,
12483
- noDiffWarnings: 0,
12484
- noDiffBlocks: 0,
12485
- repeatedErrorWarnings: 0,
12486
- repeatedErrorBlocks: 0,
12487
- hookUnregister: null
12488
- };
12482
+ var FALLBACK_SESSION = "__default__";
12483
+ var MAX_TRACKED_RUNS = 32;
12484
+ function createRunState() {
12485
+ return {
12486
+ lastFingerprint: null,
12487
+ streak: 0,
12488
+ recent: [],
12489
+ pendingBlockReason: null,
12490
+ lastDiffFingerprint: null,
12491
+ noDiffStreak: 0,
12492
+ lastErrorFingerprint: null,
12493
+ repeatedErrorStreak: 0,
12494
+ invocations: 0,
12495
+ postInvocations: 0,
12496
+ warnings: 0,
12497
+ blocks: 0,
12498
+ oscillationsDetected: 0,
12499
+ stepBudgetBlocks: 0,
12500
+ noDiffWarnings: 0,
12501
+ noDiffBlocks: 0,
12502
+ repeatedErrorWarnings: 0,
12503
+ repeatedErrorBlocks: 0
12504
+ };
12505
+ }
12506
+ var state32 = { runs: /* @__PURE__ */ new Map(), hookUnregister: null };
12507
+ function sessionKey(sessionId) {
12508
+ return sessionId || FALLBACK_SESSION;
12509
+ }
12510
+ function storeRun(key, current) {
12511
+ state32.runs.set(key, current);
12512
+ if (state32.runs.size <= MAX_TRACKED_RUNS) return;
12513
+ const oldest = state32.runs.keys().next().value;
12514
+ if (oldest !== void 0 && oldest !== key) state32.runs.delete(oldest);
12515
+ }
12516
+ function runState(sessionId) {
12517
+ const key = sessionKey(sessionId);
12518
+ let current = state32.runs.get(key);
12519
+ if (!current) {
12520
+ current = createRunState();
12521
+ storeRun(key, current);
12522
+ }
12523
+ return current;
12524
+ }
12525
+ function resetRun(sessionId) {
12526
+ storeRun(sessionKey(sessionId), createRunState());
12527
+ }
12528
+ function aggregateRuns() {
12529
+ const total = createRunState();
12530
+ for (const current of state32.runs.values()) {
12531
+ total.invocations += current.invocations;
12532
+ total.postInvocations += current.postInvocations;
12533
+ total.warnings += current.warnings;
12534
+ total.blocks += current.blocks;
12535
+ total.oscillationsDetected += current.oscillationsDetected;
12536
+ total.stepBudgetBlocks += current.stepBudgetBlocks;
12537
+ total.noDiffWarnings += current.noDiffWarnings;
12538
+ total.noDiffBlocks += current.noDiffBlocks;
12539
+ total.repeatedErrorWarnings += current.repeatedErrorWarnings;
12540
+ total.repeatedErrorBlocks += current.repeatedErrorBlocks;
12541
+ }
12542
+ return total;
12543
+ }
12489
12544
  var DEFAULTS30 = {
12490
- enabled: true,
12545
+ enabled: false,
12491
12546
  // Documented contract (feature matrix, plugin description): warn, then
12492
12547
  // block. A warn-only default meant an agent stuck re-issuing the same call
12493
12548
  // was never actually stopped unless the user happened to set any option.
@@ -12495,7 +12550,7 @@ var DEFAULTS30 = {
12495
12550
  warnAfter: 3,
12496
12551
  blockAfter: 5,
12497
12552
  oscillationWindow: 8,
12498
- maxSteps: 200,
12553
+ maxSteps: 0,
12499
12554
  noDiffWarnAfter: 6,
12500
12555
  noDiffBlockAfter: 10,
12501
12556
  repeatedErrorWarnAfter: 2,
@@ -12521,7 +12576,7 @@ function readConfig30(raw) {
12521
12576
  const rawOsc = r["oscillationWindow"] ?? r["oscillation_window"] ?? r["window"];
12522
12577
  const rawMaxSteps = r["maxSteps"] ?? r["max_steps"] ?? r["stepLimit"] ?? r["step_limit"];
12523
12578
  return {
12524
- enabled: r["enabled"] !== false,
12579
+ enabled: r["enabled"] === true,
12525
12580
  mode,
12526
12581
  warnAfter,
12527
12582
  blockAfter,
@@ -12634,7 +12689,11 @@ var plugin35 = {
12634
12689
  configSchema: {
12635
12690
  type: "object",
12636
12691
  properties: {
12637
- enabled: { type: "boolean", default: true, description: "Master switch." },
12692
+ enabled: {
12693
+ type: "boolean",
12694
+ default: false,
12695
+ description: "Opt-in master switch; disabled unless explicitly enabled."
12696
+ },
12638
12697
  mode: {
12639
12698
  type: "string",
12640
12699
  enum: ["warn", "block"],
@@ -12662,8 +12721,8 @@ var plugin35 = {
12662
12721
  maxSteps: {
12663
12722
  type: "number",
12664
12723
  minimum: 0,
12665
- default: 200,
12666
- description: "Maximum tool steps before blocking; 0 disables the step budget."
12724
+ default: 0,
12725
+ description: "Optional maximum tool steps before blocking; 0 keeps runs unlimited."
12667
12726
  },
12668
12727
  noDiffWarnAfter: {
12669
12728
  type: "number",
@@ -12698,24 +12757,7 @@ var plugin35 = {
12698
12757
  }
12699
12758
  },
12700
12759
  setup(api) {
12701
- state32.lastFingerprint = null;
12702
- state32.streak = 0;
12703
- state32.recent = [];
12704
- state32.pendingBlockReason = null;
12705
- state32.lastDiffFingerprint = null;
12706
- state32.noDiffStreak = 0;
12707
- state32.lastErrorFingerprint = null;
12708
- state32.repeatedErrorStreak = 0;
12709
- state32.invocations = 0;
12710
- state32.postInvocations = 0;
12711
- state32.warnings = 0;
12712
- state32.blocks = 0;
12713
- state32.oscillationsDetected = 0;
12714
- state32.stepBudgetBlocks = 0;
12715
- state32.noDiffWarnings = 0;
12716
- state32.noDiffBlocks = 0;
12717
- state32.repeatedErrorWarnings = 0;
12718
- state32.repeatedErrorBlocks = 0;
12760
+ state32.runs.clear();
12719
12761
  if (state32.hookUnregister) {
12720
12762
  try {
12721
12763
  state32.hookUnregister();
@@ -12728,56 +12770,57 @@ var plugin35 = {
12728
12770
  if (!cfg.enabled) return;
12729
12771
  const toolName = input.toolName ?? "unknown";
12730
12772
  if (ignoreToolsSet.has(toolName)) return;
12731
- state32.invocations += 1;
12732
- if (state32.pendingBlockReason && cfg.mode === "block") {
12733
- const reason = state32.pendingBlockReason;
12734
- state32.pendingBlockReason = null;
12735
- state32.blocks += 1;
12773
+ const current = runState(input.sessionId);
12774
+ if (current.pendingBlockReason && cfg.mode === "block") {
12775
+ const reason = current.pendingBlockReason;
12776
+ current.pendingBlockReason = null;
12777
+ current.blocks += 1;
12736
12778
  api.metrics.counter("blocks");
12737
12779
  return { decision: "block", reason };
12738
12780
  }
12739
- if (cfg.maxSteps > 0 && state32.invocations > cfg.maxSteps && cfg.mode === "block") {
12740
- state32.blocks += 1;
12741
- state32.stepBudgetBlocks += 1;
12781
+ if (cfg.maxSteps > 0 && current.invocations >= cfg.maxSteps && cfg.mode === "block") {
12782
+ current.blocks += 1;
12783
+ current.stepBudgetBlocks += 1;
12742
12784
  api.metrics.counter("blocks");
12743
12785
  api.metrics.counter("step_budget_blocks");
12744
12786
  return {
12745
12787
  decision: "block",
12746
- reason: `loop-breaker: step budget exceeded (${state32.invocations}/${cfg.maxSteps} tool calls). Stop and report what has been tried instead of continuing an unbounded loop.`
12788
+ reason: `loop-breaker: step budget exceeded (${current.invocations}/${cfg.maxSteps} tool calls). Stop and report what has been tried instead of continuing an unbounded loop.`
12747
12789
  };
12748
12790
  }
12791
+ current.invocations += 1;
12749
12792
  const fp = fingerprint(toolName, input.toolInput);
12750
- if (fp === state32.lastFingerprint) {
12751
- state32.streak += 1;
12793
+ if (fp === current.lastFingerprint) {
12794
+ current.streak += 1;
12752
12795
  } else {
12753
- state32.lastFingerprint = fp;
12754
- state32.streak = 1;
12796
+ current.lastFingerprint = fp;
12797
+ current.streak = 1;
12755
12798
  }
12756
- state32.recent.push(fp);
12757
- if (state32.recent.length > Math.max(cfg.oscillationWindow, 16)) {
12758
- state32.recent.splice(0, state32.recent.length - Math.max(cfg.oscillationWindow, 16));
12799
+ current.recent.push(fp);
12800
+ if (current.recent.length > Math.max(cfg.oscillationWindow, 16)) {
12801
+ current.recent.splice(0, current.recent.length - Math.max(cfg.oscillationWindow, 16));
12759
12802
  }
12760
- if (state32.streak >= cfg.blockAfter && cfg.mode === "block") {
12761
- state32.blocks += 1;
12803
+ if (current.streak >= cfg.blockAfter && cfg.mode === "block") {
12804
+ current.blocks += 1;
12762
12805
  api.metrics.counter("blocks");
12763
12806
  return {
12764
12807
  decision: "block",
12765
- reason: `loop-breaker: "${toolName}" has been called ${state32.streak} times in a row with identical input. This looks like a runaway loop. Change the input, try a different tool, or explain to the user why repetition is needed. (Disable this guard via config.extensions["loop-breaker"].enabled = false.)`
12808
+ reason: `loop-breaker: "${toolName}" has been called ${current.streak} times in a row with identical input. This looks like a runaway loop. Change the input, try a different tool, or explain to the user why repetition is needed. (Disable this guard via config.extensions["loop-breaker"].enabled = false.)`
12766
12809
  };
12767
12810
  }
12768
- if (state32.streak >= cfg.warnAfter) {
12769
- state32.warnings += 1;
12811
+ if (current.streak >= cfg.warnAfter) {
12812
+ current.warnings += 1;
12770
12813
  api.metrics.counter("warnings");
12771
- const remaining = cfg.mode === "block" ? cfg.blockAfter - state32.streak : null;
12814
+ const remaining = cfg.mode === "block" ? cfg.blockAfter - current.streak : null;
12772
12815
  return {
12773
12816
  decision: "allow",
12774
- additionalContext: `loop-breaker: "${toolName}" repeated ${state32.streak}x with identical input.` + (remaining !== null && remaining > 0 ? ` It will be BLOCKED after ${remaining} more identical call(s).` : "") + " If the previous result was not what you needed, change the approach instead of retrying."
12817
+ additionalContext: `loop-breaker: "${toolName}" repeated ${current.streak}x with identical input.` + (remaining !== null && remaining > 0 ? ` It will be BLOCKED after ${remaining} more identical call(s).` : "") + " If the previous result was not what you needed, change the approach instead of retrying."
12775
12818
  };
12776
12819
  }
12777
- if (isOscillating(state32.recent, cfg.oscillationWindow)) {
12778
- state32.oscillationsDetected += 1;
12820
+ if (isOscillating(current.recent, cfg.oscillationWindow)) {
12821
+ current.oscillationsDetected += 1;
12779
12822
  api.metrics.counter("oscillations");
12780
- state32.recent = [];
12823
+ current.recent = [];
12781
12824
  return {
12782
12825
  decision: "allow",
12783
12826
  additionalContext: `loop-breaker: the last ${cfg.oscillationWindow} tool calls alternate between two identical calls (A-B-A-B pattern). You appear to be undoing and redoing the same work. Step back and pick a single approach.`
@@ -12789,32 +12832,33 @@ var plugin35 = {
12789
12832
  if (!cfg.enabled) return;
12790
12833
  const toolName = input.toolName ?? "unknown";
12791
12834
  if (ignoreToolsSet.has(toolName)) return;
12792
- state32.postInvocations += 1;
12835
+ const current = runState(input.sessionId);
12836
+ current.postInvocations += 1;
12793
12837
  if (input.toolResult?.isError) {
12794
12838
  const errorFingerprint = normalizeError(input.toolResult.content);
12795
- if (errorFingerprint && errorFingerprint === state32.lastErrorFingerprint) {
12796
- state32.repeatedErrorStreak += 1;
12839
+ if (errorFingerprint && errorFingerprint === current.lastErrorFingerprint) {
12840
+ current.repeatedErrorStreak += 1;
12797
12841
  } else {
12798
- state32.lastErrorFingerprint = errorFingerprint;
12799
- state32.repeatedErrorStreak = errorFingerprint ? 1 : 0;
12842
+ current.lastErrorFingerprint = errorFingerprint;
12843
+ current.repeatedErrorStreak = errorFingerprint ? 1 : 0;
12800
12844
  }
12801
- state32.noDiffStreak = 0;
12802
- if (cfg.repeatedErrorBlockAfter > 0 && state32.repeatedErrorStreak >= cfg.repeatedErrorBlockAfter && cfg.mode === "block") {
12803
- state32.repeatedErrorBlocks += 1;
12804
- state32.pendingBlockReason = `loop-breaker: the same tool error repeated ${state32.repeatedErrorStreak} times. The next tool call is blocked so you can stop, summarize the repeated failure, and change approach.`;
12845
+ current.noDiffStreak = 0;
12846
+ if (cfg.repeatedErrorBlockAfter > 0 && current.repeatedErrorStreak >= cfg.repeatedErrorBlockAfter && cfg.mode === "block") {
12847
+ current.repeatedErrorBlocks += 1;
12848
+ current.pendingBlockReason = `loop-breaker: the same tool error repeated ${current.repeatedErrorStreak} times. The next tool call is blocked so you can stop, summarize the repeated failure, and change approach.`;
12805
12849
  }
12806
- if (state32.repeatedErrorStreak >= cfg.repeatedErrorWarnAfter) {
12807
- state32.repeatedErrorWarnings += 1;
12850
+ if (current.repeatedErrorStreak >= cfg.repeatedErrorWarnAfter) {
12851
+ current.repeatedErrorWarnings += 1;
12808
12852
  api.metrics.counter("repeated_error_warnings");
12809
12853
  return {
12810
12854
  decision: "allow",
12811
- additionalContext: `loop-breaker: same error repeated ${state32.repeatedErrorStreak}x. Do not retry the same command/tool unchanged; inspect the root cause or ask for help.`
12855
+ additionalContext: `loop-breaker: same error repeated ${current.repeatedErrorStreak}x. Do not retry the same command/tool unchanged; inspect the root cause or ask for help.`
12812
12856
  };
12813
12857
  }
12814
12858
  return;
12815
12859
  }
12816
- state32.lastErrorFingerprint = null;
12817
- state32.repeatedErrorStreak = 0;
12860
+ current.lastErrorFingerprint = null;
12861
+ current.repeatedErrorStreak = 0;
12818
12862
  if (!MUTATING_TOOLS.has(toolName)) return;
12819
12863
  const toolInput = input.toolInput ?? {};
12820
12864
  const rawTarget = toolInput["path"] ?? toolInput["TargetFile"] ?? toolInput["filePath"] ?? toolInput["targetFile"] ?? toolInput["file_path"] ?? toolInput["destination"] ?? toolInput["file"];
@@ -12826,22 +12870,22 @@ var plugin35 = {
12826
12870
  runtime.signal
12827
12871
  );
12828
12872
  if (diffFingerprint === null) return;
12829
- if (diffFingerprint === state32.lastDiffFingerprint) {
12830
- state32.noDiffStreak += 1;
12873
+ if (diffFingerprint === current.lastDiffFingerprint) {
12874
+ current.noDiffStreak += 1;
12831
12875
  } else {
12832
- state32.lastDiffFingerprint = diffFingerprint;
12833
- state32.noDiffStreak = 0;
12876
+ current.lastDiffFingerprint = diffFingerprint;
12877
+ current.noDiffStreak = 0;
12834
12878
  }
12835
- if (cfg.noDiffBlockAfter > 0 && state32.noDiffStreak >= cfg.noDiffBlockAfter && cfg.mode === "block") {
12836
- state32.noDiffBlocks += 1;
12837
- state32.pendingBlockReason = `loop-breaker: no diff was produced in the last ${state32.noDiffStreak} mutating step(s). The next tool call is blocked because continued edits are not changing the working tree.`;
12879
+ if (cfg.noDiffBlockAfter > 0 && current.noDiffStreak >= cfg.noDiffBlockAfter && cfg.mode === "block") {
12880
+ current.noDiffBlocks += 1;
12881
+ current.pendingBlockReason = `loop-breaker: no diff was produced in the last ${current.noDiffStreak} mutating step(s). The next tool call is blocked because continued edits are not changing the working tree.`;
12838
12882
  }
12839
- if (state32.noDiffStreak >= cfg.noDiffWarnAfter) {
12840
- state32.noDiffWarnings += 1;
12883
+ if (current.noDiffStreak >= cfg.noDiffWarnAfter) {
12884
+ current.noDiffWarnings += 1;
12841
12885
  api.metrics.counter("no_diff_warnings");
12842
12886
  return {
12843
12887
  decision: "allow",
12844
- additionalContext: `loop-breaker: no diff has changed for ${state32.noDiffStreak} mutating step(s). Stop repeating edits; read the file/status, explain why no change is happening, or choose a different approach.`
12888
+ additionalContext: `loop-breaker: no diff has changed for ${current.noDiffStreak} mutating step(s). Stop repeating edits; read the file/status, explain why no change is happening, or choose a different approach.`
12845
12889
  };
12846
12890
  }
12847
12891
  return;
@@ -12856,9 +12900,18 @@ var plugin35 = {
12856
12900
  timeoutMs: 2e3,
12857
12901
  failurePolicy: "open"
12858
12902
  });
12903
+ const unregisterPrompt = api.registerHook(
12904
+ "UserPromptSubmit",
12905
+ void 0,
12906
+ ((input) => {
12907
+ resetRun(input.sessionId);
12908
+ }),
12909
+ { name: "loop-breaker-turn-reset", failurePolicy: "open" }
12910
+ );
12859
12911
  state32.hookUnregister = () => {
12860
12912
  unregisterPre();
12861
12913
  unregisterPost();
12914
+ unregisterPrompt();
12862
12915
  };
12863
12916
  api.tools.register({
12864
12917
  name: "loop_breaker_status",
@@ -12867,7 +12920,8 @@ var plugin35 = {
12867
12920
  permission: "auto",
12868
12921
  category: "Diagnostics",
12869
12922
  mutating: false,
12870
- async execute() {
12923
+ async execute(_input, ctx) {
12924
+ const current = runState(ctx?.session?.id);
12871
12925
  return {
12872
12926
  ok: true,
12873
12927
  enabled: cfg.enabled,
@@ -12881,20 +12935,20 @@ var plugin35 = {
12881
12935
  repeatedErrorWarnAfter: cfg.repeatedErrorWarnAfter,
12882
12936
  repeatedErrorBlockAfter: cfg.repeatedErrorBlockAfter,
12883
12937
  ignoreTools: cfg.ignoreTools,
12884
- currentStreak: state32.streak,
12885
- noDiffStreak: state32.noDiffStreak,
12886
- repeatedErrorStreak: state32.repeatedErrorStreak,
12938
+ currentStreak: current.streak,
12939
+ noDiffStreak: current.noDiffStreak,
12940
+ repeatedErrorStreak: current.repeatedErrorStreak,
12887
12941
  counters: {
12888
- invocations: state32.invocations,
12889
- postInvocations: state32.postInvocations,
12890
- warnings: state32.warnings,
12891
- blocks: state32.blocks,
12892
- oscillationsDetected: state32.oscillationsDetected,
12893
- stepBudgetBlocks: state32.stepBudgetBlocks,
12894
- noDiffWarnings: state32.noDiffWarnings,
12895
- noDiffBlocks: state32.noDiffBlocks,
12896
- repeatedErrorWarnings: state32.repeatedErrorWarnings,
12897
- repeatedErrorBlocks: state32.repeatedErrorBlocks
12942
+ invocations: current.invocations,
12943
+ postInvocations: current.postInvocations,
12944
+ warnings: current.warnings,
12945
+ blocks: current.blocks,
12946
+ oscillationsDetected: current.oscillationsDetected,
12947
+ stepBudgetBlocks: current.stepBudgetBlocks,
12948
+ noDiffWarnings: current.noDiffWarnings,
12949
+ noDiffBlocks: current.noDiffBlocks,
12950
+ repeatedErrorWarnings: current.repeatedErrorWarnings,
12951
+ repeatedErrorBlocks: current.repeatedErrorBlocks
12898
12952
  }
12899
12953
  };
12900
12954
  }
@@ -12915,53 +12969,26 @@ var plugin35 = {
12915
12969
  }
12916
12970
  state32.hookUnregister = null;
12917
12971
  }
12918
- const final = {
12919
- invocations: state32.invocations,
12920
- postInvocations: state32.postInvocations,
12921
- warnings: state32.warnings,
12922
- blocks: state32.blocks,
12923
- oscillationsDetected: state32.oscillationsDetected,
12924
- stepBudgetBlocks: state32.stepBudgetBlocks,
12925
- noDiffWarnings: state32.noDiffWarnings,
12926
- noDiffBlocks: state32.noDiffBlocks,
12927
- repeatedErrorWarnings: state32.repeatedErrorWarnings,
12928
- repeatedErrorBlocks: state32.repeatedErrorBlocks
12929
- };
12930
- state32.lastFingerprint = null;
12931
- state32.streak = 0;
12932
- state32.recent = [];
12933
- state32.pendingBlockReason = null;
12934
- state32.lastDiffFingerprint = null;
12935
- state32.noDiffStreak = 0;
12936
- state32.lastErrorFingerprint = null;
12937
- state32.repeatedErrorStreak = 0;
12938
- state32.invocations = 0;
12939
- state32.postInvocations = 0;
12940
- state32.warnings = 0;
12941
- state32.blocks = 0;
12942
- state32.oscillationsDetected = 0;
12943
- state32.stepBudgetBlocks = 0;
12944
- state32.noDiffWarnings = 0;
12945
- state32.noDiffBlocks = 0;
12946
- state32.repeatedErrorWarnings = 0;
12947
- state32.repeatedErrorBlocks = 0;
12972
+ const final = aggregateRuns();
12973
+ state32.runs.clear();
12948
12974
  api.log.info("loop-breaker: teardown complete", { final });
12949
12975
  },
12950
12976
  async health() {
12977
+ const totals = aggregateRuns();
12951
12978
  return {
12952
12979
  ok: true,
12953
- message: `loop-breaker: ${state32.invocations} call(s) observed, ${state32.warnings + state32.noDiffWarnings + state32.repeatedErrorWarnings} warning(s), ${state32.blocks} block(s), ${state32.oscillationsDetected} oscillation(s)`,
12980
+ message: `loop-breaker: ${totals.invocations} call(s) observed, ${totals.warnings + totals.noDiffWarnings + totals.repeatedErrorWarnings} warning(s), ${totals.blocks} block(s), ${totals.oscillationsDetected} oscillation(s)`,
12954
12981
  counters: {
12955
- invocations: state32.invocations,
12956
- postInvocations: state32.postInvocations,
12957
- warnings: state32.warnings,
12958
- blocks: state32.blocks,
12959
- oscillationsDetected: state32.oscillationsDetected,
12960
- stepBudgetBlocks: state32.stepBudgetBlocks,
12961
- noDiffWarnings: state32.noDiffWarnings,
12962
- noDiffBlocks: state32.noDiffBlocks,
12963
- repeatedErrorWarnings: state32.repeatedErrorWarnings,
12964
- repeatedErrorBlocks: state32.repeatedErrorBlocks
12982
+ invocations: totals.invocations,
12983
+ postInvocations: totals.postInvocations,
12984
+ warnings: totals.warnings,
12985
+ blocks: totals.blocks,
12986
+ oscillationsDetected: totals.oscillationsDetected,
12987
+ stepBudgetBlocks: totals.stepBudgetBlocks,
12988
+ noDiffWarnings: totals.noDiffWarnings,
12989
+ noDiffBlocks: totals.noDiffBlocks,
12990
+ repeatedErrorWarnings: totals.repeatedErrorWarnings,
12991
+ repeatedErrorBlocks: totals.repeatedErrorBlocks
12965
12992
  }
12966
12993
  };
12967
12994
  }
@@ -13879,7 +13906,8 @@ var state35 = {
13879
13906
  lastDelivery: null,
13880
13907
  stopHookUnregister: null,
13881
13908
  eventUnsubscribers: [],
13882
- circuitWarned: false
13909
+ circuitWarned: false,
13910
+ disabledReason: null
13883
13911
  };
13884
13912
  var KNOWN_EVENTS = ["session.stop", "tool.error", "budget.threshold"];
13885
13913
  var DEFAULTS33 = {
@@ -14062,13 +14090,21 @@ var plugin38 = {
14062
14090
  }
14063
14091
  }
14064
14092
  state35.eventUnsubscribers = [];
14065
- const cfg = readConfig33(api.config.extensions?.["notify-hub"]);
14093
+ state35.disabledReason = null;
14094
+ const rawCfg = api.config.extensions?.["notify-hub"];
14095
+ const cfg = readConfig33(rawCfg);
14066
14096
  let active = cfg.enabled && cfg.webhookUrl.length > 0;
14097
+ const rawUrl = rawCfg?.["webhookUrl"] ?? rawCfg?.["webhook_url"] ?? rawCfg?.["url"];
14098
+ if (cfg.enabled && typeof rawUrl === "string" && rawUrl.trim() && !cfg.webhookUrl) {
14099
+ state35.disabledReason = "webhookUrl was refused: it must be an http(s) URL without credentials that does not target localhost or a private address";
14100
+ api.log.warn(`notify-hub: ${state35.disabledReason}`);
14101
+ }
14067
14102
  if (active) {
14068
14103
  try {
14069
14104
  const url = new URL(cfg.webhookUrl);
14070
14105
  const hostnameBlocked = await hasPrivateResolvedIP(url.hostname);
14071
14106
  if (hostnameBlocked) {
14107
+ state35.disabledReason = `webhookUrl was refused: ${url.hostname} resolves to a private/local IP`;
14072
14108
  api.log.warn(
14073
14109
  `notify-hub: webhook URL resolves to a private/local IP (${url.hostname}) \u2014 disabling plugin`
14074
14110
  );
@@ -14141,7 +14177,7 @@ var plugin38 = {
14141
14177
  const ch = state35.channel;
14142
14178
  if (!ch) {
14143
14179
  throw new Error(
14144
- 'no webhookUrl configured \u2014 set config.extensions["notify-hub"].webhookUrl to enable deliveries'
14180
+ state35.disabledReason ?? 'no webhookUrl configured \u2014 set config.extensions["notify-hub"].webhookUrl to enable deliveries'
14145
14181
  );
14146
14182
  }
14147
14183
  const inp = input ?? {};
@@ -17401,7 +17437,7 @@ var state41 = {
17401
17437
  lastHookWarning: new runtime_exports.BoundedMap({ max: 512, ttlMs: HOOK_WARNING_COOLDOWN_MS2 })
17402
17438
  };
17403
17439
  var DEFAULTS38 = {
17404
- enabled: false,
17440
+ enabled: true,
17405
17441
  extensions: [".ts", ".tsx", ".js", ".jsx"],
17406
17442
  maxSuggestions: 5,
17407
17443
  rules: { longFunctionLines: 50, maxParams: 5, maxNesting: 3 }
@@ -17592,7 +17628,7 @@ var plugin44 = {
17592
17628
  configSchema: {
17593
17629
  type: "object",
17594
17630
  properties: {
17595
- enabled: { type: "boolean", default: false, description: "Master switch." },
17631
+ enabled: { type: "boolean", default: true, description: "Master switch." },
17596
17632
  extensions: {
17597
17633
  type: "array",
17598
17634
  items: { type: "string" },
@@ -19007,7 +19043,7 @@ var state44 = {
19007
19043
  hookUnregister: null
19008
19044
  };
19009
19045
  var DEFAULTS42 = {
19010
- enabled: false,
19046
+ enabled: true,
19011
19047
  severity: "warn",
19012
19048
  maxFindings: 10,
19013
19049
  scanOnChange: [
@@ -19198,7 +19234,7 @@ var plugin48 = {
19198
19234
  configSchema: {
19199
19235
  type: "object",
19200
19236
  properties: {
19201
- enabled: { type: "boolean", default: false, description: "Master switch." },
19237
+ enabled: { type: "boolean", default: true, description: "Master switch." },
19202
19238
  severity: {
19203
19239
  type: "string",
19204
19240
  enum: ["warn", "block"],
@@ -33,7 +33,7 @@ var state = {
33
33
  hookUnregister: null
34
34
  };
35
35
  var DEFAULTS = {
36
- enabled: false,
36
+ enabled: true,
37
37
  extensions: [".ts", ".tsx"],
38
38
  maxFindings: 50,
39
39
  maxFiles: 2e3