opencode-swarm 7.181.2 → 7.181.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 (89) hide show
  1. package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
  2. package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
  3. package/dist/cli/{coder-settlement-993vdsd2.js → coder-settlement-vbs5rj3z.js} +10 -10
  4. package/dist/cli/{config-doctor-nr27vfv6.js → config-doctor-3cb51z08.js} +2 -2
  5. package/dist/cli/{core-t3tg4397.js → core-0h7y8z0s.js} +2 -2
  6. package/dist/cli/{curation-policy-m3x9mkj9.js → curation-policy-r4dkajw9.js} +4 -4
  7. package/dist/cli/{curator-gk392717.js → curator-9sn55eyk.js} +34 -34
  8. package/dist/cli/curator-drift-d4jkgrba.js +37 -0
  9. package/dist/cli/{curator-llm-factory-n01we8pa.js → curator-llm-factory-zwc2gpe7.js} +34 -34
  10. package/dist/cli/{evidence-summary-service-fkvj8ct1.js → evidence-summary-service-sb5seqs3.js} +14 -14
  11. package/dist/cli/{gate-evidence-1n6zffzb.js → gate-evidence-emq2y7kz.js} +5 -5
  12. package/dist/cli/{guardrail-explain-81jvm99e.js → guardrail-explain-a3hcrpwv.js} +35 -35
  13. package/dist/cli/{guardrail-log-mw9x5j8g.js → guardrail-log-ftv03v4x.js} +6 -6
  14. package/dist/cli/{guardrail-reset-6thsn6ad.js → guardrail-reset-d6qmnvff.js} +34 -34
  15. package/dist/cli/{hive-promoter-pym6se37.js → hive-promoter-pktjn46n.js} +34 -34
  16. package/dist/cli/{index-m65tnv4j.js → index-01r2wrwa.js} +1 -1
  17. package/dist/cli/{index-txx2mrsk.js → index-0ea0c676.js} +25 -11
  18. package/dist/cli/{index-gmqf6bjj.js → index-12fxtcce.js} +36 -36
  19. package/dist/cli/{index-cr61qv27.js → index-1epynxc4.js} +2 -2
  20. package/dist/cli/{index-1wpnt86a.js → index-1msszzg2.js} +5 -5
  21. package/dist/cli/{index-12ewkms0.js → index-23mdeh2a.js} +1 -1
  22. package/dist/cli/{index-5hxv1er2.js → index-3ga3ts3h.js} +56 -3
  23. package/dist/cli/{index-38cfx8g8.js → index-427x3j6p.js} +2 -2
  24. package/dist/cli/{index-wph45kyb.js → index-4qpz7mpn.js} +4 -4
  25. package/dist/cli/{index-7nr9p5bd.js → index-54sd9bbn.js} +5 -5
  26. package/dist/cli/{index-fmhd3zc8.js → index-68ben2q8.js} +2 -2
  27. package/dist/cli/{index-n5a9dq5s.js → index-6jrn46zy.js} +1 -1
  28. package/dist/cli/{index-ernt0wrg.js → index-9eg8f4zm.js} +4306 -1504
  29. package/dist/cli/{index-hbe56wtn.js → index-9s2rp2j5.js} +2 -2
  30. package/dist/cli/{index-gc6h1fmf.js → index-a2hvmc3a.js} +1 -1
  31. package/dist/cli/{index-ea9fk6v0.js → index-ap6hs8fj.js} +1 -1
  32. package/dist/cli/{index-92m05mew.js → index-by9wfeja.js} +1 -1
  33. package/dist/cli/{index-et28q7cp.js → index-dkprb20b.js} +1 -1
  34. package/dist/cli/{index-qw9aqays.js → index-dwc1wztn.js} +6 -6
  35. package/dist/cli/{index-0h39trak.js → index-enmvd9ap.js} +1 -1
  36. package/dist/cli/{index-kzmd4gs7.js → index-epq8am78.js} +1 -1
  37. package/dist/cli/{index-pqtmzee6.js → index-fsye9ygy.js} +4 -4
  38. package/dist/cli/{index-5q3xsqwm.js → index-gtpq64q7.js} +3 -3
  39. package/dist/cli/{index-5v8zws0s.js → index-hqa71xfm.js} +140 -9
  40. package/dist/cli/{index-5zy5k5w0.js → index-j97nc02q.js} +4 -4
  41. package/dist/cli/{index-bdpgfep1.js → index-jrxrjz89.js} +1 -1
  42. package/dist/cli/{index-516j9vsk.js → index-kkrbybgt.js} +27 -2
  43. package/dist/cli/{index-6mwha20t.js → index-mkpdcbye.js} +3 -3
  44. package/dist/cli/{index-6x36hhd3.js → index-myacmqmf.js} +34 -34
  45. package/dist/cli/{index-t93cp2tz.js → index-nw3z614g.js} +4 -4
  46. package/dist/cli/{index-ehvkz2zv.js → index-r4vve32m.js} +7 -7
  47. package/dist/cli/{index-m779twtf.js → index-rgfz80gt.js} +1 -1
  48. package/dist/cli/{index-6gcxgx84.js → index-s34y6gbh.js} +1 -1
  49. package/dist/cli/{index-s0zgz4p1.js → index-sf51gh68.js} +1 -1
  50. package/dist/cli/{index-2sqfrvxj.js → index-v4h99xhr.js} +2 -2
  51. package/dist/cli/{index-vmwmwahh.js → index-x4am0jxt.js} +3 -3
  52. package/dist/cli/{index-et7tg13p.js → index-y03jadcy.js} +269 -9
  53. package/dist/cli/{index-dpxg347z.js → index-y28np6j9.js} +3 -3
  54. package/dist/cli/{index-xz4q5vcg.js → index-zp6fss8c.js} +3 -3
  55. package/dist/cli/index.js +35 -35
  56. package/dist/cli/{knowledge-escalator-jmqbmfbf.js → knowledge-escalator-5q4y97e4.js} +11 -11
  57. package/dist/cli/{knowledge-events-5ksqpf4g.js → knowledge-events-x7q8yepd.js} +9 -9
  58. package/dist/cli/{knowledge-link-s2qs178z.js → knowledge-link-655xa3db.js} +3 -3
  59. package/dist/cli/{knowledge-store-q3yy56sc.js → knowledge-store-r7ng2nf8.js} +4 -4
  60. package/dist/cli/{knowledge-validator-r5kaz53f.js → knowledge-validator-e78m6cqe.js} +6 -6
  61. package/dist/cli/{mcp-h82hxnxr.js → mcp-208jwxps.js} +1 -1
  62. package/dist/cli/model-preflight-xakn1356.js +359 -0
  63. package/dist/cli/{pending-delegations-e4h6vjeb.js → pending-delegations-w4a5tg11.js} +7 -7
  64. package/dist/cli/{pr-subscriptions-j7hw3k6x.js → pr-subscriptions-8289esc9.js} +7 -7
  65. package/dist/cli/{pr-workflow-gate-js8dcry2.js → pr-workflow-gate-b7yx0x21.js} +34 -34
  66. package/dist/cli/{runner-tzgwvvn1.js → runner-b5vt1any.js} +4 -4
  67. package/dist/cli/{scan-cursor-yhf58mq2.js → scan-cursor-edpbzp72.js} +5 -5
  68. package/dist/cli/{schema-0k4st1qt.js → schema-dnsxhyr3.js} +1 -1
  69. package/dist/cli/{scope-persistence-bpptmh1e.js → scope-persistence-z1f7d4tw.js} +10 -10
  70. package/dist/cli/{server-axnran6h.js → server-t2edntv4.js} +69 -599
  71. package/dist/cli/{skill-generator-n2w032d1.js → skill-generator-5n7vf0s3.js} +13 -13
  72. package/dist/cli/{snapshot-coordination-init-kyf50x12.js → snapshot-coordination-init-t14dk9je.js} +34 -34
  73. package/dist/cli/{speckit-checkoff-m4184e1x.js → speckit-checkoff-4ws84g13.js} +3 -3
  74. package/dist/cli/{telemetry-zhcgfe70.js → telemetry-6k8t2mrw.js} +1 -1
  75. package/dist/cli/{workspace-snapshot-4m2ex1ey.js → workspace-snapshot-hsssm50w.js} +2 -2
  76. package/dist/cli/{worktree-collision-ownership-1g2414pf.js → worktree-collision-ownership-wj6337ec.js} +8 -8
  77. package/dist/cli/{worktree-isolation-x07f7cfp.js → worktree-isolation-9fyj85tc.js} +34 -34
  78. package/dist/commands/registry.d.ts +1 -1
  79. package/dist/config/agent-model.d.ts +14 -0
  80. package/dist/config/bundled-skill-dispositions.d.ts +29 -0
  81. package/dist/hooks/delegation-gate.d.ts +10 -8
  82. package/dist/hooks/task-id-resolver.d.ts +7 -0
  83. package/dist/index.js +285 -269
  84. package/dist/memory/index.d.ts +1 -0
  85. package/dist/memory/instruction-pairing.d.ts +112 -0
  86. package/dist/services/model-preflight.d.ts +103 -21
  87. package/package.json +1 -1
  88. package/dist/cli/curator-drift-hz3j3fad.js +0 -297
  89. package/dist/cli/model-preflight-5b81adsj.js +0 -170
