opencode-swarm 7.138.4 → 7.139.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +7 -6
  2. package/dist/cli/{config-doctor-31yppse4.js → config-doctor-tgnr91q3.js} +2 -2
  3. package/dist/cli/{core-65c7rvqf.js → core-g2dfh9fq.js} +1 -1
  4. package/dist/cli/{curation-policy-hs9pc1rp.js → curation-policy-2qnhgdtt.js} +7 -7
  5. package/dist/cli/{curator-drift-jpap6hv4.js → curator-drift-vfh07mqy.js} +5 -2
  6. package/dist/cli/{curator-llm-factory-wm27ra25.js → curator-llm-factory-1ftzece0.js} +28 -28
  7. package/dist/cli/{curator-54n1y4j8.js → curator-nbsag633.js} +28 -28
  8. package/dist/cli/{evidence-summary-service-hrxcf29a.js → evidence-summary-service-kzxr71yd.js} +9 -9
  9. package/dist/cli/{gate-evidence-ecx4djaw.js → gate-evidence-z2b1ymnw.js} +5 -5
  10. package/dist/cli/{guardrail-explain-1729nse8.js → guardrail-explain-vb9k9zwm.js} +29 -29
  11. package/dist/cli/{guardrail-log-yjj5srkw.js → guardrail-log-0djnvfas.js} +3 -3
  12. package/dist/cli/{hive-promoter-es2x2b0e.js → hive-promoter-bpt26yew.js} +28 -28
  13. package/dist/cli/{index-mycqmsz5.js → index-0wp67gqj.js} +452 -159
  14. package/dist/cli/{index-c60whfgh.js → index-0ymhx85c.js} +2 -2
  15. package/dist/cli/{index-5q009kc4.js → index-15526t16.js} +8 -3
  16. package/dist/cli/{index-smsy2p8n.js → index-3eddtgxt.js} +1 -1
  17. package/dist/cli/{index-4fmztnrq.js → index-4xbyy6aw.js} +2 -2
  18. package/dist/cli/{index-xybtaph7.js → index-5j6chvj8.js} +1 -1
  19. package/dist/cli/{index-39k6y018.js → index-5w7ekpy3.js} +2 -2
  20. package/dist/cli/{index-z7jgvm9b.js → index-5zyk8qgv.js} +2 -2
  21. package/dist/cli/{index-en4fb04r.js → index-668dpw5e.js} +1 -1
  22. package/dist/cli/{index-m8txs2a9.js → index-91s653xx.js} +1 -1
  23. package/dist/cli/{index-y28n2wmq.js → index-a12906mj.js} +1 -1
  24. package/dist/cli/{index-10mz5js6.js → index-a5eba0t8.js} +2 -2
  25. package/dist/cli/{index-57stye65.js → index-aymt7hzw.js} +6 -3
  26. package/dist/cli/{index-1g6xpjk3.js → index-db7wf6ke.js} +7 -7
  27. package/dist/cli/{index-m1pwkwfe.js → index-dw09fz3w.js} +1 -1
  28. package/dist/cli/{index-fsrp8wp3.js → index-ed5ykebp.js} +25 -2
  29. package/dist/cli/{index-jbge0dyh.js → index-en6qq954.js} +3 -3
  30. package/dist/cli/{index-tqshdzaw.js → index-gvhvz176.js} +2 -2
  31. package/dist/cli/{index-e1kgxchq.js → index-hgrkhxbk.js} +2 -2
  32. package/dist/cli/{index-33q2gmgd.js → index-hn0mdcmj.js} +1 -1
  33. package/dist/cli/{index-a9xny52r.js → index-ht4q3548.js} +2 -2
  34. package/dist/cli/{index-q344kj84.js → index-prmb5qfp.js} +2 -2
  35. package/dist/cli/{index-x20cgr2q.js → index-q7ezzgwq.js} +16 -2
  36. package/dist/cli/{index-ngqxzkqm.js → index-s608d3hw.js} +2 -2
  37. package/dist/cli/{index-50mwt8fx.js → index-vxbppwvf.js} +5 -5
  38. package/dist/cli/{index-rrxcfxrd.js → index-wrt6m5ze.js} +1 -1
  39. package/dist/cli/{index-4qzeef9h.js → index-x9q4bbhd.js} +1 -1
  40. package/dist/cli/{index-m40ea3zk.js → index-xec724kg.js} +2 -2
  41. package/dist/cli/{index-k27a4e5g.js → index-xka8s8jw.js} +30 -30
  42. package/dist/cli/{index-a18hk2fw.js → index-xr07r80d.js} +513 -483
  43. package/dist/cli/{index-gm3pjfhb.js → index-y1zcsf2a.js} +1 -1
  44. package/dist/cli/{index-tjwz5tct.js → index-ywysg26s.js} +4 -4
  45. package/dist/cli/index.js +28 -28
  46. package/dist/cli/{knowledge-escalator-s6z1x65b.js → knowledge-escalator-pjg2x75c.js} +8 -8
  47. package/dist/cli/{knowledge-events-nnyn6hst.js → knowledge-events-vxheptk8.js} +6 -6
  48. package/dist/cli/{knowledge-link-xc6yhxmz.js → knowledge-link-3tq8w6gs.js} +5 -5
  49. package/dist/cli/{knowledge-store-x4yfc7v3.js → knowledge-store-36mascwz.js} +6 -6
  50. package/dist/cli/{knowledge-validator-1v047gsa.js → knowledge-validator-anats6rj.js} +9 -9
  51. package/dist/cli/{pending-delegations-7dbc0j2a.js → pending-delegations-tmyf18p4.js} +5 -5
  52. package/dist/cli/{pr-subscriptions-m943zn6g.js → pr-subscriptions-hhx6yk24.js} +5 -5
  53. package/dist/cli/{runner-x1hy047y.js → runner-w30gazew.js} +6 -6
  54. package/dist/cli/{scan-cursor-jxajj6s7.js → scan-cursor-apbz9eez.js} +7 -7
  55. package/dist/cli/{schema-fj32eyae.js → schema-x9fr2ey8.js} +3 -1
  56. package/dist/cli/{scope-persistence-w7zen2wz.js → scope-persistence-3f0d4fwv.js} +4 -4
  57. package/dist/cli/{skill-generator-rt0jqcsa.js → skill-generator-20czn4kd.js} +10 -10
  58. package/dist/cli/{telemetry-0yqed3jv.js → telemetry-4tgrfhaz.js} +1 -1
  59. package/dist/cli/{worktree-collision-ownership-hjrt5hx2.js → worktree-collision-ownership-vexb0b6r.js} +38 -8
  60. package/dist/config/context-window.d.ts +210 -0
  61. package/dist/config/schema.d.ts +18 -0
  62. package/dist/evaluation/ephemeral-agent-dispatcher.d.ts +18 -0
  63. package/dist/evaluation/model-dispatcher.d.ts +1 -0
  64. package/dist/hooks/delegate-ack-collector.d.ts +1 -1
  65. package/dist/hooks/delegation-gate/worktree-collision-ownership.d.ts +7 -0
  66. package/dist/hooks/delegation-gate/worktree-isolation.d.ts +2 -1
  67. package/dist/hooks/delegation-gate/worktree-provisioning-owner.d.ts +6 -0
  68. package/dist/hooks/index.d.ts +2 -2
  69. package/dist/hooks/init-orphan-recovery.d.ts +4 -1
  70. package/dist/hooks/knowledge-application-gate.d.ts +2 -1
  71. package/dist/hooks/knowledge-application.d.ts +5 -4
  72. package/dist/hooks/knowledge-injector.d.ts +16 -0
  73. package/dist/hooks/knowledge-types.d.ts +1 -1
  74. package/dist/hooks/messages-transform.d.ts +18 -0
  75. package/dist/hooks/model-limits.d.ts +88 -30
  76. package/dist/index.js +501 -499
  77. package/dist/memory/injector.d.ts +2 -0
  78. package/dist/plan/manager.d.ts +2 -0
  79. package/dist/services/context-budget-service.d.ts +6 -1
  80. package/dist/services/run-memory.d.ts +53 -5
  81. package/dist/services/status-service.d.ts +14 -0
  82. package/dist/state.d.ts +37 -0
  83. package/dist/tools/context-status.d.ts +3 -1
  84. package/dist/tools/update-task-status.d.ts +2 -0
  85. package/dist/turbo/lean/runner.d.ts +10 -0
  86. package/dist/utils/ephemeral-session-teardown.d.ts +98 -0
  87. package/dist/utils/path-security.d.ts +95 -1
  88. package/dist/utils/swarm-artifact-cache.d.ts +13 -0
  89. package/dist/worktree/index.d.ts +1 -1
  90. package/dist/worktree/merge.d.ts +36 -3
  91. package/package.json +1 -1
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Context-window denominator resolution — the SINGLE derivation.
3
+ *
4
+ * Every "how many tokens does this model's context window hold?" question in
5
+ * the plugin resolves here: the context-budget denominator (`budgetTokens` in
6
+ * `src/hooks/system-enhancer.ts` Path A and Path B), the hard-enforcement
7
+ * threshold in `src/hooks/context-budget.ts`, the knowledge-injector headroom
8
+ * gate, the `context_status` tool, and the `/swarm status` render.
9
+ *
10
+ * ## Why this exists
11
+ *
12
+ * The denominator used to be a hardcoded 128 000. That number is stale:
13
+ * current frontier and mid-tier models ship 200k–1M windows, and 256k is
14
+ * common even for locally-hosted models. A budget that reports "87 % used"
15
+ * against a window that is really 1M does not just emit a spurious advisory —
16
+ * `context-budget.ts` HARD-PRUNES messages at `critical_threshold × limit`, so
17
+ * a too-small denominator silently deletes context the model still needed.
18
+ *
19
+ * The authoritative value is already in the host's hands. The
20
+ * `experimental.chat.system.transform` hook input carries
21
+ * `model: Model` (`@opencode-ai/plugin`), and `Model.limit.context`
22
+ * (`@opencode-ai/sdk`) is the live window for THAT provider's entry of THAT
23
+ * model. Provider-specific caps are already baked in, because the host's model
24
+ * catalog is keyed `providers[providerID].models[modelID]` — verified against
25
+ * the on-disk catalog, where `github-copilot`'s `claude-sonnet-4.5` and
26
+ * `anthropic`'s `claude-sonnet-4-5` carry DIFFERENT `limit.context` values.
27
+ * A user's `provider.<id>.models.<id>.limit.context` override in
28
+ * `opencode.json` is merged into the same object before it reaches the hook.
29
+ *
30
+ * ## Resolution order (first usable value wins)
31
+ *
32
+ * 1. `context_budget.model_limits["<providerID>/<modelID>"]`
33
+ * 2. `context_budget.model_limits["<modelID>"]`
34
+ * 3. `context_budget.model_limits.default`
35
+ * 4. the LIVE `model.limit.context` from the hook input
36
+ * 5. the static fallback table (`src/hooks/model-limits.ts`), injected as
37
+ * `fallbackLookup` — reachable only when no live value is available
38
+ * 6. `DEFAULT_MODEL_CONTEXT_TOKENS`
39
+ *
40
+ * Steps 1–3 are ABOVE the live value on purpose: a user who writes a
41
+ * `model_limits` entry is asking for a smaller *working* budget than the
42
+ * physical window, and that intent must not be overridden by the hardware.
43
+ * This is why `context_budget.model_limits` no longer carries a zod
44
+ * `.default({ default: … })` — a schema-injected value is not user intent, and
45
+ * would have shadowed the live window for every user who has a
46
+ * `context_budget` block at all. See `ContextBudgetConfigSchema` in
47
+ * `./schema.ts`.
48
+ *
49
+ * Steps 5–6 are last resorts, NOT a cap on step 4. The hand-maintained table
50
+ * is stale in the downward direction (it claims Copilot caps everything at
51
+ * 128 000; the live catalog shows Copilot Claude entries at 200 000 and
52
+ * 1 000 000), so min-capping the live value against it would reintroduce
53
+ * exactly the too-small denominator this module exists to remove.
54
+ */
55
+ /**
56
+ * LAST-RESORT context-budget denominator (tokens) — rung 6 above.
57
+ *
58
+ * Declared HERE rather than imported from `./schema`, deliberately: this module
59
+ * is loaded transitively by `src/hooks/model-limits.ts` and therefore by every
60
+ * `messages.transform` consumer, and several test files replace
61
+ * `src/config/schema` with a non-spreading `mock.module` factory. Importing a
62
+ * symbol from schema here would make Bun throw
63
+ * `SyntaxError: Export named 'DEFAULT_MODEL_CONTEXT_TOKENS' not found` in those
64
+ * files at import time (see the "mock.module() Export Completeness" section of
65
+ * the writing-tests skill). Keeping this module dependency-free removes the
66
+ * whole class. `./schema` re-exports the constant, so every existing importer
67
+ * is unaffected.
68
+ *
69
+ * It is reached only when the user configured no `model_limits` entry, the host
70
+ * supplied no usable `model.limit.context`, and the static fallback table in
71
+ * `src/hooks/model-limits.ts` had no opinion either. It is deliberately NOT the
72
+ * primary source: 128 000 is stale for current models, which routinely ship
73
+ * 200k–1M windows.
74
+ *
75
+ * Historical note: `budgetTokens` used to be derived from
76
+ * `context_budget.model_limits.default` in `src/hooks/system-enhancer.ts`, but
77
+ * `context_budget` is schema-OPTIONAL, so a user with no block fell back to
78
+ * `DEFAULT_CONTEXT_BUDGET_CONFIG.budgetTokens` in
79
+ * `src/services/context-budget-service.ts` instead. Those two numbers had
80
+ * drifted (128000 vs 40000), so the unconfigured default measured the swarm's
81
+ * architect system prompt — approximately 34.5k estimated tokens — against a
82
+ * 40k denominator and reported ~86% on turn one (34500/40000 = 86.25%; an
83
+ * earlier revision of this comment said 87%) — enough to fire the budget
84
+ * warning, the `CONTEXT PRESSURE`
85
+ * advisory (src/index.ts) and the compaction EMERGENCY tier
86
+ * (`observation/reflection/emergency` = 40/60/80) immediately.
87
+ */
88
+ export declare const DEFAULT_MODEL_CONTEXT_TOKENS = 128000;
89
+ /**
90
+ * Smallest context window this module will accept from an untrusted source.
91
+ *
92
+ * Mirrors the `z.number().min(1000)` floor that `ContextBudgetConfigSchema`
93
+ * already enforces on user-authored `model_limits` values, so live catalog data
94
+ * and user config are held to one standard. This is not theoretical: a survey
95
+ * of the host's on-disk model catalog (`~/.cache/opencode/models.json`, ~6.2k
96
+ * entries; the file is a per-machine, auto-refreshing artifact, so the exact
97
+ * total moves) found 124 entries with `limit.context: 0`. Dividing by that
98
+ * yields `Infinity` percent and would fire the EMERGENCY compaction tier on
99
+ * turn one.
100
+ */
101
+ export declare const MIN_PLAUSIBLE_CONTEXT_TOKENS = 1000;
102
+ /** Which rung of the resolution order produced the value. */
103
+ export type ContextWindowSource = 'user_provider_model' | 'user_model' | 'user_default' | 'live_model_limit' | 'static_table' | 'static_default';
104
+ export interface ContextWindowResolution {
105
+ /**
106
+ * The resolved denominator, in tokens. Always a finite integer ≥ 1, so it is
107
+ * never a divide-by-zero. Through a PARSED config it is additionally ≥ 1000:
108
+ * every rung except the user rungs is gated on
109
+ * {@link isUsableContextWindow} (≥ {@link MIN_PLAUSIBLE_CONTEXT_TOKENS}), and
110
+ * the user rungs are themselves floored at 1000 by
111
+ * `ContextBudgetConfigSchema`. A direct programmatic call may still supply a
112
+ * user value between 1 and 999 and get it back — that is deliberate
113
+ * (honouring explicit intent), and it is why the guarantee stated here is
114
+ * ≥ 1 rather than ≥ 1000.
115
+ */
116
+ tokens: number;
117
+ /** Which rung produced `tokens` — surfaced in debug logs and tests. */
118
+ source: ContextWindowSource;
119
+ }
120
+ export interface ContextWindowInputs {
121
+ /**
122
+ * `config.context_budget?.model_limits` exactly as the user authored it.
123
+ * Pass `undefined` when the user has no `context_budget` block.
124
+ */
125
+ userLimits?: Record<string, number> | undefined;
126
+ /** Model identifier, when known (e.g. `claude-sonnet-4-6`). */
127
+ modelID?: string | undefined;
128
+ /** Provider identifier, when known (e.g. `github-copilot`). */
129
+ providerID?: string | undefined;
130
+ /**
131
+ * The live `model.limit.context` for this turn. Deliberately `unknown`:
132
+ * it crosses the plugin-host boundary, and the survey above shows the
133
+ * catalog really does ship `0` for some entries.
134
+ */
135
+ liveContextLimit?: unknown;
136
+ /**
137
+ * Last-resort static lookup. Injected rather than imported so this module
138
+ * stays free of any dependency on `src/hooks/`. Returning `undefined` means
139
+ * "no opinion" and falls through to `DEFAULT_MODEL_CONTEXT_TOKENS`.
140
+ */
141
+ fallbackLookup?: ((modelID?: string, providerID?: string) => number | undefined) | undefined;
142
+ }
143
+ /**
144
+ * True when `value` can be used as a divisor at all: a finite number strictly
145
+ * greater than zero.
146
+ *
147
+ * This is the bar applied to USER-AUTHORED `model_limits` values. It is
148
+ * deliberately weaker than {@link isUsableContextWindow}: `ContextBudgetConfigSchema`
149
+ * already enforces `z.number().min(1000)` on that record, so anything smaller
150
+ * cannot reach here through a parsed config — and silently DISCARDING a value a
151
+ * user explicitly wrote would be worse than honouring it. What this does reject
152
+ * is the set that breaks arithmetic outright (`NaN`, `±Infinity`, `0`,
153
+ * negatives, non-numbers), which a direct programmatic call can still supply.
154
+ *
155
+ * The bar is `>= 1`, not `> 0` (#1619 review round 4). `resolveContextWindow`
156
+ * applies `Math.floor` to whatever this admits, so a fractional value in
157
+ * `(0, 1)` — e.g. `{ default: 0.5 }` from a programmatic caller — passed a
158
+ * `> 0` test and then floored to a **zero denominator**, producing the exact
159
+ * `Infinity %` this guard exists to prevent. `>= 1` closes that without
160
+ * discarding any value a user could author through a parsed config (the schema
161
+ * floor is 1000) and without changing the verdict for any previously-accepted
162
+ * integer.
163
+ */
164
+ export declare function isUsableConfiguredWindow(value: unknown): value is number;
165
+ /**
166
+ * True when `value` is a plausible context-window size reported by an
167
+ * UNTRUSTED source: a finite number at or above
168
+ * {@link MIN_PLAUSIBLE_CONTEXT_TOKENS}.
169
+ *
170
+ * Applied to the live `model.limit.context` and to the static fallback table.
171
+ * Stricter than {@link isUsableConfiguredWindow} because nothing validates the
172
+ * host's model catalog on the way in and no real model ships a sub-1000-token
173
+ * window — so a small value there is corrupt data, not intent. Rejects, by
174
+ * construction, `undefined`, `null`, non-numbers, `NaN`, `Infinity`,
175
+ * `-Infinity`, `0` and every negative: the shapes that turn a percentage into
176
+ * `NaN` / `Infinity` and drive spurious EMERGENCY compaction directives.
177
+ */
178
+ export declare function isUsableContextWindow(value: unknown): value is number;
179
+ /**
180
+ * Safely read `model.limit.context` off the `experimental.chat.system.transform`
181
+ * hook input's `model` object.
182
+ *
183
+ * Typed as `unknown` on purpose. The plugin `.d.ts` declares `model: Model`
184
+ * (non-optional), but this hook runs inside the host's transform chain: a throw
185
+ * here takes down ALL system enhancement for the turn, so the read never
186
+ * assumes the declared shape actually arrived.
187
+ *
188
+ * @returns the raw value, or `undefined` when the path is absent.
189
+ */
190
+ export declare function readModelContextLimit(model: unknown): unknown;
191
+ /**
192
+ * Safely read `model.id` / `model.providerID` off the hook input's `model`.
193
+ *
194
+ * Same defensive posture as {@link readModelContextLimit}: the declared type
195
+ * says these are strings, but this runs on the host boundary. Empty strings are
196
+ * normalised to `undefined` so a blank identity never becomes a config-lookup
197
+ * key like `"/"`.
198
+ */
199
+ export declare function readModelIdentity(model: unknown, key: 'id' | 'providerID'): string | undefined;
200
+ /**
201
+ * Resolve the context-window denominator and report which rung produced it.
202
+ *
203
+ * @see the module header for the full resolution order and its rationale.
204
+ */
205
+ export declare function resolveContextWindow(inputs: ContextWindowInputs): ContextWindowResolution;
206
+ /**
207
+ * Convenience wrapper around {@link resolveContextWindow} for call sites that
208
+ * only need the number.
209
+ */
210
+ export declare function resolveContextWindowTokens(inputs: ContextWindowInputs): number;
@@ -220,6 +220,24 @@ export declare const ScoringConfigSchema: z.ZodObject<{
220
220
  }, z.core.$strip>>;
