ferrycat 0.3.2 → 0.4.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.
@@ -0,0 +1,562 @@
1
+ /**
2
+ * FerryCat execution supervisor (Phase 3 Step 0).
3
+ *
4
+ * Owns the lifecycle of one automated `worker --exec / --exec-file` child process:
5
+ * - Heartbeat lease keep-alive bound to claim_token (CK-01 / CK-02 / CK-03)
6
+ * - Process-tree termination incl. grandchildren (CK-04 / CK-05)
7
+ * - Minimal environment allowlist for the child (CK-06)
8
+ * - Partial-output salvage before the execution window closes (CK-07)
9
+ *
10
+ * Deliberately introduces NO new contract states (CK-09): every outcome maps onto the
11
+ * existing CLAIMED -> COMPLETED / FAILED / TIMED_OUT transitions exposed by the engine.
12
+ * See docs/audits/2026-10-24-phase-3-multi-agent-consensus-audit_zh.md.
13
+ */
14
+ import { spawn, spawnSync } from 'node:child_process';
15
+ import path from 'node:path';
16
+ import { expandTilde } from './engine.js';
17
+ import { FulfillContractSchema } from './schemas.js';
18
+ // ---------------------------------------------------------------------------
19
+ // CK-06: Minimal child environment
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Variables the child genuinely needs to run and to locate the user's *file-based* login
23
+ * state (~/.config, %APPDATA%). Token-bearing env vars are a different threat model from
24
+ * user-authorized credential files and are stripped (see audit §1.4 "reverse trap").
25
+ */
26
+ const WINDOWS_ENV_ALLOWLIST = new Set([
27
+ 'PATH',
28
+ 'PATHEXT',
29
+ 'SYSTEMROOT',
30
+ 'SYSTEMDRIVE',
31
+ 'WINDIR',
32
+ 'COMSPEC',
33
+ 'TEMP',
34
+ 'TMP',
35
+ 'USERPROFILE',
36
+ 'USERNAME',
37
+ 'HOMEDRIVE',
38
+ 'HOMEPATH',
39
+ 'APPDATA',
40
+ 'LOCALAPPDATA',
41
+ 'PROGRAMDATA',
42
+ 'PROGRAMFILES',
43
+ 'PROGRAMFILES(X86)',
44
+ 'NUMBER_OF_PROCESSORS',
45
+ 'PROCESSOR_ARCHITECTURE',
46
+ 'LANG',
47
+ 'AGENT_NAME',
48
+ ]);
49
+ const POSIX_ENV_ALLOWLIST = new Set([
50
+ 'PATH',
51
+ 'HOME',
52
+ 'TMPDIR',
53
+ 'SHELL',
54
+ 'USER',
55
+ 'LOGNAME',
56
+ 'LANG',
57
+ 'LC_ALL',
58
+ 'LC_CTYPE',
59
+ 'TERM',
60
+ 'XDG_CONFIG_HOME',
61
+ 'XDG_DATA_HOME',
62
+ 'XDG_CACHE_HOME',
63
+ 'XDG_STATE_HOME',
64
+ 'XDG_RUNTIME_DIR',
65
+ 'AGENT_NAME',
66
+ ]);
67
+ /** Defense in depth: even an allowlisted or FERRYCAT_* name is dropped if it looks secret. */
68
+ const SECRET_NAME_PATTERNS = [
69
+ /_TOKEN$/i,
70
+ /_KEY$/i,
71
+ /_SECRET$/i,
72
+ /SECRET/i,
73
+ /PASSWORD/i,
74
+ /CREDENTIAL/i,
75
+ /^AWS_/i,
76
+ /^GITHUB_/i,
77
+ /^GH_/i,
78
+ /^NPM_/i,
79
+ /^OPENAI_/i,
80
+ /^ANTHROPIC_/i,
81
+ ];
82
+ const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_().]*$/;
83
+ export function isSecretEnvName(name) {
84
+ return SECRET_NAME_PATTERNS.some((re) => re.test(name));
85
+ }
86
+ export function buildChildEnv(contract, passEnv = [], parentEnv = process.env, platform = process.platform) {
87
+ const isWin = platform === 'win32';
88
+ const allowlist = isWin ? WINDOWS_ENV_ALLOWLIST : POSIX_ENV_ALLOWLIST;
89
+ const norm = (k) => (isWin ? k.toUpperCase() : k);
90
+ const explicit = new Set(passEnv.map((n) => n.trim()).filter((n) => ENV_NAME_PATTERN.test(n)).map(norm));
91
+ const env = {};
92
+ let dropped = 0;
93
+ for (const [key, value] of Object.entries(parentEnv)) {
94
+ if (value === undefined)
95
+ continue;
96
+ const k = norm(key);
97
+ if (explicit.has(k)) {
98
+ // Explicit --pass-env is user consent and intentionally overrides the secret filter.
99
+ env[key] = value;
100
+ continue;
101
+ }
102
+ const allowed = allowlist.has(k) || k.toUpperCase().startsWith('FERRYCAT_');
103
+ if (allowed && !isSecretEnvName(key)) {
104
+ env[key] = value;
105
+ }
106
+ else {
107
+ dropped++;
108
+ }
109
+ }
110
+ // Injected lease context always wins over anything inherited.
111
+ env.FERRYCAT_CONTRACT_ID = contract.contract_id;
112
+ env.FERRYCAT_TASK_TYPE = contract.task_type;
113
+ env.FERRYCAT_WORKSPACE_ROOT = contract.workspace_root;
114
+ env.FERRYCAT_TARGET_AGENT = contract.target_agent;
115
+ env.FERRYCAT_CLAIM_TOKEN = contract.claim_token ?? '';
116
+ return { env, dropped };
117
+ }
118
+ const activeChildren = new Set();
119
+ function resolveExecTarget(spec) {
120
+ const extraArgs = spec.args ?? [];
121
+ if (spec.execFile) {
122
+ const script = path.resolve(expandTilde(spec.execFile));
123
+ const ext = path.extname(script).toLowerCase();
124
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
125
+ return { command: process.execPath, args: [script, ...extraArgs], shell: false };
126
+ }
127
+ if (ext === '.ts') {
128
+ return { command: process.execPath, args: ['--import', 'tsx', script, ...extraArgs], shell: false };
129
+ }
130
+ if (ext === '.ps1') {
131
+ return {
132
+ command: 'powershell',
133
+ args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', script, ...extraArgs],
134
+ shell: false,
135
+ };
136
+ }
137
+ if (ext === '.sh') {
138
+ return { command: 'sh', args: [script, ...extraArgs], shell: false };
139
+ }
140
+ return { command: script, args: [...extraArgs], shell: false };
141
+ }
142
+ // --exec: explicit shell compatibility mode (FC-303), unchanged semantics.
143
+ return { command: spec.execCmd, args: [], shell: true };
144
+ }
145
+ function waitWithTimeout(p, ms) {
146
+ return new Promise((resolve) => {
147
+ const timer = setTimeout(() => resolve(false), ms);
148
+ p.then(() => {
149
+ clearTimeout(timer);
150
+ resolve(true);
151
+ });
152
+ });
153
+ }
154
+ export function createSupervisedChild(child) {
155
+ let resolveClosed;
156
+ const closedPromise = new Promise((r) => (resolveClosed = r));
157
+ const sup = {
158
+ child,
159
+ pid: child.pid,
160
+ startedAt: Date.now(),
161
+ closed: false,
162
+ closedPromise,
163
+ };
164
+ child.once('close', () => {
165
+ sup.closed = true;
166
+ resolveClosed();
167
+ });
168
+ return sup;
169
+ }
170
+ /**
171
+ * Terminate the child's whole process tree and wait until it is confirmed gone.
172
+ *
173
+ * PID-reuse safety (CK-05): we ONLY ever signal a tree that we explicitly spawned and still
174
+ * supervise via its SupervisedChild handle. Bare numeric PID inputs are strictly forbidden
175
+ * to eliminate inadvertent signaling of recycled OS PIDs.
176
+ * - POSIX: the child leads its own process group (detached: true). A PGID cannot be recycled
177
+ * while any member is alive, so kill(-pid) cannot hit an unrelated process group.
178
+ * - Windows: libuv holds the process handle until exit, so the PID cannot be recycled
179
+ * while the leader is alive. Once the leader exited, `taskkill /T` can no longer walk
180
+ * the tree; this is the documented non-equivalence vs. Job Objects (audit §1.3).
181
+ */
182
+ export async function terminateProcessTree(sup, graceMs = 3000) {
183
+ if (sup.closed)
184
+ return { skipped: 'ALREADY_EXITED', confirmed: true };
185
+ const pid = sup.pid;
186
+ if (!pid)
187
+ return { skipped: 'NO_PID', confirmed: sup.closed };
188
+ if (process.platform === 'win32') {
189
+ spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
190
+ const confirmed = await waitWithTimeout(sup.closedPromise, Math.max(2000, graceMs));
191
+ return { method: 'taskkill /T /F', confirmed };
192
+ }
193
+ const signalGroup = (sig) => {
194
+ try {
195
+ process.kill(-pid, sig);
196
+ return true;
197
+ }
198
+ catch {
199
+ return false; // ESRCH: group already empty
200
+ }
201
+ };
202
+ signalGroup('SIGTERM');
203
+ if (await waitWithTimeout(sup.closedPromise, graceMs)) {
204
+ return { method: 'SIGTERM(-pgid)', confirmed: true };
205
+ }
206
+ signalGroup('SIGKILL');
207
+ const confirmed = await waitWithTimeout(sup.closedPromise, 2000);
208
+ return { method: 'SIGTERM->SIGKILL(-pgid)', confirmed };
209
+ }
210
+ /**
211
+ * Synchronous last-resort cleanup for a forced worker exit (second Ctrl+C). Because POSIX
212
+ * children now run in their own process group, terminal SIGINT no longer reaches them, so
213
+ * the worker must take them down explicitly instead of orphaning them.
214
+ */
215
+ export function terminateActiveChildrenSync() {
216
+ let count = 0;
217
+ for (const sup of activeChildren) {
218
+ if (sup.closed || !sup.pid)
219
+ continue;
220
+ count++;
221
+ if (process.platform === 'win32') {
222
+ spawnSync('taskkill', ['/PID', String(sup.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
223
+ }
224
+ else {
225
+ try {
226
+ process.kill(-sup.pid, 'SIGKILL');
227
+ }
228
+ catch {
229
+ // group already gone
230
+ }
231
+ }
232
+ }
233
+ return count;
234
+ }
235
+ // ---------------------------------------------------------------------------
236
+ // Execution with heartbeat lease (CK-01 / CK-02 / CK-03 / CK-07)
237
+ // ---------------------------------------------------------------------------
238
+ const SALVAGE_TAIL_CHARS = 256 * 1024;
239
+ function clamp(n, lo, hi) {
240
+ return Math.min(hi, Math.max(lo, n));
241
+ }
242
+ function stripBom(content) {
243
+ return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
244
+ }
245
+ export async function executeTaskAndFulfill(engine, contract, spec, opts = {}) {
246
+ const emit = (event) => {
247
+ try {
248
+ opts.onEvent?.({ contract_id: contract.contract_id, ...event });
249
+ }
250
+ catch {
251
+ // observability must never affect lifecycle correctness
252
+ }
253
+ };
254
+ const effectiveTtlSec = opts.execTtlSec
255
+ ? Math.min(contract.exec_ttl_sec, opts.execTtlSec)
256
+ : contract.exec_ttl_sec;
257
+ const ttlMs = effectiveTtlSec * 1000;
258
+ const tickMs = opts.heartbeatIntervalMs ?? Math.max(100, Math.min(60000, Math.floor(ttlMs / 3)));
259
+ const graceMs = opts.killGraceMs ?? clamp(Math.floor(ttlMs / 10), 200, 3000);
260
+ // The guard fires early enough to terminate (grace) AND report failure before the window closes.
261
+ const guardLeadMs = graceMs + clamp(Math.floor(ttlMs / 10), 200, 2000);
262
+ const renewSlackMs = Math.min(1000, Math.floor(tickMs / 2));
263
+ const maxBuffer = opts.maxBufferBytes ?? 10 * 1024 * 1024;
264
+ const label = spec.execFile ?? spec.execCmd ?? 'exec';
265
+ const token = contract.claim_token;
266
+ const { env, dropped } = buildChildEnv(contract, spec.passEnv ?? []);
267
+ emit({ event: 'ENV_SANITIZED', dropped_vars: dropped, passed_explicitly: spec.passEnv?.length ?? 0 });
268
+ const target = resolveExecTarget(spec);
269
+ let child;
270
+ try {
271
+ child = spawn(target.command, target.args, {
272
+ env,
273
+ shell: target.shell,
274
+ stdio: ['pipe', 'pipe', 'pipe'],
275
+ detached: process.platform !== 'win32',
276
+ windowsHide: true,
277
+ });
278
+ }
279
+ catch (spawnErr) {
280
+ return failAfterExit(`External exec command failed to start: ${spawnErr instanceof Error ? spawnErr.message : String(spawnErr)}`);
281
+ }
282
+ let resolveClosed;
283
+ const closedPromise = new Promise((r) => (resolveClosed = r));
284
+ const sup = {
285
+ child,
286
+ pid: child.pid,
287
+ startedAt: Date.now(),
288
+ closed: false,
289
+ closedPromise,
290
+ };
291
+ activeChildren.add(sup);
292
+ emit({ event: 'CHILD_SPAWNED', pid: sup.pid ?? null, started_at: sup.startedAt, tick_ms: tickMs });
293
+ let stdout = '';
294
+ let stderr = '';
295
+ let stdoutBytes = 0;
296
+ let abort = null;
297
+ let salvaged = false;
298
+ let windowEndsAt = (contract.claimed_at ?? Date.now()) + ttlMs;
299
+ let budgetExhausted = false;
300
+ let tickTimer;
301
+ let guardTimer;
302
+ const exitInfo = new Promise((resolve) => {
303
+ let settled = false;
304
+ const settle = (v) => {
305
+ if (settled)
306
+ return;
307
+ settled = true;
308
+ sup.closed = true;
309
+ resolveClosed();
310
+ resolve(v);
311
+ };
312
+ child.on('error', (error) => settle({ code: null, signal: null, error }));
313
+ child.on('close', (code, signal) => settle({ code, signal }));
314
+ });
315
+ const stopTimers = () => {
316
+ if (tickTimer)
317
+ clearInterval(tickTimer);
318
+ if (guardTimer)
319
+ clearTimeout(guardTimer);
320
+ tickTimer = undefined;
321
+ guardTimer = undefined;
322
+ };
323
+ const salvageNow = (source) => {
324
+ if (!stdout && !stderr)
325
+ return;
326
+ try {
327
+ const ok = engine.recordSalvage({
328
+ contract_id: contract.contract_id,
329
+ agent_id: contract.target_agent,
330
+ claim_token: token,
331
+ salvage: {
332
+ source,
333
+ captured_at: Date.now(),
334
+ pid: sup.pid ?? null,
335
+ started_at: sup.startedAt,
336
+ stdout_bytes: stdoutBytes,
337
+ truncated: stdout.length > SALVAGE_TAIL_CHARS,
338
+ stdout_tail: stdout.slice(-SALVAGE_TAIL_CHARS),
339
+ stderr_tail: stderr.slice(-16 * 1024),
340
+ },
341
+ });
342
+ salvaged = salvaged || ok;
343
+ }
344
+ catch (err) {
345
+ emit({ event: 'SALVAGE_FAILED', error: err instanceof Error ? err.message : String(err) });
346
+ }
347
+ };
348
+ const triggerAbort = (kind, reason) => {
349
+ if (abort)
350
+ return;
351
+ abort = { kind, reason };
352
+ stopTimers();
353
+ salvageNow(kind);
354
+ emit({ event: 'CHILD_TERMINATING', kind, reason });
355
+ void terminateProcessTree(sup, graceMs).then((r) => emit({ event: 'CHILD_TERMINATED', ...r }));
356
+ };
357
+ const armGuard = () => {
358
+ if (guardTimer)
359
+ clearTimeout(guardTimer);
360
+ const delay = Math.max(0, windowEndsAt - guardLeadMs - Date.now());
361
+ guardTimer = setTimeout(() => {
362
+ triggerAbort('WINDOW_EXHAUSTED', budgetExhausted
363
+ ? 'EXEC_WINDOW_EXHAUSTED: heartbeat renewal budget exhausted (max extends or lifetime cap); child terminated before the window closed'
364
+ : 'EXEC_WINDOW_EXHAUSTED: execution window about to close without a successful renewal; child terminated');
365
+ }, delay);
366
+ };
367
+ /** Re-read the row; returns true if we still hold the lease (and syncs the window). */
368
+ const stillOwnsLease = () => {
369
+ const snap = engine.getLeaseSnapshot(contract.contract_id);
370
+ if (!snap || snap.status !== 'CLAIMED')
371
+ return false;
372
+ if (snap.claim_token && token && snap.claim_token !== token)
373
+ return false;
374
+ if (snap.claimed_at !== null) {
375
+ const synced = snap.claimed_at + snap.exec_ttl_sec * 1000;
376
+ if (synced !== windowEndsAt) {
377
+ windowEndsAt = synced;
378
+ armGuard();
379
+ }
380
+ }
381
+ return true;
382
+ };
383
+ const tick = () => {
384
+ if (abort)
385
+ return;
386
+ try {
387
+ if (!stillOwnsLease()) {
388
+ triggerAbort('LEASE_LOST', 'LEASE_LOST: contract is no longer CLAIMED under this claim_token');
389
+ return;
390
+ }
391
+ if (budgetExhausted)
392
+ return;
393
+ // Lazy renewal: only spend one of the MAX_TTL_EXTENSIONS when the next tick would land
394
+ // inside the guard lead. Renewing on every tick would burn the 5-extension budget in
395
+ // under two windows (CK-01 acceptance: ttl=30s, tick=10s, 120s run must complete).
396
+ const headroom = windowEndsAt - guardLeadMs - Date.now();
397
+ if (headroom > tickMs + renewSlackMs)
398
+ return;
399
+ const res = engine.extendTtl({
400
+ contract_id: contract.contract_id,
401
+ agent_id: contract.target_agent,
402
+ claim_token: token,
403
+ });
404
+ if (res.ok && res.expires_at) {
405
+ windowEndsAt = res.expires_at;
406
+ armGuard();
407
+ emit({ event: 'HEARTBEAT_RENEWED', extend_count: res.extend_count, expires_at: res.expires_at });
408
+ }
409
+ else if (res.status === 'TIMED_OUT') {
410
+ triggerAbort('LEASE_LOST', `LEASE_LOST: ${res.message}`);
411
+ }
412
+ else {
413
+ // CK-02 reverse binding: ok:false while still CLAIMED (extend cap / lifetime cap).
414
+ // Stop renewing; the guard (already armed for the current window) will terminate
415
+ // the tree before the window closes instead of leaving an orphan behind.
416
+ budgetExhausted = true;
417
+ if (res.expires_at)
418
+ windowEndsAt = res.expires_at;
419
+ armGuard();
420
+ emit({ event: 'HEARTBEAT_EXHAUSTED', message: res.message });
421
+ }
422
+ }
423
+ catch (err) {
424
+ // CK-03: optimistic-lock conflicts throw; never let a heartbeat kill the worker.
425
+ const message = err instanceof Error ? err.message : String(err);
426
+ emit({ event: 'HEARTBEAT_ERROR', error: message });
427
+ try {
428
+ if (!stillOwnsLease()) {
429
+ triggerAbort('LEASE_LOST', `LEASE_LOST: ${message}`);
430
+ }
431
+ }
432
+ catch (readErr) {
433
+ emit({ event: 'HEARTBEAT_ERROR', error: readErr instanceof Error ? readErr.message : String(readErr) });
434
+ }
435
+ }
436
+ };
437
+ child.stdout?.setEncoding('utf8');
438
+ child.stderr?.setEncoding('utf8');
439
+ child.stdout?.on('data', (chunk) => {
440
+ stdoutBytes += Buffer.byteLength(chunk, 'utf8');
441
+ if (stdoutBytes > maxBuffer) {
442
+ triggerAbort('MAX_BUFFER', `stdout exceeded maxBuffer (${maxBuffer} bytes)`);
443
+ return;
444
+ }
445
+ stdout += chunk;
446
+ });
447
+ child.stderr?.on('data', (chunk) => {
448
+ stderr = (stderr + chunk).slice(-64 * 1024);
449
+ });
450
+ if (child.stdin) {
451
+ child.stdin.on('error', () => {
452
+ // Child exited without consuming stdin, ignore EPIPE
453
+ });
454
+ child.stdin.write(JSON.stringify(contract));
455
+ child.stdin.end();
456
+ }
457
+ armGuard();
458
+ tickTimer = setInterval(tick, tickMs);
459
+ const exit = await exitInfo;
460
+ stopTimers();
461
+ activeChildren.delete(sup);
462
+ if (exit.error) {
463
+ return failAfterExit(`External exec command failed with code ${exit.error.code ?? 1}: ${exit.error.message}`);
464
+ }
465
+ const abortState = abort;
466
+ if (abortState?.kind === 'LEASE_LOST') {
467
+ return { ok: false, status: 'LEASE_LOST', error: abortState.reason, salvaged };
468
+ }
469
+ if (abortState) {
470
+ const res = failAfterExit(`${abortState.reason}${stderr ? ` | stderr: ${stderr.slice(-1000)}` : ''}`);
471
+ res.status = abortState.kind === 'MAX_BUFFER' ? 'EXEC_FAILED' : 'EXEC_WINDOW_EXHAUSTED';
472
+ res.salvaged = salvaged;
473
+ return res;
474
+ }
475
+ if (exit.code !== 0) {
476
+ salvageNow('NON_ZERO_EXIT');
477
+ const exitDesc = exit.signal
478
+ ? `terminated by signal ${exit.signal}`
479
+ : `failed with code ${exit.code ?? 1}`;
480
+ return failAfterExit(`External exec command ${exitDesc}: ${stderr || 'no stderr output'}`);
481
+ }
482
+ return fulfillFromStdout();
483
+ // -------------------------------------------------------------------------
484
+ function failAfterExit(reason) {
485
+ let contractStatus;
486
+ try {
487
+ contractStatus = engine.failContract({
488
+ contract_id: contract.contract_id,
489
+ agent_id: contract.target_agent,
490
+ claim_token: token,
491
+ reason: reason.slice(0, 2000),
492
+ }).status;
493
+ }
494
+ catch (failErr) {
495
+ contractStatus = `UNCHANGED (${failErr instanceof Error ? failErr.message : String(failErr)})`;
496
+ }
497
+ return { ok: false, status: 'EXEC_FAILED', error: reason, contract_status: contractStatus, salvaged };
498
+ }
499
+ function fulfillFromStdout() {
500
+ let structuredOutput;
501
+ let resultSummary = stripBom(stdout).trim();
502
+ try {
503
+ const parsed = JSON.parse(resultSummary);
504
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
505
+ if (parsed.structured_output && typeof parsed.structured_output === 'object') {
506
+ structuredOutput = parsed.structured_output;
507
+ }
508
+ else {
509
+ structuredOutput = parsed;
510
+ }
511
+ if (parsed.result_summary && typeof parsed.result_summary === 'string') {
512
+ resultSummary = parsed.result_summary;
513
+ }
514
+ }
515
+ }
516
+ catch {
517
+ // Plain text stdout, fallback to result_summary
518
+ }
519
+ const boundedSummary = (resultSummary || `Completed via automated worker exec: ${label}`).slice(0, 20000);
520
+ try {
521
+ const validatedFulfillInput = FulfillContractSchema.parse({
522
+ contract_id: contract.contract_id,
523
+ agent_id: contract.target_agent,
524
+ claim_token: token,
525
+ result_summary: boundedSummary,
526
+ structured_output: structuredOutput,
527
+ });
528
+ let fulfillRes = engine.fulfillContract(validatedFulfillInput);
529
+ if (fulfillRes.status === 'RETRY_NEEDED') {
530
+ // RETRY_NEEDED has two distinct causes with incompatible retry semantics:
531
+ // (a) schema validation failure -> retry_count increments, so retrying the
532
+ // same payload until the breaker trips is the intended behavior.
533
+ // (b) TOCTOU dirty pointers -> retry_count does NOT move. The on-disk
534
+ // baseline cannot heal between two consecutive calls, so retrying only
535
+ // burns the single ADR-0004 strike and forces a terminal POINTER_STALE
536
+ // with nobody in the loop to restore the files. Never auto-retry it.
537
+ const isToctouRejection = (fulfillRes.toctou_strike ?? 0) > 0 && (fulfillRes.retry_count ?? 0) === 0;
538
+ if (isToctouRejection) {
539
+ return {
540
+ ok: false,
541
+ status: 'TOCTOU_RESCUE_REQUIRED',
542
+ fulfill_result: fulfillRes,
543
+ error: 'Automated exec mutated pointer files. The contract stays CLAIMED with a renewed execution window. Restore the files (git stash / git checkout) and re-run, or deliver edits via structured_output.fixes instead of writing to the workspace.',
544
+ };
545
+ }
546
+ const maxAttempts = (fulfillRes.max_retries ?? 2) + 1;
547
+ for (let i = fulfillRes.retry_count ?? 1; i < maxAttempts; i++) {
548
+ fulfillRes = engine.fulfillContract(validatedFulfillInput);
549
+ if (fulfillRes.status !== 'RETRY_NEEDED')
550
+ break;
551
+ }
552
+ }
553
+ return { ok: fulfillRes.ok, status: fulfillRes.status, fulfill_result: fulfillRes };
554
+ }
555
+ catch (fulfillErr) {
556
+ const fulfillError = fulfillErr instanceof Error ? fulfillErr.message : String(fulfillErr);
557
+ const res = failAfterExit(`FULFILL_ERROR: ${fulfillError}`);
558
+ return { ...res, status: 'FULFILL_ERROR', error: fulfillError };
559
+ }
560
+ }
561
+ }
562
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAqB,MAAM,oBAAoB,CAAC;AACzE,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAmDrD,8EAA8E;AAC9E,mCAAmC;AACnC,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACpC,MAAM;IACN,SAAS;IACT,YAAY;IACZ,aAAa;IACb,QAAQ;IACR,SAAS;IACT,MAAM;IACN,KAAK;IACL,aAAa;IACb,UAAU;IACV,WAAW;IACX,UAAU;IACV,SAAS;IACT,cAAc;IACd,aAAa;IACb,cAAc;IACd,mBAAmB;IACnB,sBAAsB;IACtB,wBAAwB;IACxB,MAAM;IACN,YAAY;CACb,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,MAAM;IACN,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,SAAS;IACT,MAAM;IACN,QAAQ;IACR,UAAU;IACV,MAAM;IACN,iBAAiB;IACjB,eAAe;IACf,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,YAAY;CACb,CAAC,CAAC;AAEH,8FAA8F;AAC9F,MAAM,oBAAoB,GAAa;IACrC,UAAU;IACV,QAAQ;IACR,WAAW;IACX,SAAS;IACT,WAAW;IACX,aAAa;IACb,QAAQ;IACR,WAAW;IACX,OAAO;IACP,QAAQ;IACR,WAAW;IACX,cAAc;CACf,CAAC;AAEF,MAAM,gBAAgB,GAAG,6BAA6B,CAAC;AAEvD,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,QAAyB,EACzB,UAAoB,EAAE,EACtB,YAA+B,OAAO,CAAC,GAAG,EAC1C,WAA4B,OAAO,CAAC,QAAQ;IAE5C,MAAM,KAAK,GAAG,QAAQ,KAAK,OAAO,CAAC;IACnC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,mBAAmB,CAAC;IACtE,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,IAAI,GAAG,CACtB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAC/E,CAAC;IAEF,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACrD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpB,qFAAqF;YACrF,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACjB,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC5E,IAAI,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,GAAG,CAAC,oBAAoB,GAAG,QAAQ,CAAC,WAAW,CAAC;IAChD,GAAG,CAAC,kBAAkB,GAAG,QAAQ,CAAC,SAAS,CAAC;IAC5C,GAAG,CAAC,uBAAuB,GAAG,QAAQ,CAAC,cAAc,CAAC;IACtD,GAAG,CAAC,qBAAqB,GAAG,QAAQ,CAAC,YAAY,CAAC;IAClD,GAAG,CAAC,oBAAoB,GAAG,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC;IACtD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAC1B,CAAC;AAeD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAmB,CAAC;AAElD,SAAS,iBAAiB,CAAC,IAAc;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;IAClC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/C,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACtD,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACnF,CAAC;QACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YAClB,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACtG,CAAC;QACD,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACnB,OAAO;gBACL,OAAO,EAAE,YAAY;gBACrB,IAAI,EAAE,CAAC,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;gBACjF,KAAK,EAAE,KAAK;aACb,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YAClB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACvE,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IACD,2EAA2E;IAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,eAAe,CAAC,CAAgB,EAAE,EAAU;IACnD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACnD,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAmB;IACvD,IAAI,aAA0B,CAAC;IAC/B,MAAM,aAAa,GAAG,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,GAAG,GAAoB;QAC3B,KAAK;QACL,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,MAAM,EAAE,KAAK;QACb,aAAa;KACd,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;QACvB,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC;QAClB,aAAa,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAAoB,EACpB,UAAkB,IAAI;IAEtB,IAAI,GAAG,CAAC,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACtE,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAE9D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,SAAS,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjG,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QACpF,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;IACjD,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,GAAmB,EAAE,EAAE;QAC1C,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC,CAAC,6BAA6B;QAC7C,CAAC;IACH,CAAC,CAAC;IACF,WAAW,CAAC,SAAS,CAAC,CAAC;IACvB,IAAI,MAAM,eAAe,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,EAAE,CAAC;QACtD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACvD,CAAC;IACD,WAAW,CAAC,SAAS,CAAC,CAAC;IACvB,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACjE,OAAO,EAAE,MAAM,EAAE,yBAAyB,EAAE,SAAS,EAAE,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B;IACzC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;QACjC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG;YAAE,SAAS;QACrC,KAAK,EAAE,CAAC;QACR,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,SAAS,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvG,CAAC;aAAM,CAAC;YACN,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACpC,CAAC;YAAC,MAAM,CAAC;gBACP,qBAAqB;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,iEAAiE;AACjE,8EAA8E;AAE9E,MAAM,kBAAkB,GAAG,GAAG,GAAG,IAAI,CAAC;AAEtC,SAAS,KAAK,CAAC,CAAS,EAAE,EAAU,EAAE,EAAU;IAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,QAAQ,CAAC,OAAe;IAC/B,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACvE,CAAC;AAID,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAsB,EACtB,QAAyB,EACzB,IAAc,EACd,OAA0B,EAAE;IAE5B,MAAM,IAAI,GAAG,CAAC,KAA8B,EAAE,EAAE;QAC9C,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;QAC1D,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU;QACrC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC;QAClD,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;IAC1B,MAAM,KAAK,GAAG,eAAe,GAAG,IAAI,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjG,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7E,iGAAiG;IACjG,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACvE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC;IACtD,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC;IAEnC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,OAAO,EAAE,iBAAiB,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC;IAEtG,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,KAAmB,CAAC;IACxB,IAAI,CAAC;QACH,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE;YACzC,GAAG;YACH,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAC/B,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;YACtC,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,QAAQ,EAAE,CAAC;QAClB,OAAO,aAAa,CAClB,0CAA0C,QAAQ,YAAY,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAC5G,CAAC;IACJ,CAAC;IAED,IAAI,aAA0B,CAAC;IAC/B,MAAM,aAAa,GAAG,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,GAAG,GAAoB;QAC3B,KAAK;QACL,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,MAAM,EAAE,KAAK;QACb,aAAa;KACd,CAAC;IACF,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAEnG,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,KAAK,GAA+C,IAAI,CAAC;IAC7D,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC;IAC/D,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,IAAI,SAAqC,CAAC;IAC1C,IAAI,UAAsC,CAAC;IAE3C,MAAM,QAAQ,GAAG,IAAI,OAAO,CAC1B,CAAC,OAAO,EAAE,EAAE;QACV,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,CAAwE,EAAE,EAAE;YAC1F,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC;YAClB,aAAa,EAAE,CAAC;YAChB,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,CAAC;QACF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC1E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC,CACF,CAAC;IAEF,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,IAAI,SAAS;YAAE,aAAa,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,UAAU;YAAE,YAAY,CAAC,UAAU,CAAC,CAAC;QACzC,SAAS,GAAG,SAAS,CAAC;QACtB,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,EAAE;QACpC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;YAAE,OAAO;QAC/B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,CAAC,aAAa,CAAC;gBAC9B,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,QAAQ,EAAE,QAAQ,CAAC,YAAY;gBAC/B,WAAW,EAAE,KAAK;gBAClB,OAAO,EAAE;oBACP,MAAM;oBACN,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;oBACvB,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,IAAI;oBACpB,UAAU,EAAE,GAAG,CAAC,SAAS;oBACzB,YAAY,EAAE,WAAW;oBACzB,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,kBAAkB;oBAC7C,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,kBAAkB,CAAC;oBAC9C,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC;iBACtC;aACF,CAAC,CAAC;YACH,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,IAAe,EAAE,MAAc,EAAE,EAAE;QACvD,IAAI,KAAK;YAAE,OAAO;QAClB,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACzB,UAAU,EAAE,CAAC;QACb,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACnD,KAAK,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjG,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,IAAI,UAAU;YAAE,YAAY,CAAC,UAAU,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACnE,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAC3B,YAAY,CACV,kBAAkB,EAClB,eAAe;gBACb,CAAC,CAAC,oIAAoI;gBACtI,CAAC,CAAC,uGAAuG,CAC5G,CAAC;QACJ,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,CAAC;IAEF,uFAAuF;IACvF,MAAM,cAAc,GAAG,GAAY,EAAE;QACnC,MAAM,IAAI,GAAG,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,IAAI,CAAC,WAAW,IAAI,KAAK,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC1E,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YAC1D,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;gBAC5B,YAAY,GAAG,MAAM,CAAC;gBACtB,QAAQ,EAAE,CAAC;YACb,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,IAAI,KAAK;YAAE,OAAO;QAClB,IAAI,CAAC;YACH,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;gBACtB,YAAY,CAAC,YAAY,EAAE,kEAAkE,CAAC,CAAC;gBAC/F,OAAO;YACT,CAAC;YACD,IAAI,eAAe;gBAAE,OAAO;YAC5B,uFAAuF;YACvF,qFAAqF;YACrF,mFAAmF;YACnF,MAAM,QAAQ,GAAG,YAAY,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzD,IAAI,QAAQ,GAAG,MAAM,GAAG,YAAY;gBAAE,OAAO;YAE7C,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC;gBAC3B,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,QAAQ,EAAE,QAAQ,CAAC,YAAY;gBAC/B,WAAW,EAAE,KAAK;aACnB,CAAC,CAAC;YACH,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;gBAC7B,YAAY,GAAG,GAAG,CAAC,UAAU,CAAC;gBAC9B,QAAQ,EAAE,CAAC;gBACX,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;YACnG,CAAC;iBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBACtC,YAAY,CAAC,YAAY,EAAE,eAAe,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACN,mFAAmF;gBACnF,iFAAiF;gBACjF,yEAAyE;gBACzE,eAAe,GAAG,IAAI,CAAC;gBACvB,IAAI,GAAG,CAAC,UAAU;oBAAE,YAAY,GAAG,GAAG,CAAC,UAAU,CAAC;gBAClD,QAAQ,EAAE,CAAC;gBACX,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iFAAiF;YACjF,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACnD,IAAI,CAAC;gBACH,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;oBACtB,YAAY,CAAC,YAAY,EAAE,eAAe,OAAO,EAAE,CAAC,CAAC;gBACvD,CAAC;YACH,CAAC;YAAC,OAAO,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC1G,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;QACzC,WAAW,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,WAAW,GAAG,SAAS,EAAE,CAAC;YAC5B,YAAY,CAAC,YAAY,EAAE,8BAA8B,SAAS,SAAS,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QACD,MAAM,IAAI,KAAK,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;QACzC,MAAM,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3B,qDAAqD;QACvD,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC5C,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,QAAQ,EAAE,CAAC;IACX,SAAS,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEtC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC;IAC5B,UAAU,EAAE,CAAC;IACb,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAE3B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,aAAa,CAAC,0CAA2C,IAAI,CAAC,KAA+B,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3I,CAAC;IAED,MAAM,UAAU,GAAG,KAAmD,CAAC;IACvE,IAAI,UAAU,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;QACtC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;IACjF,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtG,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,uBAAuB,CAAC;QACxF,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACxB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACpB,UAAU,CAAC,eAAe,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM;YAC1B,CAAC,CAAC,wBAAwB,IAAI,CAAC,MAAM,EAAE;YACvC,CAAC,CAAC,oBAAoB,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;QACzC,OAAO,aAAa,CAClB,yBAAyB,QAAQ,KAAK,MAAM,IAAI,kBAAkB,EAAE,CACrE,CAAC;IACJ,CAAC;IAED,OAAO,iBAAiB,EAAE,CAAC;IAE3B,4EAA4E;IAE5E,SAAS,aAAa,CAAC,MAAc;QACnC,IAAI,cAAkC,CAAC;QACvC,IAAI,CAAC;YACH,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC;gBACnC,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,QAAQ,EAAE,QAAQ,CAAC,YAAY;gBAC/B,WAAW,EAAE,KAAK;gBAClB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;aAC9B,CAAC,CAAC,MAAM,CAAC;QACZ,CAAC;QAAC,OAAO,OAAO,EAAE,CAAC;YACjB,cAAc,GAAG,cAAc,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;QACjG,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC;IACxG,CAAC;IAED,SAAS,iBAAiB;QACxB,IAAI,gBAAqD,CAAC;QAC1D,IAAI,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAE5C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YACzC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5E,IAAI,MAAM,CAAC,iBAAiB,IAAI,OAAO,MAAM,CAAC,iBAAiB,KAAK,QAAQ,EAAE,CAAC;oBAC7E,gBAAgB,GAAG,MAAM,CAAC,iBAA4C,CAAC;gBACzE,CAAC;qBAAM,CAAC;oBACN,gBAAgB,GAAG,MAAiC,CAAC;gBACvD,CAAC;gBACD,IAAI,MAAM,CAAC,cAAc,IAAI,OAAO,MAAM,CAAC,cAAc,KAAK,QAAQ,EAAE,CAAC;oBACvE,aAAa,GAAG,MAAM,CAAC,cAAc,CAAC;gBACxC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,gDAAgD;QAClD,CAAC;QAED,MAAM,cAAc,GAAG,CAAC,aAAa,IAAI,wCAAwC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAE1G,IAAI,CAAC;YACH,MAAM,qBAAqB,GAAG,qBAAqB,CAAC,KAAK,CAAC;gBACxD,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,QAAQ,EAAE,QAAQ,CAAC,YAAY;gBAC/B,WAAW,EAAE,KAAK;gBAClB,cAAc,EAAE,cAAc;gBAC9B,iBAAiB,EAAE,gBAAgB;aACpC,CAAC,CAAC;YAEH,IAAI,UAAU,GAAG,MAAM,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC;YAE/D,IAAI,UAAU,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;gBACzC,0EAA0E;gBAC1E,6EAA6E;gBAC7E,uEAAuE;gBACvE,4EAA4E;gBAC5E,6EAA6E;gBAC7E,6EAA6E;gBAC7E,2EAA2E;gBAC3E,MAAM,iBAAiB,GACrB,CAAC,UAAU,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;gBAC7E,IAAI,iBAAiB,EAAE,CAAC;oBACtB,OAAO;wBACL,EAAE,EAAE,KAAK;wBACT,MAAM,EAAE,wBAAwB;wBAChC,cAAc,EAAE,UAAU;wBAC1B,KAAK,EACH,8OAA8O;qBACjP,CAAC;gBACJ,CAAC;gBAED,MAAM,WAAW,GAAG,CAAC,UAAU,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;gBACtD,KAAK,IAAI,CAAC,GAAG,UAAU,CAAC,WAAW,IAAI,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC/D,UAAU,GAAG,MAAM,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC;oBAC3D,IAAI,UAAU,CAAC,MAAM,KAAK,cAAc;wBAAE,MAAM;gBAClD,CAAC;YACH,CAAC;YAED,OAAO,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;QACtF,CAAC;QAAC,OAAO,UAAU,EAAE,CAAC;YACpB,MAAM,YAAY,GAAG,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAC3F,MAAM,GAAG,GAAG,aAAa,CAAC,kBAAkB,YAAY,EAAE,CAAC,CAAC;YAC5D,OAAO,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QAClE,CAAC;IACH,CAAC;AACH,CAAC"}
package/dist/types.d.ts CHANGED
@@ -82,6 +82,7 @@ export interface DispatchContractResult {
82
82
  }
83
83
  export interface PollNextTaskInput {
84
84
  agent_id?: string;
85
+ task_types?: TaskType[];
85
86
  }
86
87
  export interface PollNextTaskResult {
87
88
  has_task: boolean;
@@ -201,3 +202,43 @@ export interface InspectMeshStateInput {
201
202
  status?: ContractStatus | 'ACTIVE' | 'ALL';
202
203
  limit?: number;
203
204
  }
205
+ export interface DaemonLockRow {
206
+ id: string;
207
+ owner_token: string;
208
+ heartbeat_at: number;
209
+ expires_at: number;
210
+ metadata_json: string | null;
211
+ }
212
+ export interface AcquireDaemonLeaseInput {
213
+ lock_id?: string;
214
+ owner_token: string;
215
+ ttl_sec?: number;
216
+ metadata?: Record<string, unknown>;
217
+ }
218
+ export interface AcquireDaemonLeaseResult {
219
+ ok: boolean;
220
+ lock_id: string;
221
+ owner_token: string;
222
+ expires_at: number;
223
+ message?: string;
224
+ }
225
+ export interface RenewDaemonLeaseInput {
226
+ lock_id?: string;
227
+ owner_token: string;
228
+ ttl_sec?: number;
229
+ }
230
+ export interface RenewDaemonLeaseResult {
231
+ ok: boolean;
232
+ lock_id: string;
233
+ expires_at: number;
234
+ message?: string;
235
+ }
236
+ export interface ReleaseDaemonLeaseInput {
237
+ lock_id?: string;
238
+ owner_token: string;
239
+ }
240
+ export interface ReleaseDaemonLeaseResult {
241
+ ok: boolean;
242
+ lock_id: string;
243
+ message?: string;
244
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ferrycat",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "mcpName": "io.github.GuoBug/ferrycat",
5
5
  "description": "FerryCat: Contract-driven cross-host AI agent handoff & review bus over MCP and SQLite WAL",
6
6
  "type": "module",
@@ -21,6 +21,7 @@
21
21
  "scripts": {
22
22
  "build": "tsc",
23
23
  "prepack": "npm run build",
24
+ "bump": "node scripts/bump-version.js",
24
25
  "start": "node --experimental-sqlite dist/index.js",
25
26
  "test": "node --experimental-sqlite --import tsx test/run-all.ts"
26
27
  },