opencode-swarm 7.139.2 → 7.139.4

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 (110) hide show
  1. package/.opencode/skills/phase-wrap/SKILL.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/background/stage-b-gates.d.ts +5 -1
  4. package/dist/background/task-envelope.d.ts +4 -4
  5. package/dist/cli/{config-doctor-hrb61yqe.js → config-doctor-whxr386t.js} +6 -6
  6. package/dist/cli/{core-g2dfh9fq.js → core-aj5g7an6.js} +5 -5
  7. package/dist/cli/{curation-policy-4tp0mbcb.js → curation-policy-anym2y3v.js} +8 -8
  8. package/dist/cli/{curator-drift-vfh07mqy.js → curator-drift-49p3jnte.js} +7 -7
  9. package/dist/cli/{curator-vpcs1p1v.js → curator-e8cvb55d.js} +41 -41
  10. package/dist/cli/curator-llm-factory-h9xbpgmw.js +61 -0
  11. package/dist/cli/{dispatch-b1fge4xk.js → dispatch-2s23fbcs.js} +4 -4
  12. package/dist/cli/{evidence-summary-service-kzxr71yd.js → evidence-summary-service-g82bsjqa.js} +16 -16
  13. package/dist/cli/{gate-evidence-z2b1ymnw.js → gate-evidence-72cgrvs2.js} +6 -5
  14. package/dist/cli/guardrail-explain-0be7msvq.js +62 -0
  15. package/dist/cli/{guardrail-log-2ftew0p3.js → guardrail-log-9mm5443g.js} +9 -9
  16. package/dist/cli/{hive-promoter-mpf0cfkk.js → hive-promoter-nnzs5x1r.js} +41 -41
  17. package/dist/cli/{index-w71cq27a.js → index-0e95eck5.js} +5 -5
  18. package/dist/cli/{index-5zyk8qgv.js → index-0xkjhgrf.js} +5 -5
  19. package/dist/cli/{index-q7ezzgwq.js → index-1kyex7z2.js} +1 -1
  20. package/dist/cli/{index-668dpw5e.js → index-33skgk1a.js} +1 -1
  21. package/dist/cli/{index-vm4xw9z3.js → index-41hkt36g.js} +2 -2
  22. package/dist/cli/{index-y111zefa.js → index-4nt52yr9.js} +80 -31
  23. package/dist/cli/{index-3jcyn8g6.js → index-4wjk296r.js} +1 -1
  24. package/dist/cli/{index-3radty3f.js → index-5azdnaty.js} +2 -2
  25. package/dist/cli/{index-8p9pasnp.js → index-5s23pfsy.js} +8 -8
  26. package/dist/cli/{index-5w7ekpy3.js → index-5z2xps64.js} +2 -2
  27. package/dist/cli/{index-0wp67gqj.js → index-6nzmp937.js} +61 -111
  28. package/dist/cli/{index-gvhvz176.js → index-6r522srn.js} +2 -2
  29. package/dist/cli/{index-4xbyy6aw.js → index-6rxgb5r9.js} +10 -6
  30. package/dist/cli/{index-cyw81ngh.js → index-6vh6vrnk.js} +10 -10
  31. package/dist/cli/{index-a12906mj.js → index-6z0a940s.js} +8 -8
  32. package/dist/cli/{index-wsdnttf4.js → index-74hpm6sh.js} +1 -1
  33. package/dist/cli/{index-6w1re1qn.js → index-9cdj4020.js} +6 -6
  34. package/dist/cli/{index-y47dwge1.js → index-e1k9vpqn.js} +5 -5
  35. package/dist/cli/{index-y64jv2pe.js → index-ejbcpbzp.js} +7 -7
  36. package/dist/cli/{index-6c5f309q.js → index-gfmrw2f5.js} +2 -2
  37. package/dist/cli/{index-prmb5qfp.js → index-j77frjq8.js} +7 -7
  38. package/dist/cli/{index-ffpmqx30.js → index-jnfyxeap.js} +4 -4
  39. package/dist/cli/{index-e9tx99ds.js → index-k41dn8me.js} +3 -3
  40. package/dist/cli/{index-x9q4bbhd.js → index-k46me75h.js} +4 -4
  41. package/dist/cli/{index-s608d3hw.js → index-nrx08gcr.js} +6 -2
  42. package/dist/cli/{index-ktagrqxw.js → index-pg84msyc.js} +7 -7
  43. package/dist/cli/{index-a5eba0t8.js → index-rsrx77fg.js} +6 -6
  44. package/dist/cli/index-s0qc4gj6.js +298 -0
  45. package/dist/cli/{index-q11t5erh.js → index-s58vghgj.js} +7 -7
  46. package/dist/cli/{index-se8xc78k.js → index-sa15nfgg.js} +45 -45
  47. package/dist/cli/{index-wrt6m5ze.js → index-sd657fkp.js} +291 -75
  48. package/dist/cli/{index-yr6x707c.js → index-vfsqpcz6.js} +16 -16
  49. package/dist/cli/{index-a7nh382r.js → index-vyqd1hzm.js} +20593 -19882
  50. package/dist/cli/{index-8gftfvpp.js → index-xb1zq933.js} +1 -1
  51. package/dist/cli/{index-3eddtgxt.js → index-xmhhhjjw.js} +39 -4
  52. package/dist/cli/index.js +43 -43
  53. package/dist/cli/{knowledge-escalator-n9pmx4qr.js → knowledge-escalator-a6z30y9p.js} +10 -10
  54. package/dist/cli/{knowledge-events-raevm18w.js → knowledge-events-deq0ds8e.js} +7 -7
  55. package/dist/cli/{knowledge-link-3tq8w6gs.js → knowledge-link-bmbatt8s.js} +6 -6
  56. package/dist/cli/{knowledge-store-bcfqz8fr.js → knowledge-store-vdr19mnq.js} +8 -8
  57. package/dist/cli/{knowledge-validator-jxqj96cx.js → knowledge-validator-5hgacnrf.js} +11 -11
  58. package/dist/cli/{pending-delegations-tmyf18p4.js → pending-delegations-7ayryxbg.js} +8 -8
  59. package/dist/cli/{pr-subscriptions-hhx6yk24.js → pr-subscriptions-w5y08ebc.js} +8 -8
  60. package/dist/cli/{runner-w30gazew.js → runner-h7vmgkth.js} +7 -6
  61. package/dist/cli/{scan-cursor-ah5swtyh.js → scan-cursor-d6qcg9hr.js} +9 -9
  62. package/dist/cli/{schema-f7jgrtxj.js → schema-y1zekrag.js} +3 -3
  63. package/dist/cli/{scope-persistence-3f0d4fwv.js → scope-persistence-tj6qway9.js} +7 -7
  64. package/dist/cli/{skill-generator-hp7as46c.js → skill-generator-bw8xhzzn.js} +13 -13
  65. package/dist/cli/{task-envelope-qn0qtnh0.js → task-envelope-jp2fdsvn.js} +11 -8
  66. package/dist/cli/{telemetry-4tgrfhaz.js → telemetry-62cn5t24.js} +1 -1
  67. package/dist/cli/{workspace-snapshot-h5rzw37b.js → workspace-snapshot-w3tg4ne5.js} +2 -2
  68. package/dist/cli/{worktree-collision-ownership-vexb0b6r.js → worktree-collision-ownership-hs5c2qzp.js} +9 -9
  69. package/dist/commands/approve-plan-critic.d.ts +2 -2
  70. package/dist/commands/registry.d.ts +1 -1
  71. package/dist/config/agent-model.d.ts +35 -0
  72. package/dist/db/qa-gate-profile.d.ts +11 -3
  73. package/dist/evidence/manager.d.ts +5 -2
  74. package/dist/evidence/phase-participation.d.ts +44 -0
  75. package/dist/hooks/compaction-customizer.d.ts +30 -2
  76. package/dist/hooks/context-budget.d.ts +1 -1
  77. package/dist/hooks/delegation-gate.d.ts +3 -3
  78. package/dist/hooks/guardrails/index.d.ts +5 -1
  79. package/dist/hooks/guardrails/messages-transform.d.ts +2 -0
  80. package/dist/hooks/guardrails/pre-check-result.d.ts +20 -0
  81. package/dist/index.js +616 -609
  82. package/dist/prm/escalation.d.ts +34 -0
  83. package/dist/prm/index.d.ts +6 -2
  84. package/dist/prm/pattern-detector.d.ts +22 -1
  85. package/dist/prm/types.d.ts +12 -2
  86. package/dist/sast/semgrep.d.ts +15 -32
  87. package/dist/services/compaction-service.d.ts +3 -1
  88. package/dist/session/snapshot-writer.d.ts +1 -0
  89. package/dist/state.d.ts +119 -19
  90. package/dist/summaries/schema.d.ts +2 -2
  91. package/dist/tools/context-status.d.ts +4 -1
  92. package/dist/tools/convene-council.d.ts +1 -1
  93. package/dist/tools/lint.d.ts +48 -22
  94. package/dist/tools/pre-check-batch.d.ts +41 -13
  95. package/dist/tools/quality-budget.d.ts +3 -2
  96. package/dist/tools/resolve-working-directory.d.ts +4 -4
  97. package/dist/tools/sast-baseline.d.ts +1 -0
  98. package/dist/tools/sast-scan.d.ts +4 -1
  99. package/dist/tools/secretscan.d.ts +2 -2
  100. package/dist/tools/set-qa-gates.d.ts +5 -4
  101. package/dist/tools/submit-phase-council-verdicts.d.ts +1 -1
  102. package/dist/tools/write-final-council-evidence.d.ts +1 -1
  103. package/dist/utils/bun-compat.d.ts +20 -0
  104. package/dist/utils/external-tool-runner.d.ts +10 -0
  105. package/dist/utils/project-boundary.d.ts +34 -0
  106. package/package.json +1 -1
  107. package/dist/cli/curator-llm-factory-hvw13hmr.js +0 -61
  108. package/dist/cli/guardrail-explain-k62h3c92.js +0 -62
  109. package/dist/cli/index-5j6chvj8.js +0 -159
  110. package/dist/cli/{index-z6xqpmqg.js → index-ee3x66a5.js} +3 -3
