opencode-swarm 7.132.1 → 7.133.0

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 (102) hide show
  1. package/.opencode/skills/swarm-pr-review/SKILL.md +15 -5
  2. package/README.md +4 -1
  3. package/dist/agents/agent-output-schema.d.ts +74 -0
  4. package/dist/agents/critic.d.ts +2 -1
  5. package/dist/agents/reviewer.d.ts +1 -1
  6. package/dist/background/completion-observer.d.ts +2 -0
  7. package/dist/background/pending-delegations.d.ts +39 -1
  8. package/dist/background/stage-b-gates.d.ts +2 -0
  9. package/dist/cli/{config-doctor-bp5spb79.js → config-doctor-8nbdwnn6.js} +2 -2
  10. package/dist/cli/{core-wg3re04e.js → core-egwpry19.js} +2 -2
  11. package/dist/cli/{curation-policy-7zp2m9gv.js → curation-policy-gae3xm5d.js} +2 -2
  12. package/dist/cli/{curator-28hcw9hx.js → curator-e88bm4mg.js} +17 -18
  13. package/dist/cli/{curator-llm-factory-rd0s95fw.js → curator-llm-factory-1a7esr69.js} +17 -18
  14. package/dist/cli/{evidence-summary-service-jfagmvrh.js → evidence-summary-service-nhsmsap8.js} +3 -3
  15. package/dist/cli/{guardrail-explain-742dhpec.js → guardrail-explain-zgja2b9m.js} +18 -19
  16. package/dist/cli/{guardrail-log-aa112fxv.js → guardrail-log-fjgyx76r.js} +3 -3
  17. package/dist/cli/{hive-promoter-8whgvcry.js → hive-promoter-2b04e0wh.js} +17 -18
  18. package/dist/cli/{index-9cf1yr67.js → index-0vd91yb8.js} +3 -3
  19. package/dist/cli/{index-w9cb5zwh.js → index-17zjp67y.js} +1 -1
  20. package/dist/cli/{index-djwemsjn.js → index-66zght9d.js} +1 -1
  21. package/dist/cli/{index-6ccynjv3.js → index-7ftthvqt.js} +2 -2
  22. package/dist/cli/{index-jgfcp9bh.js → index-bz08438z.js} +1 -1
  23. package/dist/cli/{index-mwkwyej1.js → index-cpest8sa.js} +5 -5
  24. package/dist/cli/{index-y5qp59rc.js → index-hfpw5m0e.js} +9 -3
  25. package/dist/cli/{index-3s9rfnqq.js → index-j5pnah1w.js} +4 -4
  26. package/dist/cli/{index-jj4earrh.js → index-kc1m6vpr.js} +2 -2
  27. package/dist/cli/{index-x3p6ktva.js → index-mhcge2ry.js} +34 -23
  28. package/dist/cli/{index-yvv1wt2h.js → index-mmvaf4t6.js} +1 -1
  29. package/dist/cli/{index-b21a4zw4.js → index-mwqfyjja.js} +37751 -33949
  30. package/dist/cli/{index-fgcmxjp7.js → index-sp7tm8t7.js} +1 -1
  31. package/dist/cli/{index-4hapy63e.js → index-sw82jvf2.js} +2 -2
  32. package/dist/cli/{index-630badhp.js → index-tj2mkfb3.js} +149 -6
  33. package/dist/cli/{index-rt5jgktq.js → index-wz36r0fk.js} +1 -1
  34. package/dist/cli/{index-bvp2v7k1.js → index-wzb4dv5s.js} +263 -16
  35. package/dist/cli/index.js +21 -20
  36. package/dist/cli/{knowledge-escalator-j0nceas7.js → knowledge-escalator-hzkw1ban.js} +3 -3
  37. package/dist/cli/{knowledge-events-gpqc62jr.js → knowledge-events-pbycdhqd.js} +1 -1
  38. package/dist/cli/{knowledge-store-ggxr3ww5.js → knowledge-store-45jawb2p.js} +1 -1
  39. package/dist/cli/{knowledge-validator-007smap6.js → knowledge-validator-ge576f8n.js} +4 -4
  40. package/dist/cli/pending-delegations-kxy8mdqg.js +75 -0
  41. package/dist/cli/{scan-cursor-wz4bff7a.js → scan-cursor-kvw872be.js} +2 -2
  42. package/dist/cli/{schema-023gwne7.js → schema-a2zm2m3r.js} +6 -2
  43. package/dist/cli/{scope-persistence-dm5bab50.js → scope-persistence-5qypzxxm.js} +1 -1
  44. package/dist/cli/{skill-generator-rtsg1m7x.js → skill-generator-ch79hvxh.js} +5 -5
  45. package/dist/commands/command-dispatch.d.ts +7 -0
  46. package/dist/commands/index.d.ts +7 -0
  47. package/dist/commands/registry.d.ts +16 -0
  48. package/dist/commands/review.d.ts +21 -0
  49. package/dist/config/agent-names.d.ts +3 -3
  50. package/dist/config/evidence-schema.d.ts +47 -47
  51. package/dist/config/index.d.ts +2 -2
  52. package/dist/config/schema.d.ts +58 -6
  53. package/dist/consensus/contracts.d.ts +3 -3
  54. package/dist/evaluation/contracts.d.ts +1 -1
  55. package/dist/evaluation/ephemeral-agent-dispatcher.d.ts +64 -0
  56. package/dist/evaluation/model-dispatcher.d.ts +11 -5
  57. package/dist/evaluation/public-api.d.ts +1 -1
  58. package/dist/hooks/auto-review.d.ts +30 -65
  59. package/dist/hooks/delegation-gate.d.ts +1 -1
  60. package/dist/hooks/guardrails/index.d.ts +32 -0
  61. package/dist/hooks/guardrails/messages-transform.d.ts +7 -0
  62. package/dist/hooks/guardrails/tool-before.d.ts +8 -0
  63. package/dist/hooks/guardrails.d.ts +1 -1
  64. package/dist/hooks/pr-workflow-response-gate.d.ts +44 -26
  65. package/dist/hooks/review-receipt-collector.d.ts +53 -10
  66. package/dist/hooks/review-receipt-scope.d.ts +74 -0
  67. package/dist/hooks/review-receipt.d.ts +104 -10
  68. package/dist/hooks/reviewer-scope-file-fingerprint.d.ts +21 -0
  69. package/dist/hooks/reviewer-scope-lifecycle.d.ts +19 -0
  70. package/dist/hooks/task-result-classifier.d.ts +6 -0
  71. package/dist/index.d.ts +2 -0
  72. package/dist/index.js +665 -612
  73. package/dist/memory/schema.d.ts +5 -5
  74. package/dist/review/contracts.d.ts +29 -0
  75. package/dist/review/diff-source.d.ts +134 -0
  76. package/dist/review/engine.d.ts +60 -0
  77. package/dist/review/evidence.d.ts +88 -0
  78. package/dist/review/finding-validator.d.ts +77 -0
  79. package/dist/review/phase-runner.d.ts +37 -0
  80. package/dist/review/runtime.d.ts +32 -0
  81. package/dist/scope/scope-binding.d.ts +9 -0
  82. package/dist/services/config-doctor.d.ts +12 -0
  83. package/dist/state.d.ts +179 -0
  84. package/dist/tools/dispatch-lanes.d.ts +2 -0
  85. package/dist/tools/lean-turbo-review.d.ts +4 -1
  86. package/dist/tools/lean-turbo-run-phase.d.ts +3 -1
  87. package/dist/tools/phase-complete/gates/final-review-gate.d.ts +18 -0
  88. package/dist/tools/phase-complete/gates/index.d.ts +1 -0
  89. package/dist/tools/phase-complete/gates/types.d.ts +10 -0
  90. package/dist/tools/phase-complete.d.ts +13 -1
  91. package/dist/tools/plugin-registration.d.ts +3 -1
  92. package/dist/tools/swarm-command.d.ts +4 -1
  93. package/dist/tools/tool-metadata.d.ts +22 -22
  94. package/dist/turbo/lean/integration.d.ts +18 -2
  95. package/dist/turbo/lean/reviewer.d.ts +19 -3
  96. package/dist/turbo/lean/runner.d.ts +1 -1
  97. package/package.json +1 -1
  98. package/dist/cli/index-y2pfmd0z.js +0 -259
  99. package/dist/cli/pending-delegations-37t4xecr.js +0 -34
  100. package/dist/cli/{curator-drift-y1wm4atk.js → curator-drift-ea0p8nh1.js} +2 -2
  101. package/dist/cli/{index-4ff0x4cg.js → index-74tptrss.js} +0 -0
  102. package/dist/cli/{index-03zyn94g.js → index-r260ybay.js} +4 -4