221
221
  }, z.core.$strip>;
222
222
  export type ScoringConfig = z.infer<typeof ScoringConfigSchema>;
223
+ /**
224
+ * LAST-RESORT context-budget denominator (tokens), used only when nothing
225
+ * better is available — rung 6 of the resolution order.
226
+ *
227
+ * DEFINED in `./context-window.ts` and re-exported here for the existing
228
+ * importers. It cannot live in this file: `context-window.ts` is loaded
229
+ * transitively by every context-budget consumer, and several test files replace
230
+ * `src/config/schema` with a non-spreading `mock.module` factory, so an import
231
+ * edge from `context-window.ts` into this module would make Bun throw
232
+ * `SyntaxError: Export named 'DEFAULT_MODEL_CONTEXT_TOKENS' not found` at
233
+ * import time. See the constant's own doc comment for the full history.
234
+ *
235
+ * A hardcoded denominator is wrong for any model whose window is not 128k. The
236
+ * budget denominator is derived from the live `model.limit.context` the host
237
+ * hands to `experimental.chat.system.transform`; this constant survives only as
238
+ * the no-information floor.
239
+ */
240
+ export { DEFAULT_MODEL_CONTEXT_TOKENS } from './context-window';
223
241
  export declare const ContextBudgetConfigSchema: z.ZodObject<{
224
242
  enabled: z.ZodDefault<z.ZodBoolean>;
225
243
  warn_threshold: z.ZodDefault<z.ZodNumber>;
@@ -4,6 +4,14 @@ import { log } from '../utils/logger.js';
4
4
  import type { ModelOverride } from '../utils/model-dispatch-fallback.js';
5
5
  export declare const DEFAULT_EPHEMERAL_TIMEOUT_MS = 120000;
6
6
  export declare const DEFAULT_EPHEMERAL_CLEANUP_TIMEOUT_MS = 500;
7
+ /**
8
+ * Bounded timeout for the graceful `session.abort()` step that precedes delete
9
+ * in the ephemeral teardown (#2123). opencode's `SessionProcessor.cleanup` can
10
+ * take up to ~250 ms under interruption (it awaits each pending tool-call
11
+ * deferred with a 250 ms timeout), so this must stay well above that or the
12
+ * abort would give up before the final part/message flush lands.
13
+ */
14
+ export declare const DEFAULT_EPHEMERAL_ABORT_TIMEOUT_MS = 5000;
7
15
  export declare const DEFAULT_EPHEMERAL_PROMPT_BYTE_LIMIT: number;
8
16
  export declare const MAX_EPHEMERAL_PROMPT_BYTE_LIMIT: number;
9
17
  export declare const DEFAULT_EPHEMERAL_RESPONSE_BYTE_LIMIT: number;
@@ -48,8 +56,18 @@ export type EphemeralAgentDispatchResult = {
48
56
  responseBytes: number;
49
57
  costFields?: DelegationCostFields;
50
58
  };
59
+ /**
60
+ * Bounded graceful abort of an ephemeral session. opencode's
61
+ * `POST /session/{id}/abort` resolves only after `Fiber.interrupt` on the
62
+ * run-loop fiber — which runs `SessionProcessor.cleanup` (the final
63
+ * `updatePart`/`updateMessage` flush) as a finalizer. Awaiting this BEFORE the
64
+ * delete is what closes the FOREIGN KEY constraint race (#2123). If the session
65
+ * is already idle, abort is a fast no-op. Best-effort: never throws.
66
+ */
67
+ export declare function boundedAbortEphemeralSession(client: OpencodeClient, sessionId: string, timeoutMs?: number): Promise<void>;
51
68
  export declare function boundedDeleteEphemeralSession(client: OpencodeClient, sessionId: string, timeoutMs?: number): Promise<void>;
52
69
  export declare const _internals: {
70
+ boundedAbort: typeof boundedAbortEphemeralSession;
53
71
  boundedDelete: typeof boundedDeleteEphemeralSession;
54
72
  log: typeof log;
55
73
  };
@@ -48,6 +48,7 @@ export declare function resolveEvaluationAgentName(agents: readonly Pick<Agent,
48
48
  * primitive remains the single owner of cleanup and logging behavior.
49
49
  */
50
50
  export declare const _internals: {
51
+ boundedAbort: typeof ephemeralDispatcherInternals.boundedAbort;
51
52
  boundedDelete: typeof ephemeralDispatcherInternals.boundedDelete;
52
53
  log: typeof ephemeralDispatcherInternals.log;
53
54
  };
@@ -7,7 +7,7 @@
7
7
  * prompt) against the ack markers in the subagent's transcript:
8
8
  *
9
9
  * - For every ack whose ID was actually shown, emit a receipt event of the
10
- * matching type (applied / ignored / violated / n_a). Acks for IDs that were
10
+ * matching type (applied / ignored / contradicted / violated / n_a). Acks for IDs that were
11
11
  * never shown are DROPPED (anti-spoofing).
12
12
  * - For every CRITICAL directive that was shown but never acknowledged, emit a
13
13
  * `violated` event with reason `unacknowledged` and append an audit line to
@@ -1,3 +1,10 @@
1
+ import * as fs from 'node:fs';
2
+ import { removeWorktreeProvisioningOwner } from './worktree-provisioning-owner';
3
+ /** File-operation seam for deterministic fail-closed recovery tests. */
4
+ export declare const _internals: {
5
+ statSync: fs.StatSyncFn;
6
+ removeWorktreeProvisioningOwner: typeof removeWorktreeProvisioningOwner;
7
+ };
1
8
  export interface StandardWorktreeCollisionIdentity {
2
9
  directory: string;
3
10
  parentSessionId: string;
@@ -12,7 +12,7 @@ import type { PluginConfig, WorktreeIsolationConfig } from '../../config';
12
12
  import { tryAcquireLock } from '../../parallel/file-locks';
13
13
  import { bunSpawn } from '../../utils/bun-compat';
14
14
  import type { WorktreeHandle } from '../../worktree';
15
- import { attemptMergeBackFromDirty, cleanupOrphanedBranches, postMergeCleanup, provisionWorktree, removeWorktree, startupOrphanRecovery } from '../../worktree';
15
+ import { attemptMergeBackFromDirty, cleanupOrphanedBranches, postMergeCleanup, provisionWorktree, pruneStaleWorktreeMetadata, removeWorktree, startupOrphanRecovery } from '../../worktree';
16
16
  import type { DirtyMergeOptions, MergeOperationProvenance } from '../../worktree/merge';
17
17
  import { recordWorktreeProvisioningOwner, removeWorktreeProvisioningOwner } from './worktree-provisioning-owner';
18
18
  /**
@@ -301,6 +301,7 @@ export declare const _internals: {
301
301
  removeWorktree: typeof removeWorktree;
302
302
  attemptMergeBackFromDirty: typeof attemptMergeBackFromDirty;
303
303
  postMergeCleanup: typeof postMergeCleanup;
304
+ pruneStaleWorktreeMetadata: typeof pruneStaleWorktreeMetadata;
304
305
  /** FR-201: read lane runtime profile from disk for spawn injection. */
305
306
  readLaneEnvFileFromDisk: typeof readLaneEnvFileFromDisk;
306
307
  /** FR-104: exposes serializationStateBySessionID for test setup (SC-111/SC-112/SC-113). */
@@ -1,3 +1,9 @@
1
+ /**
2
+ * A provisional marker covers the bounded gap before `git worktree add`
3
+ * materializes the exact lane path. Once that path exists, path liveness wins
4
+ * and the marker remains protected regardless of age.
5
+ */
6
+ export declare const WORKTREE_PROVISIONING_OWNER_LEASE_MS: number;
1
7
  export declare const WORKTREE_LIFECYCLE_LOCK_FILE = ".swarm/locks/init-orphan-recovery.lock";
2
8
  export interface WorktreeProvisioningOwner {
3
9
  schemaVersion: 1 | 2;
@@ -11,8 +11,8 @@ export { extractCurrentPhase, extractCurrentPhaseFromPlan, extractCurrentTask, e
11
11
  export { createFullAutoInterceptHook } from './full-auto-intercept';
12
12
  export { checkFileAuthority, createGuardrailsHooks, DEFAULT_AGENT_AUTHORITY_RULES, } from './guardrails';
13
13
  export { classifyMessage, classifyMessages, containsPlanContent, getCompletedToolOutputs, getToolNames, getToolParts, isDuplicateToolRead, isStaleError, isToolResult, MessagePriority, type MessagePriorityType, type MessageWithParts, } from './message-priority';
14
- export { consolidateSystemMessages } from './messages-transform';
15
- export { extractModelInfo, NATIVE_MODEL_LIMITS, PROVIDER_CAPS, resolveModelLimit, } from './model-limits';
14
+ export { consolidateSystemMessages, consolidateSystemMessagesInPlace, } from './messages-transform';
15
+ export { extractModelInfo, extractSessionId, lookupStaticModelLimit, NATIVE_MODEL_LIMITS, PROVIDER_CAPS, resolveModelLimit, } from './model-limits';
16
16
  export { type CuratorDelegateFactory, createPhaseMonitorHook, } from './phase-monitor';
17
17
  export { createPipelineTrackerHook } from './pipeline-tracker';
18
18
  export { createRepoGraphBuilderHook, type RepoGraphBuilderHook, } from './repo-graph-builder';
@@ -18,8 +18,9 @@ import * as fs from 'node:fs';
18
18
  import { scanDelegationFallbacksForRecovery, scanDelegationsForRecovery } from '../background/pending-delegations.js';
19
19
  import { isLocked, listActiveLocks, tryAcquireLock } from '../parallel/file-locks';
20
20
  import { removeWorktree } from '../worktree/core';
21
+ import { scanRegisteredWorktreeLiveness } from '../worktree/merge';
21
22
  import { scanWorktreeMergeFailuresForRecovery } from './delegation-gate/worktree-merge-status';
22
- import { scanWorktreeProvisioningOwnersForRecovery } from './delegation-gate/worktree-provisioning-owner';
23
+ import { removeWorktreeProvisioningOwner, scanWorktreeProvisioningOwnersForRecovery } from './delegation-gate/worktree-provisioning-owner';
23
24
  type OwnershipTagSessionScan = {
24
25
  status: 'ok';
25
26
  sessionIds: string[];
@@ -49,6 +50,8 @@ export declare const _internals: {
49
50
  scanDelegationFallbacksForRecovery: typeof scanDelegationFallbacksForRecovery;
50
51
  scanWorktreeMergeFailuresForRecovery: typeof scanWorktreeMergeFailuresForRecovery;
51
52
  scanWorktreeProvisioningOwnersForRecovery: typeof scanWorktreeProvisioningOwnersForRecovery;
53
+ scanRegisteredWorktreeLiveness: typeof scanRegisteredWorktreeLiveness;
54
+ removeWorktreeProvisioningOwner: typeof removeWorktreeProvisioningOwner;
52
55
  };
53
56
  export interface InitOrphanRecoveryResult {
54
57
  attempted: boolean;
@@ -4,7 +4,8 @@
4
4
  * Two integration points:
5
5
  *
6
6
  * 1. `experimental.chat.messages.transform` — scans the latest
7
- * architect-authored message for `KNOWLEDGE_APPLIED|IGNORED|VIOLATED`
7
+ * architect-authored message for
8
+ * `KNOWLEDGE_APPLIED|IGNORED|CONTRADICTED|VIOLATED`
8
9
  * markers and records them via `recordAcknowledgmentDeduped`. This
9
10
  * runs BEFORE the architect's next tool call so the toolBefore gate
10
11
  * sees the ack.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Knowledge application tracking — distinguishes shown / acknowledged / applied
3
- * / ignored / violated outcomes for injected knowledge directives.
3
+ * / ignored / contradicted / violated outcomes for injected knowledge directives.
4
4
  *
5
5
  * Writes one JSONL line per outcome to `.swarm/knowledge-application.jsonl`,
6
6
  * and updates per-entry retrieval-outcome counters on the source knowledge file.
@@ -13,12 +13,13 @@ export declare const MAX_LEGACY_APPLICATION_LOG_ENTRIES = 5000;
13
13
  * Recognised forms (case-insensitive, line-anchored or inline):
14
14
  * KNOWLEDGE_APPLIED: <id>
15
15
  * KNOWLEDGE_IGNORED: <id> reason=<reason>
16
+ * KNOWLEDGE_CONTRADICTED: <id> reason=<observable conflict>
16
17
  * KNOWLEDGE_VIOLATED: <id> reason=<reason>
17
18
  * KNOWLEDGE_N_A: <id> reason=<reason> (delegate contract, Change 1)
18
19
  */
19
20
  export interface ParsedAcknowledgment {
20
21
  id: string;
21
- result: 'applied' | 'ignored' | 'violated' | 'n_a';
22
+ result: 'applied' | 'ignored' | 'contradicted' | 'violated' | 'n_a';
22
23
  reason?: string;
23
24
  }
24
25
  export declare function parseAcknowledgments(text: string): ParsedAcknowledgment[];
@@ -32,7 +33,7 @@ export interface RecordContext {
32
33
  }
33
34
  /** Record one or more knowledge IDs as "shown" (injected into context). */
34
35
  export declare function recordKnowledgeShown(directory: string, ids: string[], ctx: RecordContext): Promise<void>;
35
- /** Record an explicit acknowledgment outcome (applied / ignored / violated).
36
+ /** Record an explicit acknowledgment outcome (applied / ignored / contradicted / violated).
36
37
  * Per-(sessionId, knowledgeId, result) dedup is enforced by the
37
38
  * caller via swarmState.knowledgeAckDedup; this fn always records when
38
39
  * invoked, so test code can trigger duplicates if needed. The runtime
@@ -57,7 +58,7 @@ export interface ShownNotAppliedQuery {
57
58
  }
58
59
  /**
59
60
  * Returns the subset of `knowledgeIds` that have at least one "shown" record
60
- * in the audit log without a subsequent "applied"/"ignored"/"violated" record
61
+ * in the audit log without a subsequent "applied"/"ignored"/"contradicted"/"violated" record
61
62
  * in the same task or phase scope.
62
63
  */
63
64
  export declare function getShownButNotAcknowledged(directory: string, q: ShownNotAppliedQuery): Promise<string[]>;
@@ -92,6 +92,10 @@ export declare function defaultExpectedToolsForAgent(agentName: string): string[
92
92
  * are stripped on both sides so `mega_coder` matches a bare `coder`.
93
93
  */
94
94
  export declare function matchesDelegateScope(entry: Pick<RankedEntry, 'applies_to_agents' | 'applies_to_tools'>, role: string, expectedTools: readonly string[]): boolean;
95
+ /** Inserts the knowledge block just before the last user message (recency position). */
96
+ declare function injectKnowledgeMessage(output: {
97
+ messages?: MessageWithParts[];
98
+ }, text: string): void;
95
99
  /**
96
100
  * Creates a knowledge injection hook that injects relevant knowledge into the
97
101
  * architect's message context at phase start. Supports caching for re-injection
@@ -114,3 +118,15 @@ export declare const _internals: {
114
118
  recordLessonsShown: typeof recordLessonsShown;
115
119
  confirmEntriesPhase: typeof confirmEntriesPhase;
116
120
  };
121
+ /**
122
+ * Tier-0 test seam (see `.opencode/skills/writing-tests/SKILL.md`). Exposes the
123
+ * real injection primitive and its sentinel so the
124
+ * `consolidateSystemMessages` interaction test (issue #1619) exercises the
125
+ * production splice position and message shape instead of a hand-copied fixture
126
+ * that could silently drift from this module.
127
+ */
128
+ export declare const _test_exports: {
129
+ injectKnowledgeMessage: typeof injectKnowledgeMessage;
130
+ INJECTION_SENTINEL: string;
131
+ };
132
+ export {};
@@ -549,7 +549,7 @@ export interface KnowledgeRetrievalContext {
549
549
  planConstraints?: string[];
550
550
  mode?: RetrievalMode;
551
551
  }
552
- export type KnowledgeApplicationResult = 'shown' | 'acknowledged' | 'applied' | 'ignored' | 'violated';
552
+ export type KnowledgeApplicationResult = 'shown' | 'acknowledged' | 'applied' | 'ignored' | 'contradicted' | 'violated';
553
553
  /** One line of .swarm/knowledge-application.jsonl. */
554
554
  export interface KnowledgeApplicationRecord {
555
555
  timestamp: string;
@@ -31,4 +31,22 @@ type PartsMessage = {
31
31
  };
32
32
  export type Message = FlatMessage | PartsMessage;
33
33
  export declare function consolidateSystemMessages(messages: Message[]): Message[];
34
+ /**
35
+ * In-place variant of {@link consolidateSystemMessages} for use inside an
36
+ * `experimental.chat.messages.transform` handler.
37
+ *
38
+ * The OpenCode host invokes each plugin hook as `M(input, output)`, **discards
39
+ * the handler's return value**, and afterwards reads its OWN local message
40
+ * array (host binary offset ~100,667,665:
41
+ * `yield* d.trigger("experimental.chat.messages.transform",{},{messages:C})`
42
+ * followed by `Me.toModelMessagesEffect(C,Z)`). Therefore
43
+ * `output.messages = consolidateSystemMessages(output.messages)` is a *rebind*
44
+ * that the host never observes — it silently did nothing in production from the
45
+ * day it was written until issue #1619. Only mutating the array the host handed
46
+ * us is observable.
47
+ *
48
+ * The consolidated result is computed BEFORE the array is cleared, because
49
+ * `consolidateSystemMessages` reads the same array it is passed.
50
+ */
51
+ export declare function consolidateSystemMessagesInPlace(messages: Message[]): void;
34
52
  export {};
@@ -1,19 +1,41 @@
1
1
  /**
2
- * Provider-Aware Model Limit Resolution
2
+ * Provider-Aware Model Limit Resolution — STATIC FALLBACK ONLY.
3
3
  *
4
- * Resolves context window limits based on the model and provider platform.
5
- * The same model has different context limits depending on the provider:
6
- * - Claude Sonnet 4.6: 200k native, 128k on Copilot
7
- * - GPT-5: 400k native, 128k on Copilot
8
- * - Copilot caps ALL models at 128k prompt, regardless of native limit
4
+ * > **The two tables below are a last resort, not the source of truth.**
5
+ * > The authoritative context window is the live `model.limit.context` the host
6
+ * > hands to `experimental.chat.system.transform`, resolved through
7
+ * > `src/config/context-window.ts`. That value is already provider-specific:
8
+ * > the host's model catalog is keyed `providers[providerID].models[modelID]`,
9
+ * > and the on-disk catalog shows `github-copilot`'s `claude-sonnet-4.5` and
10
+ * > `anthropic`'s `claude-sonnet-4-5` carrying different `limit.context`
11
+ * > values. These tables are consulted only when no live value reached the
12
+ * > call site (the `experimental.chat.messages.transform` paths on the very
13
+ * > first turn of a session, before any `system.transform` has run).
14
+ *
15
+ * **These tables are known-stale and are deliberately NOT used to cap the live
16
+ * value.** `PROVIDER_CAPS` claims Copilot caps everything at 128 000; the live
17
+ * catalog shows Copilot Claude entries at 200 000 (`claude-sonnet-4.5`,
18
+ * `claude-sonnet-4.6`) and 1 000 000 (`claude-fable-5`), with only `gpt-4.1`
19
+ * actually at 128 000. Min-capping a correct live value against that would
20
+ * reintroduce the too-small denominator the live derivation exists to remove.
21
+ * `NATIVE_MODEL_LIMITS` is stale in the same direction (`claude-sonnet-4` is
22
+ * listed at 200 000; the catalog reports 216 000).
23
+ *
24
+ * Do not "refresh" these tables by hand — that is the maintenance treadmill the
25
+ * live derivation replaced. They exist so a first-turn `messages.transform`
26
+ * consumer degrades to a plausible number instead of the flat 128 000 floor.
9
27
  */
10
28
  /**
11
29
  * Native model context limits (in tokens) when used on their native platform.
30
+ *
31
+ * Static fallback only — see the module header.
12
32
  */
13
33
  export declare const NATIVE_MODEL_LIMITS: Record<string, number>;
14
34
  /**
15
35
  * Provider-specific context caps that override native limits.
16
- * These are typically lower than native limits (e.g., Copilot caps at 128k).
36
+ *
37
+ * Static fallback only, and known-stale — see the module header. This is NOT
38
+ * applied as a ceiling over the live `model.limit.context`.
17
39
  */
18
40
  export declare const PROVIDER_CAPS: Record<string, number>;
19
41
  /**
@@ -51,46 +73,82 @@ export declare function extractModelInfo(messages: MessageWithParts[]): {
51
73
  modelID?: string;
52
74
  providerID?: string;
53
75
  };
76
+ /**
77
+ * Extracts the sessionID from a `experimental.chat.messages.transform` message
78
+ * array, scanning newest-first.
79
+ *
80
+ * The messages hooks receive no `input.sessionID` (the plugin type declares
81
+ * `input: {}`), but every message's `info` carries one. This is the key used to
82
+ * look up the live `model.limit.context` that the `system.transform` hook
83
+ * recorded for the session — see `getLiveContextWindow` in `src/state.ts`.
84
+ *
85
+ * @returns the sessionID, or `undefined` when no message carries one.
86
+ */
87
+ export declare function extractSessionId(messages: MessageWithParts[] | undefined): string | undefined;
88
+ /**
89
+ * Static-table lookup used as `fallbackLookup` for
90
+ * {@link resolveContextWindow}. Preserves the historical precedence of
91
+ * the two tables relative to each other (provider cap first, then a
92
+ * longest-prefix native match), and is reachable only when no live
93
+ * `model.limit.context` was available.
94
+ */
95
+ export declare function lookupStaticModelLimit(modelID?: string, providerID?: string): number | undefined;
54
96
  /**
55
97
  * Resolves the context limit for a given model/provider combination.
56
98
  *
57
- * Resolution order (first match wins):
58
- * 1. Check configOverrides["provider/model"] (e.g., "copilot/claude-sonnet-4-6": 200000)
59
- * 2. Check configOverrides[modelID] (e.g., "claude-sonnet-4-6": 200000)
60
- * 3. Check PROVIDER_CAPS[providerID] (e.g., copilot 128000)
61
- * 4. Check NATIVE_MODEL_LIMITS with prefix matching (e.g., "claude-sonnet-4" matches "claude-sonnet-4-6-20260301")
62
- * 5. Check configOverrides.default
63
- * 6. Fall back to 128000
99
+ * Thin adapter over the single derivation in `src/config/context-window.ts` —
100
+ * this function exists so the `experimental.chat.messages.transform` consumers
101
+ * (`context-budget.ts`, `knowledge-injector.ts`, `tools/context-status.ts`)
102
+ * keep their existing `(modelID, providerID, overrides)` call shape. All
103
+ * ordering logic lives in the shared resolver; see its module header.
104
+ *
105
+ * Resolution order (first usable value wins):
106
+ * 1. `configOverrides["<providerID>/<modelID>"]`
107
+ * 2. `configOverrides["<modelID>"]`
108
+ * 3. `configOverrides.default`
109
+ * 4. `liveContextLimit` — the host's live `model.limit.context` for this session
110
+ * 5. `PROVIDER_CAPS[providerID]`, else a longest-prefix `NATIVE_MODEL_LIMITS`
111
+ * match (both known-stale; see the module header)
112
+ * 6. `DEFAULT_MODEL_CONTEXT_TOKENS` (128000)
113
+ *
114
+ * Two ordering changes vs. the pre-#1619 implementation, both deliberate:
115
+ * - `configOverrides.default` moved from below the static tables to above
116
+ * them. An explicitly authored user value losing to a hardcoded table was a
117
+ * defect; it is now consistent with the compound and model-only keys, which
118
+ * already outranked the tables.
119
+ * - The live `model.limit.context` was inserted above both tables, which is
120
+ * the entire point of this change.
121
+ *
122
+ * Any value that is not a finite number ≥ 1000 is skipped rather than used, so
123
+ * a malformed override or a catalog entry with `limit.context: 0` can never
124
+ * produce a `NaN` / `Infinity` percentage.
64
125
  *
65
126
  * @param modelID - The model identifier (e.g., "claude-sonnet-4-6", "gpt-5")
66
- * @param providerID - The provider identifier (e.g., "copilot", "anthropic")
67
- * @param configOverrides - User configuration overrides
127
+ * @param providerID - The provider identifier (e.g., "github-copilot", "anthropic")
128
+ * @param configOverrides - User configuration overrides (`context_budget.model_limits`)
129
+ * @param liveContextLimit - The live `model.limit.context` recorded for this
130
+ * session by the `system.transform` hook, when one has been seen
68
131
  * @returns The resolved context limit in tokens
69
132
  *
70
133
  * @example
71
- * // Provider cap (copilot)
72
- * resolveModelLimit("claude-sonnet-4-6", "copilot", {})
73
- * // Returns: 128000
74
- *
75
- * @example
76
- * // Native limit (anthropic)
77
- * resolveModelLimit("claude-sonnet-4-6", "anthropic", {})
134
+ * // Live value wins over the stale static tables
135
+ * resolveModelLimit("claude-sonnet-4-6", "github-copilot", {}, 200000)
78
136
  * // Returns: 200000
79
137
  *
80
138
  * @example
81
- * // Override beats cap
82
- * resolveModelLimit("gpt-5", "copilot", { "copilot/gpt-5": 200000 })
83
- * // Returns: 200000
139
+ * // Explicit user override beats the live value
140
+ * resolveModelLimit("gpt-5", "github-copilot", { "github-copilot/gpt-5": 60000 }, 400000)
141
+ * // Returns: 60000
84
142
  *
85
143
  * @example
86
- * // Prefix match for model variants
144
+ * // No live value: falls back to the static table (prefix match)
87
145
  * resolveModelLimit("claude-sonnet-4-6-20260301", "anthropic", {})
88
146
  * // Returns: 200000
89
147
  *
90
148
  * @example
91
- * // Full fallback
92
- * resolveModelLimit(undefined, undefined, {})
149
+ * // Malformed live value is ignored, not divided by
150
+ * resolveModelLimit(undefined, undefined, {}, 0)
93
151
  * // Returns: 128000
94
152
  */
95
- export declare function resolveModelLimit(modelID?: string, providerID?: string, configOverrides?: Record<string, number>): number;
153
+ export declare function resolveModelLimit(modelID?: string, providerID?: string, configOverrides?: Record<string, number>, liveContextLimit?: unknown): number;
96
154
  export {};