@@ -3,6 +3,30 @@
3
3
  * Implements a 3-strike protocol for pattern detection escalation
4
4
  */
5
5
  import type { CourseCorrection, EscalationState, PatternMatch } from './types';
6
+ /**
7
+ * Resolves the LADDER key for a match — the identity whose 1→2→3 strike count
8
+ * this detection belongs to (issue #2134 follow-up).
9
+ *
10
+ * The ladder used to be keyed by pattern TYPE alone, so unrelated occurrences
11
+ * accumulated into one count: a coder that read-then-re-read three different
12
+ * files produced three `repetition_loop` strikes on three different targets and
13
+ * hit the hard stop, even though it had not repeated itself even twice on any
14
+ * one of them. "Three strikes" has to mean "the same behaviour three times".
15
+ *
16
+ * A pattern reporting exactly ONE affected target gets a per-target ladder —
17
+ * `repetition_loop`, `ping_pong` and `stuck_on_test` all name the single file or
18
+ * target they are about, so that target IS the behaviour's identity.
19
+ *
20
+ * A pattern reporting a SET of targets keeps the per-pattern-type ladder.
21
+ * `context_thrash` and `expansion_drift` describe one ongoing episode over a
22
+ * growing collection of targets; keying those by target would mint a fresh
23
+ * ladder on every tool call and they could never escalate at all — the exact
24
+ * fail-open shape that the per-detector containment review caught the first time.
25
+ *
26
+ * Agents are deliberately not in the key. Escalation is about the work, not who
27
+ * did it, and `ping_pong` names two agents by construction.
28
+ */
29
+ export declare function resolveLadderKey(match: PatternMatch): string;
6
30
  /**
7
31
  * Creates a default EscalationState with all counters reset and flags cleared.
8
32
  * Exported for testing purposes.
@@ -48,6 +72,16 @@ export declare class EscalationTracker {
48
72
  * @returns The current EscalationState copy
49
73
  */
