@dsh-cc/subagent-resume-pins 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/plugin.js ADDED
@@ -0,0 +1,354 @@
1
+ /**
2
+ * The resume-pins cordis plugin (plan §4.6-§4.10): one `PinStore`, the
3
+ * `subagents-resume` settings namespace, the `tools/pre-execute` resume gate,
4
+ * the `tools/post-execute` notice/annotation listeners, and the
5
+ * `agent/request` runtime overlay.
6
+ *
7
+ * Zero-op when unmounted: pins are simply unread and behavior is today's
8
+ * legacy behavior. When mounted, only pinned children are affected — a
9
+ * missing pin is a legacy/foreign child (pass-through), and a same-epoch
10
+ * followup to a live Activation is untouched.
11
+ *
12
+ * Durability ordering: every deny persists `resume.state='blocked'` (reason)
13
+ * through the shared store — an atomic disk rewrite plus synchronous cache
14
+ * publication — BEFORE the deny decision returns, so the overlay listener
15
+ * fails any unmonitored resume of that child visibly. An all-passing gate
16
+ * clears the stored blocked state. Gate and overlay share the ONE store
17
+ * exposed as the `resumePinStore` service (spawn capture prefers it too).
18
+ *
19
+ * @module @dsh-cc/subagent-resume-pins/plugin
20
+ */
21
+ import { existsSync } from 'node:fs';
22
+ import { isAbsolute, join } from 'node:path';
23
+ import { realpathSync } from 'node:fs';
24
+ import { spawnSync } from 'node:child_process';
25
+ import z from '@deepseek-ai/schemastery';
26
+ import { SessionId } from '@deepseek-ai/dsh-session';
27
+ import { loadAgentsDir, discoverBundledAgents } from '@dsh-cc/claude-code-agents';
28
+ import { resolveDetailedAlias } from '@dsh-cc/model-aliases';
29
+ import { evaluateGate } from "./gate.js";
30
+ import { PinBlockedError, applyPinOverlay } from "./overlay.js";
31
+ import { definitionFingerprint } from "./fingerprint.js";
32
+ import { RESUME_POLICY_NAMESPACE, readResumePolicy, } from "./policy.js";
33
+ import { serializePerKey, ExecutionNoticeBus } from "./serialize.js";
34
+ import { PinStore } from "./store.js";
35
+ /**
36
+ * The settings section schema (plan §4.9): one constrained enum field per
37
+ * knob with explicit defaults, mirroring how `cc-model-aliases` registers its
38
+ * schema — invalid spellings are rejected at write time by the settings
39
+ * service; `readResumePolicy` keeps tolerating hand-edited documents.
40
+ */
41
+ export const ResumePolicySchema = z.object({
42
+ onUnavailableModel: z.union([z.const('block'), z.const('route-current')]).default('block'),
43
+ onDefinitionChanged: z.union([z.const('resume-with-notice'), z.const('block')]).default('resume-with-notice'),
44
+ onWorkspaceChanged: z.union([z.const('resume-with-notice'), z.const('block')]).default('resume-with-notice'),
45
+ });
46
+ /** Cordis plugin id. */
47
+ export const name = 'cc-subagent-resume-pins';
48
+ /** The service key the spawn capture resolves to share the store. */
49
+ export const RESUME_PIN_STORE = 'resumePinStore';
50
+ /** The spawn-time git identity probe timeout (matches the capture probe). */
51
+ const GIT_PROBE_TIMEOUT_MS = 2_000;
52
+ /**
53
+ * Normalize one `git rev-parse` output to a cwd-anchored absolute path (git
54
+ * prints repository-relative values like `.git`), realpath'ed when cheap so
55
+ * the same repo reached through different paths compares equal. The `'unknown'`
56
+ * sentinel passes through. Residual limit: two DIFFERENT repos initialized
57
+ * sequentially at the same path normalize to the same path string — the
58
+ * worktree↔standalone and cwd-move cases ARE detected; same-path replacement
59
+ * of a standalone repo by another standalone repo is not.
60
+ */
61
+ function normalizeGitPath(cwd, value) {
62
+ if (value === 'unknown' || value.length === 0)
63
+ return 'unknown';
64
+ const absolute = isAbsolute(value) ? value : join(cwd, value);
65
+ try {
66
+ return realpathSync(absolute);
67
+ }
68
+ catch {
69
+ return absolute;
70
+ }
71
+ }
72
+ /** Best-effort current git identity of one cwd ('unknown' sentinels on failure). */
73
+ function gitIdentity(cwd) {
74
+ try {
75
+ const result = spawnSync('git', ['-C', cwd, 'rev-parse', '--git-dir', '--git-common-dir', '--abbrev-ref', 'HEAD'], { timeout: GIT_PROBE_TIMEOUT_MS, encoding: 'utf8' });
76
+ const [gitDir, gitCommonDir, branch] = (result.stdout ?? '').trim().split('\n');
77
+ if (result.status !== 0 || gitDir === undefined || gitCommonDir === undefined || branch === undefined) {
78
+ return { gitDir: 'unknown', gitCommonDir: 'unknown', branch: 'unknown' };
79
+ }
80
+ return {
81
+ gitDir: normalizeGitPath(cwd, gitDir),
82
+ gitCommonDir: normalizeGitPath(cwd, gitCommonDir),
83
+ branch,
84
+ };
85
+ }
86
+ catch {
87
+ return { gitDir: 'unknown', gitCommonDir: 'unknown', branch: 'unknown' };
88
+ }
89
+ }
90
+ /**
91
+ * Re-fingerprint a named definition OUTSIDE the registry cache (§4.4).
92
+ * Bundled pins re-fingerprint from the CURRENT in-package bundled registry.
93
+ * Project/user pins re-read the EXACT recorded `baseDir`+`filename`, falling
94
+ * back to an `agentType` lookup only when the recorded file is gone (that is
95
+ * the changed-by-replacement class). `'missing'` for a gone/unreadable
96
+ * definition; `null` when no current information exists (a pin without a
97
+ * file location).
98
+ */
99
+ export async function refingerprintDefinition(pin) {
100
+ const definition = pin.definition;
101
+ if (definition.kind !== 'named')
102
+ return null;
103
+ if (definition.source === 'bundled') {
104
+ try {
105
+ const found = discoverBundledAgents().find(def => def.agentType === definition.agentType);
106
+ return found === undefined ? 'missing' : definitionFingerprint(found);
107
+ }
108
+ catch {
109
+ return 'missing';
110
+ }
111
+ }
112
+ if (definition.baseDir === undefined || definition.filename === undefined)
113
+ return null;
114
+ try {
115
+ const defs = await loadAgentsDir(definition.baseDir, definition.source);
116
+ const found = defs.find(def => def.filename === definition.filename)
117
+ ?? defs.find(def => def.agentType === definition.agentType);
118
+ return found === undefined ? 'missing' : definitionFingerprint(found);
119
+ }
120
+ catch {
121
+ return 'missing';
122
+ }
123
+ }
124
+ /** The text of one tool result's content blocks (for annotation matching). */
125
+ function resultText(result) {
126
+ return result.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('');
127
+ }
128
+ /**
129
+ * Mount the resume-pin gate, overlay, notices, and policy namespace.
130
+ * @param ctx - the plug context.
131
+ * @param config - pins root (or an injected store).
132
+ */
133
+ export function apply(ctx, config) {
134
+ const store = config.store ?? new PinStore(config.pinsRoot);
135
+ ctx.provide(RESUME_PIN_STORE, store);
136
+ // §4.9: register the policy namespace when a settings provider is mounted;
137
+ // read LIVE on every gate evaluation (a flip is authoritative immediately).
138
+ const settings = ctx.get('settings');
139
+ const scope = settings?.register?.(RESUME_POLICY_NAMESPACE, ResumePolicySchema);
140
+ const policy = () => readResumePolicy(scope?.get?.());
141
+ // Pre→post communication: gate-computed notices for a child's NEXT
142
+ // send_message result, keyed by the tool execution identity (`exec.token`,
143
+ // the registry-assigned opaque call identity present on BOTH the pre- and
144
+ // post-execute payloads) so a failing send can never leak its notice into a
145
+ // later call. Per-child promise chains serialize gate evaluation +
146
+ // persistence + followup admission, so concurrent sends to one cold child
147
+ // cannot interleave their decisions or cross-deliver notices.
148
+ const pendingNotices = new ExecutionNoticeBus();
149
+ const childLocks = new Map();
150
+ /**
151
+ * Lookup-level read: an unsafe childId (harness session ids are
152
+ * unconstrained branded strings) is a validation failure at LOOKUP level →
153
+ * passthrough `undefined`, exactly like an absent pin. Only WRITE paths
154
+ * reject unsafe ids.
155
+ */
156
+ const safeRead = (childId) => {
157
+ try {
158
+ return store.read(childId);
159
+ }
160
+ catch {
161
+ return undefined;
162
+ }
163
+ };
164
+ const readPin = safeRead;
165
+ /**
166
+ * Persist a deny to the pin BEFORE the decision returns (§4.6 ordering).
167
+ * Propagates write failure: the caller keeps denying with a reason that
168
+ * names the persistence failure — a pending deny is never downgraded to a
169
+ * followup just because the durable marker could not be written.
170
+ */
171
+ const persistBlocked = (pin, decision) => {
172
+ if (decision.action !== 'deny')
173
+ return;
174
+ store.update(pin.childId, draft => {
175
+ draft.resume = { state: 'blocked', reason: decision.reason };
176
+ draft.lastNotice = decision.reason;
177
+ });
178
+ };
179
+ /**
180
+ * Persist a passing evaluation: clear blocked state, cache overlay/notices.
181
+ * Propagates write failure — a pending PASS/route-current must not followup
182
+ * until the required durable state is published; the caller denies with
183
+ * `STORE_WRITE_FAILURE` instead.
184
+ */
185
+ const persistPass = (pin, decision) => {
186
+ store.update(pin.childId, draft => {
187
+ draft.resume = {
188
+ state: 'ok',
189
+ ...(decision.overlay !== undefined ? { overlay: decision.overlay } : {}),
190
+ };
191
+ draft.lastNotice = decision.notices.length > 0 ? decision.notices.join('\n') : undefined;
192
+ });
193
+ };
194
+ const gateEnv = async (pin, callingAgent) => {
195
+ let sessionExists = false;
196
+ try {
197
+ const persistence = ctx.sessionPersistence;
198
+ if (persistence?.inspect !== undefined) {
199
+ await persistence.inspect(SessionId(pin.childId));
200
+ sessionExists = true;
201
+ }
202
+ }
203
+ catch {
204
+ sessionExists = false;
205
+ }
206
+ let restrictableNames = new Set();
207
+ try {
208
+ const view = ctx.tools
209
+ .view?.(callingAgent);
210
+ restrictableNames = view?.restrictableNames ?? new Set();
211
+ }
212
+ catch {
213
+ restrictableNames = new Set();
214
+ }
215
+ // The calling parent's CURRENT route (AgentOptions) — the route-current
216
+ // fallback overlays onto this, never onto the pinned tuple.
217
+ const currentRoute = callingAgent?.options;
218
+ const llm = ctx.get('llm');
219
+ return {
220
+ sessionExists,
221
+ cwdExists: existsSync(pin.workspace.cwd),
222
+ currentGit: gitIdentity(pin.workspace.cwd),
223
+ currentDefinitionFingerprint: await refingerprintDefinition(pin),
224
+ restrictableNames,
225
+ resolveCallConfig: config => {
226
+ if (llm?.resolveCallConfig === undefined)
227
+ return Promise.reject(new Error('no llm service for the availability preflight'));
228
+ return llm.resolveCallConfig(config);
229
+ },
230
+ resolveDetailed: selector => resolveDetailedAlias(ctx, selector),
231
+ ...(currentRoute !== undefined ? { currentRoute } : {}),
232
+ };
233
+ };
234
+ // §4.6: the resume gate. Fires on every tool call; acts only on
235
+ // `send_message` to a PINNED child with no live Activation. Gate
236
+ // evaluation + persistence + followup admission are serialized per child.
237
+ ctx.on('tools/pre-execute', async (exec, next) => {
238
+ if (exec.name !== 'send_message')
239
+ return next();
240
+ const target = exec.arguments?.subagent_id;
241
+ if (typeof target !== 'string' || target.length === 0)
242
+ return next();
243
+ return serializePerKey(childLocks, target, async () => {
244
+ const found = readPin(target);
245
+ if (found === undefined)
246
+ return next(); // legacy/foreign child: pass through
247
+ if ('kind' in found) {
248
+ return { kind: 'deny', reason: `[PIN_UNREADABLE] resume pin for ${target} is unreadable (${found.reason}); refusing to resume` };
249
+ }
250
+ // Same-epoch followup to a live agent: untouched.
251
+ if (ctx.agents.get(SessionId(target)) !== undefined)
252
+ return next();
253
+ const decision = await evaluateGate(found, await gateEnv(found, exec.agent), policy());
254
+ if (decision.action === 'deny') {
255
+ try {
256
+ persistBlocked(found, decision);
257
+ }
258
+ catch (error) {
259
+ // Fail closed: the deny stands even when the durable marker could
260
+ // not be written; the reason names the persistence failure.
261
+ return {
262
+ kind: 'deny',
263
+ reason: `${decision.reason} (resume pin persistence failed: ${error.message})`,
264
+ };
265
+ }
266
+ return { kind: 'deny', reason: decision.reason };
267
+ }
268
+ // Durability ordering: the pass result (cleared state, overlay cache,
269
+ // notices) is published synchronously BEFORE the followup is queued —
270
+ // a store-write failure denies with a store-write-failure code and the
271
+ // followup never happens.
272
+ try {
273
+ persistPass(found, decision);
274
+ }
275
+ catch (error) {
276
+ return {
277
+ kind: 'deny',
278
+ reason: `[STORE_WRITE_FAILURE] resume pin could not publish the gate result for ${target} (${error.message}); refusing to resume until the durable state is written`,
279
+ };
280
+ }
281
+ if (decision.notices.length > 0) {
282
+ pendingNotices.publish(exec.token, decision.notices);
283
+ }
284
+ return next();
285
+ });
286
+ });
287
+ // §4.7: prefix the gate's notices onto the send_message result; annotate
288
+ // list_agents for pinned children.
289
+ ctx.on('tools/post-execute', async (exec, result, next) => {
290
+ const out = await next();
291
+ if (out.kind !== 'accept')
292
+ return out;
293
+ if (exec.name === 'send_message') {
294
+ const notices = pendingNotices.take(exec.token);
295
+ if (notices.length > 0) {
296
+ return { kind: 'accept', content: [...notices.map(text => ({ type: 'text', text })), ...(out.content ?? [])] };
297
+ }
298
+ return out;
299
+ }
300
+ if (exec.name === 'list_agents') {
301
+ const annotations = [];
302
+ const text = resultText(result);
303
+ for (const childId of store.ids()) {
304
+ if (!text.includes(childId))
305
+ continue;
306
+ const pin = readPin(childId);
307
+ if (pin === undefined)
308
+ continue;
309
+ if ('kind' in pin) {
310
+ annotations.push(`[resume-pin] ${childId}: state blocked (pin unreadable)`);
311
+ continue;
312
+ }
313
+ const current = await refingerprintDefinition(pin);
314
+ const pinnedFingerprint = pin.definition.kind === 'named' ? pin.definition.fingerprint : undefined;
315
+ const definitionChanged = current !== null && (pinnedFingerprint === undefined || current !== pinnedFingerprint);
316
+ const parts = [`state ${pin.resume.state}`];
317
+ if (definitionChanged)
318
+ parts.push('definition changed');
319
+ if (pin.lastNotice !== undefined)
320
+ parts.push(pin.lastNotice);
321
+ annotations.push(`[resume-pin] ${childId}: ${parts.join('; ')}`);
322
+ }
323
+ if (annotations.length > 0) {
324
+ return { kind: 'accept', content: [...(out.content ?? []), { type: 'text', text: annotations.join('\n') }] };
325
+ }
326
+ }
327
+ return out;
328
+ });
329
+ // §4.8: the request-time overlay — every turn of a pinned child, whatever
330
+ // resumed it. Miss → passthrough; blocked or corrupt → visible failure
331
+ // (fail-closed: the corrupt pin file IS the durable blocked marker). The
332
+ // read is read-through: a file deleted or corrupted out-of-band is never
333
+ // served from the cache.
334
+ ctx.on('agent/request', async ({ agent }, next) => {
335
+ const resolved = await next();
336
+ if (agent === undefined)
337
+ return resolved;
338
+ const pin = safeRead(String(agent.id));
339
+ if (pin === undefined)
340
+ return resolved;
341
+ if ('kind' in pin) {
342
+ throw new PinBlockedError(`resume pin for ${String(agent.id)} is unreadable (${pin.reason}); refusing the request`);
343
+ }
344
+ try {
345
+ return applyPinOverlay(resolved, pin);
346
+ }
347
+ catch (error) {
348
+ if (error instanceof PinBlockedError)
349
+ throw error;
350
+ return resolved;
351
+ }
352
+ });
353
+ }
354
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The explicit runtime policy for resume gates (plan §4.9): the
3
+ * `subagents-resume` settings namespace (the plan's `subagents.resume` —
4
+ * the settings namespace grammar only accepts kebab-case). Read LIVE on
5
+ * every gate evaluation: a policy flip is authoritative for the next
6
+ * evaluation, and a persisted `blocked` state never short-circuits it.
7
+ *
8
+ * Every value is an explicit, inspectable choice — there is no silent
9
+ * substitution anywhere. `WORKSPACE_MISSING`, `PIN_ORPHANED`,
10
+ * `PINNED_TOOL_UNAVAILABLE`, and `PIN_UNREADABLE` always block regardless of
11
+ * this policy: no safe fallback exists for them.
12
+ *
13
+ * @module @dsh-cc/subagent-resume-pins/policy
14
+ */
15
+ import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
16
+ /** The settings namespace carrying the resume policy. */
17
+ export declare const RESUME_POLICY_NAMESPACE: SettingsNamespace;
18
+ /** What happens when the pinned provider/model route is no longer available. */
19
+ export type OnUnavailableModel = 'block' | 'route-current';
20
+ /** What happens when a named definition's fingerprint changed (or is gone). */
21
+ export type OnDefinitionChanged = 'resume-with-notice' | 'block';
22
+ /** What happens when the workspace's canonical repo identity changed. */
23
+ export type OnWorkspaceChanged = 'resume-with-notice' | 'block';
24
+ /** The resolved resume policy consumed by the gate. */
25
+ export interface ResumePolicy {
26
+ readonly onUnavailableModel: OnUnavailableModel;
27
+ readonly onDefinitionChanged: OnDefinitionChanged;
28
+ readonly onWorkspaceChanged: OnWorkspaceChanged;
29
+ }
30
+ /** The defaults every value falls back to; fail-closed on the model knob. */
31
+ export declare const RESUME_POLICY_DEFAULTS: ResumePolicy;
32
+ /**
33
+ * Resolve the live policy from one raw settings section: unknown fields are
34
+ * ignored; an invalid value falls back to its default (the section schema and
35
+ * write-time validation are the first line — this keeps a hand-edited document
36
+ * from being read as an unintended policy). Absent section → the defaults.
37
+ * @param raw - the live `subagents-resume` section (any shape).
38
+ * @returns the resolved policy.
39
+ */
40
+ export declare function readResumePolicy(raw: unknown): ResumePolicy;
41
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAElE,yDAAyD;AACzD,eAAO,MAAM,uBAAuB,EAAE,iBAAyD,CAAA;AAE/F,gFAAgF;AAChF,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,eAAe,CAAA;AAE1D,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,GAAG,OAAO,CAAA;AAEhE,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,oBAAoB,GAAG,OAAO,CAAA;AAE/D,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAA;IAC/C,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAA;IACjD,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAA;CAChD;AAED,6EAA6E;AAC7E,eAAO,MAAM,sBAAsB,EAAE,YAIpC,CAAA;AAKD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAU3D"}
package/lib/policy.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The explicit runtime policy for resume gates (plan §4.9): the
3
+ * `subagents-resume` settings namespace (the plan's `subagents.resume` —
4
+ * the settings namespace grammar only accepts kebab-case). Read LIVE on
5
+ * every gate evaluation: a policy flip is authoritative for the next
6
+ * evaluation, and a persisted `blocked` state never short-circuits it.
7
+ *
8
+ * Every value is an explicit, inspectable choice — there is no silent
9
+ * substitution anywhere. `WORKSPACE_MISSING`, `PIN_ORPHANED`,
10
+ * `PINNED_TOOL_UNAVAILABLE`, and `PIN_UNREADABLE` always block regardless of
11
+ * this policy: no safe fallback exists for them.
12
+ *
13
+ * @module @dsh-cc/subagent-resume-pins/policy
14
+ */
15
+ import { settingsNamespace } from '@deepseek-ai/dsh-settings';
16
+ /** The settings namespace carrying the resume policy. */
17
+ export const RESUME_POLICY_NAMESPACE = settingsNamespace('subagents-resume');
18
+ /** The defaults every value falls back to; fail-closed on the model knob. */
19
+ export const RESUME_POLICY_DEFAULTS = {
20
+ onUnavailableModel: 'block',
21
+ onDefinitionChanged: 'resume-with-notice',
22
+ onWorkspaceChanged: 'resume-with-notice',
23
+ };
24
+ const ON_UNAVAILABLE = ['block', 'route-current'];
25
+ const ON_CHANGED = ['resume-with-notice', 'block'];
26
+ /**
27
+ * Resolve the live policy from one raw settings section: unknown fields are
28
+ * ignored; an invalid value falls back to its default (the section schema and
29
+ * write-time validation are the first line — this keeps a hand-edited document
30
+ * from being read as an unintended policy). Absent section → the defaults.
31
+ * @param raw - the live `subagents-resume` section (any shape).
32
+ * @returns the resolved policy.
33
+ */
34
+ export function readResumePolicy(raw) {
35
+ if (typeof raw !== 'object' || raw === null)
36
+ return RESUME_POLICY_DEFAULTS;
37
+ const record = raw;
38
+ const pick = (value, values, fallback) => typeof value === 'string' && values.includes(value) ? value : fallback;
39
+ return {
40
+ onUnavailableModel: pick(record['onUnavailableModel'], ON_UNAVAILABLE, RESUME_POLICY_DEFAULTS.onUnavailableModel),
41
+ onDefinitionChanged: pick(record['onDefinitionChanged'], ON_CHANGED, RESUME_POLICY_DEFAULTS.onDefinitionChanged),
42
+ onWorkspaceChanged: pick(record['onWorkspaceChanged'], ON_CHANGED, RESUME_POLICY_DEFAULTS.onWorkspaceChanged),
43
+ };
44
+ }
45
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAG7D,yDAAyD;AACzD,MAAM,CAAC,MAAM,uBAAuB,GAAsB,iBAAiB,CAAC,kBAAkB,CAAC,CAAA;AAkB/F,6EAA6E;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAiB;IAClD,kBAAkB,EAAE,OAAO;IAC3B,mBAAmB,EAAE,oBAAoB;IACzC,kBAAkB,EAAE,oBAAoB;CACzC,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,eAAe,CAAU,CAAA;AAC1D,MAAM,UAAU,GAAG,CAAC,oBAAoB,EAAE,OAAO,CAAU,CAAA;AAE3D;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,sBAAsB,CAAA;IAC1E,MAAM,MAAM,GAAG,GAA8B,CAAA;IAC7C,MAAM,IAAI,GAAG,CAAmB,KAAc,EAAE,MAAoB,EAAE,QAAW,EAAK,EAAE,CACtF,OAAO,KAAK,KAAK,QAAQ,IAAK,MAA4B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAU,CAAC,CAAC,CAAC,QAAQ,CAAA;IACpG,OAAO;QACL,kBAAkB,EAAE,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,EAAE,cAAc,EAAE,sBAAsB,CAAC,kBAAkB,CAAC;QACjH,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,EAAE,UAAU,EAAE,sBAAsB,CAAC,mBAAmB,CAAC;QAChH,kBAAkB,EAAE,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,EAAE,UAAU,EAAE,sBAAsB,CAAC,kBAAkB,CAAC;KAC9G,CAAA;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Plugin-side concurrency primitives (plan §4.6/§4.7): per-child gate
3
+ * serialization and execution-token-keyed notice delivery.
4
+ *
5
+ * - {@link serializePerKey} runs tasks with the same key strictly in start
6
+ * order (a per-key promise chain): concurrent gate evaluations +
7
+ * persistence + followup admission for one cold child cannot interleave.
8
+ * A failing task propagates its rejection to its own caller only — the
9
+ * chain continues for later tasks.
10
+ * - {@link ExecutionNoticeBus} keys pending gate notices by the tool
11
+ * execution identity (`exec.token`, present on both the pre- and
12
+ * post-execute payloads), so a failing send can never leak its notice into
13
+ * a later call: only the very same execution may take them.
14
+ *
15
+ * @module @dsh-cc/subagent-resume-pins/serialize
16
+ */
17
+ /** Run `task` after any pending same-key task settles; keyed FIFO order. */
18
+ export declare function serializePerKey<T>(locks: Map<string, Promise<unknown>>, key: string, task: () => Promise<T>): Promise<T>;
19
+ /** Pending gate notices keyed by tool execution identity. */
20
+ export declare class ExecutionNoticeBus {
21
+ private readonly pending;
22
+ /** Publish the notices a passing gate produced for ONE execution. */
23
+ publish(token: unknown, notices: readonly string[]): void;
24
+ /** Take (and clear) the notices published for exactly this execution. */
25
+ take(token: unknown): string[];
26
+ }
27
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,EACpC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACrB,OAAO,CAAC,CAAC,CAAC,CAIZ;AAED,6DAA6D;AAC7D,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IAEvD,qEAAqE;IACrE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIzD,yEAAyE;IACzE,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE;CAK/B"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Plugin-side concurrency primitives (plan §4.6/§4.7): per-child gate
3
+ * serialization and execution-token-keyed notice delivery.
4
+ *
5
+ * - {@link serializePerKey} runs tasks with the same key strictly in start
6
+ * order (a per-key promise chain): concurrent gate evaluations +
7
+ * persistence + followup admission for one cold child cannot interleave.
8
+ * A failing task propagates its rejection to its own caller only — the
9
+ * chain continues for later tasks.
10
+ * - {@link ExecutionNoticeBus} keys pending gate notices by the tool
11
+ * execution identity (`exec.token`, present on both the pre- and
12
+ * post-execute payloads), so a failing send can never leak its notice into
13
+ * a later call: only the very same execution may take them.
14
+ *
15
+ * @module @dsh-cc/subagent-resume-pins/serialize
16
+ */
17
+ /** Run `task` after any pending same-key task settles; keyed FIFO order. */
18
+ export function serializePerKey(locks, key, task) {
19
+ const tail = (locks.get(key) ?? Promise.resolve()).then(task, task);
20
+ locks.set(key, tail.catch(() => { }));
21
+ return tail;
22
+ }
23
+ /** Pending gate notices keyed by tool execution identity. */
24
+ export class ExecutionNoticeBus {
25
+ pending = new Map();
26
+ /** Publish the notices a passing gate produced for ONE execution. */
27
+ publish(token, notices) {
28
+ if (notices.length > 0)
29
+ this.pending.set(token, [...notices]);
30
+ }
31
+ /** Take (and clear) the notices published for exactly this execution. */
32
+ take(token) {
33
+ const notices = this.pending.get(token);
34
+ this.pending.delete(token);
35
+ return notices ?? [];
36
+ }
37
+ }
38
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAC7B,KAAoC,EACpC,GAAW,EACX,IAAsB;IAEtB,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACnE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAA;IACpC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,6DAA6D;AAC7D,MAAM,OAAO,kBAAkB;IACZ,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAA;IAEvD,qEAAqE;IACrE,OAAO,CAAC,KAAc,EAAE,OAA0B;QAChD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAA;IAC/D,CAAC;IAED,yEAAyE;IACzE,IAAI,CAAC,KAAc;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACvC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1B,OAAO,OAAO,IAAI,EAAE,CAAA;IACtB,CAAC;CACF"}
package/lib/store.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Per-child pin store: one JSON file per child under `<pinsRoot>/<childId>.json`.
3
+ *
4
+ * Durability and coherence contract (plan §4.1/§4.6):
5
+ * - `write` creates a pin and fails if one already exists (preallocated ids).
6
+ * - `update` rewrites the file atomically (temp file + rename) AND publishes
7
+ * the mutated pin to the store's single synchronous in-memory cache, so no
8
+ * stale-cache read can serve the first request after a gate mutation.
9
+ * - `read` resolves `undefined` for an absent pin (legacy/foreign child,
10
+ * pass-through) and a `{kind:'corrupt'}` sentinel for a file that exists
11
+ * but is unparseable or unsupported-version — fail-closed callers
12
+ * distinguish those; it never throws.
13
+ * - `remove` tombstone-deletes the file and the cache entry.
14
+ * - childIds are UUID-ish; anything containing a path separator or `..` is
15
+ * rejected before it can traverse out of `pinsRoot`.
16
+ *
17
+ * @module @dsh-cc/subagent-resume-pins/store
18
+ */
19
+ import { type ResumePin, type ResumePinDraft } from './pin.ts';
20
+ /** Distinguishing result for an existing-but-unreadable pin file. */
21
+ export type CorruptPin = {
22
+ readonly kind: 'corrupt';
23
+ readonly reason: string;
24
+ };
25
+ /**
26
+ * Owns the pin files under one root and the single shared in-memory cache the
27
+ * gate and overlay listeners consult.
28
+ */
29
+ export declare class PinStore {
30
+ private readonly pinsRoot;
31
+ private readonly cache;
32
+ constructor(pinsRoot: string);
33
+ /** The on-disk path for one child's pin (validates the childId first). */
34
+ pathFor(childId: string): string;
35
+ /**
36
+ * Read a pin by child id: `undefined` when absent, the corrupt sentinel when
37
+ * the file exists but cannot be parsed. Read-through: the disk is re-read on
38
+ * every call (files are tiny, one per model request), so a file deleted or
39
+ * corrupted out-of-band is never served stale from the cache; the cache only
40
+ * accelerates nothing — it exists to publish synchronous in-process updates
41
+ * and is invalidated by any failed disk read.
42
+ */
43
+ read(childId: string): ResumePin | CorruptPin | undefined;
44
+ /** Read only the in-memory cache: `undefined` on miss (no disk access). */
45
+ getCached(childId: string): ResumePin | undefined;
46
+ /** Create the pin file; fails when a pin for this child already exists. */
47
+ write(pin: ResumePin): void;
48
+ /**
49
+ * Atomically rewrite a pin through `mutator` (a structural clone is handed
50
+ * to the mutator) and publish the result to the shared cache synchronously.
51
+ */
52
+ update(childId: string, mutator: (draft: ResumePinDraft) => void): ResumePin;
53
+ /** Tombstone-delete the pin file and its cache entry (idempotent). */
54
+ remove(childId: string): void;
55
+ /**
56
+ * The child ids with a pin file on disk (best-effort; used by the
57
+ * `list_agents` annotation). Not guaranteed cached or readable.
58
+ */
59
+ ids(): string[];
60
+ private atomicWrite;
61
+ }
62
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAAqC,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAEjG,qEAAqE;AACrE,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAS9E;;;GAGG;AACH,qBAAa,QAAQ;IAGP,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;gBAExB,QAAQ,EAAE,MAAM;IAI7C,0EAA0E;IAC1E,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAKhC;;;;;;;OAOG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS;IAwBzD,2EAA2E;IAC3E,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIjD,2EAA2E;IAC3E,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI;IAS3B;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS;IAc5E,sEAAsE;IACtE,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK7B;;;OAGG;IACH,GAAG,IAAI,MAAM,EAAE;IAUf,OAAO,CAAC,WAAW;CAKpB"}