opencode-swarm 7.135.2 → 7.135.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.
- package/.opencode/skills/critic-gate/SKILL.md +10 -0
- package/dist/agents/explorer.d.ts +1 -1
- package/dist/agents/index.d.ts +12 -0
- package/dist/cli/{config-doctor-pj3yvxdb.js → config-doctor-fgmemknc.js} +3 -2
- package/dist/cli/{core-4va9z2ng.js → core-4z1s2ak1.js} +3 -2
- package/dist/cli/{curation-policy-b2caf3v3.js → curation-policy-frs28q57.js} +3 -3
- package/dist/cli/{curator-drift-2ta83hss.js → curator-drift-jpap6hv4.js} +2 -2
- package/dist/cli/{curator-llm-factory-9983528d.js → curator-llm-factory-pr59p535.js} +25 -24
- package/dist/cli/{curator-r1s2mwf2.js → curator-psq8m2j7.js} +25 -24
- package/dist/cli/{dispatch-wtk6dxky.js → dispatch-b1fge4xk.js} +2 -2
- package/dist/cli/{evidence-summary-service-feby8ny0.js → evidence-summary-service-w006jnpg.js} +5 -5
- package/dist/cli/{explorer-15gqzxh8.js → explorer-swjc297z.js} +1 -1
- package/dist/cli/{gate-evidence-84b03mb4.js → gate-evidence-aenyz6vt.js} +2 -2
- package/dist/cli/{guardrail-explain-pwxvg5n0.js → guardrail-explain-ws45zchy.js} +26 -25
- package/dist/cli/{guardrail-log-yzpz8b7t.js → guardrail-log-gbzek1pz.js} +5 -4
- package/dist/cli/{hive-promoter-586dhndj.js → hive-promoter-gdbg4nyd.js} +25 -24
- package/dist/cli/index-3jcyn8g6.js +1219 -0
- package/dist/cli/{index-hdy2nd5h.js → index-45t7w06b.js} +4 -4
- package/dist/cli/{index-hvrrq3f3.js → index-4g3nq4sh.js} +1 -1
- package/dist/cli/{index-02cqjna3.js → index-5s1q6xcw.js} +4 -4
- package/dist/cli/{index-340chr1e.js → index-66w4jjg6.js} +10124 -10005
- package/dist/cli/{index-dg1njnms.js → index-6mjf4vr1.js} +1 -1
- package/dist/cli/{index-9c3gf2wd.js → index-ajehqzvn.js} +6 -4
- package/dist/cli/{index-2pxk6qg4.js → index-b34vbxmx.js} +4 -2
- package/dist/cli/{index-d5psymdk.js → index-c9ddxv4k.js} +57 -8
- package/dist/cli/{index-j0kte6gd.js → index-cg413zhk.js} +8 -1210
- package/dist/cli/{index-7pnvf04s.js → index-cggqh2dz.js} +10 -10
- package/dist/cli/{index-bs4m85v1.js → index-cgqekdja.js} +2 -2
- package/dist/cli/{index-j92ser8n.js → index-ck9rx6j1.js} +5 -3
- package/dist/cli/{index-jgkjxpdh.js → index-fx7bn93y.js} +2 -2
- package/dist/cli/{index-qqkw794p.js → index-g86vfxnk.js} +3 -3
- package/dist/cli/{index-3m7kpnqf.js → index-jsp8r6f9.js} +1 -1
- package/dist/cli/{index-8gw8p7yh.js → index-mysdsqtk.js} +21 -4
- package/dist/cli/{index-3xbx305x.js → index-tcn457d5.js} +1 -1
- package/dist/cli/{index-ckdva4rs.js → index-tegdwx34.js} +5 -5
- package/dist/cli/{index-b0eva00y.js → index-vh39nxpx.js} +1 -1
- package/dist/cli/{index-73dfyfhq.js → index-x20cgr2q.js} +1 -1
- package/dist/cli/{index-cwdy5vw9.js → index-xfeg0c0c.js} +31 -29
- package/dist/cli/{index-g3meqrsg.js → index-ys04j1np.js} +12 -10
- package/dist/cli/index.js +28 -26
- package/dist/cli/{knowledge-escalator-w7by1m8m.js → knowledge-escalator-0060q7we.js} +4 -4
- package/dist/cli/{knowledge-events-n8xxsay3.js → knowledge-events-yh2qwa6k.js} +6 -2
- package/dist/cli/knowledge-link-mm1w967j.js +1 -1
- package/dist/cli/{knowledge-store-xzmvfnx3.js → knowledge-store-geqaez58.js} +2 -2
- package/dist/cli/{knowledge-validator-5q1amsq6.js → knowledge-validator-4zq4vm7t.js} +5 -5
- package/dist/cli/{pending-delegations-mt5gs1bt.js → pending-delegations-qajsxct0.js} +4 -4
- package/dist/cli/{pr-subscriptions-v5xd3j6d.js → pr-subscriptions-qhr41epq.js} +4 -4
- package/dist/cli/{scan-cursor-1q1zmt4b.js → scan-cursor-nk2rybhq.js} +3 -3
- package/dist/cli/{schema-8mtsfspm.js → schema-th3tx32c.js} +2 -1
- package/dist/cli/scope-persistence-h2fpgxww.js +2 -2
- package/dist/cli/{skill-generator-54xbjgak.js → skill-generator-1947bbkn.js} +6 -6
- package/dist/cli/{worktree-collision-ownership-15hymf1y.js → worktree-collision-ownership-13btcj9g.js} +5 -5
- package/dist/commands/approve-plan-critic.d.ts +21 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/config/cache-paths.d.ts +48 -0
- package/dist/config/constants.d.ts +18 -0
- package/dist/config/host-path.d.ts +95 -0
- package/dist/config/lane-context.d.ts +117 -0
- package/dist/config/lane-permissions.d.ts +277 -0
- package/dist/config/schema.d.ts +10 -0
- package/dist/config/swarm-branch.d.ts +130 -0
- package/dist/hooks/curator-postmortem.d.ts +8 -0
- package/dist/hooks/delegate-ack-collector.d.ts +21 -0
- package/dist/hooks/delegation-gate.d.ts +42 -1
- package/dist/hooks/knowledge-events.d.ts +33 -1
- package/dist/hooks/skill-propagation-gate.d.ts +10 -0
- package/dist/index.js +225 -212
- package/dist/memory/schema.d.ts +4 -4
- package/dist/services/knowledge-diagnostics.d.ts +2 -0
- package/dist/tools/approve-plan-critic.d.ts +5 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/manifest.d.ts +1 -0
- package/dist/tools/tool-metadata.d.ts +4 -0
- package/package.json +1 -1
- package/dist/cli/{index-bfd4xfpp.js → index-4qzeef9h.js} +3 -3
- package/dist/cli/{index-ryqnn3nv.js → index-mrtms113.js} +3 -3
- package/dist/cli/{index-m0ynxkaa.js → index-vm4xw9z3.js} +3 -3
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped `external_directory` permission rules for swarm worktree-lane
|
|
3
|
+
* instances.
|
|
4
|
+
*
|
|
5
|
+
* ## The problem this solves
|
|
6
|
+
*
|
|
7
|
+
* A lane session is created against a new directory, so OpenCode builds it a
|
|
8
|
+
* fresh directory-keyed permission universe with an empty `approved` list. The
|
|
9
|
+
* host's default agent ruleset ends in `external_directory: { "*": "ask" }`, and
|
|
10
|
+
* a lane instance has no TUI attached to answer an ask — `Permission.ask` parks
|
|
11
|
+
* on a deferred with no timeout and the lane hangs forever.
|
|
12
|
+
*
|
|
13
|
+
* ## The mechanism
|
|
14
|
+
*
|
|
15
|
+
* The plugin `config` hook runs once per OpenCode instance (`Plugin.state` is
|
|
16
|
+
* built through the same directory-keyed `InstanceState` cache as
|
|
17
|
+
* `Permission.state`), so in a lane instance it runs with `ctx.directory` set to
|
|
18
|
+
* the lane path. Rules merged into the top-level `permission` block there are
|
|
19
|
+
* folded by the host into **every** agent's ruleset:
|
|
20
|
+
*
|
|
21
|
+
* ```js
|
|
22
|
+
* // opencode host, Agent.state
|
|
23
|
+
* l = a.fromConfig(p.permission ?? {}) // top-level config permission
|
|
24
|
+
* r = { build: { permission: a.merge(c, ..., l) }, plan: {...}, general: {...},
|
|
25
|
+
* explore: {...}, compaction: {...}, title: {...}, summary: {...} }
|
|
26
|
+
* for (const [n, s] of Object.entries(p.agent ?? {})) {
|
|
27
|
+
* let e = r[n];
|
|
28
|
+
* if (!e) e = r[n] = { ..., permission: a.merge(c, l), ... }; // swarm agents
|
|
29
|
+
* e.permission = a.merge(e.permission, a.fromConfig(s.permission ?? {}));
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Top-level is therefore strictly broader than per-agent injection: it reaches
|
|
34
|
+
* the host-native agents (`build`, `plan`, `general`, `explore`, ...) that a
|
|
35
|
+
* lane can also run, and it lands *before* any per-agent block so explicit
|
|
36
|
+
* per-agent user config still wins. Injecting the same rules per-agent as well
|
|
37
|
+
* would duplicate every rule in every ruleset and would make our injected rules
|
|
38
|
+
* outrank the user's own top-level entries — the wrong precedence.
|
|
39
|
+
*
|
|
40
|
+
* ## DISCLOSED ASSUMPTION: hook-before-agents ordering
|
|
41
|
+
*
|
|
42
|
+
* This design requires the `config` hook to run before `Agent.state` reads the
|
|
43
|
+
* config. What is PROVEN: both sides touch the *same* object — `Plugin.state`
|
|
44
|
+
* does `U = yield* Config.get()` and mutates `U` in place, `Agent.state` does
|
|
45
|
+
* `p = yield* Config.get()`, and `Config.state` is InstanceState-cached, so
|
|
46
|
+
* `get()` returns one shared instance. There is no copy; only ordering is in
|
|
47
|
+
* question.
|
|
48
|
+
*
|
|
49
|
+
* What is NOT proven: any static ordering guarantee. Two bounded searches of
|
|
50
|
+
* the host binary came back negative — the Agent layer's deps
|
|
51
|
+
* (`deps:[J.node,O.node,P.node,_.node,$.node,Ve]`, offset 100817167) do not
|
|
52
|
+
* include the Plugin node (the three symbols the Agent chunk imports from the
|
|
53
|
+
* Plugin chunk are `$G`, `b8`, `r1`, none of which is the Plugin service), and
|
|
54
|
+
* `Plugin.init` (offset 102128681) has no caller anywhere in the bundle. The
|
|
55
|
+
* hook therefore fires lazily, when `Plugin.state` is first populated by a
|
|
56
|
+
* `Plugin.trigger` / `Plugin.list`.
|
|
57
|
+
*
|
|
58
|
+
* The evidence that it holds is empirical and strong: opencode-swarm's agents
|
|
59
|
+
* are registered ONLY by this config hook, and they demonstrably appear in the
|
|
60
|
+
* TUI, which is impossible unless the hook ran before `Agent.state` was built.
|
|
61
|
+
* Treat it as a verified-in-practice property of this host build rather than a
|
|
62
|
+
* contract. If a future OpenCode release reorders layer construction, the
|
|
63
|
+
* symptom is loud and immediate — no swarm agents at all — not a silent
|
|
64
|
+
* permission regression.
|
|
65
|
+
*
|
|
66
|
+
* ## Rule ordering is load-bearing
|
|
67
|
+
*
|
|
68
|
+
* The host evaluates with `findLast` over the flattened rule list:
|
|
69
|
+
*
|
|
70
|
+
* ```js
|
|
71
|
+
* function c(j, J, ...K) {
|
|
72
|
+
* return K.flat().findLast((z) => g.match(j, z.permission) && g.match(J, z.pattern))
|
|
73
|
+
* ?? { action: "ask", permission: j, pattern: "*" };
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* and `fromConfig` preserves `Object.entries` order. So **later wins**, and the
|
|
78
|
+
* catch-all `"*": "deny"` must be emitted FIRST, before the specific allows.
|
|
79
|
+
* Emitting it last would deny everything. `tests/unit/config/lane-permissions.test.ts`
|
|
80
|
+
* pins this against a faithful re-implementation of the host's own
|
|
81
|
+
* `fromConfig`/`merge`/`evaluate`, so the ordering contract is verified rather
|
|
82
|
+
* than assumed.
|
|
83
|
+
*
|
|
84
|
+
* Two further host behaviours matter and are deliberately relied upon:
|
|
85
|
+
*
|
|
86
|
+
* - `Wildcard.match` compiles the rule pattern with `*` -> `.*` under the `s`
|
|
87
|
+
* (dotAll) flag, so `<dir>/*` covers the entire subtree, and it normalises
|
|
88
|
+
* `\` to `/` on both sides before matching, so native Windows paths are fine.
|
|
89
|
+
* - After merging config, the host appends
|
|
90
|
+
* `external_directory: { <Global.Path.data>/tool-output/*: "allow" }` to every
|
|
91
|
+
* agent unless a rule already matches that exact pattern with `action: "deny"`.
|
|
92
|
+
* Our catch-all uses pattern `"*"`, which does not satisfy that exact-string
|
|
93
|
+
* check, so the append still happens and still lands last. Tool output stays
|
|
94
|
+
* readable. The emitted ruleset is intentionally not the final ruleset.
|
|
95
|
+
*
|
|
96
|
+
* @module config/lane-permissions
|
|
97
|
+
*/
|
|
98
|
+
import { addDeferredWarning } from '../services/warning-buffer';
|
|
99
|
+
import type { LaneContext } from './lane-context';
|
|
100
|
+
import { resolveLaneContext } from './lane-context';
|
|
101
|
+
/**
|
|
102
|
+
* Lane permission policy. Mirrors `worktree.lane_permissions` in
|
|
103
|
+
* `src/config/schema.ts`.
|
|
104
|
+
*/
|
|
105
|
+
export type LanePermissionMode = 'scoped_allow' | 'deny' | 'off';
|
|
106
|
+
/** Permission actions OpenCode understands for a rule. */
|
|
107
|
+
type PermissionAction = 'allow' | 'ask' | 'deny';
|
|
108
|
+
/** Result of assembling a lane's `external_directory` rule map. */
|
|
109
|
+
interface LaneRuleBuild {
|
|
110
|
+
/** The rule map, in emission (== evaluation) order. */
|
|
111
|
+
rules: Record<string, PermissionAction>;
|
|
112
|
+
/**
|
|
113
|
+
* User-configured patterns whose `"ask"` was coerced to `"deny"` because a
|
|
114
|
+
* lane has no TUI that could answer an ask. Empty in the common case.
|
|
115
|
+
*/
|
|
116
|
+
coercedAskPatterns: string[];
|
|
117
|
+
}
|
|
118
|
+
/** An allowlisted directory plus the justification for granting it. */
|
|
119
|
+
interface LaneAllowlistEntry {
|
|
120
|
+
/** Absolute directory path. */
|
|
121
|
+
dir: string;
|
|
122
|
+
/**
|
|
123
|
+
* The emitted `external_directory` rule pattern for {@link dir}.
|
|
124
|
+
*
|
|
125
|
+
* Computed ONCE here and reused by both consumers — the rule map and the
|
|
126
|
+
* `.swarm/events.jsonl` record. Deriving it twice meant a second round of
|
|
127
|
+
* `realpathSync.native` per entry on the config-hook path and, more
|
|
128
|
+
* importantly, left room for the two to disagree: if they ever did, the
|
|
129
|
+
* event log would misreport the rule that was actually emitted, which is
|
|
130
|
+
* precisely the observability this subsystem exists to provide.
|
|
131
|
+
*/
|
|
132
|
+
pattern: string;
|
|
133
|
+
/** Why this directory is justified — recorded in `.swarm/events.jsonl`. */
|
|
134
|
+
reason: string;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Converts an absolute directory into the `external_directory` rule pattern
|
|
138
|
+
* that covers it and everything beneath it.
|
|
139
|
+
*
|
|
140
|
+
* The literal part of the directory is escaped for the host's matcher. The host
|
|
141
|
+
* compiles a rule pattern as:
|
|
142
|
+
*
|
|
143
|
+
* ```js
|
|
144
|
+
* o.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".")
|
|
145
|
+
* // then: new RegExp("^"+compiled+"$","si")
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* Note what is NOT escaped: `*` and `?`. A directory legitimately named `a*`
|
|
149
|
+
* (legal on POSIX) would compile to `a.*` and silently grant every sibling
|
|
150
|
+
* starting with `a`; `?` becomes a single-character wildcard the same way. The
|
|
151
|
+
* matcher offers no escape syntax for either, so such a directory cannot be
|
|
152
|
+
* expressed exactly. {@link isExpressibleDirectory} therefore drops it from the
|
|
153
|
+
* allowlist rather than emitting an over-broad grant: dropping fails safe (the
|
|
154
|
+
* path is denied, and its absence is visible in the event log), emitting would
|
|
155
|
+
* fail open.
|
|
156
|
+
*
|
|
157
|
+
* The matcher also uses the `i` flag, so patterns are case-insensitive even on
|
|
158
|
+
* case-sensitive filesystems. `/home/u/Work/*` will therefore also match
|
|
159
|
+
* `/home/u/work/...`. That widening is inherent to the host and cannot be
|
|
160
|
+
* avoided from a rule pattern; it is recorded here so it is a known, reviewed
|
|
161
|
+
* property rather than a surprise.
|
|
162
|
+
*/
|
|
163
|
+
declare function laneDirectoryPattern(dir: string): string;
|
|
164
|
+
/**
|
|
165
|
+
* Builds the justified `external_directory` allowlist for a lane.
|
|
166
|
+
*
|
|
167
|
+
* Every entry is a directory the lane provably needs in order to do the work it
|
|
168
|
+
* was created for. Nothing is added "just in case" — an over-broad allowlist
|
|
169
|
+
* here is the security cost of this fix, so each entry carries its reason and
|
|
170
|
+
* is asserted in tests.
|
|
171
|
+
*
|
|
172
|
+
* @param lane - Resolved lane context.
|
|
173
|
+
* @returns Deduplicated absolute directories with justifications.
|
|
174
|
+
*/
|
|
175
|
+
declare function buildLaneAllowlist(lane: LaneContext, configuredSkillPaths?: readonly string[], configuredSkillUrls?: readonly string[]): LaneAllowlistEntry[];
|
|
176
|
+
/**
|
|
177
|
+
* Builds the `external_directory` rule map to write into the top-level
|
|
178
|
+
* `permission` block of a lane instance's config.
|
|
179
|
+
*
|
|
180
|
+
* Emission order (which IS the evaluation order, later-wins):
|
|
181
|
+
*
|
|
182
|
+
* 1. `"*": "deny"` — our catch-all, so nothing can be left pending in a lane
|
|
183
|
+
* that has no TUI to answer it. This is the only mechanism that delivers
|
|
184
|
+
* the approved policy's "answered, not hung" guarantee now that the host's
|
|
185
|
+
* `permission.ask` plugin hook is confirmed dead (never triggered by
|
|
186
|
+
* opencode 1.18.10).
|
|
187
|
+
* 2. Our justified allowlist — beats the catch-all because it comes later.
|
|
188
|
+
* 3. Whatever the user already configured — beats everything, because explicit
|
|
189
|
+
* user configuration must always outrank a plugin default. A user entry
|
|
190
|
+
* keyed `"*"` replaces our catch-all's value in place (same key), so
|
|
191
|
+
* `external_directory: "allow"` in `opencode.json` still means allow-all.
|
|
192
|
+
*
|
|
193
|
+
* ## Why `"ask"` is coerced to `"deny"` INSIDE A LANE
|
|
194
|
+
*
|
|
195
|
+
* A user's `allow` and `deny` are honoured verbatim. `ask` is not, and that is
|
|
196
|
+
* deliberate: a lane instance has no TUI attached, so `ask` cannot be answered
|
|
197
|
+
* by anyone. It is not a third policy choice there — it is a guaranteed
|
|
198
|
+
* indefinite hang, which is the exact defect this module exists to remove.
|
|
199
|
+
* Honouring it literally would let a top-level `external_directory: "ask"` (or
|
|
200
|
+
* any `"ask"` pattern) silently reinstate the bug while this code reported
|
|
201
|
+
* success. Coercion resolves to the fail-safe direction (`deny`, never
|
|
202
|
+
* `allow`), the affected patterns are named in the advisory, and they are
|
|
203
|
+
* recorded under `coercedAskPatterns` in the `.swarm/events.jsonl` entry so the
|
|
204
|
+
* decision is auditable rather than silent. Users who genuinely want the
|
|
205
|
+
* prompting behaviour back have `worktree.lane_permissions: "off"`.
|
|
206
|
+
*
|
|
207
|
+
* @param mode - Configured lane permission policy.
|
|
208
|
+
* @param lane - Resolved lane context.
|
|
209
|
+
* @param existing - Current `external_directory` value from the config being
|
|
210
|
+
* mutated, if any.
|
|
211
|
+
* @returns The rule map plus any patterns whose `ask` was coerced, or `null`
|
|
212
|
+
* when the policy is `off` (the caller must then touch nothing).
|
|
213
|
+
*/
|
|
214
|
+
declare function buildLaneExternalDirectoryRules(mode: LanePermissionMode, lane: LaneContext, existing?: unknown, precomputedAllowlist?: readonly LaneAllowlistEntry[]): LaneRuleBuild | null;
|
|
215
|
+
/**
|
|
216
|
+
* Renders the operator-facing remedy text for a lane whose permissions are
|
|
217
|
+
* being scoped.
|
|
218
|
+
*
|
|
219
|
+
* The permission system itself carries an action, not a message — a `deny` rule
|
|
220
|
+
* cannot explain itself. So the explanation is delivered out-of-band (a deferred
|
|
221
|
+
* warning plus a `.swarm/events.jsonl` record) and names the exact
|
|
222
|
+
* `opencode.json` edit that widens the allowlist.
|
|
223
|
+
*/
|
|
224
|
+
declare function renderLanePermissionAdvisory(mode: Exclude<LanePermissionMode, 'off'>, lane: LaneContext, allowlist: readonly LaneAllowlistEntry[], coercedAskPatterns?: readonly string[]): string;
|
|
225
|
+
/**
|
|
226
|
+
* Tier-0 test seam (see the writing-tests skill): these are pure functions with
|
|
227
|
+
* no external dependencies, so they are tested directly rather than mocked.
|
|
228
|
+
* They are intentionally NOT part of the module's public API — only
|
|
229
|
+
* {@link applyLanePermissions} is called from production.
|
|
230
|
+
*/
|
|
231
|
+
export declare const _test_exports: {
|
|
232
|
+
buildLaneAllowlist: typeof buildLaneAllowlist;
|
|
233
|
+
buildLaneExternalDirectoryRules: typeof buildLaneExternalDirectoryRules;
|
|
234
|
+
laneDirectoryPattern: typeof laneDirectoryPattern;
|
|
235
|
+
renderLanePermissionAdvisory: typeof renderLanePermissionAdvisory;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Test-only dependency-injection seam (AGENTS.md invariant 7). Tests replace
|
|
239
|
+
* these to assert the event record and advisory without touching the real
|
|
240
|
+
* filesystem or the process-wide warning buffer. Restore in `afterEach`.
|
|
241
|
+
*/
|
|
242
|
+
export declare const _internals: {
|
|
243
|
+
resolveLaneContext: typeof resolveLaneContext;
|
|
244
|
+
addDeferredWarning: typeof addDeferredWarning;
|
|
245
|
+
appendFileSync: (p: string, data: string, enc: BufferEncoding) => void;
|
|
246
|
+
mkdirSync: (p: string, opts: {
|
|
247
|
+
recursive: boolean;
|
|
248
|
+
}) => string | undefined;
|
|
249
|
+
};
|
|
250
|
+
/** Outcome of {@link applyLanePermissions}, returned for tests and logging. */
|
|
251
|
+
export interface LanePermissionApplication {
|
|
252
|
+
/** `true` when the directory was recognised as a swarm worktree lane. */
|
|
253
|
+
lane: boolean;
|
|
254
|
+
/** Effective policy. `undefined` when `lane` is `false`. */
|
|
255
|
+
mode?: LanePermissionMode;
|
|
256
|
+
/** Rules written into `permission.external_directory`, when any. */
|
|
257
|
+
rules?: Record<string, PermissionAction>;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Applies scoped lane permissions to an OpenCode config object, in place.
|
|
261
|
+
*
|
|
262
|
+
* Called from the plugin `config` hook. When `directory` is not a swarm
|
|
263
|
+
* worktree lane this returns immediately and mutates NOTHING — ordinary
|
|
264
|
+
* sessions must be byte-for-byte unaffected, which
|
|
265
|
+
* `tests/unit/config/lane-permissions-config.test.ts` asserts directly.
|
|
266
|
+
*
|
|
267
|
+
* @param opencodeConfig - The live merged config object the host handed to the
|
|
268
|
+
* `config` hook. Mutated in place, as the hook contract
|
|
269
|
+
* requires.
|
|
270
|
+
* @param directory - The plugin's own `ctx.directory`. Under this host that
|
|
271
|
+
* IS the instance directory, so in a lane instance it is
|
|
272
|
+
* the lane path.
|
|
273
|
+
* @param mode - Configured `worktree.lane_permissions` policy.
|
|
274
|
+
* @returns What was decided, for logging and tests.
|
|
275
|
+
*/
|
|
276
|
+
export declare function applyLanePermissions(opencodeConfig: Record<string, unknown>, directory: string, mode: LanePermissionMode): LanePermissionApplication;
|
|
277
|
+
export {};
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -1197,6 +1197,11 @@ export declare const WorktreeIsolationConfigSchema: z.ZodObject<{
|
|
|
1197
1197
|
skip: "skip";
|
|
1198
1198
|
copy: "copy";
|
|
1199
1199
|
}>>;
|
|
1200
|
+
lane_permissions: z.ZodDefault<z.ZodEnum<{
|
|
1201
|
+
scoped_allow: "scoped_allow";
|
|
1202
|
+
deny: "deny";
|
|
1203
|
+
off: "off";
|
|
1204
|
+
}>>;
|
|
1200
1205
|
serialization_release_after_dispatches: z.ZodDefault<z.ZodNumber>;
|
|
1201
1206
|
serialization_release_after_ms: z.ZodDefault<z.ZodNumber>;
|
|
1202
1207
|
runtime_isolation: z.ZodOptional<z.ZodObject<{
|
|
@@ -2449,6 +2454,11 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
2449
2454
|
skip: "skip";
|
|
2450
2455
|
copy: "copy";
|
|
2451
2456
|
}>>;
|
|
2457
|
+
lane_permissions: z.ZodDefault<z.ZodEnum<{
|
|
2458
|
+
scoped_allow: "scoped_allow";
|
|
2459
|
+
deny: "deny";
|
|
2460
|
+
off: "off";
|
|
2461
|
+
}>>;
|
|
2452
2462
|
serialization_release_after_dispatches: z.ZodDefault<z.ZodNumber>;
|
|
2453
2463
|
serialization_release_after_ms: z.ZodDefault<z.ZodNumber>;
|
|
2454
2464
|
runtime_isolation: z.ZodOptional<z.ZodObject<{
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The swarm worktree BRANCH GRAMMAR — single source of truth for both building
|
|
3
|
+
* and recognising the branch names that identify a swarm-owned worktree.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a grammar and not a prefix
|
|
6
|
+
*
|
|
7
|
+
* Lane detection (`src/config/lane-context.ts`) uses the branch name as its
|
|
8
|
+
* ownership signal because, unlike the worktree's path, it is invariant across
|
|
9
|
+
* all three provisioning layouts (default `.swarm-worktrees`, a configured
|
|
10
|
+
* `worktree.worktree_dir`, and the Windows path-budget fallback that relocates
|
|
11
|
+
* a lane to `<os.tmpdir()>/swwt/...`).
|
|
12
|
+
*
|
|
13
|
+
* Matching a bare `swarm/` PREFIX is not safe. A user running
|
|
14
|
+
* `git worktree add -b swarm/my-own-experiment ../scratch` would be classified
|
|
15
|
+
* as a lane, and their ordinary interactive session would then have
|
|
16
|
+
* `external_directory: { "*": "deny", ... }` injected. That is strictly worse
|
|
17
|
+
* than the hang this subsystem exists to fix, because the host's
|
|
18
|
+
* `Permission.ask` short-circuits on a deny before it ever creates a deferred:
|
|
19
|
+
*
|
|
20
|
+
* ```js
|
|
21
|
+
* // opencode 1.18.10, Permission.ask
|
|
22
|
+
* if (W.action === "deny") return yield* new U.DeniedError({ ... });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* — so no prompt is raised and "Allow always" can never be reached. The user
|
|
26
|
+
* has no in-session recovery.
|
|
27
|
+
*
|
|
28
|
+
* The grammar below therefore matches the COMPLETE shape
|
|
29
|
+
* `makeWorktreeBranchName` emits, including a session segment constrained to
|
|
30
|
+
* OpenCode's `ses_`-prefixed identifier form. `tests/unit/config/swarm-branch.test.ts`
|
|
31
|
+
* holds a round-trip property test over the producer and a negative corpus of
|
|
32
|
+
* plausible human-authored branch names.
|
|
33
|
+
*
|
|
34
|
+
* @module config/swarm-branch
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* True when `sessionId` is a session identifier the lane grammar can encode.
|
|
38
|
+
*
|
|
39
|
+
* Tool arguments are LLM-supplied (`sessionID: z.string()`) and the host's own
|
|
40
|
+
* `SessionID` brand is only `isStartsWith("ses")`, so a value like `ses-run-1`
|
|
41
|
+
* passes the host but yields a branch the recogniser cannot match — a lane that
|
|
42
|
+
* silently skips permission scoping and hangs. Callers that accept a session id
|
|
43
|
+
* from outside should check this before provisioning.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isSwarmSessionId(sessionId: unknown): sessionId is string;
|
|
46
|
+
/**
|
|
47
|
+
* Builds the branch name for a swarm worktree.
|
|
48
|
+
*
|
|
49
|
+
* The ONLY producer of these names. `makeWorktreeBranchName` in
|
|
50
|
+
* `src/worktree/core.ts` delegates here so the producer and
|
|
51
|
+
* {@link matchSwarmLaneBranch} cannot drift.
|
|
52
|
+
*
|
|
53
|
+
* @param sessionId - Parent session identifier.
|
|
54
|
+
* @param id - Execution-unit identifier (task or lane id).
|
|
55
|
+
* @param purpose - Worktree purpose (e.g. `lane`).
|
|
56
|
+
* @param legacyLane - Use Lean Turbo's `swarm-lane/<sessionId>/<id>` style.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildSwarmBranchName(sessionId: string, id: string, purpose: string, legacyLane: boolean): string;
|
|
59
|
+
/**
|
|
60
|
+
* Recognises the DEFAULT lane PATH shape,
|
|
61
|
+
* `<base>/.swarm-worktrees/<sessionId>/<id>`.
|
|
62
|
+
*
|
|
63
|
+
* This is the fallback ownership signal for a worktree whose HEAD is detached
|
|
64
|
+
* or unreadable, so its branch cannot be consulted. It must be as tight as the
|
|
65
|
+
* branch grammar: matching "any path containing a `.swarm-worktrees` segment"
|
|
66
|
+
* captures a user's own `git worktree add -b my-feature ../.swarm-worktrees/manual-user-wt`,
|
|
67
|
+
* which then has an unrecoverable deny-by-default injected into an ordinary
|
|
68
|
+
* interactive session.
|
|
69
|
+
*
|
|
70
|
+
* `provisionWorktree` builds this path as
|
|
71
|
+
* `path.resolve(resolveWorktreeBaseDir(directory), sessionId, id)`, i.e. exactly
|
|
72
|
+
* two segments after the base, so the shape check requires exactly that — with
|
|
73
|
+
* the session segment held to the SAME `ses_…` constraint the branch grammar
|
|
74
|
+
* uses. The directory name comes from the same `SWARM_WORKTREE_DIR_NAME`
|
|
75
|
+
* constant `resolveWorktreeBaseDir` builds with, so the three definitions
|
|
76
|
+
* cannot drift.
|
|
77
|
+
*
|
|
78
|
+
* KNOWN RESIDUAL (accepted, not a defect to chase): a worktree the USER created
|
|
79
|
+
* at exactly `<base>/.swarm-worktrees/ses_<alnum>/<id>` on a non-swarm branch
|
|
80
|
+
* still classifies as a lane. Reaching it requires deliberately creating a
|
|
81
|
+
* directory literally named `ses_<alnum>` under a `.swarm-worktrees` base, and
|
|
82
|
+
* the same leniency is what keeps a REAL lane detected after someone checks out
|
|
83
|
+
* a different branch inside it. Narrowing further would trade a far more likely
|
|
84
|
+
* false negative (a real lane silently unscoped, i.e. the original hang) for a
|
|
85
|
+
* far less likely false positive.
|
|
86
|
+
*
|
|
87
|
+
* Note this recognises only the DEFAULT layout. A `worktree_dir` override or
|
|
88
|
+
* the Windows path-budget fallback produces a different path, and a lane in
|
|
89
|
+
* those layouts with a detached HEAD is a false NEGATIVE — no permission
|
|
90
|
+
* changes, i.e. today's behaviour. That is the safe direction.
|
|
91
|
+
*
|
|
92
|
+
* @param lanePath - An already-resolved absolute path.
|
|
93
|
+
*/
|
|
94
|
+
export declare function matchSwarmLanePath(lanePath: string): {
|
|
95
|
+
sessionId: string;
|
|
96
|
+
id: string;
|
|
97
|
+
} | undefined;
|
|
98
|
+
/** A branch name recognised as a swarm worktree lane. */
|
|
99
|
+
export interface SwarmLaneBranch {
|
|
100
|
+
/** Worktree purpose. `'lane'` for the legacy style, which encodes no purpose. */
|
|
101
|
+
purpose: string;
|
|
102
|
+
sessionId: string;
|
|
103
|
+
id: string;
|
|
104
|
+
style: 'purpose' | 'legacy-lane';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Recognises a branch name produced by {@link buildSwarmBranchName}.
|
|
108
|
+
*
|
|
109
|
+
* Matches the complete grammar and nothing wider:
|
|
110
|
+
* - `swarm/<purpose>/<sessionId>/<id>` — exactly 4 segments
|
|
111
|
+
* - `swarm-lane/<sessionId>/<id>` — exactly 3 segments
|
|
112
|
+
*
|
|
113
|
+
* with `<sessionId>` matching `ses_[A-Za-z0-9]+` and `<purpose>` / `<id>` each
|
|
114
|
+
* a single non-empty, non-dot segment. Anything else — including a bare
|
|
115
|
+
* `swarm/my-own-experiment`, or a real lane name with extra trailing segments —
|
|
116
|
+
* returns `undefined`, which classifies the worktree as NOT swarm-owned and
|
|
117
|
+
* leaves it completely untouched.
|
|
118
|
+
*
|
|
119
|
+
* @returns The parsed branch, or `undefined` when the name is not a swarm lane.
|
|
120
|
+
*/
|
|
121
|
+
export declare function matchSwarmLaneBranch(branch: string): SwarmLaneBranch | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* Tier-0 test seam (writing-tests skill). The prefix list is an implementation
|
|
124
|
+
* detail of the grammar — production code goes through
|
|
125
|
+
* {@link buildSwarmBranchName} / {@link matchSwarmLaneBranch} — but the branch
|
|
126
|
+
* tests assert that both documented styles are actually exercised.
|
|
127
|
+
*/
|
|
128
|
+
export declare const _test_exports: {
|
|
129
|
+
SWARM_WORKTREE_BRANCH_PREFIXES: readonly ["swarm/", "swarm-lane/"];
|
|
130
|
+
};
|
|
@@ -36,6 +36,14 @@ interface KnowledgeEventSummary {
|
|
|
36
36
|
applied: number;
|
|
37
37
|
violated: number;
|
|
38
38
|
ignored: number;
|
|
39
|
+
/**
|
|
40
|
+
* Shown to a delegate that ended its Task without any ack marker or receipt.
|
|
41
|
+
* Audit-only silence, NOT a verdict — kept strictly separate from
|
|
42
|
+
* applied/violated/ignored so it never enters an application- or
|
|
43
|
+
* violation-rate calculation. Surfaced so a post-mortem can say
|
|
44
|
+
* "entry X: shown N times, unacknowledged M times" instead of nothing.
|
|
45
|
+
*/
|
|
46
|
+
unacknowledged: number;
|
|
39
47
|
confidence: number;
|
|
40
48
|
status: string;
|
|
41
49
|
}
|
|
@@ -12,6 +12,11 @@
|
|
|
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
|
|
14
14
|
* `.swarm/unacknowledged-criticals.jsonl`.
|
|
15
|
+
* - For every NON-CRITICAL directive that was shown but never acknowledged,
|
|
16
|
+
* emit a neutral, audit-only `unacknowledged` event (reason
|
|
17
|
+
* `no_ack_marker`). Silence is not a violation for non-criticals — it is a
|
|
18
|
+
* visibility signal, so it is never escalated, never audited to the
|
|
19
|
+
* criticals file, and never mutates a counter.
|
|
15
20
|
*
|
|
16
21
|
* Stateless by design: it re-parses the prompt rather than relying on
|
|
17
22
|
* cross-hook mutable state, so it is safe under parallel delegations. Fail-open:
|
|
@@ -31,7 +36,23 @@ export interface CollectDelegateAcksResult {
|
|
|
31
36
|
id: string;
|
|
32
37
|
type: string;
|
|
33
38
|
}>;
|
|
39
|
+
/**
|
|
40
|
+
* CRITICAL directives shown to the delegate that it never acknowledged. Each
|
|
41
|
+
* one is a contract violation: emitted as `violated`/`unacknowledged`, audited
|
|
42
|
+
* to `.swarm/unacknowledged-criticals.jsonl`, and fed to the repeat-mistake
|
|
43
|
+
* escalator.
|
|
44
|
+
*/
|
|
34
45
|
unacknowledgedCriticals: string[];
|
|
46
|
+
/**
|
|
47
|
+
* NON-critical directives shown to the delegate that it never acknowledged.
|
|
48
|
+
* These are NOT contract violations — the ack contract only obliges the
|
|
49
|
+
* delegate to answer for criticals. Each is emitted as a neutral,
|
|
50
|
+
* audit-only `unacknowledged` event so silent non-critical delivery is
|
|
51
|
+
* visible to the curator post-mortem instead of vanishing. They are never
|
|
52
|
+
* escalated, never written to the criticals audit file, and never produce
|
|
53
|
+
* promotion evidence.
|
|
54
|
+
*/
|
|
55
|
+
unacknowledgedNonCritical: string[];
|
|
35
56
|
}
|
|
36
57
|
/**
|
|
37
58
|
* Core reconciliation used by both the runtime hook and tests. Returns a summary
|
|
@@ -265,11 +265,50 @@ interface MessageWithParts {
|
|
|
265
265
|
info: MessageInfo;
|
|
266
266
|
parts: MessagePart[];
|
|
267
267
|
}
|
|
268
|
+
declare function extractPlanCriticVerdict(output: unknown): 'APPROVED' | 'NEEDS_REVISION' | 'REJECTED' | null;
|
|
268
269
|
/**
|
|
269
270
|
* Returns whether the plan in the given directory has a valid plan-critic
|
|
270
271
|
* approval. Does not throw — returns `false` for any failure (fail-closed).
|
|
271
272
|
*/
|
|
272
273
|
export declare function isPlanCriticApproved(directory: string): Promise<boolean>;
|
|
274
|
+
/**
|
|
275
|
+
* Escape hatch for the ratchet-tighter `critic_pre_plan` gate (issue #2012).
|
|
276
|
+
*
|
|
277
|
+
* When the critic returns APPROVED but the mechanical snapshot recorder
|
|
278
|
+
* ({@link recordPlanCriticApprovalSnapshotIfApplicable}) fails to persist it
|
|
279
|
+
* (verdict-format mismatch, dispatch-signal miss, or a plan.json read race),
|
|
280
|
+
* the gate permanently blocks ALL coder delegations because `critic_pre_plan`
|
|
281
|
+
* defaults to `true` and cannot be disabled (ratchet-tighter). This records a
|
|
282
|
+
* manual `plan_critic_gate` approval snapshot so the gate unblocks, with a
|
|
283
|
+
* distinct `method: 'manual_override'` audit marker so a human or downstream
|
|
284
|
+
* review can distinguish a manual approval from a mechanical critic approval.
|
|
285
|
+
*
|
|
286
|
+
* This mirrors the established escape-hatch pattern (PR_REVIEW gate #1898:
|
|
287
|
+
* `abortPrWorkflow` + `/swarm abort-pr-workflow` + `abort_pr_workflow` tool).
|
|
288
|
+
*
|
|
289
|
+
* Fail-closed preconditions:
|
|
290
|
+
* - The session must be an active **architect** session. The escape hatch is an
|
|
291
|
+
* escalation; non-architect callers are rejected so a coder/reviewer cannot
|
|
292
|
+
* self-unblock.
|
|
293
|
+
* - A plan.json must exist; you cannot approve a non-existent plan.
|
|
294
|
+
*
|
|
295
|
+
* @param directory - Project root containing `.swarm/`
|
|
296
|
+
* @param sessionID - The caller's session id (must be an architect session)
|
|
297
|
+
* @param options.reason - Optional human/agent-supplied reason (audited)
|
|
298
|
+
* @param options.userConfirmed - `true` only when invoked via the restricted
|
|
299
|
+
* `/swarm approve-plan-critic` command (human-run); `false` when invoked via
|
|
300
|
+
* the `approve_plan_critic` tool (agent-initiated). Recorded in the audit so a
|
|
301
|
+
* self-approve is visible.
|
|
302
|
+
*/
|
|
303
|
+
export declare function forceRecordPlanCriticApproval(directory: string, sessionID: string, options?: {
|
|
304
|
+
reason?: string;
|
|
305
|
+
userConfirmed?: boolean;
|
|
306
|
+
}): Promise<{
|
|
307
|
+
planId: string;
|
|
308
|
+
recordedAt: string;
|
|
309
|
+
reason?: string;
|
|
310
|
+
userConfirmed: boolean;
|
|
311
|
+
}>;
|
|
273
312
|
export declare function resolveDelegatedPlanTaskId(args: Record<string, unknown>, knownPlanTaskIds?: ReadonlySet<string>): string | null;
|
|
274
313
|
/**
|
|
275
314
|
* Builds a cause-specific diagnostic for `prepareCoderScope`'s SCOPE_NOT_DECLARED
|
|
@@ -332,7 +371,9 @@ export declare const _internals: {
|
|
|
332
371
|
bindBackgroundCoderReservationForDispatch: typeof import("../background/pending-delegations.js").bindBackgroundCoderReservation;
|
|
333
372
|
releaseBackgroundCoderReservationForDispatch: typeof import("../background/pending-delegations.js").releaseBackgroundCoderReservation;
|
|
334
373
|
resetStandardWorktreeIsolationState: typeof resetStandardWorktreeIsolationState;
|
|
335
|
-
PLAN_CRITIC_TASK_SIGNALS: readonly ["critic-gate", "plan critic", "review plan", "review the plan", "plan.md", "approve the plan", "plan approval"];
|
|
374
|
+
PLAN_CRITIC_TASK_SIGNALS: readonly ["critic-gate", "plan critic", "review plan", "review the plan", "plan.md", "approve the plan", "plan approval", "pre-implementation review", "evaluate this plan", "evaluate the plan", "assess this plan", "assess the plan", "plan soundness", "before implementation", "review the plan below"];
|
|
375
|
+
extractPlanCriticVerdict: typeof extractPlanCriticVerdict;
|
|
376
|
+
forceRecordPlanCriticApproval: typeof forceRecordPlanCriticApproval;
|
|
336
377
|
provisionWorktree: typeof _wtiInternals.provisionWorktree;
|
|
337
378
|
removeWorktree: typeof _wtiInternals.removeWorktree;
|
|
338
379
|
attemptMergeBackFromDirty: typeof _wtiInternals.attemptMergeBackFromDirty;
|
|
@@ -74,7 +74,20 @@ export interface ReceiptEvent {
|
|
|
74
74
|
* counter (there is no `knowledge_id` to credit; this is a trace-level
|
|
75
75
|
* tombstone, not application credit).
|
|
76
76
|
*/
|
|
77
|
-
| 'no_relevant'
|
|
77
|
+
| 'no_relevant'
|
|
78
|
+
/**
|
|
79
|
+
* A shown non-critical directive reached the end of a delegate Task with no
|
|
80
|
+
* ack marker and no receipt. Audit-only visibility signal; never penalizes
|
|
81
|
+
* the entry's outcome/violation counters.
|
|
82
|
+
*
|
|
83
|
+
* Motivation: before this event, only CRITICAL silence produced a signal (a
|
|
84
|
+
* `violated`/`unacknowledged` event). Non-critical silence was invisible, so
|
|
85
|
+
* a corpus with 1 critical entry out of 103 reported ~4% receipt compliance
|
|
86
|
+
* with no way to see where the other 96% went. This is the missing
|
|
87
|
+
* observation, NOT a verdict: the delegate filed nothing, so there is no
|
|
88
|
+
* terminal to credit or penalize.
|
|
89
|
+
*/
|
|
90
|
+
| 'unacknowledged';
|
|
78
91
|
schema_version?: number;
|
|
79
92
|
event_id: string;
|
|
80
93
|
trace_id: string;
|
|
@@ -213,11 +226,30 @@ export declare function newEventId(): string;
|
|
|
213
226
|
* {@link recordKnowledgeEvent}, which swallows errors.
|
|
214
227
|
*/
|
|
215
228
|
export declare function appendKnowledgeEvent(directory: string, event: KnowledgeEventInput): Promise<KnowledgeEvent>;
|
|
229
|
+
/**
|
|
230
|
+
* Append several events under ONE lock acquisition and ONE cap-trim pass.
|
|
231
|
+
* Multi-event emitters on awaited paths (e.g. the delegate ack-collector's
|
|
232
|
+
* per-directive `unacknowledged` loop, up to `delegate_max_inject_count`
|
|
233
|
+
* events per delegation) must use this instead of N sequential
|
|
234
|
+
* {@link appendKnowledgeEvent} calls — each of those takes the directory
|
|
235
|
+
* lock and re-reads the whole log for the FIFO trim, which is material on
|
|
236
|
+
* cold filesystems and under parallel delegations.
|
|
237
|
+
*
|
|
238
|
+
* Throws on I/O failure — hot paths should prefer
|
|
239
|
+
* {@link recordKnowledgeEventsBatch}, which swallows errors.
|
|
240
|
+
*/
|
|
241
|
+
export declare function appendKnowledgeEventsBatch(directory: string, events: KnowledgeEventInput[]): Promise<KnowledgeEvent[]>;
|
|
216
242
|
/**
|
|
217
243
|
* Fail-open variant of {@link appendKnowledgeEvent} for hot paths (hooks, tool
|
|
218
244
|
* execution). Never throws; logs a warning and returns null on failure.
|
|
219
245
|
*/
|
|
220
246
|
export declare function recordKnowledgeEvent(directory: string, event: KnowledgeEventInput): Promise<KnowledgeEvent | null>;
|
|
247
|
+
/**
|
|
248
|
+
* Fail-open variant of {@link appendKnowledgeEventsBatch} for hot paths.
|
|
249
|
+
* Never throws; logs a warning and returns null on failure (all-or-nothing:
|
|
250
|
+
* the batch is a single append, so there are no partial writes to report).
|
|
251
|
+
*/
|
|
252
|
+
export declare function recordKnowledgeEventsBatch(directory: string, events: KnowledgeEventInput[]): Promise<KnowledgeEvent[] | null>;
|
|
221
253
|
/**
|
|
222
254
|
* Append one event to the shared, cross-project hive events log. Use for audit
|
|
223
255
|
* tombstones of mutations to the hive store so any project can read why a hive
|
|
@@ -29,6 +29,16 @@ import { appendSkillUsageEntry, readSkillUsageEntries, readSkillUsageEntriesTail
|
|
|
29
29
|
export declare function loadRoutingSkills(directory: string, targetAgent: string): string[];
|
|
30
30
|
/** Agents that should receive skill context in delegations. */
|
|
31
31
|
export declare const SKILL_CAPABLE_AGENTS: Set<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Skill root directories to scan for SKILL.md files.
|
|
34
|
+
*
|
|
35
|
+
* Exported because `src/config/lane-permissions.ts` resolves these same
|
|
36
|
+
* project-relative roots to absolute directories when building the
|
|
37
|
+
* `external_directory` allowlist for a worktree-lane instance. Keeping one
|
|
38
|
+
* definition means a new skill root cannot be added here and silently become
|
|
39
|
+
* an unanswerable permission prompt inside a lane.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SKILL_SEARCH_ROOTS: string[];
|
|
32
42
|
/**
|
|
33
43
|
* Maximum number of session-scoped skill-usage tail entries to process for
|
|
34
44
|
* skill scoring. This applies to the bounded tail-read window only.
|