50
74
  getState(): EscalationState;
75
+ /**
76
+ * Returns a defensive copy of just the ladder counts (issue #2134 follow-up).
77
+ *
78
+ * `src/prm/index.ts` mirrors these onto the session after every strike so a
79
+ * tracker rebuilt mid-session restores the same keyspace it counts in. Using
80
+ * `getState()` there deep-cloned the whole state — including `stepRange`,
81
+ * `affectedAgents` and `affectedTargets` of the last match — on the
82
+ * per-tool-call hot path, for one field.
83
+ */
84
+ getLadderCounts(): Map<string, number>;
51
85
  /**
52
86
  * Resets all escalation counts and flags to their default values.
53
87
  * Clears pattern counts and all flags.
@@ -13,8 +13,8 @@
13
13
  * while PRM provides deeper multi-pattern detection and escalation.
14
14
  */
15
15
  export { formatCourseCorrectionForInjection, generateCourseCorrection, } from './course-correction';
16
- export { createDefaultEscalationState, EscalationTracker } from './escalation';
17
- export { detectContextThrash, detectExpansionDrift, detectPatterns, detectPingPong, detectRepetitionLoop, detectStuckOnTest, } from './pattern-detector';
16
+ export { createDefaultEscalationState, EscalationTracker, resolveLadderKey, } from './escalation';
17
+ export { detectContextThrash, detectExpansionDrift, detectPatterns, detectPingPong, detectRepetitionLoop, detectStuckOnTest, resolvePatternThreshold, } from './pattern-detector';
18
18
  export type { CourseCorrection, EscalationState, PatternDetectionResult, PatternMatch, PatternSeverity, PatternType, PrmConfig, TaxonomyCategory, TrajectoryEntry, } from './types';
19
19
  import type { LearningConfig } from '../config/schema.js';
20
20
  import { appendInsightCandidates } from '../hooks/micro-reflector.js';
@@ -78,6 +78,10 @@ interface ResettablePrmSessionState {
78
78
  prmHardStopInjectPending?: boolean;
79
79
  prmTrajectoryStep?: number;
80
80
  prmInjectedAdvisoryKeys?: Set<string>;
81
+ /** Issue #2134 — episode ledger; reset with the trajectory cursor it indexes. */
82
+ prmStruckEpisodes?: Map<string, number>;
83
+ /** Issue #2134 follow-up — ladder counts; reset with the ledger. */
84
+ prmLadderCounts?: Map<string, number>;
81
85
  replayArtifactPath?: string | null;
82
86
  }
83
87
  export declare function resetPrmSessionState(session: ResettablePrmSessionState, sessionId?: string): void;
@@ -2,7 +2,7 @@
2
2
  * PRM Pattern Detector
3
3
  * Rule-based pattern detection for trajectory analysis
4
4
  */
5
- import type { PatternDetectionResult, PatternMatch, PrmConfig, TrajectoryEntry } from './types';
5
+ import type { PatternDetectionResult, PatternMatch, PatternType, PrmConfig, TrajectoryEntry } from './types';
6
6
  /**
7
7
  * Sanitize a string to prevent prompt injection attacks.
8
8
  * Removes newlines, carriage returns, backticks, and common injection patterns.
@@ -12,6 +12,27 @@ import type { PatternDetectionResult, PatternMatch, PrmConfig, TrajectoryEntry }
12
12
  * @returns Sanitized string safe for embedding in prompts
13
13
  */
14
14
  export declare function sanitizeString(input: string): string;
15
+ /**
16
+ * Resolves the configured occurrence threshold for a pattern type, falling back
17
+ * to the built-in default.
18
+ *
19
+ * Exported for the escalation ladder (issue #2134): the ladder re-strikes a
20
+ * still-running episode once it has grown by another threshold's worth of
21
+ * occurrences, so it must resolve the threshold exactly the way the detectors do
22
+ * or the two layers disagree about what "another occurrence" means.
23
+ *
24
+ * The threshold is NOT in a single unit across detectors, and the ladder does not
25
+ * pretend otherwise — it is repeats for `repetition_loop`, run length for
26
+ * `context_thrash`, cycle count for `stuck_on_test`, alternating-entry count for
27
+ * `ping_pong` (whose `occurrenceCount` is ROUND TRIPS, i.e. half that unit, so it
28
+ * re-strikes at twice the nominal spacing), and a growth RATIO for
29
+ * `expansion_drift` (whose `occurrenceCount` is a float near 1.5–3.0, so the
30
+ * growth ground is effectively inert for it and its re-strikes come from the
31
+ * new-episode ground instead — its comparison window steps by `windowSize`, so
32
+ * its episode start step genuinely advances). Every one of the five is verified
33
+ * to still reach a hard stop; see `src/prm/__tests__/issue-2134-episode-gate.test.ts`.
34
+ */
35
+ export declare function resolvePatternThreshold(config: PrmConfig, pattern: PatternType): number;
15
36
  /**
16
37
  * Detect repetition_loop pattern
17
38
  * Same agent targets same file with same action within N steps
@@ -107,8 +107,18 @@ export interface PrmConfig {
107
107
  * Per-session escalation tracking state
108
108
  */