@@ -68,9 +68,9 @@ export declare const ConsensusMineRequestSchema: z.ZodObject<{
68
68
  declare const ConsensusProposedTargetSchema: z.ZodEnum<{
69
69
  tooling: "tooling";
70
70
  none: "none";
71
+ orchestration: "orchestration";
71
72
  prompt: "prompt";
72
73
  skill: "skill";
73
- orchestration: "orchestration";
74
74
  }>;
75
75
  export type ConsensusProposedTarget = z.infer<typeof ConsensusProposedTargetSchema>;
76
76
  /**
@@ -151,9 +151,9 @@ export declare const ConsensusAttributeV1Schema: z.ZodObject<{
151
151
  proposedTarget: z.ZodEnum<{
152
152
  tooling: "tooling";
153
153
  none: "none";
154
+ orchestration: "orchestration";
154
155
  prompt: "prompt";
155
156
  skill: "skill";
156
- orchestration: "orchestration";
157
157
  }>;
158
158
  }, z.core.$strict>;
159
159
  /**
@@ -390,9 +390,9 @@ export declare const ConsensusReportV1Schema: z.ZodObject<{
390
390
  proposedTarget: z.ZodEnum<{
391
391
  tooling: "tooling";
392
392
  none: "none";
393
+ orchestration: "orchestration";
393
394
  prompt: "prompt";
394
395
  skill: "skill";
395
- orchestration: "orchestration";
396
396
  }>;
397
397
  }, z.core.$strict>>;
398
398
  proposals: z.ZodArray<z.ZodObject<{
@@ -278,9 +278,9 @@ export declare const PromotionDecisionV1Schema: z.ZodObject<{
278
278
  runId: z.ZodString;
279
279
  decidedAt: z.ZodISODateTime;
280
280
  status: z.ZodEnum<{
281
+ reject: "reject";
281
282
  inconclusive: "inconclusive";
282
283
  accept: "accept";
283
- reject: "reject";
284
284
  }>;
285
285
  reasons: z.ZodArray<z.ZodString>;
286
286
  baseline: z.ZodObject<{
@@ -0,0 +1,64 @@
1
+ import type { OpencodeClient } from '@opencode-ai/sdk';
2
+ import type { DelegationCostFields } from '../services/cost-accounting.js';
3
+ import { log } from '../utils/logger.js';
4
+ import type { ModelOverride } from '../utils/model-dispatch-fallback.js';
5
+ export declare const DEFAULT_EPHEMERAL_TIMEOUT_MS = 120000;
6
+ export declare const DEFAULT_EPHEMERAL_CLEANUP_TIMEOUT_MS = 500;
7
+ export declare const DEFAULT_EPHEMERAL_PROMPT_BYTE_LIMIT: number;
8
+ export declare const MAX_EPHEMERAL_PROMPT_BYTE_LIMIT: number;
9
+ export declare const DEFAULT_EPHEMERAL_RESPONSE_BYTE_LIMIT: number;
10
+ export type ReadOnlyToolDenials = Readonly<Record<string, false>>;
11
+ /**
12
+ * Fail-closed tool policy for isolated review/evaluation agents.
13
+ *
14
+ * OpenCode merges this request map with the selected agent's configured tools,
15
+ * so omitting a registered plugin tool leaves it enabled. Deny every canonical
16
+ * plugin tool (including deceptively mutable tools such as lint and recursive
17
+ * tools such as swarm_command) plus built-in mutation/dispatch escape hatches.
18
+ * Upstream read-only discovery tools remain available.
19
+ */
20
+ export declare const DEFAULT_READ_ONLY_TOOLS: ReadOnlyToolDenials;
21
+ export type EphemeralAgentDispatchRequest = {
22
+ client: OpencodeClient;
23
+ directory: string;
24
+ parentSessionId?: string;
25
+ /** Already-resolved OpenCode agent name. Agent lookup belongs to the caller. */
26
+ agentName: string;
27
+ model?: ModelOverride;
28
+ /** Optional system prompt for this isolated session. */
29
+ system?: string;
30
+ prompt: string;
31
+ /** Explicit false-only tool map. The primitive never grants tools implicitly. */
32
+ readOnlyTools: ReadOnlyToolDenials;
33
+ title?: string;
34
+ timeoutMs: number;
35
+ cleanupTimeoutMs?: number;
36
+ promptByteLimit?: number;
37
+ responseByteLimit?: number;
38
+ abortSignal?: AbortSignal;
39
+ };
40
+ export type EphemeralAgentDispatchResult = {
41
+ status: 'completed' | 'timeout' | 'cancelled' | 'error';
42
+ agentName: string;
43
+ modelId?: string;
44
+ text: string;
45
+ error?: string;
46
+ durationMs: number;
47
+ promptBytes: number;
48
+ responseBytes: number;
49
+ costFields?: DelegationCostFields;
50
+ };
51
+ export declare function boundedDeleteEphemeralSession(client: OpencodeClient, sessionId: string, timeoutMs?: number): Promise<void>;
52
+ export declare const _internals: {
53
+ boundedDelete: typeof boundedDeleteEphemeralSession;
54
+ log: typeof log;
55
+ };
56
+ /**
57
+ * Dispatch one read-only agent in a fresh, parent-bound session.
58
+ *
59
+ * This is deliberately policy-free: callers resolve the agent and model, provide
60
+ * the replacement system prompt, and own retry/fallback policy. The primitive
61
+ * owns only bounded session creation/prompting, transcript caps, accounting, and
62
+ * awaited best-effort cleanup.
63
+ */
64
+ export declare function dispatchEphemeralAgent(request: EphemeralAgentDispatchRequest): Promise<EphemeralAgentDispatchResult>;
@@ -1,5 +1,6 @@
1
1
  import type { Agent, OpencodeClient } from '@opencode-ai/sdk';
2
- import { log } from '../utils/logger.js';
2
+ import type { DelegationCostFields } from '../services/cost-accounting.js';
3
+ import { _internals as ephemeralDispatcherInternals } from './ephemeral-agent-dispatcher.js';
3
4
  export type EvaluationModelDispatchRequest = {
4
5
  directory: string;
5
6
  agentName: string;
@@ -18,13 +19,18 @@ export type EvaluationModelDispatchResult = {
18
19
  text: string;
19
20
  durationMs: number;
20
21
  error?: string;
22
+ promptBytes?: number;
23
+ responseBytes?: number;
24
+ costFields?: DelegationCostFields;
21
25
  };
22
26
  export type EvaluationModelDispatcher = (request: EvaluationModelDispatchRequest) => Promise<EvaluationModelDispatchResult>;
23
27
  export declare function resolveEvaluationAgentName(agents: readonly Pick<Agent, 'name'>[], logicalName: string, preferredSwarm?: string): string;
24
- declare function boundedDelete(client: OpencodeClient, sessionId: string, timeoutMs?: number): Promise<void>;
28
+ /**
29
+ * Compatibility proxy for the existing evaluation test seam. The shared
30
+ * primitive remains the single owner of cleanup and logging behavior.
31
+ */
25
32
  export declare const _internals: {
26
- boundedDelete: typeof boundedDelete;
27
- log: typeof log;
33
+ boundedDelete: typeof ephemeralDispatcherInternals.boundedDelete;
34
+ log: typeof ephemeralDispatcherInternals.log;
28
35
  };
29
36
  export declare function createEvaluationModelDispatcher(client: OpencodeClient): EvaluationModelDispatcher;
30
- export {};
@@ -31,7 +31,7 @@ export declare const evaluationV1: ((options: EvaluateCandidateV1Options) => Pro
31
31
  decisionId: string;
32
32
  runId: string;
33
33
  decidedAt: string;
34
- status: "inconclusive" | "accept" | "reject";
34
+ status: "reject" | "inconclusive" | "accept";
35
35
  reasons: string[];
36
36
  baseline: {
37
37
  baselineRunId: string;
@@ -1,90 +1,55 @@
1
1
  /**
2
- * Auto-review hook (auto-review machinery, piece B) — opt-in.
2
+ * Automatic task-completion review hook.
3
3
  *
4
- * When `auto_review.enabled` is true, completing a task
5
- * (`update_task_status` status 'completed') and/or a phase
6
- * (`phase_complete`) automatically dispatches the registered reviewer agent
7
- * over a fresh ephemeral session to review the current execution diff —
8
- * the same "second model reviews the work in a clean context" pattern used
9
- * by Claude Code's auto-review and Codex's review model. The reviewer agent
10
- * carries its own configured model (`agents.reviewer.model`), so the review
11
- * model is independently configurable from the coder/architect models.
12
- *
13
- * The pass is ADVISORY and fully fail-open:
14
- * - fire-and-forget from `tool.execute.after` (never blocks the tool)
15
- * - verdicts are persisted as durable review receipts
16
- * (`.swarm/review-receipts/`, scope-fingerprinted over the diff) and an
17
- * `auto_review` event is appended to `.swarm/events.jsonl`
18
- * - a REJECTED or unparseable verdict injects a `[AUTO-REVIEW]` advisory
19
- * into the architect's next prompt; APPROVED stays silent
20
- *
21
- * Bounds (AGENTS.md invariants 3 and 8): the diff subprocess uses execFile
22
- * with cwd/timeout/maxBuffer and ignored stdin; dispatches are guarded by a
23
- * per-session in-flight set plus a 60s cooldown in a bounded FIFO map.
24
- *
25
- * #1896 / #1905: on a transient/quota dispatch error the reviewer fails over to
26
- * a configured `fallback_models` entry via the shared `dispatchWithModelFallback`
27
- * helper, instead of immediately writing a `verdict: 'error'` event (a quota
28
- * blip previously dropped the review pass with no recovery). The SDK error
29
- * envelope is preserved in the thrown message so the classifier sees the quota
30
- * token on the dominant SDK error shape.
4
+ * Phase/plan review is owned by the phase_complete tool body so it can await a
5
+ * durable artifact before the evidence-only gate runs. This hook therefore owns
6
+ * only task-completion review, remains fire-and-forget, and delegates every
7
+ * model/diff/persistence decision to the shared review engine.
31
8
  */
32
9
  import { type AutoReviewConfig } from '../config/schema.js';
33
- import { type ModelOverride } from '../utils/model-dispatch-fallback.js';
34
- /** Test-only: clear module-level dispatch tracking. */
35
- export declare function resetAutoReviewTracking(): void;
36
- export type ExecutionDiffResult = {
37
- status: 'ok';
38
- diff: string;
39
- } | {
40
- status: 'clean';
41
- } | {
42
- status: 'error';
43
- reason: string;
44
- };
45
- /**
46
- * Collect the execution diff for review: `git diff HEAD` (tracked changes)
47
- * plus a porcelain summary of untracked files. Distinguishes a clean working
48
- * tree from collection failures (git missing, timeout, diff exceeding the
49
- * 2× maxBuffer cap) so events report honestly. Output is truncated to
50
- * `maxBytes`.
51
- */
52
- declare function computeExecutionDiff(directory: string, maxBytes: number): Promise<ExecutionDiffResult>;
53
- declare function dispatchReviewer(directory: string, prompt: string, agentName: string, timeoutMs: number, parentSessionId: string, model?: ModelOverride): Promise<string>;
10
+ import type { ReviewModelDispatcher } from '../review/contracts.js';
11
+ import { type ReviewEngineResult, runReviewEngine } from '../review/engine.js';
12
+ import { type ReviewAgentModelRegistry } from '../review/runtime.js';
54
13
  export interface AutoReviewRunInput {
55
14
  directory: string;
56
15
  sessionID: string;
57
- trigger: 'task_completion' | 'phase_boundary';
16
+ trigger: 'task_completion';
58
17
  taskId?: string;
59
- phase?: number;
60
- config: Required<Pick<AutoReviewConfig, 'timeout_ms' | 'max_diff_kb'>>;
18
+ config: AutoReviewConfig;
19
+ dispatcher?: ReviewModelDispatcher;
20
+ generatedAgentNames?: Iterable<string>;
21
+ activeAgentName?: string;
22
+ agentModelRegistry?: ReviewAgentModelRegistry;
61
23
  injectAdvisory: (sessionId: string, message: string) => void;
62
24
  }
63
- /**
64
- * Execute one auto-review pass: collect diff → dispatch reviewer over an
65
- * ephemeral session → persist receipt + event → advisory on REJECTED or
66
- * unparseable output. Fully fail-open; never throws.
67
- */
68
- export declare function runAutoReview(input: AutoReviewRunInput): Promise<void>;
25
+ export declare function runAutoReview(input: AutoReviewRunInput): Promise<ReviewEngineResult | undefined>;
69
26
  export interface AutoReviewHookOptions {
70
27
  config: AutoReviewConfig;
71
28
  directory: string;
29
+ dispatcher?: ReviewModelDispatcher;
30
+ generatedAgentNames?: Iterable<string>;
31
+ agentModelRegistry?: ReviewAgentModelRegistry;
32
+ getActiveAgentName?: (sessionID: string) => string | undefined;
72
33
  injectAdvisory: (sessionId: string, message: string) => void;
73
34
  }
35
+ export interface AutoReviewToolAfterContext {
36
+ /**
37
+ * Arguments recovered from the callID snapshot. The OpenCode SDK's
38
+ * `tool.execute.after` output does not carry the original tool arguments.
39
+ */
40
+ args: Record<string, unknown> | null;
41
+ }
74
42
  export declare function createAutoReviewHook(options: AutoReviewHookOptions): {
75
43
  toolAfter: (input: {
76
44
  tool: string;
77
45
  sessionID: string;
78
46
  callID?: string;
79
- }, output: {
80
- args?: unknown;
81
- output?: unknown;
82
- }) => Promise<void>;
47
+ }, context: AutoReviewToolAfterContext) => Promise<void>;
48
+ /** Instance-local test/lifecycle seam; never affects another plugin instance. */
49
+ resetTracking: () => void;
83
50
  };
84
51
  export declare const _internals: {
85
- computeExecutionDiff: typeof computeExecutionDiff;
86
- dispatchReviewer: typeof dispatchReviewer;
52
+ runReviewEngine: typeof runReviewEngine;
87
53
  runAutoReview: typeof runAutoReview;
88
54
  now: () => number;
89
55
  };
90
- export {};
@@ -268,7 +268,7 @@ interface MessageWithParts {
268
268
  * approval. Does not throw — returns `false` for any failure (fail-closed).
269
269
  */
270
270
  export declare function isPlanCriticApproved(directory: string): Promise<boolean>;
271
- declare function resolveDelegatedPlanTaskId(args: Record<string, unknown>, knownPlanTaskIds?: ReadonlySet<string>): string | null;
271
+ export declare function resolveDelegatedPlanTaskId(args: Record<string, unknown>, knownPlanTaskIds?: ReadonlySet<string>): string | null;
272
272
  /**
273
273
  * Builds a cause-specific diagnostic for `prepareCoderScope`'s SCOPE_NOT_DECLARED
274
274
  * throw when `resolveDelegatedPlanTaskId` returns null. Re-runs extraction with
@@ -19,6 +19,13 @@ export declare const _internals: {
19
19
  resolveFallbackModel: typeof resolveFallbackModel;
20
20
  dcCheckJunctionCreation: typeof dcCheckJunctionCreation;
21
21
  extractErrorSignal: typeof extractErrorSignal;
22
+ /**
23
+ * Test/inspection seams for the no-op detector's bounded session state
24
+ * (invariant 8). Production code does not call these; they exist so the
25
+ * eviction bound can be asserted without exporting the maps themselves.
26
+ */
27
+ noOpStateSize: () => number;
28
+ hasNoOpState: (sessionID: string) => boolean;
22
29
  };
23
30
  /**
24
31
  * Issue #853 Layer B: tools that are structurally blocked while
@@ -36,6 +43,31 @@ export declare function enforceSpecDriftGate(directory: string | undefined, tool
36
43
  * Extracts bounded provider/error signal from unknown hook error payloads.
37
44
  */
38
45
  declare function extractErrorSignal(errorContent: unknown): string;
46
+ /**
47
+ * v6.33.1: No-op work detector state.
48
+ *
49
+ * AGENTS.md invariant 8: module-level, session-keyed state must have an explicit
50
+ * eviction strategy. Both containers below are keyed by `sessionID` and were
51
+ * previously unbounded — they grew for the lifetime of the plugin process, since
52
+ * nothing removed a key when a session ended. `noOpWarningIssued` was only ever
53
+ * `delete`d on a reset, and `toolCallsSinceLastWrite` never shrank at all.
54
+ *
55
+ * Bounded LRU, evicting the LEAST-RECENTLY-TOUCHED session.
56
+ *
57
+ * Plain insertion-order (FIFO) eviction is wrong here and actively harmful.
58
+ * `Map.set()` on an EXISTING key does not move it, so the first session created
59
+ * in the process stays permanently at the front of the iteration order. In an
60
+ * OpenCode plugin process that first session is the architect — the very session
61
+ * this detector exists to watch — making it the guaranteed first eviction victim
62
+ * while a session touched once and abandoned later survives. Measured: with a
63
+ * FIFO bound, an architect climbing toward the threshold had its counter evicted
64
+ * and reset mid-climb, and its 15th consecutive no-write call produced ZERO
65
+ * warnings. A bound must not silence the detector it was added to protect.
66
+ *
67
+ * {@link touchNoOpSession} therefore deletes before setting, moving the key to
68
+ * the back on every touch, so eviction genuinely targets quiet sessions.
69
+ */
70
+ export declare const MAX_TRACKED_NO_OP_SESSIONS = 200;
39
71
  /**
40
72
  * Redacts sensitive values from a shell command string before audit logging.
41
73
  * Single-sourced from ./helpers (which additionally redacts home paths).
@@ -32,6 +32,13 @@ type ChatMessageLike = {
32
32
  text?: unknown;
33
33
  }>;
34
34
  };
35
+ /**
36
+ * Shared by the runaway-output advisory's TEXT and its once-per-drain dedupe
37
+ * predicate, so the two can never drift apart again. They previously did: the
38
+ * predicate tested for the string 'runaway output', which the pushed message
39
+ * never contained, making the guard permanently inert.
40
+ */
41
+ export declare const RUNAWAY_OUTPUT_ADVISORY_MARKER = "Model is generating analysis without taking action";
35
42
  export declare function getMostRecentAssistantText(messages: ChatMessageLike[]): string;
36
43
  export declare function isTransientProviderFailureText(text: string): boolean;
37
44
  export declare function getProviderFailureFingerprint(text: string): string;
@@ -37,6 +37,14 @@ export interface ToolBeforeContext {
37
37
  * trusted roots when exempting `git worktree remove --force` (issue #1708).
38
38
  */
39
39
  worktreeBaseDirOverrides?: string[];
40
+ /** Hold exact child-write provenance until the matching after-hook succeeds. */
41
+ rememberReviewerScopeWrite?: (input: {
42
+ callID: string;
43
+ parentSessionID: string;
44
+ taskId: string;
45
+ coderCallID: string;
46
+ file: string;
47
+ }) => void;
40
48
  }
41
49
  /**
42
50
  * Creates a toolBefore handler with the given shared context.
@@ -10,5 +10,5 @@
10
10
  * - Layer 2 (Hard Block @ 100%): Throws error in toolBefore to block further calls, injects STOP message
11
11
  */
12
12
  export { type AgentRule, type AttestationRecord, buildEffectiveRules, checkFileAuthority, checkFileAuthorityWithRules, checkWriteTargetForSymlink, clearGuardrailsCaches, DEFAULT_AGENT_AUTHORITY_RULES, getGlobMatcher, hashArgs, isOnDifferentFilesystemRoot, normalizePathWithCache, recordAttestation, validateAndRecordAttestation, validateAttestation, } from './guardrails/file-authority';
13
- export { _internals, createGuardrailsHooks, enforceSpecDriftGate, redactShellCommand, SPEC_DRIFT_BLOCKED_TOOLS, } from './guardrails/index';
13
+ export { _internals, createGuardrailsHooks, enforceSpecDriftGate, MAX_TRACKED_NO_OP_SESSIONS, redactShellCommand, SPEC_DRIFT_BLOCKED_TOOLS, } from './guardrails/index';
14
14
  export { deleteStoredInputArgs, getStoredInputArgs, setStoredInputArgs, } from './guardrails/stored-input-args';
@@ -26,26 +26,38 @@ export declare const DEFAULT_MAX_CONSECUTIVE_UNPRODUCTIVE_WAKES = 5;
26
26
  */
27
27
  export declare const DEFAULT_WAKE_COOLDOWN_MS = 30000;
28
28
  /**
29
- * Minimum time between FULL workflow banners prepended to a single session's
30
- * completed text parts. The textComplete hook prepends a banner to every
31
- * architect text part while the durable gate exists; without a cooldown a
32
- * burst of reasoning parts repeats the full multi-line banner many times
33
- * (field report: ~8 repeats between two model thoughts). Within the cooldown a
34
- * short one-line marker is prepended instead; the full banner (and its
35
- * suspension/interruption recovery notices) returns once the cooldown elapses.
29
+ * Minimum time between FULL workflow banners for a single session. The banner
30
+ * is injected at most once per assistant MESSAGE (see the `banneredMessages`
31
+ * map in {@link createPrWorkflowResponseGate}); this cooldown only chooses
32
+ * whether that one injection is the full multi-line banner or the short
33
+ * one-line marker.
36
34
  *
37
- * The input event exposes only `{ sessionID }` — there is NO message/part ID —
38
- * so this dedupe is necessarily a per-session cooldown, not a per-message one.
39
- *
40
- * Suspended and user-interrupted states BYPASS the cooldown entirely: those are
41
- * invariant-10 operational notices that must appear in full on every part.
35
+ * Suspended and user-interrupted states BYPASS the cooldown: those are
36
+ * invariant-10 operational notices that must not be downgraded to the short
37
+ * marker. They are still subject to the per-message dedupe — "always visible"
38
+ * means visible on every user-facing turn, not repeated on every part of it.
42
39
  * Overridable via `createPrWorkflowResponseGate({ bannerCooldownMs })`.
43
40
  */
44
41
  export declare const DEFAULT_BANNER_COOLDOWN_MS = 20000;
42
+ /**
43
+ * Absolute per-session injection ceiling used ONLY when the host does not
44
+ * supply `messageID`. The pinned host contract
45
+ * (`@opencode-ai/plugin` `index.d.ts`: `experimental.text.complete` receives
46
+ * `{ sessionID, messageID, partID }`, all required) always supplies it, so this
47
+ * path is defensive. Without a ceiling, a host that omitted `messageID` would
48
+ * fall back to the wall-clock window alone — which is exactly the behavior that
49
+ * produced the measured flood (968 marker-only lines, 55.3% of a real review
50
+ * transcript), because a cooldown only downgrades an injection, it never
51
+ * suppresses one.
52
+ */
53
+ export declare const MAX_FALLBACK_BANNER_INJECTIONS_PER_SESSION = 20;
45
54
  /**
46
55
  * Bounded FIFO map of tracked wake budgets. Invariant 8 (session/global
47
- * state): module-level state must have an explicit eviction strategy. The
48
- * banner-stamp map (last full-banner instant per session) shares this bound.
56
+ * state): module-level state must have an explicit eviction strategy. Three
57
+ * further per-session maps share this same bound and the same FIFO discipline:
58
+ * `bannerStamps` (instant of the last full banner), `banneredMessages` (the
59
+ * assistant message already carrying a banner), and `fallbackInjections` (the
60
+ * injection count on the messageID-less path).
49
61
  */
50
62
  export declare const MAX_TRACKED_WAKE_SESSIONS = 200;
51
63
  interface PrWorkflowResponseGateClient {
@@ -59,19 +71,23 @@ interface WakeBudget {
59
71
  }
60
72
  /**
61
73
  * Prevent an architect from masquerading a premature text output as a terminal
62
- * verdict. The text-complete hook prepends a workflow-active banner to
63
- * architect-session text parts while the durable gate exists; the model's
64
- * original text is preserved below the banner. To avoid spamming the full
65
- * multi-line banner across a burst of reasoning parts, the full banner is
66
- * throttled per session (see DEFAULT_BANNER_COOLDOWN_MS) within the cooldown a
67
- * short one-line marker is prepended instead. session.idle then mechanically
68
- * resumes that session.
74
+ * verdict. The text-complete hook prepends a workflow-active banner to the
75
+ * FIRST substantive text part of each architect message while the durable gate
76
+ * exists; the model's original text is preserved below the banner.
77
+ *
78
+ * Injection is bounded on three independent axes, because a banner that is
79
+ * merely throttled is still injected on every part:
80
+ * 1. blank parts are never decorated (a banner labelling no content is noise);
81
+ * 2. a part that already opens with a banner is never re-decorated;
82
+ * 3. at most ONE injection per assistant `messageID`.
83
+ * The wall-clock cooldown (see DEFAULT_BANNER_COOLDOWN_MS) then only chooses
84
+ * whether that single injection is the full banner or the short marker.
69
85
  *
70
- * The resume loop is bounded (see DEFAULT_MAX_CONSECUTIVE_UNPRODUCTIVE_WAKES)
71
- * so a session that cannot make progress suspends instead of spinning
72
- * forever. Suspended and user-interrupted parts bypass the cooldown and always
73
- * carry the full suspension/interruption recovery notices so the user-visible
74
- * surface always names the recovery path.
86
+ * session.idle mechanically resumes a gated session. The resume loop is bounded
87
+ * (see DEFAULT_MAX_CONSECUTIVE_UNPRODUCTIVE_WAKES) so a session that cannot make
88
+ * progress suspends instead of spinning forever. Suspended and user-interrupted
89
+ * messages bypass the cooldown so their recovery notices are never downgraded to
90
+ * the short marker.
75
91
  */
76
92
  export declare function createPrWorkflowResponseGate(options: {
77
93
  directory: string;
@@ -86,6 +102,8 @@ export declare function createPrWorkflowResponseGate(options: {
86
102
  }) => Promise<void>;
87
103
  textComplete: (input: {
88
104
  sessionID?: string;
105
+ messageID?: string;
106
+ partID?: string;
89
107
  }, output: {
90
108
  text: string;
91
109
  }) => Promise<void>;
@@ -4,8 +4,8 @@
4
4
  * Parses the mandated reviewer OUTPUT FORMAT (`VERDICT:` / `RISK:` /
5
5
  * `ISSUES:` / `FIXES:`) from a returning reviewer Task delegation and
6
6
  * persists it as a durable review receipt under `.swarm/review-receipts/`
7
- * via the existing receipt store (scope-fingerprinted over the delegation
8
- * prompt, which defines the reviewed scope).
7
+ * via the existing receipt store. Scope is derived from guardrails-observed
8
+ * modified files and their current content, never architect-authored prose.
9
9
  *
10
10
  * Before this collector, reviewer verdicts existed only as free text inside
11
11
  * the architect's context — re-reviews and drift verification had no durable
@@ -15,7 +15,14 @@
15
15
  *
16
16
  * Fail-open: parsing or persistence failures never block tool execution.
17
17
  */
18
- export type ParsedReviewSeverity = 'critical' | 'high' | 'medium';
18
+ import { type ReviewFinding } from '../agents/agent-output-schema.js';
19
+ import { type AutoReviewConfig } from '../config/schema.js';
20
+ import type { ReviewModelDispatcher } from '../review/contracts.js';
21
+ import { type FindingValidationScheduler } from '../review/finding-validator.js';
22
+ import { type ReviewAgentModelRegistry } from '../review/runtime.js';
23
+ import { persistReviewReceipt, type ReviewFindingSeverity, updateReviewReceiptValidations } from './review-receipt.js';
24
+ import { buildReviewerTaskScope, resolveReviewerTaskScope } from './review-receipt-scope.js';
25
+ export type ParsedReviewSeverity = ReviewFindingSeverity;
19
26
  export interface ParsedReviewIssue {
20
27
  /** Raw issue line (trimmed, bullet stripped) */
21
28
  text: string;
@@ -23,6 +30,8 @@ export interface ParsedReviewIssue {
23
30
  severity: ParsedReviewSeverity;
24
31
  /** `path:line` reference when one appears in the line */
25
32
  location?: string;
33
+ /** Machine-readable source finding, when structured output parsed. */
34
+ finding?: ReviewFinding;
26
35
  }
27
36
  export interface ParsedReviewerOutput {
28
37
  verdict: 'approved' | 'rejected';
@@ -32,30 +41,64 @@ export interface ParsedReviewerOutput {
32
41
  issues: ParsedReviewIssue[];
33
42
  /** Required-change lines from the FIXES section */
34
43
  fixes: string[];
44
+ outputMode: 'structured' | 'legacy';
45
+ overallConfidence?: number;
46
+ structuredFindings?: ReviewFinding[];
35
47
  }
36
48
  /**
37
49
  * Parse the reviewer agent's mandated output block. Returns null when no
38
- * unambiguous line-anchored `VERDICT: APPROVED|REJECTED` is present
39
- * including when multiple anchored verdict lines DISAGREE (ambiguous output
40
- * fails toward "no machine-readable verdict", never toward approval).
50
+ * single unambiguous line-anchored `VERDICT: APPROVED|REJECTED` is present,
51
+ * including missing, duplicate, or contradictory verdict lines. A structured
52
+ * verdict must match that single legacy line so ambiguous output fails toward
53
+ * "no machine-readable verdict", never toward approval.
41
54
  */
42
- export declare function parseReviewerOutput(text: string): ParsedReviewerOutput | null;
55
+ export declare function parseReviewerOutput(text: string, options?: {
56
+ structured?: boolean;
57
+ }): ParsedReviewerOutput | null;
43
58
  export interface ReviewerReceiptInput {
44
59
  tool: unknown;
45
60
  args?: unknown;
46
61
  sessionID?: unknown;
62
+ callID?: unknown;
47
63
  }
48
64
  export interface ReviewerReceiptOutput {
49
65
  output?: unknown;
66
+ state?: unknown;
67
+ status?: unknown;
68
+ error?: unknown;
69
+ errors?: unknown;
70
+ metadata?: unknown;
71
+ result?: unknown;
50
72
  }
51
73
  export interface ReviewerReceiptTranscriptInput {
52
74
  targetAgent?: string;
53
- prompt: string;
75
+ prompt?: string;
54
76
  transcript: string;
55
77
  sessionID?: string;
56
78
  sessionId?: string;
79
+ taskId?: string;
80
+ reviewerCallID?: string;
81
+ /** Production collectors consume the exact claimed coder generation. */
82
+ consumeHandoff?: boolean;
57
83
  }
58
- export declare function collectReviewerReceiptFromTranscript(directory: string, input: ReviewerReceiptTranscriptInput): Promise<string | null>;
84
+ export interface ReviewerReceiptValidationOptions {
85
+ dispatcher?: ReviewModelDispatcher;
86
+ config?: AutoReviewConfig;
87
+ generatedAgentNames?: Iterable<string>;
88
+ agentModelRegistry?: ReviewAgentModelRegistry;
89
+ injectAdvisory?: (sessionID: string, message: string) => void;
90
+ /** Shared only within the owning plugin instance. */
91
+ validationScheduler?: FindingValidationScheduler;
92
+ }
93
+ /** Test seam for the otherwise process-global telemetry sink. */
94
+ export declare const _internals: {
95
+ delegationEnd: (sessionId: string, agentName: string, taskId: string, result: string, costFields?: Partial<import("../services/cost-accounting.js").DelegationCostFields>) => void;
96
+ resolveReviewerTaskScope: typeof resolveReviewerTaskScope;
97
+ buildReviewerTaskScope: typeof buildReviewerTaskScope;
98
+ persistReviewReceipt: typeof persistReviewReceipt;
99
+ updateReviewReceiptValidations: typeof updateReviewReceiptValidations;
100
+ };
101
+ export declare function collectReviewerReceiptFromTranscript(directory: string, input: ReviewerReceiptTranscriptInput, validationOptions?: ReviewerReceiptValidationOptions): Promise<string | null>;
59
102
  /**
60
103
  * `tool.execute.after` collector. When a reviewer Task returns, parse its
61
104
  * verdict block and persist a durable review receipt. No-op for non-reviewer
@@ -63,4 +106,4 @@ export declare function collectReviewerReceiptFromTranscript(directory: string,
63
106
  *
64
107
  * Returns the persisted receipt path (for tests/telemetry) or null.
65
108
  */
66
- export declare function collectReviewerReceiptAfter(directory: string, input: ReviewerReceiptInput, output: ReviewerReceiptOutput): Promise<string | null>;
109
+ export declare function collectReviewerReceiptAfter(directory: string, input: ReviewerReceiptInput, output: ReviewerReceiptOutput, validationOptions?: ReviewerReceiptValidationOptions): Promise<string | null>;