@@ -7,6 +7,7 @@ export type { MemoryGatewayOptions, ProposeMemoryInput, RecallMemoryInput, Recor
7
7
  export { createConfiguredMemoryProvider, createConfiguredMemoryProviderForRoot, createMemoryGateway, MemoryGateway, } from './gateway';
8
8
  export { type HeldoutRecallCorpusCase, type HeldoutRecallCorpusManifest, type LoadedHeldoutRecallScenario, loadHeldoutRecallEvaluationCorpus, loadHeldoutRecallEvaluationScenarios, validateHeldoutRecallCorpusManifest, } from './heldout-evaluation-corpus';
9
9
  export { createMemoryLifecycleHooks, type MemoryLifecycleHookOptions, type MemoryLifecycleHooks, } from './injector';
10
+ export { DEFAULT_INSTRUCTION_PAIRING_TASKS, type InstructionPairingArmResult, type InstructionPairingDirectivePriority, type InstructionPairingOptions, type InstructionPairingPair, type InstructionPairingRecord, type InstructionPairingReport, type InstructionPairingTask, runInstructionSelectionPairing, } from './instruction-pairing';
10
11
  export { backupLegacyJsonl, getLegacyJsonlFileStatus, getLegacyOutcomeJsonlSignature, type JsonlBackupResult, type JsonlImportPayload, type JsonlInvalidRow, type JsonlMigrationReport, LEGACY_JSONL_MIGRATION_NAME, LEGACY_JSONL_MIGRATION_VERSION, LEGACY_JSONL_OUTCOME_META_KEY, readLegacyJsonl, readMigrationReport, resolveMemoryStorageDir, resolveSqliteDatabasePath, writeJsonlExport, writeMigrationReport, } from './jsonl-migration';
11
12
  export { LocalJsonlMemoryProvider } from './local-jsonl-provider';
12
13
  export { buildMemoryMaintenanceReport, type MemoryMaintenanceReport, type MemoryMaintenanceReportOptions, type MemoryRecallUsageByMemory, type MemoryRecallUsageByRole, type MemorySupersededChain, shouldCompactMemory, } from './maintenance';
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Paired cached-vs-uncached outcome evidence for instruction selection (#2672).
3
+ *
4
+ * Runs the REAL knowledge-injector hook (the plugin's instruction-selection and
5
+ * instruction-cache surface) over deterministic, offline fixture tasks and
6
+ * reports paired per-task outcomes: quality (which directive labels survive
7
+ * selection), latency per arm, cache reads, uncached cost, and the rendered
8
+ * prefix length at the host-renderable carrier boundary.
9
+ *
10
+ * Contract discipline (issue #2672):
11
+ * - Same task/model/budget on both arms; the uncached arm is the regeneration
12
+ * reference and the cached arm replays byte-identical context on a warm
13
+ * per-instance cache.
14
+ * - Negative results are RETAINED rows (`negative_result: true` when the
15
+ * cached arm did not improve the paired quality outcome). On the offline
16
+ * deterministic corpus both arms run the same selection algorithm over the
17
+ * same records, so `quality_outcome` is 'identical' by design — the honest
18
+ * "caching did not change instruction quality" negative result.
19
+ * - NO percentage or savings claim exists anywhere in the report: deltas are
20
+ * absolute numbers and every quantity names its denominator in `measurement`.
21
+ * - `identity.instruction_set_digest` is the evidence handle a HarnessOpt
22
+ * lineage record (#2503) can reference; this module never modifies the
23
+ * harness-optimizer — #2503 remains the broad held-out comparison owner.
24
+ */
25
+ export type InstructionPairingDirectivePriority = 'critical' | 'high' | 'medium' | 'low';
26
+ export interface InstructionPairingRecord {
27
+ /**
28
+ * Stable identifier recovered from the rendered prefix. Constrained to
29
+ * `[A-Za-z0-9-]` (PRR-012): other characters still reach the fixture
30
+ * store verbatim but can never be matched back by the sentinel pattern,
31
+ * silently undercounting `quality.expected_hit_count` for that record.
32
+ */
33
+ label: string;
34
+ lesson: string;
35
+ directivePriority?: InstructionPairingDirectivePriority;
36
+ tags?: string[];
37
+ }
38
+ export interface InstructionPairingTask {
39
+ id: string;
40
+ lastUserMessage: string;
41
+ expectedLabels: string[];
42
+ records: InstructionPairingRecord[];
43
+ }
44
+ export interface InstructionPairingOptions {
45
+ /**
46
+ * Project directory context. Fixture stores live in disposable temp roots
47
+ * (never under the caller's `.swarm/`); when `writeReport` is set, the
48
+ * durable report artifact is written under this directory's
49
+ * `.swarm/memory/`.
50
+ */
51
+ directory: string;
52
+ /** Omitted falls back to the deterministic DEFAULT_INSTRUCTION_PAIRING_TASKS. */
53
+ tasks?: InstructionPairingTask[];
54
+ modelId?: string;
55
+ injectCharBudget?: number;
56
+ /** Explicit report destination; overrides the `writeReport` default path. */
57
+ reportPath?: string;
58
+ /** Write the report to `<directory>/.swarm/memory/instruction-pairing-report.json`. */
59
+ writeReport?: boolean;
60
+ }
61
+ export interface InstructionPairingArmResult {
62
+ latency_ms: number;
63
+ /** Injections served from the warm context cache in the measured invocation (0 or 1). */
64
+ cache_reads: number;
65
+ /** How cache_reads was determined ('events-file-delta' = observed, 'protocol-assumed' = identical-context replay). */
66
+ cache_reads_basis: string;
67
+ rendered_prefix_chars: number;
68
+ /** The paired uncached arm's regeneration latency, reported on both arms (paired attribution, absolute ms). */
69
+ uncached_cost: number;
70
+ quality: {
71
+ expected_hit_count: number;
72
+ expected_total: number;
73
+ selected_labels: string[];
74
+ };
75
+ }
76
+ export interface InstructionPairingPair {
77
+ task: {
78
+ id: string;
79
+ };
80
+ arm_cached: InstructionPairingArmResult;
81
+ arm_uncached: InstructionPairingArmResult;
82
+ quality_outcome: 'identical' | 'improved' | 'degraded';
83
+ negative_result: boolean;
84
+ }
85
+ export interface InstructionPairingReport {
86
+ schema_version: 1;
87
+ generated_at: string;
88
+ identity: {
89
+ task_set_id: string;
90
+ model_id: string;
91
+ budget_chars: number;
92
+ instruction_set_digest: string;
93
+ };
94
+ pairs: InstructionPairingPair[];
95
+ measurement: {
96
+ latency_denominator: string;
97
+ cost_denominator: string;
98
+ prefix_denominator: string;
99
+ };
100
+ negative_results_retained: boolean;
101
+ cache_invalidation: {
102
+ verified: boolean;
103
+ detail: string;
104
+ };
105
+ }
106
+ /** Deterministic offline corpus used when `options.tasks` is omitted. */
107
+ export declare const DEFAULT_INSTRUCTION_PAIRING_TASKS: InstructionPairingTask[];
108
+ /**
109
+ * Runs the paired cached-vs-uncached control over the task set and returns the
110
+ * report. Deterministic and offline (lexical store, hive disabled, real hook).
111
+ */
112
+ export declare function runInstructionSelectionPairing(options: InstructionPairingOptions): Promise<InstructionPairingReport>;
@@ -1,18 +1,21 @@
1
1
  /**
2
- * Agent model-resolution preflight (issue #2271 bug 4).
2
+ * Agent model-resolution preflight (issue #2271 bug 4; issue #2680).
3
3
  *
4
- * Agent model strings (DEFAULT_MODELS plus user `agents.<role>.model`
5
- * overrides) are handed to the OpenCode host unvalidated. When one does not
6
- * resolve ("Model not found", "Forbidden"), dispatch-lanes classifies the
7
- * failure as permanent, the affected agent never runs, and the plan-critic
8
- * gate wedges into manual `approve_plan_critic` overrides because no APPROVED
9
- * verdict is ever produced.
4
+ * Agent model strings (DEFAULT_MODELS plus user `agents.<role>.model` /
5
+ * `swarms.<id>.agents.<role>.model` overrides) are handed to the OpenCode host
6
+ * unvalidated. When one does not resolve ("Model not found", "Forbidden"),
7
+ * dispatch-lanes classifies the failure as permanent, the affected agent never
8
+ * runs, and the plan-critic gate wedges into manual `approve_plan_critic`
9
+ * overrides because no APPROVED verdict is ever produced.
10
10
  *
11
- * This module validates configured model IDs against the live provider
12
- * catalog (`client.provider.list()`). Every check is FAIL-OPEN: if the catalog
13
- * is unreachable or malformed, results come back `unknown` and callers must
14
- * proceed the preflight only acts on a POSITIVE confirmation that a model
15
- * does not resolve.
11
+ * This module validates the FINAL effective model selection for every ENABLED
12
+ * legacy and prefixed multi-swarm role (issue #2680) against the live provider
13
+ * catalog (`client.provider.list()`), reusing the exact-name resolution
14
+ * helpers from src/config/agent-model.ts. Every check is FAIL-OPEN: if the
15
+ * catalog is unreachable or malformed, results come back `unknown` and callers
16
+ * must proceed — the preflight only acts on a POSITIVE confirmation that a
17
+ * model does not resolve (or that no final selection exists at all, the
18
+ * catalog-independent `missing-selection` class).
16
19
  */
17
20
  import type { OpencodeClient } from '@opencode-ai/sdk';
18
21
  import type { PluginConfig } from '../config/schema.js';
@@ -21,12 +24,17 @@ export declare const PROVIDER_LIST_TIMEOUT_MS = 2000;
21
24
  export interface ConfiguredAgentModel {
22
25
  /** Agent role the model is configured for. */
23
26
  agent: string;
24
- /** Full model id in `provider/model` form. */
27
+ /** Full model id in `provider/model` form. Empty when no final selection exists. */
25
28
  model: string;
26
29
  /** Where the value came from. */
27
30
  source: 'default' | 'override' | 'fallback';
31
+ /**
32
+ * Issue #2680: whether the entry's role is registered as a primary agent
33
+ * (OpenCode's UI owns its model selection) or a dispatchable subagent.
34
+ */
35
+ mode?: 'primary' | 'subagent';
28
36
  }
29
- export type ModelResolutionStatus = 'ok' | 'unresolved' | 'unknown';
37
+ export type ModelResolutionStatus = 'ok' | 'unresolved' | 'unknown' | 'missing-selection' | 'host-controlled';
30
38
  export interface ModelResolution {
31
39
  agent: string;
32
40
  model: string;
@@ -34,6 +42,7 @@ export interface ModelResolution {
34
42
  status: ModelResolutionStatus;
35
43
  /** Human-readable explanation for non-ok statuses. */
36
44
  detail?: string;
45
+ mode?: ConfiguredAgentModel['mode'];
37
46
  }
38
47
  export interface ModelPreflightResult {
39
48
  /** False when the catalog could not be fetched — every entry is `unknown`. */
@@ -53,29 +62,102 @@ export declare function splitModelId(model: string): {
53
62
  * Collect every model id the swarm could dispatch with: DEFAULT_MODELS
54
63
  * entries, user agent overrides, and declared fallback_models. Overriding one
55
64
  * role replaces its default (mirrors getModelForAgent precedence).
65
+ *
66
+ * Superseded by collectEnabledAgentModels (issue #2680): this legacy view
67
+ * enumerates the top-level config plus a blanket DEFAULT_MODELS pass and
68
+ * cannot see multi-swarm per-swarm selections or enabled-role gating. Retained
69
+ * unchanged for API compatibility; runModelPreflight no longer consumes it.
56
70
  */
57
71
  export declare function collectConfiguredAgentModels(config?: PluginConfig): ConfiguredAgentModel[];
72
+ /**
73
+ * Issue #2680: compute the generated agent names the plugin would register for
74
+ * this configuration — the "enabled role" domain the preflight must validate.
75
+ * Mirrors createAgents' generation rules (legacy single default swarm vs
76
+ * per-swarm prefixing; object-level top-level merge; disabled exclusion;
77
+ * feature-gated roles only while their flag is on). The live
78
+ * swarmState.generatedAgentNames registry stays authoritative when available;
79
+ * this computation is the config-only fallback (doctor, unit tests) and is
80
+ * drift-guarded by the parity test.
81
+ */
82
+ export declare function enumerateEnabledAgentRoles(config?: PluginConfig): string[];
83
+ /** Options for the enabled-role preflight (issue #2680). */
84
+ export interface EnabledAgentPreflightOptions {
85
+ /**
86
+ * The live registry of generated agent names the host actually registered
87
+ * (swarmState.generatedAgentNames). Authoritative when provided; the
88
+ * config-only enumeration is the fallback.
89
+ */
90
+ generatedAgentNames?: readonly string[];
91
+ /**
92
+ * The registered agents map (name → { model, mode }) as produced by
93
+ * createAgents/getAgentConfigs. When provided, `mode` comes from it;
94
+ * otherwise primaries are derived via resolvePrimaryAgentNames.
95
+ */
96
+ registeredAgents?: Record<string, {
97
+ model?: unknown;
98
+ mode?: unknown;
99
+ }>;
100
+ }
101
+ /**
102
+ * Issue #2680: collect the FINAL effective model selection for every enabled
103
+ * legacy and prefixed multi-swarm role, plus each role's explicitly configured
104
+ * fallback entries. Final selections are computed with the shipped exact-name
105
+ * helpers (resolveRegisteredAgentModel / resolveConfiguredAgentModel) so
106
+ * defaults, swarm prefixes, and the object-level merge match dispatch reality.
107
+ *
108
+ * Outcome classes (see resolveAgainstCatalog):
109
+ * - an explicitly configured but blank model, or a registry name that resolves
110
+ * to no target at all, yields an EMPTY model — the distinct
111
+ * `missing-selection` class (never silently treated as a successful default);
112
+ * - primary roles are tagged `mode: 'primary'` → `host-controlled` (OpenCode's
113
+ * UI owns their selection);
114
+ * - fallback entries appear only when explicitly configured and recorded —
115
+ * never synthesized (curator roles inherit explorer's chain at runtime via
116
+ * resolveFallbackModel; that inheritance is documented, not duplicated here).
117
+ */
118
+ export declare function collectEnabledAgentModels(config?: PluginConfig, opts?: EnabledAgentPreflightOptions): ConfiguredAgentModel[];
58
119
  /** Provider catalog snapshot: provider id → set of model ids. */
59
120
  export type ProviderCatalog = Map<string, Set<string>>;
60
121
  /** Test/gate seam: drop the cached catalogs (used after config changes and denials). */
61
122
  export declare function invalidateProviderCatalogCache(): void;
123
+ /**
124
+ * PR #2782 review PRR-001: neutralize control characters before interpolating
125
+ * config- or host-sourced strings (model ids, agent names, provider detail)
126
+ * into console.warn / thrown-Error text. Newlines forge multi-line
127
+ * plugin-looking output and ESC sequences drive the terminal; control runs
128
+ * collapse to a single space (same policy as the doctor's stripControlChars).
129
+ */
130
+ export declare function sanitizePreflightText(value: string): string;
62
131
  /**
63
132
  * Fetch the provider catalog. Returns null when the catalog cannot be
64
133
  * obtained or parsed — callers fail open on null.
65
134
  */
66
135
  export declare function fetchProviderCatalog(client: OpencodeClient | null): Promise<ProviderCatalog | null>;
67
136
  /**
68
- * Validate collected models against a catalog. A model whose provider is
69
- * absent from the catalog, or whose provider exists but does not list the
70
- * model, is `unresolved` but only when the catalog itself was successfully
71
- * fetched (callers must treat a null catalog as fail-open, never unresolved).
137
+ * Validate collected models against a catalog. Classification order (issue
138
+ * #2680): an EMPTY model is the catalog-independent `missing-selection` class;
139
+ * a primary role is `host-controlled` (OpenCode's UI owns its selection
140
+ * never a preflight failure and never a consumer-default warning); otherwise
141
+ * the catalog decides — a model whose provider is absent from the catalog, or
142
+ * whose provider exists but does not list the model, is `unresolved` — but
143
+ * only when the catalog itself was successfully fetched (callers must treat a
144
+ * null catalog as fail-open, never unresolved).
72
145
  */
73
146
  export declare function resolveAgainstCatalog(models: ConfiguredAgentModel[], catalog: ProviderCatalog | null): ModelPreflightResult;
74
147
  /**
75
- * End-to-end preflight: collect configured models, fetch the catalog, and
76
- * resolve. Fail-open on every catalog error.
148
+ * Issue #2680: render the operator warning for a preflight result. Reports
149
+ * ONLY actionable classes (`unresolved`, `missing-selection`) — ok/unknown
150
+ * entries and host-controlled primaries stay silent, so disabled, optional,
151
+ * and merely-uninvoked roles never produce consumer-default noise. Returns
152
+ * null when there is nothing to report.
153
+ */
154
+ export declare function formatModelPreflightWarning(result: ModelPreflightResult): string | null;
155
+ /**
156
+ * End-to-end preflight: collect the FINAL effective selection for every
157
+ * enabled role (issue #2680), fetch the catalog, and resolve. Fail-open on
158
+ * every catalog error.
77
159
  */
78
- export declare function runModelPreflight(config: PluginConfig | undefined, client: OpencodeClient | null): Promise<ModelPreflightResult>;
160
+ export declare function runModelPreflight(config: PluginConfig | undefined, client: OpencodeClient | null, opts?: EnabledAgentPreflightOptions): Promise<ModelPreflightResult>;
79
161
  /**
80
162
  * Resolves a single model id against the live catalog for gate preflights.
81
163
  * Returns 'unknown' whenever the catalog cannot be fetched — gates must only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.181.2",
3
+ "version": "7.181.4",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,297 +0,0 @@
1
- // @bun
2
- import {
3
- canonicalProjectKey,
4
- getGlobalEventBus,
5
- getGroupCommitWriter,
6
- importLegacyJsonFiles
7
- } from "./index-et7tg13p.js";
8
- import {
9
- readEffectiveSpecSync
10
- } from "./index-m779twtf.js";
11
- import"./index-kzmd4gs7.js";
12
- import {
13
- readSwarmFileAsync
14
- } from "./index-gc6h1fmf.js";
15
- import"./index-2s7dvzff.js";
16
- import"./index-0725jpp7.js";
17
- import {
18
- DURABILITY_CLASSES,
19
- getProjectDb,
20
- projectDbExists
21
- } from "./index-vvgpgcyn.js";
22
- import"./index-rdm3px0g.js";
23
- import"./index-ry16d01s.js";
24
- import"./index-1xnse449.js";
25
- import"./index-c1ggtmpm.js";
26
- import"./index-ejhxgyvm.js";
27
- import"./index-7g4c7s5r.js";
28
- import"./index-989v83pb.js";
29
- import"./index-n5a9dq5s.js";
30
- import"./index-zxdawwc7.js";
31
- import"./index-5hxv1er2.js";
32
- import"./index-2t21pv2v.js";
33
- import"./index-bk5tah7q.js";
34
- import"./index-gknz3bv1.js";
35
- import"./index-zpsr8rpc.js";
36
- import {
37
- init_logger,
38
- warn
39
- } from "./index-zgwm4ryv.js";
40
- import"./index-a76rekgs.js";
41
-
42
- // src/db/phase-report-store.ts
43
- import { readdirSync } from "fs";
44
- import { join } from "path";
45
- var PHASE_REPORT_LEGACY_PREFIXES = {
46
- curator_drift: "drift-report-phase-",
47
- design_doc_drift: "doc-drift-phase-"
48
- };
49
- var importedRoots = new Set;
50
- function hasAnyPhaseReportState(directory) {
51
- if (projectDbExists(directory))
52
- return true;
53
- const root = canonicalProjectKey(directory);
54
- let entries;
55
- try {
56
- entries = readdirSync(join(root, ".swarm"));
57
- } catch {
58
- return false;
59
- }
60
- for (const prefix of Object.values(PHASE_REPORT_LEGACY_PREFIXES)) {
61
- if (entries.some((name) => name.startsWith(prefix) && name.endsWith(".json") && !name.includes(".imported"))) {
62
- return true;
63
- }
64
- }
65
- return false;
66
- }
67
- function kindCount(db, kind) {
68
- return db.query("SELECT COUNT(*) as count FROM phase_report WHERE kind = ?").get(kind)?.count ?? 0;
69
- }
70
- function ensurePhaseReportsImported(directory) {
71
- const root = canonicalProjectKey(directory);
72
- if (importedRoots.has(root))
73
- return;
74
- for (const kind of Object.keys(PHASE_REPORT_LEGACY_PREFIXES)) {
75
- importLegacyJsonFiles(directory, {
76
- filePrefix: PHASE_REPORT_LEGACY_PREFIXES[kind],
77
- kind,
78
- kindCount: (db) => kindCount(db, kind),
79
- upsertRow: (db, phase, payload) => {
80
- db.run(`INSERT INTO phase_report (kind, phase, payload, updated_at)
81
- VALUES (?, ?, ?, datetime('now'))
82
- ON CONFLICT(kind, phase) DO UPDATE SET
83
- payload = excluded.payload,
84
- updated_at = datetime('now')`, [kind, phase, payload]);
85
- }
86
- });
87
- }
88
- importedRoots.add(root);
89
- }
90
- async function upsertPhaseReportDb(directory, kind, phase, payload) {
91
- ensurePhaseReportsImported(directory);
92
- const writer = getGroupCommitWriter(directory);
93
- writer.enqueue({
94
- durability: DURABILITY_CLASSES.phase_report,
95
- run: (db) => {
96
- db.run(`INSERT INTO phase_report (kind, phase, payload, updated_at)
97
- VALUES (?, ?, ?, datetime('now'))
98
- ON CONFLICT(kind, phase) DO UPDATE SET
99
- payload = excluded.payload,
100
- updated_at = datetime('now')`, [kind, phase, payload]);
101
- }
102
- });
103
- await writer.flush();
104
- }
105
- function readPhaseReportsDb(directory, kind) {
106
- if (!hasAnyPhaseReportState(directory))
107
- return [];
108
- ensurePhaseReportsImported(directory);
109
- const db = getProjectDb(directory);
110
- return db.query("SELECT phase, payload FROM phase_report WHERE kind = ? ORDER BY phase").all(kind).map((row) => ({ phase: row.phase, payload: row.payload }));
111
- }
112
- function phaseReportLocator(kind, phase) {
113
- return `swarm.db:phase_report(${kind},${phase})`;
114
- }
115
-
116
- // src/hooks/curator-drift.ts
117
- init_logger();
118
- async function readPriorDriftReports(directory) {
119
- const reports = [];
120
- try {
121
- for (const row of readPhaseReportsDb(directory, "curator_drift")) {
122
- try {
123
- const report = JSON.parse(row.payload);
124
- if (typeof report.phase !== "number" || typeof report.alignment !== "string" || typeof report.timestamp !== "string" || typeof report.drift_score !== "number" || typeof report.schema_version !== "number" || !Array.isArray(report.compounding_effects)) {
125
- warn(`[curator-drift] Skipping corrupt drift report: phase ${row.phase}`);
126
- continue;
127
- }
128
- reports.push(report);
129
- } catch {
130
- warn(`[curator-drift] Skipping unreadable drift report: phase ${row.phase}`);
131
- }
132
- }
133
- } catch {
134
- return [];
135
- }
136
- reports.sort((a, b) => a.phase - b.phase);
137
- return reports;
138
- }
139
- async function writeDriftReport(directory, report) {
140
- const locator = phaseReportLocator("curator_drift", report.phase);
141
- try {
142
- await upsertPhaseReportDb(directory, "curator_drift", report.phase, JSON.stringify(report, null, 2));
143
- } catch (err) {
144
- throw new Error(`[curator-drift] Failed to write drift report to ${locator}: ${String(err)}`);
145
- }
146
- return locator;
147
- }
148
- var _internals = {
149
- readPriorDriftReports,
150
- writeDriftReport,
151
- runDeterministicDriftCheck,
152
- buildDriftInjectionText
153
- };
154
- function extractRequirementIds(text) {
155
- const matches = text.match(/(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)?FR-\d{3}/g);
156
- return matches ? [...new Set(matches)] : [];
157
- }
158
- function bareSuffixCovers(specId, refId) {
159
- return refId === specId || !refId.includes("/") && specId.endsWith(`/${refId}`);
160
- }
161
- async function runDeterministicDriftCheck(directory, phase, curatorResult, config, injectAdvisory) {
162
- try {
163
- const planMd = await readSwarmFileAsync(directory, "plan.md");
164
- const specMd = readEffectiveSpecSync(directory)?.content ?? null;
165
- const priorReports = await _internals.readPriorDriftReports(directory);
166
- const complianceCount = curatorResult.compliance.length;
167
- const warningCompliance = curatorResult.compliance.filter((obs) => obs.severity === "warning");
168
- let alignment = "ALIGNED";
169
- let driftScore = 0;
170
- const specRequirements = specMd ? extractRequirementIds(specMd) : [];
171
- const planRequirements = planMd ? extractRequirementIds(planMd) : [];
172
- const digestRequirements = extractRequirementIds(JSON.stringify(curatorResult.digest) + JSON.stringify(curatorResult.compliance) + JSON.stringify(curatorResult.knowledge_recommendations ?? []));
173
- if (!planMd) {
174
- alignment = "MINOR_DRIFT";
175
- driftScore = 0.3;
176
- } else if (specRequirements.length > 0) {
177
- const coveredInPlan = specRequirements.filter((fr) => planRequirements.some((ref) => bareSuffixCovers(fr, ref)));
178
- const coveredInDigest = specRequirements.filter((fr) => digestRequirements.some((ref) => bareSuffixCovers(fr, ref)));
179
- const specCoverageRatio = coveredInPlan.length / specRequirements.length;
180
- const implementationRatio = coveredInDigest.length / specRequirements.length;
181
- if (specCoverageRatio < 0.5) {
182
- alignment = "MAJOR_DRIFT";
183
- driftScore = Math.min(0.9, 0.6 + (1 - specCoverageRatio) * 0.3);
184
- } else if (warningCompliance.length >= 3) {
185
- alignment = "MAJOR_DRIFT";
186
- driftScore = Math.min(0.9, 0.5 + warningCompliance.length * 0.1);
187
- } else if (warningCompliance.length >= 1 || complianceCount >= 3) {
188
- alignment = "MINOR_DRIFT";
189
- driftScore = Math.min(0.49, 0.2 + complianceCount * 0.05);
190
- } else if (implementationRatio < 0.5) {
191
- alignment = "MINOR_DRIFT";
192
- driftScore = Math.min(0.6, 0.2 + (1 - implementationRatio) * 0.3);
193
- }
194
- } else {
195
- if (warningCompliance.length >= 3) {
196
- alignment = "MAJOR_DRIFT";
197
- driftScore = Math.min(0.9, 0.5 + warningCompliance.length * 0.1);
198
- } else if (warningCompliance.length >= 1 || complianceCount >= 3) {
199
- alignment = "MINOR_DRIFT";
200
- driftScore = Math.min(0.49, 0.2 + complianceCount * 0.05);
201
- }
202
- }
203
- const keyCorrections = warningCompliance.map((obs) => obs.description);
204
- const firstDeviation = warningCompliance.length > 0 ? {
205
- phase,
206
- task: "unknown",
207
- description: warningCompliance[0]?.description ?? ""
208
- } : null;
209
- const requirementsChecked = curatorResult.digest.tasks_total;
210
- const requirementsSatisfied = curatorResult.digest.tasks_completed;
211
- const coverageNote = specRequirements.length > 0 ? ` [${digestRequirements.filter((ref) => specRequirements.some((fr) => bareSuffixCovers(fr, ref))).length}/${specRequirements.length} FRs covered]` : "";
212
- const injectionSummaryRaw = `Phase ${phase}: ${alignment} (${driftScore.toFixed(2)})${coverageNote} \u2014 ${firstDeviation ? firstDeviation.description : "all requirements on track"}.${keyCorrections.length > 0 ? `Correction: ${keyCorrections[0] ?? ""}.` : ""}`;
213
- const injectionSummary = injectionSummaryRaw.slice(0, config.drift_inject_max_chars);
214
- const report = {
215
- schema_version: 1,
216
- phase,
217
- timestamp: new Date().toISOString(),
218
- alignment,
219
- drift_score: driftScore,
220
- first_deviation: firstDeviation,
221
- compounding_effects: priorReports.filter((r) => r.alignment !== "ALIGNED").map((r) => `Phase ${r.phase}: ${r.alignment}`).slice(0, 5),
222
- corrections: keyCorrections.slice(0, 5),
223
- requirements_checked: requirementsChecked,
224
- requirements_satisfied: requirementsSatisfied,
225
- scope_additions: [],
226
- injection_summary: injectionSummary
227
- };
228
- const reportPath = await _internals.writeDriftReport(directory, report);
229
- getGlobalEventBus().publish("curator.drift.completed", {
230
- phase,
231
- alignment,
232
- drift_score: driftScore,
233
- report_path: reportPath
234
- });
235
- if (injectAdvisory && alignment !== "ALIGNED" && driftScore > 0) {
236
- try {
237
- const advisoryText = `CURATOR DRIFT DETECTED (phase ${phase}, score ${driftScore.toFixed(2)}): ${injectionSummary.slice(0, 300)}. Review the phase-${phase} drift report stored in .swarm/swarm.db (${phaseReportLocator("curator_drift", phase)}) and address spec alignment before proceeding. Consider running critic_drift_verifier before phase completion to get a proper drift review.`;
238
- injectAdvisory(advisoryText);
239
- } catch {}
240
- }
241
- const injectionText = injectionSummary;
242
- return {
243
- phase,
244
- report,
245
- report_path: reportPath,
246
- injection_text: injectionText
247
- };
248
- } catch (err) {
249
- getGlobalEventBus().publish("curator.error", {
250
- operation: "drift",
251
- phase,
252
- error: String(err)
253
- });
254
- const defaultReport = {
255
- schema_version: 1,
256
- phase,
257
- timestamp: new Date().toISOString(),
258
- alignment: "ALIGNED",
259
- drift_score: 0,
260
- first_deviation: null,
261
- compounding_effects: [],
262
- corrections: [],
263
- requirements_checked: 0,
264
- requirements_satisfied: 0,
265
- scope_additions: [],
266
- injection_summary: `Phase ${phase}: drift analysis unavailable (${String(err)})`
267
- };
268
- return {
269
- phase,
270
- report: defaultReport,
271
- report_path: "",
272
- injection_text: ""
273
- };
274
- }
275
- }
276
- function buildDriftInjectionText(report, maxChars) {
277
- if (maxChars <= 0) {
278
- return "";
279
- }
280
- let text;
281
- if (report.alignment === "ALIGNED" && report.drift_score < 0.1) {
282
- text = `<drift_report>Phase ${report.phase}: ALIGNED, all requirements on track.</drift_report>`;
283
- } else {
284
- const keyFinding = report.first_deviation?.description ?? "no deviation recorded";
285
- const score = report.drift_score ?? 0;
286
- const correctionClause = report.corrections?.[0] ? `Correction: ${report.corrections[0]}.` : "";
287
- text = `<drift_report>Phase ${report.phase}: ${report.alignment} (${score.toFixed(2)}) \u2014 ${keyFinding}. ${correctionClause}</drift_report>`;
288
- }
289
- return text.slice(0, maxChars);
290
- }
291
- export {
292
- writeDriftReport,
293
- runDeterministicDriftCheck,
294
- readPriorDriftReports,
295
- buildDriftInjectionText,
296
- _internals
297
- };