109
109
  export interface EscalationState {
110
- /** Pattern type to detection count mapping */
111
- patternCounts: Map<PatternType, number>;
110
+ /**
111
+ * Ladder identity to strike-count mapping.
112
+ *
113
+ * Keyed by `resolveLadderKey(match)` (issue #2134 follow-up), NOT by pattern
114
+ * type: `pattern|target` for a single-target pattern, bare `pattern` for one
115
+ * reporting a growing target set. Keying the ladder by pattern type alone let
116
+ * unrelated occurrences on different files accumulate into a hard stop.
117
+ *
118
+ * Distinct from `AgentSessionState.prmPatternCounts`, which stays keyed by
119
+ * pattern type as the observable per-pattern tally.
120
+ */
121
+ patternCounts: Map<string, number>;
112
122
  /** Current escalation level (0=none, 1=guidance, 2=strong guidance, 3=hard stop) */
113
123
  escalationLevel: number;
114
124
  /** Last pattern detected (if any) */
@@ -2,6 +2,7 @@
2
2
  * Semgrep Integration for Tier B SAST Enhancement
3
3
  * Provides optional Semgrep detection and invocation for advanced static analysis
4
4
  */
5
+ import { resolveExecutableFromPath, runExternalTool } from '../utils/external-tool-runner';
5
6
  import type { SastFinding } from './rules/index.js';
6
7
  /**
7
8
  * Semgrep CLI options
@@ -19,6 +20,8 @@ export interface SemgrepOptions {
19
20
  lang?: string;
20
21
  /** When true, use --config auto instead of local rulesDir (for profile-driven languages) */
21
22
  useAutoConfig?: boolean;
23
+ /** Host/tool cancellation propagated to the shared subprocess runner. */
24
+ abortSignal?: AbortSignal;
22
25
  }
23
26
  /**
24
27
  * Result from Semgrep execution
@@ -33,6 +36,8 @@ export interface SemgrepResult {
33
36
  /** Engine label for the findings */
34
37
  engine: 'tier_a' | 'tier_a+tier_b';
35
38
  }
