dorfl 0.11.1 → 0.11.3
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/dist/arbiter-refs.d.ts +139 -0
- package/dist/arbiter-refs.d.ts.map +1 -0
- package/dist/arbiter-refs.js +114 -0
- package/dist/arbiter-refs.js.map +1 -0
- package/dist/do.d.ts +11 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +108 -14
- package/dist/do.js.map +1 -1
- package/dist/harness.d.ts +44 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +89 -15
- package/dist/item-lock.js.map +1 -1
- package/dist/ledger-write.d.ts.map +1 -1
- package/dist/ledger-write.js +38 -3
- package/dist/ledger-write.js.map +1 -1
- package/dist/needs-attention.d.ts +43 -0
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +211 -50
- package/dist/needs-attention.js.map +1 -1
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +133 -26
- package/dist/pi-harness.js.map +1 -1
- package/dist/protocol/REVIEW-PROTOCOL.md +1 -1
- package/dist/reap-agent-tree.d.ts +108 -0
- package/dist/reap-agent-tree.d.ts.map +1 -0
- package/dist/reap-agent-tree.js +173 -0
- package/dist/reap-agent-tree.js.map +1 -0
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +17 -0
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.d.ts +46 -2
- package/dist/review-verdict.d.ts.map +1 -1
- package/dist/review-verdict.js +49 -3
- package/dist/review-verdict.js.map +1 -1
- package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +1 -1
- package/dist/tasker-review-loop.d.ts +13 -0
- package/dist/tasker-review-loop.d.ts.map +1 -1
- package/dist/tasker-review-loop.js +120 -8
- package/dist/tasker-review-loop.js.map +1 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +161 -17
- package/dist/tasking.js.map +1 -1
- package/dist/watch-session.d.ts +35 -4
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +54 -7
- package/dist/watch-session.js.map +1 -1
- package/dist/worktree-writer-lock.d.ts +99 -0
- package/dist/worktree-writer-lock.d.ts.map +1 -0
- package/dist/worktree-writer-lock.js +158 -0
- package/dist/worktree-writer-lock.js.map +1 -0
- package/package.json +1 -1
- package/src/arbiter-refs.ts +222 -0
- package/src/do.ts +155 -18
- package/src/harness.ts +45 -0
- package/src/item-lock.ts +93 -19
- package/src/ledger-write.ts +41 -5
- package/src/needs-attention.ts +282 -59
- package/src/pi-harness.ts +136 -28
- package/src/reap-agent-tree.ts +221 -0
- package/src/repo-mirror.ts +22 -0
- package/src/review-verdict.ts +73 -5
- package/src/tasker-review-loop.ts +129 -7
- package/src/tasking.ts +198 -16
- package/src/watch-session.ts +83 -7
- package/src/worktree-writer-lock.ts +217 -0
package/dist/pi-harness.js
CHANGED
|
@@ -2,7 +2,8 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { NullHarness, pidAlive, registerHarness, } from './harness.js';
|
|
4
4
|
import { generateSessionPath } from './session-path.js';
|
|
5
|
-
import {
|
|
5
|
+
import { lastAssistantTurn, isOutputCappedTurn } from './watch-session.js';
|
|
6
|
+
import { reapProcessGroup } from './reap-agent-tree.js';
|
|
6
7
|
/**
|
|
7
8
|
* The **pi** harness adapter (ADR §5) — the first real agent harness
|
|
8
9
|
* `dorfl` drives. It fulfils the harness seam introduced by
|
|
@@ -137,8 +138,10 @@ export class PiHarness {
|
|
|
137
138
|
detail: status === 0 ? undefined : (result.stderr ?? '').trim(),
|
|
138
139
|
// The agent's ANSWER (task `harness-agent-output`): the LAST assistant
|
|
139
140
|
// turn's text read from the session `.jsonl` pi just wrote — NOT piped
|
|
140
|
-
// stdout (which is drained). Shares `watch-session.ts`'s reader.
|
|
141
|
-
|
|
141
|
+
// stdout (which is drained). Shares `watch-session.ts`'s reader. The
|
|
142
|
+
// same turn's stop_reason/usage feed the outputCapped cap-truncation
|
|
143
|
+
// signal (observation `tasker-review-edits-payload-caps-the-verdict-response`).
|
|
144
|
+
...readAssistantOutput(sessionFile),
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
/**
|
|
@@ -186,8 +189,47 @@ export class PiHarness {
|
|
|
186
189
|
cwd: input.dir,
|
|
187
190
|
env: input.env ?? process.env,
|
|
188
191
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
192
|
+
// PROCESS-GROUP LEADER (observation
|
|
193
|
+
// `checkpoint-releases-lock-while-predecessor-agent-still-writes`): pi's
|
|
194
|
+
// pgid becomes its own pid, so the deadline stop can signal the WHOLE
|
|
195
|
+
// agent tree with `kill(-pgid)` and VERIFY it is gone. Without this,
|
|
196
|
+
// `child.kill()` reached exactly one pid: subagents / MCP servers / tool
|
|
197
|
+
// subshells survived, were re-parented to init (so no ppid walk could even
|
|
198
|
+
// find them), and kept writing into the worktree while a SUCCESSOR agent
|
|
199
|
+
// was already editing it. A pgid is inherited by every descendant and is
|
|
200
|
+
// unaffected by re-parenting, which is why it is the only usable handle.
|
|
201
|
+
// We deliberately do NOT `unref()` here: the parent keeps supervising the
|
|
202
|
+
// child (and forwards its own termination to the group, below).
|
|
203
|
+
detached: true,
|
|
189
204
|
});
|
|
190
205
|
record.pid = child.pid; // the liveness anchor, recorded like spawnSync.
|
|
206
|
+
// The group id equals the leader's pid because we spawned `detached`.
|
|
207
|
+
const pgid = child.pid;
|
|
208
|
+
// `detached: true` takes pi OUT of our terminal's foreground process group,
|
|
209
|
+
// so a Ctrl-C / `kill` aimed at the runner would no longer reach it — which
|
|
210
|
+
// would WIDEN the very "aborting `do` does not kill the spawned agent tree"
|
|
211
|
+
// gap this change is closing. Forward our own termination to the group for
|
|
212
|
+
// as long as the child is live, so detaching strictly improves reachability
|
|
213
|
+
// instead of trading one orphan class for another.
|
|
214
|
+
const forwardSignal = (signal) => () => {
|
|
215
|
+
if (pgid === undefined) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
process.kill(-pgid, signal);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Already gone; nothing to forward to.
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const onSigint = forwardSignal('SIGINT');
|
|
226
|
+
const onSigterm = forwardSignal('SIGTERM');
|
|
227
|
+
process.on('SIGINT', onSigint);
|
|
228
|
+
process.on('SIGTERM', onSigterm);
|
|
229
|
+
const stopForwarding = () => {
|
|
230
|
+
process.off('SIGINT', onSigint);
|
|
231
|
+
process.off('SIGTERM', onSigterm);
|
|
232
|
+
};
|
|
191
233
|
let stderr = '';
|
|
192
234
|
child.stderr?.on('data', (chunk) => {
|
|
193
235
|
stderr += chunk.toString('utf8');
|
|
@@ -225,19 +267,32 @@ export class PiHarness {
|
|
|
225
267
|
return;
|
|
226
268
|
}
|
|
227
269
|
timedOut = true;
|
|
270
|
+
// Signal the whole GROUP, not just pi: the descendants are exactly the
|
|
271
|
+
// processes that outlive it and keep writing to the worktree.
|
|
228
272
|
try {
|
|
229
|
-
|
|
273
|
+
if (pgid !== undefined) {
|
|
274
|
+
process.kill(-pgid, 'SIGTERM');
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
child.kill('SIGTERM');
|
|
278
|
+
}
|
|
230
279
|
}
|
|
231
280
|
catch {
|
|
232
|
-
// Best-effort:
|
|
233
|
-
// handler will still settle the promise
|
|
281
|
+
// Best-effort: an already-exited group throws ESRCH; the `exit`
|
|
282
|
+
// handler will still settle the promise, and the post-exit reap below
|
|
283
|
+
// is what actually VERIFIES the tree is gone.
|
|
234
284
|
}
|
|
235
285
|
hardTimer = setTimeout(() => {
|
|
236
286
|
if (settled) {
|
|
237
287
|
return;
|
|
238
288
|
}
|
|
239
289
|
try {
|
|
240
|
-
|
|
290
|
+
if (pgid !== undefined) {
|
|
291
|
+
process.kill(-pgid, 'SIGKILL');
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
child.kill('SIGKILL');
|
|
295
|
+
}
|
|
241
296
|
}
|
|
242
297
|
catch {
|
|
243
298
|
// Best-effort: see above.
|
|
@@ -253,6 +308,7 @@ export class PiHarness {
|
|
|
253
308
|
}
|
|
254
309
|
settled = true;
|
|
255
310
|
clearDeadlineTimers();
|
|
311
|
+
stopForwarding();
|
|
256
312
|
reject(new Error(`failed to spawn pi (${this.piBin}): ${err.message}`));
|
|
257
313
|
});
|
|
258
314
|
// Resolve on `exit` (pi itself terminated), NOT `close`: `close` waits for
|
|
@@ -265,6 +321,7 @@ export class PiHarness {
|
|
|
265
321
|
}
|
|
266
322
|
settled = true;
|
|
267
323
|
clearDeadlineTimers();
|
|
324
|
+
stopForwarding();
|
|
268
325
|
// Release our end of the stdio pipes so a leaked grandchild's inherited
|
|
269
326
|
// FDs stop keeping our streams referenced; `unref` the child handle too.
|
|
270
327
|
child.stdout?.destroy();
|
|
@@ -272,15 +329,57 @@ export class PiHarness {
|
|
|
272
329
|
child.stdin?.destroy();
|
|
273
330
|
child.unref?.();
|
|
274
331
|
const status = code ?? -1;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
332
|
+
const settleWith = (reap) => {
|
|
333
|
+
resolve({
|
|
334
|
+
ok: status === 0 && !timedOut,
|
|
335
|
+
record,
|
|
336
|
+
detail: status === 0 && !timedOut
|
|
337
|
+
? undefined
|
|
338
|
+
: stderr.trim() || undefined,
|
|
339
|
+
timedOut: timedOut ? true : undefined,
|
|
340
|
+
...(reap ? { reap } : {}),
|
|
341
|
+
// Read the agent's ANSWER from the `.jsonl` at `exit` — the same
|
|
342
|
+
// last-assistant-turn read `launch` does at return (task
|
|
343
|
+
// `harness-agent-output`); the process has exited so the log is final.
|
|
344
|
+
// The same turn's stop_reason/usage feed the outputCapped signal.
|
|
345
|
+
...readAssistantOutput(sessionFile),
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
if (!timedOut || pgid === undefined) {
|
|
349
|
+
// Normal exit: we signalled nothing, so there is nothing to prove and
|
|
350
|
+
// nothing to kill. Byte-for-byte the pre-existing behaviour — in
|
|
351
|
+
// particular we do NOT reap a group the agent may have deliberately left
|
|
352
|
+
// running behind a successful run.
|
|
353
|
+
settleWith();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// DEADLINE STOP: pi's own exit says NOTHING about its descendants — that
|
|
357
|
+
// assumption is the defect. Before this promise resolves (which is the
|
|
358
|
+
// runner's cue to save WIP, release the lock and dispatch a SUCCESSOR into
|
|
359
|
+
// this same worktree), reap the group and VERIFY it is gone. Bounded by
|
|
360
|
+
// construction, so this cannot reintroduce the resolve-on-`exit` hang the
|
|
361
|
+
// doc-comment above guards against: a tree that will not die resolves with
|
|
362
|
+
// `reaped: false` and the caller refuses to release the lock.
|
|
363
|
+
void reapProcessGroup({ pgid })
|
|
364
|
+
.then((result) => {
|
|
365
|
+
settleWith({
|
|
366
|
+
reaped: result.reaped,
|
|
367
|
+
pgid,
|
|
368
|
+
escalatedToSigkill: result.escalatedToSigkill,
|
|
369
|
+
detail: result.detail,
|
|
370
|
+
});
|
|
371
|
+
})
|
|
372
|
+
.catch((err) => {
|
|
373
|
+
// A throw here means we could not even RUN the verification, which is
|
|
374
|
+
// indistinguishable from "might still be alive" — report it as an
|
|
375
|
+
// unproven reap rather than silently claiming success.
|
|
376
|
+
settleWith({
|
|
377
|
+
reaped: false,
|
|
378
|
+
pgid,
|
|
379
|
+
detail: `could not verify that the agent process group ${pgid} exited ` +
|
|
380
|
+
`(${err instanceof Error ? err.message : String(err)}); treating ` +
|
|
381
|
+
'the predecessor as possibly still writing to the worktree.',
|
|
382
|
+
});
|
|
284
383
|
});
|
|
285
384
|
});
|
|
286
385
|
// Feed the same prepared prompt on stdin, then close it (pi reads to EOF).
|
|
@@ -371,14 +470,18 @@ export function piSessionExists(record) {
|
|
|
371
470
|
return record.session !== undefined && existsSync(record.session);
|
|
372
471
|
}
|
|
373
472
|
/**
|
|
374
|
-
* Read the LAST assistant
|
|
375
|
-
* `sessionFile` — the agent's final ANSWER
|
|
376
|
-
* (
|
|
377
|
-
*
|
|
473
|
+
* Read the LAST assistant turn's output from the pi session `.jsonl` at
|
|
474
|
+
* `sessionFile` — the agent's final ANSWER (`output`) PLUS the output-cap
|
|
475
|
+
* signal (`outputCapped`, when the turn was truncated at the model's output-token
|
|
476
|
+
* cap before it finished). Surfaced through the harness seam as
|
|
477
|
+
* `LaunchResult.output` / `LaunchResult.outputCapped` (task `harness-agent-output`;
|
|
478
|
+
* observation `tasker-review-edits-payload-caps-the-verdict-response`). Called by
|
|
479
|
+
* BOTH `launch` (at return) and `launchAsync` (at `exit`), AFTER pi has exited so
|
|
480
|
+
* the log is complete.
|
|
378
481
|
*
|
|
379
|
-
* It REUSES `watch-session.ts`'s {@link
|
|
380
|
-
* not two). An absent file (pi never wrote it) yields `
|
|
381
|
-
*
|
|
482
|
+
* It REUSES `watch-session.ts`'s {@link lastAssistantTurn} (one `.jsonl` parser,
|
|
483
|
+
* not two). An absent file (pi never wrote it) yields `{}`, as does a log with no
|
|
484
|
+
* assistant text — a read error is never thrown back into the launch.
|
|
382
485
|
*
|
|
383
486
|
* Studied (task `pi-harness-polish`, finding
|
|
384
487
|
* `work/notes/findings/pi-harness-channels.md`, pinned against pi 0.73.1 +
|
|
@@ -391,15 +494,19 @@ export function piSessionExists(record) {
|
|
|
391
494
|
* so a future stream/HTTP-shaped harness (opencode-style) still fits: the file
|
|
392
495
|
* shape lives BEHIND this reader and is not observable through the seam.
|
|
393
496
|
*/
|
|
394
|
-
function
|
|
497
|
+
function readAssistantOutput(sessionFile) {
|
|
395
498
|
let jsonl;
|
|
396
499
|
try {
|
|
397
500
|
jsonl = readFileSync(sessionFile, 'utf8');
|
|
398
501
|
}
|
|
399
502
|
catch {
|
|
400
|
-
return
|
|
503
|
+
return {}; // no session log on disk — no answer to surface.
|
|
401
504
|
}
|
|
402
|
-
|
|
505
|
+
const turn = lastAssistantTurn(jsonl);
|
|
506
|
+
return {
|
|
507
|
+
output: turn.text,
|
|
508
|
+
outputCapped: isOutputCappedTurn(turn) ? turn.outputTokens : undefined,
|
|
509
|
+
};
|
|
403
510
|
}
|
|
404
511
|
// Register the pi adapter so `status`/`do`/`gc` resolve liveness for `pi` jobs
|
|
405
512
|
// to THIS adapter (PID + session pointer) rather than the null fallback. A
|
package/dist/pi-harness.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi-harness.js","sourceRoot":"","sources":["../src/pi-harness.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pi-harness.js","sourceRoot":"","sources":["../src/pi-harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAE,SAAS,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,SAAS,CAAC;AACjD,OAAO,EACN,WAAW,EACX,QAAQ,EACR,eAAe,GAOf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAC,iBAAiB,EAAE,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAC,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AAGtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AA4BhD;;;;;;;;GAQG;AACH,MAAM,OAAO,SAAS;IACZ,OAAO,GAAG,IAAI,CAAC;IACP,KAAK,CAAS;IACd,SAAS,CAAW;IAErC,YAAY,UAA4B,EAAE;QACzC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;QAC7C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED;;;;;;OAMG;IACK,kBAAkB,CAAC,KAAkB;QAC5C,OAAO,CACN,KAAK,CAAC,OAAO,IAAI,mBAAmB,CAAC,EAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAC,CAAC,CACtE,CAAC;IACH,CAAC;IAED,yEAAyE;IACjE,SAAS,CAAC,KAAkB,EAAE,WAAmB;QACxD,oEAAoE;QACpE,sEAAsE;QACtE,MAAM,SAAS,GACd,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE;YAC9C,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC;YAC1B,CAAC,CAAC,EAAE,CAAC;QACP,0EAA0E;QAC1E,sEAAsE;QACtE,uEAAuE;QACvE,oEAAoE;QACpE,OAAO;YACN,GAAG,SAAS;YACZ,GAAG,IAAI,CAAC,SAAS;YACjB,SAAS;YACT,WAAW;YACX,WAAW;SACX,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAkB;QACxB,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;YAC1C,wEAAwE;YACxE,yEAAyE;YACzE,2BAA2B;YAC3B,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;YAC7B,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;SAC3B,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACd,uBAAuB,IAAI,CAAC,KAAK,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAC7D,CAAC;QACH,CAAC;QACD,2EAA2E;QAC3E,4EAA4E;QAC5E,2CAA2C;QAC3C,MAAM,MAAM,GAAoB;YAC/B,OAAO,EAAE,IAAI;YACb,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACxC,OAAO,EAAE,WAAW;SACpB,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QACnC,OAAO;YACN,EAAE,EAAE,MAAM,KAAK,CAAC;YAChB,MAAM;YACN,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;YAC/D,uEAAuE;YACvE,uEAAuE;YACvE,qEAAqE;YACrE,qEAAqE;YACrE,gFAAgF;YAChF,GAAG,mBAAmB,CAAC,WAAW,CAAC;SACnC,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,WAAW,CAAC,KAAkB;QAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAChD,MAAM,MAAM,GAAoB;YAC/B,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACxC,OAAO,EAAE,WAAW;SACpB,CAAC;QACF,OAAO,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;gBACrC,kEAAkE;gBAClE,8DAA8D;gBAC9D,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;gBAC7B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBAC/B,oCAAoC;gBACpC,yEAAyE;gBACzE,sEAAsE;gBACtE,qEAAqE;gBACrE,yEAAyE;gBACzE,2EAA2E;gBAC3E,yEAAyE;gBACzE,yEAAyE;gBACzE,yEAAyE;gBACzE,0EAA0E;gBAC1E,gEAAgE;gBAChE,QAAQ,EAAE,IAAI;aACd,CAAC,CAAC;YACH,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,gDAAgD;YACxE,sEAAsE;YACtE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC;YACvB,4EAA4E;YAC5E,4EAA4E;YAC5E,4EAA4E;YAC5E,2EAA2E;YAC3E,4EAA4E;YAC5E,mDAAmD;YACnD,MAAM,aAAa,GAAG,CAAC,MAAsB,EAAE,EAAE,CAAC,GAAS,EAAE;gBAC5D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACxB,OAAO;gBACR,CAAC;gBACD,IAAI,CAAC;oBACJ,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBAC7B,CAAC;gBAAC,MAAM,CAAC;oBACR,uCAAuC;gBACxC,CAAC;YACF,CAAC,CAAC;YACF,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;YAC3C,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACjC,MAAM,cAAc,GAAG,GAAS,EAAE;gBACjC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACnC,CAAC,CAAC;YACF,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gBAC1C,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;YACH,2EAA2E;YAC3E,qEAAqE;YACrE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACnC,sEAAsE;YACtE,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,uEAAuE;YACvE,sEAAsE;YACtE,yEAAyE;YACzE,oEAAoE;YACpE,iEAAiE;YACjE,qEAAqE;YACrE,uEAAuE;YACvE,yCAAyC;YACzC,IAAI,SAAqC,CAAC;YAC1C,IAAI,SAAqC,CAAC;YAC1C,MAAM,mBAAmB,GAAG,GAAS,EAAE;gBACtC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;oBAC7B,YAAY,CAAC,SAAS,CAAC,CAAC;oBACxB,SAAS,GAAG,SAAS,CAAC;gBACvB,CAAC;gBACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;oBAC7B,YAAY,CAAC,SAAS,CAAC,CAAC;oBACxB,SAAS,GAAG,SAAS,CAAC;gBACvB,CAAC;YACF,CAAC,CAAC;YACF,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACpC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC1D,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC3B,IAAI,OAAO,EAAE,CAAC;wBACb,OAAO;oBACR,CAAC;oBACD,QAAQ,GAAG,IAAI,CAAC;oBAChB,uEAAuE;oBACvE,8DAA8D;oBAC9D,IAAI,CAAC;wBACJ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;4BACxB,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;wBAChC,CAAC;6BAAM,CAAC;4BACP,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBACvB,CAAC;oBACF,CAAC;oBAAC,MAAM,CAAC;wBACR,gEAAgE;wBAChE,sEAAsE;wBACtE,8CAA8C;oBAC/C,CAAC;oBACD,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;wBAC3B,IAAI,OAAO,EAAE,CAAC;4BACb,OAAO;wBACR,CAAC;wBACD,IAAI,CAAC;4BACJ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gCACxB,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;4BAChC,CAAC;iCAAM,CAAC;gCACP,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;4BACvB,CAAC;wBACF,CAAC;wBAAC,MAAM,CAAC;4BACR,0BAA0B;wBAC3B,CAAC;oBACF,CAAC,EAAE,yBAAyB,CAAC,CAAC;oBAC9B,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;gBACrB,CAAC,EAAE,MAAM,CAAC,CAAC;gBACX,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YACrB,CAAC;YACD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACzB,IAAI,OAAO,EAAE,CAAC;oBACb,OAAO;gBACR,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC;gBACf,mBAAmB,EAAE,CAAC;gBACtB,cAAc,EAAE,CAAC;gBACjB,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACzE,CAAC,CAAC,CAAC;YACH,2EAA2E;YAC3E,2EAA2E;YAC3E,2EAA2E;YAC3E,4EAA4E;YAC5E,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,IAAI,OAAO,EAAE,CAAC;oBACb,OAAO;gBACR,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC;gBACf,mBAAmB,EAAE,CAAC;gBACtB,cAAc,EAAE,CAAC;gBACjB,wEAAwE;gBACxE,yEAAyE;gBACzE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBACxB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBACxB,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;gBACvB,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;gBAChB,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC;gBAC1B,MAAM,UAAU,GAAG,CAAC,IAA2B,EAAQ,EAAE;oBACxD,OAAO,CAAC;wBACP,EAAE,EAAE,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ;wBAC7B,MAAM;wBACN,MAAM,EACL,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ;4BACxB,CAAC,CAAC,SAAS;4BACX,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,SAAS;wBAC9B,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;wBACrC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAC,IAAI,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;wBACvB,iEAAiE;wBACjE,yDAAyD;wBACzD,uEAAuE;wBACvE,kEAAkE;wBAClE,GAAG,mBAAmB,CAAC,WAAW,CAAC;qBACnC,CAAC,CAAC;gBACJ,CAAC,CAAC;gBACF,IAAI,CAAC,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACrC,sEAAsE;oBACtE,iEAAiE;oBACjE,yEAAyE;oBACzE,mCAAmC;oBACnC,UAAU,EAAE,CAAC;oBACb,OAAO;gBACR,CAAC;gBACD,yEAAyE;gBACzE,uEAAuE;gBACvE,2EAA2E;gBAC3E,wEAAwE;gBACxE,0EAA0E;gBAC1E,2EAA2E;gBAC3E,8DAA8D;gBAC9D,KAAK,gBAAgB,CAAC,EAAC,IAAI,EAAC,CAAC;qBAC3B,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;oBAChB,UAAU,CAAC;wBACV,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,IAAI;wBACJ,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;wBAC7C,MAAM,EAAE,MAAM,CAAC,MAAM;qBACrB,CAAC,CAAC;gBACJ,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;oBACvB,sEAAsE;oBACtE,kEAAkE;oBAClE,uDAAuD;oBACvD,UAAU,CAAC;wBACV,MAAM,EAAE,KAAK;wBACb,IAAI;wBACJ,MAAM,EACL,iDAAiD,IAAI,UAAU;4BAC/D,IAAI,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc;4BAClE,4DAA4D;qBAC7D,CAAC,CAAC;gBACJ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,2EAA2E;YAC3E,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAChC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC;YACD,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,iBAAiB,CAAC,KAA6B;QAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC;YAC3C,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,KAAK,CAAC,OAAO;SACtB,CAAC,CAAC;QACH,oEAAoE;QACpE,sEAAsE;QACtE,MAAM,SAAS,GACd,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE;YAC9C,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC;YAC1B,CAAC,CAAC,EAAE,CAAC;QACP,4EAA4E;QAC5E,oEAAoE;QACpE,8BAA8B;QAC9B,MAAM,IAAI,GAAG,CAAC,GAAG,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;YAC1C,0EAA0E;YAC1E,wEAAwE;YACxE,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,0EAA0E;YAC1E,iEAAiE;YACjE,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;SAC7B,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACd,uBAAuB,IAAI,CAAC,KAAK,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAC7D,CAAC;QACH,CAAC;QACD,OAAO,EAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,EAAC,CAAC;IACxC,CAAC;IAED,OAAO,CAAC,MAAqB;QAC5B,OAAO,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED;;;;;;OAMG;IACH,cAAc,CAAC,MAAqB;QACnC,OAAO,MAAM,CAAC,OAAO,CAAC;IACvB,CAAC;CACD;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,MAAqB;IACpD,OAAO,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,SAAS,mBAAmB,CAAC,WAAmB;IAI/C,IAAI,KAAa,CAAC;IAClB,IAAI,CAAC;QACJ,KAAK,GAAG,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC,CAAC,iDAAiD;IAC7D,CAAC;IACD,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACtC,OAAO;QACN,MAAM,EAAE,IAAI,CAAC,IAAI;QACjB,YAAY,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;KACtE,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,2EAA2E;AAC3E,gFAAgF;AAChF,2EAA2E;AAC3E,eAAe,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;AAEjC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,OAG7B;IACA,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,IAAI,SAAS,CAAC,EAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,WAAW,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -102,7 +102,7 @@ The verdict is a single JSON object with this shape (the **emitted-shape contrac
|
|
|
102
102
|
Several caller-specific optional channels MAY ride on the same JSON object. They are OPT-IN: each caller's prompt names which ones to fill. The shape they take when present:
|
|
103
103
|
|
|
104
104
|
- `review` — a single deliberately-authored, human-readable REVIEW string the caller posts as a comment on the PR (leads with Approved/Blocked, then the lenses + the destination-check reasoning). Plain text inside the JSON string. Advisory only — never gates the verdict.
|
|
105
|
-
- `edits` — full-content edits to apply between passes in an improver loop: an array of `{path, content}
|
|
105
|
+
- `edits` — full-content edits to apply between passes in an improver loop: an array of `{path, content | src}`. `path` is a repo-relative target (typically `work/tasks/backlog/<slug>.md`). The body is EITHER inline `content` (the FULL replacement file body — the legacy small-edit form) OR `src` (a repo-relative scratch path the agent WROTE the full body to — the DECOUPLED form the tasker improver loop uses, so the edits payload cannot cap-truncate the verdict response). The runner reads the body, applies it to `path` through its scope fence, and reaps the scratch; the agent does no git.
|
|
106
106
|
- `edit` — for the lone-task review only: a single in-memory full-replacement task BODY (the markdown AFTER the frontmatter), applied before the next round. No path — the task has not been emitted yet.
|
|
107
107
|
- `questions` — an array of strings carrying open questions for a human to answer (the non-converge sink in the lone-task review).
|
|
108
108
|
- `uncertainTasks` — for the tasker improver loop: specific tasks to emit `needsAnswers: true` with the questions in their bodies. Each is `{path, questions: string[]}`.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Reap a stopped agent's whole PROCESS TREE, and VERIFY it is gone** (spec
|
|
3
|
+
* `graceful-pre-timeout-wip-checkpoint`, observation
|
|
4
|
+
* `checkpoint-releases-lock-while-predecessor-agent-still-writes`).
|
|
5
|
+
*
|
|
6
|
+
* ## The gap this closes
|
|
7
|
+
*
|
|
8
|
+
* The deadline checkpoint used to `child.kill('SIGTERM')` the agent process and
|
|
9
|
+
* treat the signal as if it were the outcome: the moment pi's own `exit` fired,
|
|
10
|
+
* the runner saved the WIP, RELEASED the item lock, and let the next tick
|
|
11
|
+
* dispatch a CONTINUATION agent into the SAME worktree. But `child.kill` signals
|
|
12
|
+
* exactly ONE pid, and a modern agent is a TREE (subagent processes, MCP servers,
|
|
13
|
+
* model proxies, tool subshells). Those descendants survive the parent's SIGTERM,
|
|
14
|
+
* and once pi exits they are re-parented to init — so they can no longer even be
|
|
15
|
+
* FOUND by walking `ppid`, while they keep writing into the worktree.
|
|
16
|
+
*
|
|
17
|
+
* Observed on a real run: the checkpoint saved WIP at 02:13, a continuation agent
|
|
18
|
+
* onboarded into the same worktree at ~02:15, and the predecessor's session log
|
|
19
|
+
* kept being written until 02:19:29 — four minutes INTO the successor's run,
|
|
20
|
+
* whose opening `git status` had already read the tree as clean. Nothing was lost
|
|
21
|
+
* only because the two happened to touch different files. The shape is the
|
|
22
|
+
* defect: one lock, one working tree, two live writers. A write landing after the
|
|
23
|
+
* successor's `git status` is invisible to it; a write landing during its edits
|
|
24
|
+
* can be clobbered either way; and the successor can commit the predecessor's
|
|
25
|
+
* half-finished edits as its own, under a message describing something else.
|
|
26
|
+
*
|
|
27
|
+
* ## Why the PROCESS GROUP is the handle
|
|
28
|
+
*
|
|
29
|
+
* A pid-tree walk cannot work here: the descendants we must reap are precisely
|
|
30
|
+
* the ones that OUTLIVE the parent, and an orphan's `ppid` is gone. A process
|
|
31
|
+
* GROUP id, by contrast, is inherited by every descendant and is NOT changed by
|
|
32
|
+
* re-parenting. So if the agent is spawned as a group LEADER (`detached: true`,
|
|
33
|
+
* making its pgid equal its pid), `kill(-pgid, …)` reaches the entire tree,
|
|
34
|
+
* orphans included — which is why {@link reapProcessGroup} takes a pgid and why
|
|
35
|
+
* `pi-harness.ts` spawns the deadline-capable async launch detached.
|
|
36
|
+
*
|
|
37
|
+
* ## Signal, then VERIFY — never assume
|
|
38
|
+
*
|
|
39
|
+
* The point of the whole module is that sending a signal is not evidence that
|
|
40
|
+
* anything died. So: SIGTERM the group, POLL until it is actually gone, escalate
|
|
41
|
+
* to SIGKILL after a grace, keep polling, and if it STILL will not die, say so
|
|
42
|
+
* LOUDLY and let the caller refuse to release the lock. A checkpoint that cannot
|
|
43
|
+
* prove the predecessor is dead must not hand the worktree to a successor.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* How long to wait after SIGTERM before escalating to SIGKILL. Matches
|
|
47
|
+
* `pi-harness.ts`'s `DEADLINE_SIGKILL_GRACE_MS` intent: enough for an agent to
|
|
48
|
+
* flush its session log and exit cleanly, short enough not to stall a CI leg.
|
|
49
|
+
*/
|
|
50
|
+
export declare const REAP_SIGTERM_GRACE_MS = 10000;
|
|
51
|
+
/** How long to keep waiting after SIGKILL before declaring the reap FAILED. */
|
|
52
|
+
export declare const REAP_SIGKILL_TIMEOUT_MS = 5000;
|
|
53
|
+
/** The outcome of a {@link reapProcessGroup} attempt. */
|
|
54
|
+
export interface ReapResult {
|
|
55
|
+
/**
|
|
56
|
+
* True iff the group is VERIFIED gone (observed non-existent, not merely
|
|
57
|
+
* signalled). Only a `true` here licenses releasing the item lock and
|
|
58
|
+
* dispatching a successor into the same worktree.
|
|
59
|
+
*/
|
|
60
|
+
reaped: boolean;
|
|
61
|
+
/** True iff SIGKILL was needed (the tree ignored SIGTERM) — worth reporting. */
|
|
62
|
+
escalatedToSigkill: boolean;
|
|
63
|
+
/** Total wall-clock ms spent waiting for the tree to die. */
|
|
64
|
+
waitedMs: number;
|
|
65
|
+
/** A human-readable account, always populated (the LOUD failure text). */
|
|
66
|
+
detail: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Is any process still alive in process group `pgid`?
|
|
70
|
+
*
|
|
71
|
+
* `process.kill(-pgid, 0)` is the liveness probe: signal 0 performs the
|
|
72
|
+
* permission/existence check WITHOUT delivering a signal (the same technique
|
|
73
|
+
* `harness.ts`'s {@link pidAlive} uses for a single pid, widened to the group).
|
|
74
|
+
*
|
|
75
|
+
* - It THROWS `ESRCH` when no process in the group exists ⇒ the group is gone.
|
|
76
|
+
* - It THROWS `EPERM` when the group exists but we may not signal it. That is
|
|
77
|
+
* still "alive", and reporting it as dead would be the very assumption this
|
|
78
|
+
* module exists to remove — so `EPERM` reads as ALIVE.
|
|
79
|
+
* - It succeeds ⇒ alive.
|
|
80
|
+
*/
|
|
81
|
+
export declare function processGroupAlive(pgid: number): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* SIGTERM process group `pgid`, wait for it to ACTUALLY exit, escalate to
|
|
84
|
+
* SIGKILL after {@link REAP_SIGTERM_GRACE_MS}, and report whether the tree is
|
|
85
|
+
* VERIFIED gone.
|
|
86
|
+
*
|
|
87
|
+
* Bounded by construction: at worst `sigtermGraceMs + sigkillTimeoutMs` before it
|
|
88
|
+
* returns `reaped: false` with a loud `detail`. It never waits indefinitely, so
|
|
89
|
+
* it cannot reintroduce the runner-hang the async launch's resolve-on-`exit`
|
|
90
|
+
* discipline exists to avoid.
|
|
91
|
+
*/
|
|
92
|
+
export declare function reapProcessGroup(params: {
|
|
93
|
+
/** The process GROUP id to reap (the group leader's pid). */
|
|
94
|
+
pgid: number;
|
|
95
|
+
sigtermGraceMs?: number;
|
|
96
|
+
sigkillTimeoutMs?: number;
|
|
97
|
+
/** Injectable sleep so tests need not burn real seconds. */
|
|
98
|
+
wait?: (ms: number) => Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Injectable liveness probe (default {@link processGroupAlive}). Exists so the
|
|
101
|
+
* REFUSAL path — a tree that survives SIGTERM *and* SIGKILL — can be tested
|
|
102
|
+
* deterministically. There is no portable way to create a genuinely unkillable
|
|
103
|
+
* process, and the alternative (a group we truly cannot signal) would risk the
|
|
104
|
+
* test process itself, so the probe is the seam.
|
|
105
|
+
*/
|
|
106
|
+
alive?: (pgid: number) => boolean;
|
|
107
|
+
}): Promise<ReapResult>;
|
|
108
|
+
//# sourceMappingURL=reap-agent-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reap-agent-tree.d.ts","sourceRoot":"","sources":["../src/reap-agent-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAKH;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAE7C,yDAAyD;AACzD,MAAM,WAAW,UAAU;IAC1B;;;;OAIG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,gFAAgF;IAChF,kBAAkB,EAAE,OAAO,CAAC;IAC5B,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CACf;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBvD;AAWD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC9C,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAClC,GAAG,OAAO,CAAC,UAAU,CAAC,CAyEtB"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Reap a stopped agent's whole PROCESS TREE, and VERIFY it is gone** (spec
|
|
3
|
+
* `graceful-pre-timeout-wip-checkpoint`, observation
|
|
4
|
+
* `checkpoint-releases-lock-while-predecessor-agent-still-writes`).
|
|
5
|
+
*
|
|
6
|
+
* ## The gap this closes
|
|
7
|
+
*
|
|
8
|
+
* The deadline checkpoint used to `child.kill('SIGTERM')` the agent process and
|
|
9
|
+
* treat the signal as if it were the outcome: the moment pi's own `exit` fired,
|
|
10
|
+
* the runner saved the WIP, RELEASED the item lock, and let the next tick
|
|
11
|
+
* dispatch a CONTINUATION agent into the SAME worktree. But `child.kill` signals
|
|
12
|
+
* exactly ONE pid, and a modern agent is a TREE (subagent processes, MCP servers,
|
|
13
|
+
* model proxies, tool subshells). Those descendants survive the parent's SIGTERM,
|
|
14
|
+
* and once pi exits they are re-parented to init — so they can no longer even be
|
|
15
|
+
* FOUND by walking `ppid`, while they keep writing into the worktree.
|
|
16
|
+
*
|
|
17
|
+
* Observed on a real run: the checkpoint saved WIP at 02:13, a continuation agent
|
|
18
|
+
* onboarded into the same worktree at ~02:15, and the predecessor's session log
|
|
19
|
+
* kept being written until 02:19:29 — four minutes INTO the successor's run,
|
|
20
|
+
* whose opening `git status` had already read the tree as clean. Nothing was lost
|
|
21
|
+
* only because the two happened to touch different files. The shape is the
|
|
22
|
+
* defect: one lock, one working tree, two live writers. A write landing after the
|
|
23
|
+
* successor's `git status` is invisible to it; a write landing during its edits
|
|
24
|
+
* can be clobbered either way; and the successor can commit the predecessor's
|
|
25
|
+
* half-finished edits as its own, under a message describing something else.
|
|
26
|
+
*
|
|
27
|
+
* ## Why the PROCESS GROUP is the handle
|
|
28
|
+
*
|
|
29
|
+
* A pid-tree walk cannot work here: the descendants we must reap are precisely
|
|
30
|
+
* the ones that OUTLIVE the parent, and an orphan's `ppid` is gone. A process
|
|
31
|
+
* GROUP id, by contrast, is inherited by every descendant and is NOT changed by
|
|
32
|
+
* re-parenting. So if the agent is spawned as a group LEADER (`detached: true`,
|
|
33
|
+
* making its pgid equal its pid), `kill(-pgid, …)` reaches the entire tree,
|
|
34
|
+
* orphans included — which is why {@link reapProcessGroup} takes a pgid and why
|
|
35
|
+
* `pi-harness.ts` spawns the deadline-capable async launch detached.
|
|
36
|
+
*
|
|
37
|
+
* ## Signal, then VERIFY — never assume
|
|
38
|
+
*
|
|
39
|
+
* The point of the whole module is that sending a signal is not evidence that
|
|
40
|
+
* anything died. So: SIGTERM the group, POLL until it is actually gone, escalate
|
|
41
|
+
* to SIGKILL after a grace, keep polling, and if it STILL will not die, say so
|
|
42
|
+
* LOUDLY and let the caller refuse to release the lock. A checkpoint that cannot
|
|
43
|
+
* prove the predecessor is dead must not hand the worktree to a successor.
|
|
44
|
+
*/
|
|
45
|
+
/** Poll interval while waiting for a signalled group to actually exit. */
|
|
46
|
+
const REAP_POLL_MS = 50;
|
|
47
|
+
/**
|
|
48
|
+
* How long to wait after SIGTERM before escalating to SIGKILL. Matches
|
|
49
|
+
* `pi-harness.ts`'s `DEADLINE_SIGKILL_GRACE_MS` intent: enough for an agent to
|
|
50
|
+
* flush its session log and exit cleanly, short enough not to stall a CI leg.
|
|
51
|
+
*/
|
|
52
|
+
export const REAP_SIGTERM_GRACE_MS = 10_000;
|
|
53
|
+
/** How long to keep waiting after SIGKILL before declaring the reap FAILED. */
|
|
54
|
+
export const REAP_SIGKILL_TIMEOUT_MS = 5_000;
|
|
55
|
+
/** Sleep helper (injectable clock is not needed: callers inject `wait` in tests). */
|
|
56
|
+
function sleep(ms) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const timer = setTimeout(resolve, ms);
|
|
59
|
+
timer.unref?.();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Is any process still alive in process group `pgid`?
|
|
64
|
+
*
|
|
65
|
+
* `process.kill(-pgid, 0)` is the liveness probe: signal 0 performs the
|
|
66
|
+
* permission/existence check WITHOUT delivering a signal (the same technique
|
|
67
|
+
* `harness.ts`'s {@link pidAlive} uses for a single pid, widened to the group).
|
|
68
|
+
*
|
|
69
|
+
* - It THROWS `ESRCH` when no process in the group exists ⇒ the group is gone.
|
|
70
|
+
* - It THROWS `EPERM` when the group exists but we may not signal it. That is
|
|
71
|
+
* still "alive", and reporting it as dead would be the very assumption this
|
|
72
|
+
* module exists to remove — so `EPERM` reads as ALIVE.
|
|
73
|
+
* - It succeeds ⇒ alive.
|
|
74
|
+
*/
|
|
75
|
+
export function processGroupAlive(pgid) {
|
|
76
|
+
if (!Number.isInteger(pgid) || pgid <= 1) {
|
|
77
|
+
// pgid 0/1 (or a bogus value) would mean "our own group" / init — signalling
|
|
78
|
+
// those would be catastrophic, so never claim they are ours to reap.
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
process.kill(-pgid, 0);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
const code = err.code;
|
|
87
|
+
if (code === 'EPERM') {
|
|
88
|
+
return true; // exists, just not signallable by us.
|
|
89
|
+
}
|
|
90
|
+
return false; // ESRCH (or anything else): treat as gone.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Signal a whole process group, tolerating an already-dead group. */
|
|
94
|
+
function signalGroup(pgid, signal) {
|
|
95
|
+
try {
|
|
96
|
+
process.kill(-pgid, signal);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// ESRCH: already gone — the wait loop below observes that and succeeds.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* SIGTERM process group `pgid`, wait for it to ACTUALLY exit, escalate to
|
|
104
|
+
* SIGKILL after {@link REAP_SIGTERM_GRACE_MS}, and report whether the tree is
|
|
105
|
+
* VERIFIED gone.
|
|
106
|
+
*
|
|
107
|
+
* Bounded by construction: at worst `sigtermGraceMs + sigkillTimeoutMs` before it
|
|
108
|
+
* returns `reaped: false` with a loud `detail`. It never waits indefinitely, so
|
|
109
|
+
* it cannot reintroduce the runner-hang the async launch's resolve-on-`exit`
|
|
110
|
+
* discipline exists to avoid.
|
|
111
|
+
*/
|
|
112
|
+
export async function reapProcessGroup(params) {
|
|
113
|
+
const { pgid, sigtermGraceMs = REAP_SIGTERM_GRACE_MS, sigkillTimeoutMs = REAP_SIGKILL_TIMEOUT_MS, wait = sleep, alive = processGroupAlive, } = params;
|
|
114
|
+
const started = Date.now();
|
|
115
|
+
if (!alive(pgid)) {
|
|
116
|
+
return {
|
|
117
|
+
reaped: true,
|
|
118
|
+
escalatedToSigkill: false,
|
|
119
|
+
waitedMs: 0,
|
|
120
|
+
detail: `agent process group ${pgid} was already gone (nothing to reap).`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// 1. SOFT: ask the whole tree to stop, then WAIT for it to be observably gone.
|
|
124
|
+
signalGroup(pgid, 'SIGTERM');
|
|
125
|
+
while (Date.now() - started < sigtermGraceMs) {
|
|
126
|
+
if (!alive(pgid)) {
|
|
127
|
+
const waitedMs = Date.now() - started;
|
|
128
|
+
return {
|
|
129
|
+
reaped: true,
|
|
130
|
+
escalatedToSigkill: false,
|
|
131
|
+
waitedMs,
|
|
132
|
+
detail: `agent process group ${pgid} exited on SIGTERM after ${waitedMs}ms ` +
|
|
133
|
+
'(verified gone).',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
await wait(REAP_POLL_MS);
|
|
137
|
+
}
|
|
138
|
+
// 2. HARD: the tree ignored SIGTERM through the grace. SIGKILL it and keep
|
|
139
|
+
// verifying — a wedged agent still holding the worktree must not survive
|
|
140
|
+
// into the successor's run.
|
|
141
|
+
signalGroup(pgid, 'SIGKILL');
|
|
142
|
+
const killDeadline = Date.now() + sigkillTimeoutMs;
|
|
143
|
+
while (Date.now() < killDeadline) {
|
|
144
|
+
if (!alive(pgid)) {
|
|
145
|
+
const waitedMs = Date.now() - started;
|
|
146
|
+
return {
|
|
147
|
+
reaped: true,
|
|
148
|
+
escalatedToSigkill: true,
|
|
149
|
+
waitedMs,
|
|
150
|
+
detail: `agent process group ${pgid} ignored SIGTERM and was SIGKILLed; ` +
|
|
151
|
+
`exited after ${waitedMs}ms (verified gone).`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
await wait(REAP_POLL_MS);
|
|
155
|
+
}
|
|
156
|
+
// 3. LOUD FAILURE: we cannot prove the predecessor is dead. Say exactly that,
|
|
157
|
+
// and exactly what the caller must not do as a result.
|
|
158
|
+
const waitedMs = Date.now() - started;
|
|
159
|
+
return {
|
|
160
|
+
reaped: false,
|
|
161
|
+
escalatedToSigkill: true,
|
|
162
|
+
waitedMs,
|
|
163
|
+
detail: `REFUSING TO PROCEED: agent process group ${pgid} is STILL ALIVE ${waitedMs}ms ` +
|
|
164
|
+
'after SIGTERM and SIGKILL (an unkillable/uninterruptible descendant — e.g. a ' +
|
|
165
|
+
'process wedged in a kernel call, or one we lack permission to signal). It may ' +
|
|
166
|
+
'still be WRITING to the worktree, so the item lock must NOT be released and no ' +
|
|
167
|
+
'successor agent may onboard here: two live writers in one working tree can ' +
|
|
168
|
+
'silently clobber each other and let a successor commit the predecessor’s ' +
|
|
169
|
+
`half-finished edits. Inspect and kill it by hand (\`ps -g ${pgid}\`, ` +
|
|
170
|
+
`\`kill -9 -${pgid}\`) before re-running this item.`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=reap-agent-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reap-agent-tree.js","sourceRoot":"","sources":["../src/reap-agent-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-mirror.d.ts","sourceRoot":"","sources":["../src/repo-mirror.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAE,aAAa,EAAC,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"repo-mirror.d.ts","sourceRoot":"","sources":["../src/repo-mirror.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAE,aAAa,EAAC,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,sBAAsB,CAAC;AAW5D,OAAO,EAAC,aAAa,EAAC,MAAM,eAAe,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,GAChC,MAAM,GAAG,SAAS,CAOpB;AAED;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,MAAM,WAAW,mBAAmB;IACnC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IAClC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAsE7E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,mBAAmB,GAC1B,kBAAkB,CAqBpB;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC9B,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GACrB,MAAM,CAKR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GACrB,MAAM,GAAG,SAAS,CAUpB;AAkCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE;IACpD,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC7B,GAAG,MAAM,CAmBT;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GACrB,MAAM,CAER;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC9C,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB,GAAG,OAAO,CAYV"}
|
package/dist/repo-mirror.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { git, run } from './git.js';
|
|
4
4
|
import { REPO_CONFIG_FILENAME, REPO_CONFIG_FILENAME_LEGACY, loadRepoConfigFromContent, resolveRepoConfigFromLoaded, } from './repo-config.js';
|
|
5
5
|
import { encodeRepoKey } from './repo-key.js';
|
|
6
|
+
import { LOCK_REF_PREFIX } from './item-lock.js';
|
|
6
7
|
export { encodeRepoKey } from './repo-key.js';
|
|
7
8
|
/**
|
|
8
9
|
* Read a bare hub mirror's `origin` URL (`git -C <mirror> remote get-url
|
|
@@ -83,6 +84,22 @@ export function ensureMirror(options) {
|
|
|
83
84
|
// materialisation has its base; the per-branch onboard reads still fail loudly
|
|
84
85
|
// downstream if a needed ref is genuinely absent.
|
|
85
86
|
run('git', ['fetch', 'origin', '+refs/heads/*:refs/heads/*'], path, { env });
|
|
87
|
+
// PRUNE the per-item LOCK namespace against the arbiter (observation
|
|
88
|
+
// `gc-ledger-reports-mirror-stale-lock-refs-as-arbiter-state`): the all-heads
|
|
89
|
+
// fetch above covers only `refs/heads/*`, so `refs/dorfl/lock/*` was never
|
|
90
|
+
// pruned here and released locks accumulated on the mirror indefinitely — a
|
|
91
|
+
// `gc --ledger` (or any `status`/`scan` read) that reads the mirror then
|
|
92
|
+
// reported locks that no longer exist on the arbiter. Best-effort (soft):
|
|
93
|
+
// an unreachable arbiter leaves the existing lock refs in place (the
|
|
94
|
+
// read-side `listItemLocks`/`listItemLockEntries` read the arbiter DIRECTLY
|
|
95
|
+
// via `ls-remote`, so this is the proactive keep-the-mirror-clean pass, not
|
|
96
|
+
// the load-bearing one).
|
|
97
|
+
run('git', [
|
|
98
|
+
'fetch',
|
|
99
|
+
'--prune',
|
|
100
|
+
'origin',
|
|
101
|
+
`+${LOCK_REF_PREFIX}/*:${LOCK_REF_PREFIX}/*`,
|
|
102
|
+
], path, { env });
|
|
86
103
|
fetched = true;
|
|
87
104
|
}
|
|
88
105
|
else {
|