39
+ declare function resolveSemgrepBinary(): string | null;
40
+ declare function getAvailabilityProbeCwd(): string;
36
41
  export declare const _internals: {
37
42
  isSemgrepAvailable: typeof isSemgrepAvailable;
38
43
  checkSemgrepAvailable: typeof checkSemgrepAvailable;
@@ -40,48 +45,26 @@ export declare const _internals: {
40
45
  runSemgrep: typeof runSemgrep;
41
46
  getRulesDirectory: typeof getRulesDirectory;
42
47
  hasBundledRules: typeof hasBundledRules;
43
- executeWithTimeout: typeof executeWithTimeout;
48
+ resolveExecutableFromPath: typeof resolveExecutableFromPath;
49
+ resolveSemgrepBinary: typeof resolveSemgrepBinary;
50
+ runExternalTool: typeof runExternalTool;
51
+ getAvailabilityProbeCwd: typeof getAvailabilityProbeCwd;
44
52
  };
45
53
  /**
46
- * Check if Semgrep CLI is available on the system
47
- * Uses caching to avoid shelling out on every check
48
- * @returns true if Semgrep is available, false otherwise
54
+ * Synchronous compatibility surface.
55
+ * Returns the last confirmed async availability result when cached; otherwise
56
+ * falls back to a pure PATH-resolution heuristic without spawning.
49
57
  */
50
58
  export declare function isSemgrepAvailable(): boolean;
51
59
  /**
52
- * Check if Semgrep is available (async version for consistency)
53
- * @returns Promise resolving to availability status
60
+ * Bounded async availability probe used by the live batch path.
61
+ * Caches only the result of the real shared-runner probe.
54
62
  */
55
- export declare function checkSemgrepAvailable(): Promise<boolean>;
63
+ export declare function checkSemgrepAvailable(cwd?: string, abortSignal?: AbortSignal): Promise<boolean>;
56
64
  /**
57
65
  * Reset the Semgrep availability cache (useful for testing)
58
66
  */
59
67
  export declare function resetSemgrepCache(): void;
60
- /**
61
- * Execute a command with timeout
62
- * @param command - Command to execute
63
- * @param args - Command arguments (safe array, no shell injection)
64
- * @param options - Execution options including timeout
65
- * @returns Promise resolving to command output
66
- */
67
- declare function executeWithTimeout(command: string, args: string[], options: {
68
- cwd?: string;
69
- timeoutMs: number;
70
- maxOutputBytes?: number;
71
- /**
72
- * Test-only hook invoked once with the spawned child's pid, immediately
73
- * after `spawn()` returns. Lets tests assert the child is actually
74
- * reaped (not just that a placeholder exit code was set) without
75
- * mocking `node:child_process` (which would leak across test files —
76
- * AGENTS.md invariant 7). No production caller passes this.
77
- */
78
- onSpawn?: (pid: number | undefined) => void;
79
- }): Promise<{
80
- stdout: string;
81
- stderr: string;
82
- exitCode: number;
83
- truncated: boolean;
84
- }>;
85
68
  /**
86
69
  * Run Semgrep on specified files
87
70
  * @param options - Semgrep options
@@ -7,11 +7,13 @@
7
7
  * - Reflection (default 60%): re-summarise into tighter format
8
8
  * - Emergency (default 80%): hard truncation to system + current task + last N turns
9
9
  *
10
- * Consumes `swarmState.lastBudgetPct` (set by system-enhancer.ts after each budget calc).
10
+ * Consumes the per-session budget pct recorded by system-enhancer.ts after
11
+ * each budget calc (`getSessionBudgetPct`).
11
12
  * Never throws. Advisory system message injection via callback.
12
13
  */
13
14
  import type { CompactionConfig } from '../config/schema';
14
15
  export type { CompactionConfig };
16
+ export declare const MAX_TRACKED_COMPACTION_SESSIONS = 500;
15
17
  export interface CompactionServiceHook {
16
18
  toolAfter: (input: {
17
19
  tool: string;
@@ -30,6 +30,7 @@ export interface SerializedAgentSession {
30
30
  tool: string;
31
31
  taskId: string;
32
32
  timestamp: number;
33
+ code?: string;
33
34
  } | null;
34
35
  partialGateWarningsIssuedForTask: string[];
35
36
  completionGateWarnedForTask: string[];
package/dist/state.d.ts CHANGED
@@ -166,6 +166,7 @@ export interface AgentSessionState {
166
166
  tool: string;
167
167
  taskId: string;
168
168
  timestamp: number;
169
+ code?: string;
169
170
  } | null;
170
171
  /** Task IDs for which partial gate warning has already been issued (prevents per-task spam) */
171
172
  partialGateWarningsIssuedForTask: Set<string>;
@@ -413,6 +414,55 @@ export interface AgentSessionState {
413
414
  * fields, and this one is additive.
414
415
  */
415
416
  prmHardStopInjectPending?: boolean;
417
+ /**
418
+ * Issue #2134 — episode ledger for the escalation ladder.
419
+ *
420
+ * Maps an episode key (`patternType|startStep`) to the occurrence count that
421
+ * episode had when it last recorded a strike. A detector re-emits the SAME
422
+ * ongoing episode on every tool call with a growing `stepRange[1]` (a coder
423
+ * reading more files extends one `context_thrash` run), and the trajectory
424
+ * cursor cannot suppress that because the episode's end step always advances
425
+ * past it. Counting each re-emission as a fresh strike walked ONE ordinary
426
+ * episode from level 1 to level 3 in three tool calls and wedged healthy coder
427
+ * sessions behind a hard stop.
428
+ *
429
+ * A match strikes when its episode is new, or when that episode has gained
430
+ * another full threshold's worth of occurrences since it last struck. Both
431
+ * rungs are needed: three of the five detectors never advance an episode's
432
+ * start step while it runs, so without the growth rung they would strike
433
+ * exactly once and could never reach level 2 or 3. See the EPISODE GATE
434
+ * comment in `src/prm/index.ts` for the per-detector derivation.
435
+ *
436
+ * Optional for the same reason as `prmHardStopInjectPending`: ~25 existing
437
+ * test/session literals enumerate the required PRM fields, and this is additive.
438
+ */
439
+ prmStruckEpisodes?: Map<string, number>;
440
+ /**
441
+ * Issue #2134 follow-up — mirror of the escalation tracker's LADDER counts,
442
+ * keyed by `resolveLadderKey(match)` (`pattern|target`, or bare `pattern` for
443
+ * a pattern reporting a growing target set).
444
+ *
445
+ * Deliberately separate from `prmPatternCounts`, which stays keyed by pattern
446
+ * type as the observable per-pattern tally that telemetry and tests read. The
447
+ * two are different keyspaces and must not be conflated: the tracker-rebuild
448
+ * path below used to seed itself from `prmPatternCounts`, which would now
449
+ * hand pattern-type keys to a ladder that counts by target and silently
450
+ * mis-restore every count.
451
+ */
452
+ prmLadderCounts?: Map<string, number>;
453
+ /**
454
+ * Issue #2134 — dispatch identity of the delegation whose start already reset
455
+ * this session's PRM state. Holds the Task tool `callID`, which is unique per
456
+ * dispatch.
457
+ *
458
+ * The reset hook (`delegationGateHooks.taskMetadata`) is driven by
459
+ * `message.part.updated`, which fires repeatedly for the SAME Task part as the
460
+ * child runs. An unguarded reset there would clear the escalation ladder on
461
+ * every part update and disarm PRM permanently — a fail-open far worse than
462
+ * the wedge it is meant to prevent. Comparing against this field makes the
463
+ * reset exactly once per dispatch.
464
+ */
465
+ prmDelegationCallId?: string;
416
466
  /** Per-session escalation tracker instance (set lazily by PRM hook) */
417
467
  prmEscalationTracker?: EscalationTracker;
418
468
  /** Cross-turn set of already-injected PRM advisory dedupe keys
@@ -596,26 +646,44 @@ export declare const swarmState: {
596
646
  * matching). Populated by `src/index.ts` after `createAgents`.
597
647
  */
598
648
  generatedAgentNames: string[];
599
- /** Last known context budget percentage (0-100), updated by system-enhancer */
600
- lastBudgetPct: number;
601
649
  /**
602
- * The DENOMINATOR `lastBudgetPct` was computed against, in tokens. Written
603
- * by system-enhancer at the same two statements that write `lastBudgetPct`,
604
- * because a percentage without its denominator cannot be turned back into a
605
- * token estimate. `/swarm status` used to reconstruct the estimate from a
606
- * hardcoded constant, so a user whose real denominator differed saw a token
607
- * figure that did not match the percentage next to it. 0 means "no budget
608
- * report has run yet".
650
+ * Last known context budget per session keyed by sessionID.
651
+ *
652
+ * `pct` is the budget percentage (0-100); `tokens` is the DENOMINATOR it was
653
+ * computed against. They are stored together because a percentage without
654
+ * its denominator cannot be turned back into a token estimate, and
655
+ * `/swarm status` renders both reading them from different sources would
656
+ * show a token figure that does not match the percentage beside it.
657
+ *
658
+ * Keyed rather than global (AGENTS.md invariant 8). These were two bare
659
+ * module-level scalars, which was harmless only while the budget check threw
660
+ * on every call and left them pinned at 0. Now that the check runs, a bare
661
+ * global leaks across sessions: a busy session reporting 85% would trip the
662
+ * default-on compaction service's emergency tier — and the `>= 50%` CONTEXT
663
+ * PRESSURE advisory — inside an unrelated small session. compaction-service
664
+ * keys its *hysteresis* per session, so the wrong session's state machine
665
+ * would advance on another session's number.
666
+ *
667
+ * Write via `setSessionBudget`, read via `getSessionBudgetPct` /
668
+ * `getSessionBudgetTokens`; `/swarm status` uses `getDisplayBudget`.
609
669
  */
610
- lastBudgetTokens: number;
670
+ lastBudgetBySession: Map<string, {
671
+ pct: number;
672
+ tokens: number;
673
+ }>;
611
674
  /**
612
- * Live `model.limit.context` per session — keyed by sessionID, recorded by
613
- * the `experimental.chat.system.transform` hook (the only hook the host
675
+ * Live `model.limit.context` per session — keyed by sessionID and bound to
676
+ * the reporting model/provider identity. Recorded by the
677
+ * `experimental.chat.system.transform` hook (the only hook the host
614
678
  * gives a `Model` to) and read by the `experimental.chat.messages.transform`
615
679
  * consumers, which receive messages but no model object. Bounded via
616
680
  * {@link setLiveContextWindow} (AGENTS.md invariant 8).
617
681
  */
618
- liveContextWindows: Map<string, number>;
682
+ liveContextWindows: Map<string, {
683
+ tokens?: number;
684
+ modelID?: string;
685
+ providerID?: string;
686
+ }>;
619
687
  /** Per-session guardrail state — keyed by sessionID */
620
688
  agentSessions: Map<string, AgentSessionState>;
621
689
  /** In-flight rehydration promises — awaited by rehydrateState before clearing agentSessions */
@@ -1157,13 +1225,15 @@ export declare function getResolvedAutoProceed(session: AgentSessionState, planA
1157
1225
  export declare function setSessionEnvironment(sessionId: string, profile: EnvironmentProfile): void;
1158
1226
  export declare function getSessionEnvironment(sessionId: string): EnvironmentProfile | undefined;
1159
1227
  export declare function ensureSessionEnvironment(sessionId: string): EnvironmentProfile;
1228
+ export declare const MAX_TRACKED_BUDGET_SESSIONS = 500;
1160
1229
  export declare const MAX_TRACKED_CRITICAL_SHOWN = 500;
1161
1230
  export declare const MAX_TRACKED_KNOWLEDGE_ACKS = 5000;
1162
1231
  export declare const MAX_TRACKED_GATE_DENIALS = 500;
1163
1232
  export declare const MAX_TRACKED_CONTEXT_WINDOWS = 500;
1164
1233
  /**
1165
1234
  * Record the live `model.limit.context` the host reported for `sessionID`,
1166
- * FIFO-evicting the oldest entry past {@link MAX_TRACKED_CONTEXT_WINDOWS}.
1235
+ * bound to the reporting model/provider identity and FIFO-evicting the oldest
1236
+ * entry past {@link MAX_TRACKED_CONTEXT_WINDOWS}.
1167
1237
  *
1168
1238
  * Only accepts a finite number ≥ 1 — the caller (`src/config/context-window.ts`
1169
1239
  * `isUsableContextWindow`) applies the real plausibility floor, and storing a
@@ -1171,14 +1241,44 @@ export declare const MAX_TRACKED_CONTEXT_WINDOWS = 500;
1171
1241
  * rejected value leaves any previously recorded window in place rather than
1172
1242
  * clobbering it, so one malformed turn does not blank a good reading.
1173
1243
  */
1174
- export declare function setLiveContextWindow(sessionID: string | undefined, tokens: unknown): void;
1244
+ export declare function setLiveContextWindow(sessionID: string | undefined, tokens: unknown, identity?: {
1245
+ modelID?: string;
1246
+ providerID?: string;
1247
+ }): void;
1175
1248
  /**
1176
1249
  * Read the live context window recorded for `sessionID`, or `undefined` when
1177
- * no `system.transform` has run for it yet (first turn of a session, or a
1178
- * consumer that never sees a sessionID). Callers must degrade to the static
1179
- * resolution rungs rather than assume a value.
1250
+ * no `system.transform` has run for it yet or the requested model/provider
1251
+ * identity does not exactly match the reporter. Callers must degrade to the
1252
+ * static resolution rungs rather than reuse a stale handoff denominator.
1180
1253
  */
1181
- export declare function getLiveContextWindow(sessionID: string | undefined): number | undefined;
1254
+ export declare function getLiveContextWindow(sessionID: string | undefined, identity?: {
1255
+ modelID?: string;
1256
+ providerID?: string;
1257
+ }): number | undefined;
1258
+ export declare function getLiveContextModelIdentity(sessionID: string | undefined): {
1259
+ modelID?: string;
1260
+ providerID?: string;
1261
+ } | undefined;
1262
+ /** Record this session's budget percentage and the denominator it was computed
1263
+ * against, FIFO-evicting the oldest entry if the cap is exceeded. The two are
1264
+ * written together so `/swarm status` can never pair a pct from one report
1265
+ * with a denominator from another. */
1266
+ export declare function setSessionBudget(sessionID: string, pct: number, tokens: number): void;
1267
+ /** This session's last budget percentage, or 0 if it has not been measured.
1268
+ * Consumers that ACT on a session (compaction, the context-pressure advisory)
1269
+ * MUST use this rather than any cross-session aggregate. */
1270
+ export declare function getSessionBudgetPct(sessionID: string | undefined): number;
1271
+ /** The denominator this session's percentage was computed against, or 0. */
1272
+ export declare function getSessionBudgetTokens(sessionID: string | undefined): number;
1273
+ /** The most-pressured session's budget snapshot, for process-wide DISPLAY only
1274
+ * (`/swarm status` has no single session in scope). Returns the pct and its
1275
+ * OWN denominator as a pair, so the two figures shown side by side always come
1276
+ * from the same report. Null when nothing has been measured yet. Never use
1277
+ * this to decide whether to act on a particular session. */
1278
+ export declare function getDisplayBudget(): {
1279
+ pct: number;
1280
+ tokens: number;
1281
+ } | null;
1182
1282
  /** Set the critical shown ids for a session, FIFO-evicting the oldest entry
1183
1283
  * if the cap is exceeded. Re-setting an existing key keeps insertion order
1184
1284
  * fresh for that key (delete-then-set). */
@@ -22,8 +22,8 @@ export declare const MAX_LIST_ITEMS = 5;
22
22
  /** Verdict vocabulary — mirrors the phase-council gate (APPROVE | CONCERNS | REJECT). */
23
23
  export declare const SupervisorVerdictSchema: z.ZodEnum<{
24
24
  APPROVE: "APPROVE";
25
- CONCERNS: "CONCERNS";
26
25
  REJECT: "REJECT";
26
+ CONCERNS: "CONCERNS";
27
27
  }>;
28
28
  export type SupervisorVerdict = z.infer<typeof SupervisorVerdictSchema>;
29
29
  /** Count whitespace-delimited words in a string. */
@@ -130,8 +130,8 @@ export declare const ArchitectureSupervisorReportSchema: z.ZodObject<{
130
130
  phase: z.ZodNumber;
131
131
  verdict: z.ZodEnum<{
132
132
  APPROVE: "APPROVE";
133
- CONCERNS: "CONCERNS";
134
133
  REJECT: "REJECT";
134
+ CONCERNS: "CONCERNS";
135
135
  }>;
136
136
  findings: z.ZodDefault<z.ZodArray<z.ZodObject<{
137
137
  severity: z.ZodEnum<{
@@ -83,7 +83,10 @@ export declare const _internals: {
83
83
  * by the system.transform hook; `undefined` when none has been seen yet
84
84
  * @returns Context status with token usage, limit, and threshold state
85
85
  */
86
- declare function computeContextHeadroom(messages: ContextMessage[], warnThreshold?: number, criticalThreshold?: number, modelLimitsConfig?: Record<string, number>, liveContextLimit?: unknown): ContextStatusResult;
86
+ declare function computeContextHeadroom(messages: ContextMessage[], warnThreshold?: number, criticalThreshold?: number, modelLimitsConfig?: Record<string, number>, liveContextLimit?: unknown, modelIdentity?: {
87
+ modelID?: string;
88
+ providerID?: string;
89
+ }): ContextStatusResult;
87
90
  /**
88
91
  * context_status tool — read-only context window headroom report.
89
92
  *
@@ -15,8 +15,8 @@ export declare const ArgsSchema: z.ZodObject<{
15
15
  }>;
16
16
  verdict: z.ZodEnum<{
17
17
  APPROVE: "APPROVE";
18
- CONCERNS: "CONCERNS";
19
18
  REJECT: "REJECT";
19
+ CONCERNS: "CONCERNS";
20
20
  }>;
21
21
  confidence: z.ZodNumber;
22
22
  findings: z.ZodArray<z.ZodObject<{
@@ -1,8 +1,19 @@
1
+ import * as fs from 'node:fs';
1
2
  import type { tool } from '@opencode-ai/plugin';
3
+ import { isCommandAvailable } from '../build/discovery';
4
+ import { type ExternalToolRunOptions, type ExternalToolRunResult } from '../utils/external-tool-runner';
2
5
  export declare const MAX_OUTPUT_BYTES = 512000;
3
6
  export declare const MAX_COMMAND_LENGTH = 500;
4
7
  export declare const SUPPORTED_LINTERS: readonly ["biome", "eslint"];
5
8
  export type SupportedLinter = (typeof SUPPORTED_LINTERS)[number];
9
+ type ResolvedLinterSource = 'local-native' | 'local-shim' | 'safe-package-bin' | 'path-native' | 'path-shim' | 'legacy-test-probe';
10
+ export interface ResolvedLinterCommand {
11
+ linter: SupportedLinter;
12
+ executable: string;
13
+ argsPrefix: string[];
14
+ displayPrefix: string[];
15
+ source: ResolvedLinterSource;
16
+ }
6
17
  export type AdditionalLinter = 'ruff' | 'clippy' | 'golangci-lint' | 'checkstyle' | 'ktlint' | 'dotnet-format' | 'cppcheck' | 'swiftlint' | 'dart-analyze' | 'rubocop' | 'phpstan' | 'pint' | 'php-cs-fixer' | 'phpcs';
7
18
  export interface LintSuccessResult {
8
19
  success: true;
@@ -28,46 +39,61 @@ export { containsControlChars, containsPathTraversal, } from '../utils/path-secu
28
39
  export declare function validateArgs(args: unknown): args is {
29
40
  mode: 'fix' | 'check';
30
41
  };
42
+ export declare function getBiomeBinPath(directory: string): string;
43
+ export declare function getEslintBinPath(directory: string): string;
31
44
  export declare function getLinterCommand(linter: SupportedLinter, mode: 'fix' | 'check', projectDir: string): string[];
32
- /**
33
- * Build the shell command for an additional (non-JS/TS) linter.
34
- * cppcheck has no --fix mode; csharp and some others behave differently.
35
- */
36
- export declare function getAdditionalLinterCommand(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string): string[];
45
+ export declare function resolveLinterBinPath(linter: SupportedLinter, projectDir: string): string;
46
+ export declare function resolveLinterCommand(linter: SupportedLinter, directory: string): Promise<ResolvedLinterCommand | null>;
47
+ export declare function detectResolvedLinter(directory?: string, abortSignal?: AbortSignal): Promise<ResolvedLinterCommand | null>;
37
48
  /**
38
49
  * Detect the first available additional (non-JS/TS) linter for the current project.
39
50
  * Returns null when no additional linter is detected or its binary is unavailable.
40
51
  */
41
52
  export declare function detectAdditionalLinter(cwd: string): 'ruff' | 'clippy' | 'golangci-lint' | 'checkstyle' | 'ktlint' | 'dotnet-format' | 'cppcheck' | 'swiftlint' | 'dart-analyze' | 'rubocop' | 'phpstan' | 'pint' | 'php-cs-fixer' | 'phpcs' | null;
42
- /** Compute the local biome binary path for a given project directory. */
43
- export declare function getBiomeBinPath(directory: string): string;
44
- /**
45
- * Resolve the binary path for a linter, using the same hierarchy as detectAvailableLinter:
46
- * 1. Local node_modules/.bin
47
- * 2. Ancestor node_modules/.bin (monorepo)
48
- * 3. process.env.PATH scan
49
- * 4. Local path as fallback (may not exist)
50
- */
51
- export declare function resolveLinterBinPath(linter: SupportedLinter, projectDir: string): string;
52
- /** Compute the local eslint binary path for a given project directory. */
53
- export declare function getEslintBinPath(directory: string): string;
54
53
  export declare function detectAvailableLinter(directory?: string): Promise<SupportedLinter | null>;
55
54
  /** Internal implementation — accepts pre-computed binary paths for testability. */
56
- export declare function _detectAvailableLinter(_projectDir: string, biomeBin: string, eslintBin: string): Promise<SupportedLinter | null>;
57
- export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check', directory: string): Promise<LintResult>;
55
+ export declare function _detectAvailableLinter(projectDir: string, biomeBin: string, eslintBin: string): Promise<SupportedLinter | null>;
56
+ declare function runResolvedLint(command: ResolvedLinterCommand, mode: 'fix' | 'check', directory: string, abortSignal?: AbortSignal): Promise<LintResult>;
57
+ /**
58
+ * Build the shell command for an additional (non-JS/TS) linter.
59
+ * cppcheck has no --fix mode; csharp and some others behave differently.
60
+ */
61
+ export declare function getAdditionalLinterCommand(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string): string[] | null;
62
+ export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check', directory: string, abortSignal?: AbortSignal): Promise<LintResult>;
58
63
  /**
59
64
  * Run an additional (non-JS/TS) linter.
60
65
  * Follows the same structure as runLint() but uses getAdditionalLinterCommand().
61
66
  */
62
- export declare function runAdditionalLint(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string): Promise<LintResult>;
67
+ export declare function runAdditionalLint(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string, abortSignal?: AbortSignal): Promise<LintResult>;
63
68
  export declare const lint: ReturnType<typeof tool>;
64
69
  /**
65
70
  * DI seam for testability. Contains all test-mocked exports.
66
71
  * Internal calls should use _internals.fn() instead of fn() directly.
67
72
  */
68
73
  export declare const _internals: {
69
- detectAvailableLinter: typeof detectAvailableLinter;
70
- runLint: typeof runLint;
74
+ MAX_PACKAGE_MANIFEST_BYTES: number;
75
+ MAX_PATH_SHIM_BYTES: number;
76
+ arch: () => string;
77
+ closeSync: typeof fs.closeSync;
78
+ comSpec: () => string | undefined;
71
79
  detectAdditionalLinter: typeof detectAdditionalLinter;
80
+ detectAvailableLinter: typeof detectAvailableLinter;
81
+ detectResolvedLinter: typeof detectResolvedLinter;
82
+ execPath: () => string;
83
+ existsSync: typeof fs.existsSync;
84
+ fstatSync: typeof fs.fstatSync;
85
+ isCommandAvailable: typeof isCommandAvailable;
86
+ openSync: typeof fs.openSync;
87
+ pathEnv: () => string;
88
+ platform: () => NodeJS.Platform;
89
+ readFileSync: typeof fs.readFileSync;
90
+ readSync: typeof fs.readSync;
91
+ readdirSync: typeof fs.readdirSync;
92
+ realpathSync: typeof fs.realpathSync;
93
+ resolveLinterCommand: typeof resolveLinterCommand;
72
94
  runAdditionalLint: typeof runAdditionalLint;
95
+ runExternalTool: (options: ExternalToolRunOptions) => Promise<ExternalToolRunResult>;
96
+ runLint: typeof runLint;
97
+ runResolvedLint: typeof runResolvedLint;
98
+ statSync: typeof fs.statSync;
73
99
  };