@akagilnc/pi-workflow-roles 0.1.4782 → 0.1.4802

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +104 -215
  4. package/dist/headless-host/production-host.js +149 -229
  5. package/dist/migrate-book-topology.js +1 -1
  6. package/dist/pi/role-turn-host.js +1 -1
  7. package/dist/public-cli/auto-resume.js +9 -15
  8. package/dist/public-cli/countersign-run.js +75 -87
  9. package/dist/public-cli/diarist-run.js +6 -26
  10. package/dist/public-cli/inspector-run.js +1 -1
  11. package/dist/public-cli/main.js +64 -177
  12. package/dist/public-cli/notary-run.js +0 -3
  13. package/dist/public-cli/option-definitions.js +1 -1
  14. package/dist/public-cli/post-admission.js +35 -65
  15. package/dist/public-cli/reviewer-run.js +1 -1
  16. package/dist/public-cli/run-lifecycle.js +14 -46
  17. package/dist/public-cli/seat-ticket-binding.js +11 -25
  18. package/dist/public-role-summons.js +2 -0
  19. package/package.json +1 -1
  20. package/souls/coder.md +1 -2
  21. package/src/headless-host/role-turn-host.ts +49 -12
  22. package/src/pi/role-turn-host.ts +1 -1
  23. package/src/public-cli/auto-resume.ts +19 -18
  24. package/src/public-cli/countersign-run.ts +76 -129
  25. package/src/public-cli/diarist-run.ts +6 -33
  26. package/src/public-cli/inspector-run.ts +1 -1
  27. package/src/public-cli/invocation.ts +5 -0
  28. package/src/public-cli/notary-run.ts +3 -7
  29. package/src/public-cli/option-definitions.ts +1 -1
  30. package/src/public-cli/post-admission.ts +37 -69
  31. package/src/public-cli/reviewer-run.ts +1 -1
  32. package/src/public-cli/run-lifecycle.ts +18 -66
  33. package/src/public-cli/seat-ticket-binding.ts +12 -28
  34. package/src/public-role-summons.ts +15 -6
  35. package/src/role-runtime.ts +9 -0
@@ -14937,7 +14937,7 @@ var init_option_definitions = __esm({
14937
14937
  },
14938
14938
  resume: {
14939
14939
  command: "resume",
14940
- summary: "Resume a role run under the live seat table (model/host/engine); session principal must still exist. [message] applies only to seats that accept caller instruction; Notary/\u7B26\u5B9D\u90CE must omit message and derives evidence from the existing source-run/dossier binding. Global --model/--thinking/--host/--engine must be placed before <runId> (either before `resume` or between `resume` and <runId>); the one argv after <runId> is the opaque message, not a flag position (#471).",
14940
+ summary: "Resume a role run under the live seat table (model/host/engine); session principal must still exist. Every callable seat, including Notary/\u7B26\u5B9D\u90CE, accepts the optional caller message and passes it through unchanged as the continuation prompt. Notary explicit new still accepts only its source-run locator, not a caller prompt. Global --model/--thinking/--host/--engine must be placed before <runId> (either before `resume` or between `resume` and <runId>); the one argv after <runId> is the opaque message, not a flag position (#471).",
14941
14941
  usage: ["ak-role resume <runId> [message]"],
14942
14942
  examples: [
14943
14943
  "ak-role resume 01abc\u2026",
@@ -134,7 +134,7 @@ function piUserDialogueBody(request) {
134
134
  const _exhaustive = request.continuation;
135
135
  return _exhaustive;
136
136
  })();
137
- return request.continuation.kind === "resume" && request.activation.role === "reviewer"
137
+ return request.continuation.kind === "resume"
138
138
  ? rawPrompt
139
139
  : applyPiNativeSkillInvocation(request.methods, rawPrompt);
140
140
  }
@@ -20,7 +20,7 @@ import { AUTO_RESUME_LIMIT, describeErrorIdentity, acquireRunWriterLease, markRu
20
20
  import { parseAutoResumeLimit } from "./config.js";
21
21
  import { processCancelSignalName } from "./process-cancel.js";
22
22
  import { isLawfulTypedTerminalOutcome, formatTerminalResult } from "./terminal.js";
23
- import { attachRecordedSubmissions, presentFailureTerminal, presentStructuralRejection, resolveControlledFailureResumeObservation, } from "./settlement.js";
23
+ import { attachRecordedSubmissions, presentFailureTerminal, resolveControlledFailureResumeObservation, } from "./settlement.js";
24
24
  const dummyIo = { stdout: () => { }, stderr: () => { } };
25
25
  /**
26
26
  * Persist run-state after a host-turn result, outside the retried dispatch try.
@@ -352,17 +352,6 @@ export async function runWithAutoResumeLoop(options) {
352
352
  let everyAttemptThrew = true;
353
353
  const retainedErrorFiles = [];
354
354
  while (true) {
355
- let lease;
356
- try {
357
- lease = await acquireRunWriterLease(options.admitted.runDirectory, (diagnostic) => options.io.stderr(diagnostic));
358
- }
359
- catch (error) {
360
- if (error instanceof RunWriterLeaseHeldError) {
361
- presentStructuralRejection(error, options.io);
362
- return { exitCode: 2 };
363
- }
364
- throw error;
365
- }
366
355
  let result;
367
356
  // Set only when the caught throw is a TurnDispatchedFailure (#840 r9 判词
368
357
  // class 1): the host turn genuinely started this attempt even though
@@ -370,14 +359,19 @@ export async function runWithAutoResumeLoop(options) {
370
359
  // not a replay of the initial one.
371
360
  let turnStartedBeforeThrow = false;
372
361
  try {
362
+ // #987: guard the initial new turn with the existing lease, but do not
363
+ // pre-block a real host CLI resume on a package-side live holder.
364
+ const lease = isFirst
365
+ ? await acquireRunWriterLease(options.admitted.runDirectory)
366
+ : undefined;
373
367
  result = await options.dispatch(currentPayload, lease, isFirst, dummyIo);
374
368
  }
375
369
  catch (error) {
376
370
  // Owner 2026-08-23: 「出了异常,就原地记录错误信息,然后重试。」
377
371
  // Retain the whole exception in place (per-attempt full file + dossier
378
372
  // pointer); recording failure must not break the retry path (PR #418
379
- // diagnostic-sink-isolation precedent). The dispatcher owns lease release
380
- // in its own finally, so the retry round starts with the lock free.
373
+ // diagnostic-sink-isolation precedent). When a lease is held inside
374
+ // dispatch, that path owns release in its own finally.
381
375
  lastThrownError = error;
382
376
  turnStartedBeforeThrow = error instanceof TurnDispatchedFailure;
383
377
  const attempt = dispatchOrdinal;
@@ -508,7 +502,7 @@ export async function runWithAutoResumeLoop(options) {
508
502
  // throws and beforeDispatch failures retry the initial payload (#840 / #416).
509
503
  if (result?.turnDispatched === true || turnStartedBeforeThrow) {
510
504
  currentPayload = options.buildResumePayload();
505
+ isFirst = false;
511
506
  }
512
- isFirst = false;
513
507
  }
514
508
  }
@@ -1,9 +1,36 @@
1
+ /**
2
+ * Public Countersign Role run: admit ticket materials → court-pipeline prior
3
+ * station (起居郎) → shared post-admission coordinator → settle Terminal result
4
+ * (#572 / ADR 0074 / ADR 0075 / #742 / #771). #599: manual resume continues the
5
+ * exact session via explicit package runId. #987 Result 7: public entry no longer
6
+ * selects a prior run by ticket number; gate same-parent re-summons keep
7
+ * parentRunPath resume (#747 / ADR 0079 gate face). Explicit `ak-role new` mints
8
+ * fresh; explicit `ak-role resume <runId>` continues a named run.
9
+ *
10
+ * Court admission auto-runs 起居郎 so the 起居郎 LLM asserts the court target;
11
+ * mechanical layer only verifies; countersign reuses that typed identity for bind
12
+ * (ADR 0075 / 0081). Code never matches instruction text against book-known
13
+ * numbers. Who may call 起居郎 and in what order is not written into law
14
+ * (ADR 0075 不规定谁调用起居郎、顺序归调用者); the present admission effect is what this
15
+ * seat currently does. 起居录 path delivery is owned once by post-admission
16
+ * (#709 / ADR 0081). Court refresh may run on resume; it is not a resume
17
+ * precondition and does not rewrite or reject host resume (#987).
18
+ *
19
+ * Wiring (#771 / #863 / #987): gate parentRunPath resume (when present) runs
20
+ * before identity mint; public path without parentRunPath always materializes a
21
+ * new run after identity. 起居郎 escalate (认不出) and typed failure terminals
22
+ * (incl. verification failure) settle as countersign controlled failure — never
23
+ * wash into 真无票. Only a true missing lawful typed terminal stays unbound and
24
+ * continues the body (r5 unbound-continue). Bound refresh hands the typed key to
25
+ * 起居郎 so freeze loads issue face (ADR 0075: 每次过庭都跑是调用者用法 / typed handoff).
26
+ */
27
+ import { resolve } from "node:path";
1
28
  import { engineSessionMaterialFromOptions, pickEngineAxis, } from "../package-resources/engine-material.js";
2
29
  import { CliUsageError } from "./cli-errors.js";
3
30
  import { projectCourtTicketNumbers } from "../diarist-contracts.js";
4
31
  import { readableGateItem } from "../readable-gate-item.js";
5
32
  import { isSafePositiveTicketNumber } from "../run-ticket-number.js";
6
- import { admitCountersignInvocation, bindAdmittedTicketNumber, bindCourtTicketNumbersOnAdmitted, buildCountersignTransportPrompt, freezePreparedAttachmentsIntoRun, materializeCountersignInvocation, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
33
+ import { admitCountersignInvocation, bindAdmittedTicketNumber, bindCourtTicketNumbersOnAdmitted, buildCountersignTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
7
34
  import { prepareSummonsResumeMaterials, presentControlledFailure, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, StationChildExhaustedError, } from "./post-admission.js";
8
35
  import { loadResumableCountersignRun, markRunAdmitted, } from "./run-lifecycle.js";
9
36
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
@@ -275,6 +302,27 @@ export async function runPublicCountersign(argv, env, io, parseCountersignArgv)
275
302
  throw error;
276
303
  }
277
304
  let admitted;
305
+ const gateParentRunPath = typeof env.parentRunPath === "string" && env.parentRunPath.trim() !== ""
306
+ ? env.parentRunPath
307
+ : undefined;
308
+ if (gateParentRunPath !== undefined) {
309
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction ?? parsed.instruction;
310
+ const resumed = await tryResumeSameTicketSeatRun({
311
+ home: env.home,
312
+ projectRoot: resolve(parsed.project ?? env.cwd),
313
+ role: "countersign",
314
+ parentRunPath: gateParentRunPath,
315
+ freshSummons: env.freshSummons,
316
+ summons: {
317
+ sourceRunPath: gateParentRunPath,
318
+ instruction: resumeInstruction,
319
+ instructionEmpty: resumeInstruction.trim() === "",
320
+ },
321
+ resume: (runId, materials) => runPublicCountersignResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
322
+ });
323
+ if (resumed !== undefined)
324
+ return resumed;
325
+ }
278
326
  try {
279
327
  admitted = await admitCountersignInvocation({
280
328
  home: env.home,
@@ -312,27 +360,26 @@ export async function runPublicCountersign(argv, env, io, parseCountersignArgv)
312
360
  ...(env.model === undefined ? {} : { model: env.model }),
313
361
  });
314
362
  };
315
- // #637 / #771 / ADR 0079: ticket identity is the 起居郎 LLM typed assertion
316
- // (never mechanical matching of summons text). Resolve that typed key before
317
- // materializing a run: same-ticket re-summons write only to the retained run.
318
- // Controlled failures materialize below so they still have a durable page. The test
319
- // seam `runCourtDiaristStation` defers identity to beforeDispatch; generic
320
- // hook failures stay on the parent call-local budget, exhausted nested
321
- // station children still skip parent auto-resume (#840 父子不层叠).
363
+ // #747 / #987: gate same-parent resume before identity mint. Public entry
364
+ // without parentRunPath never selects a prior run by ticket number.
365
+ // #637 / #771: ticket identity is the 起居郎 LLM typed assertion (never
366
+ // mechanical matching of summons text). Resolve that typed key before
367
+ // materializing a first-mint run. Controlled failures materialize below so
368
+ // they still have a durable page. The test seam `runCourtDiaristStation`
369
+ // defers identity to beforeDispatch; generic hook failures stay on the
370
+ // parent call-local budget, exhausted nested station children still skip
371
+ // parent auto-resume (#840 父子不层叠).
322
372
  let typedTicket;
323
373
  let typedCourtTicketNumbers;
324
374
  let identityDiaristRan = false;
325
375
  if (env.runCourtDiaristStation === undefined) {
326
376
  let outcome;
327
377
  try {
328
- // The identity child cannot name the newly minted countersign id as parent:
329
- // same-ticket lookup may intentionally never materialize that run. A selected
330
- // retained run receives its normally correlated refresh child during resume.
331
378
  outcome = await invokeCourtDiarist({
332
379
  instruction: parsed.instruction,
333
380
  projectRoot: admitted.projectRoot,
334
381
  failureLabel: "unbound summons",
335
- // #969: parent durable ticket is the resume/bind key (ADR 0079).
382
+ // #969: parent durable ticket is the bind key (not a resume lookup).
336
383
  ...(env.boundTicketNumber === undefined
337
384
  ? {}
338
385
  : { boundTicketNumber: env.boundTicketNumber }),
@@ -375,7 +422,7 @@ export async function runPublicCountersign(argv, env, io, parseCountersignArgv)
375
422
  // failure above; bound refresh still fails honest via station.
376
423
  if (outcome.identity.kind === "ticket") {
377
424
  // #969: parent durable handoff wins over 起居郎 re-assert when both
378
- // present (receipt/assert mismatch → parent resume key).
425
+ // present (receipt/assert mismatch → parent bind key).
379
426
  typedTicket = isSafePositiveTicketNumber(env.boundTicketNumber)
380
427
  ? env.boundTicketNumber
381
428
  : outcome.identity.ticketNumber;
@@ -384,62 +431,19 @@ export async function runPublicCountersign(argv, env, io, parseCountersignArgv)
384
431
  }
385
432
  else if (isSafePositiveTicketNumber(env.boundTicketNumber)) {
386
433
  // Parent board handoff present; 起居郎 returned true-unbound — still
387
- // resume/bind under the parent key (omitted receipt ticketNumber path).
434
+ // bind under the parent key (omitted receipt ticketNumber path).
388
435
  typedTicket = env.boundTicketNumber;
389
436
  }
390
- if (typedTicket !== undefined) {
391
- // #969/#879: gate re-ask / parent submission body share summons.instruction
392
- // (reask wins; argv instruction remains the 起居郎 identity face above).
393
- const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction ?? parsed.instruction;
394
- const summons = {
395
- instruction: resumeInstruction,
396
- instructionEmpty: resumeInstruction.trim() === "",
397
- };
398
- const resumed = await tryResumeSameTicketSeatRun({
399
- home: env.home,
400
- projectRoot: admitted.projectRoot,
401
- role: "countersign",
402
- ticketNumber: typedTicket,
403
- freshSummons: env.freshSummons,
404
- summons,
405
- resume: async (runId, materials) => {
406
- // Consume the same pre-identity snapshot into the retained run; never
407
- // reopen caller paths after identity has run.
408
- const retained = await loadResumableCountersignRun(env.home, runId, env.principalAuthority);
409
- if (retained.admitted === undefined) {
410
- throw new Error(`retained countersign run disappeared before resume: ${runId}`);
411
- }
412
- const frozenPaths = (await freezePreparedAttachmentsIntoRun(preparedAttachments, retained.admitted.runDirectory, `s-${Date.now().toString(36)}`)).map((attachment) => attachment.frozenPath);
413
- const preparedMaterials = {
414
- ...(materials ?? {}),
415
- ...(frozenPaths.length === 0
416
- ? {}
417
- : { attachmentPaths: frozenPaths }),
418
- };
419
- // Identity 起居郎 asserted unbound (no issue face). Resume still runs
420
- // the bound refresh station under the typed key (ADR 0075: 每次过庭都跑是调用者用法).
421
- // #871: hand the identity set so resume can whole-replace the run fact
422
- // when this summons produced a new typed set (never union).
423
- return await runPublicCountersignResume({
424
- runId,
425
- summons: preparedMaterials,
426
- }, {
427
- ...env,
428
- ...(typedCourtTicketNumbers === undefined
429
- ? {}
430
- : { pendingCourtTicketNumbers: typedCourtTicketNumbers }),
431
- }, io);
432
- },
433
- });
434
- if (resumed !== undefined) {
435
- return resumed;
436
- }
437
- }
438
437
  }
439
- // No prior run was selected. Materialize this invocation now; true-unbound,
440
- // first-ticket and deferred test-seam paths all retain their own durable page.
438
+ // No prior same-parent run was selected. Materialize this invocation now;
439
+ // true-unbound, first-ticket and deferred test-seam paths all retain their
440
+ // own durable page. Public re-summons without parentRunPath always mint.
441
441
  await materializeAdmission();
442
442
  await markRunAdmitted(admitted, env.principalAuthority);
443
+ if (gateParentRunPath !== undefined) {
444
+ await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
445
+ admitted = { ...admitted, sourceRunPath: gateParentRunPath };
446
+ }
443
447
  if (identityDiaristRan && typedTicket !== undefined) {
444
448
  try {
445
449
  await bindAdmittedTicketNumber(admitted, typedTicket);
@@ -525,13 +529,12 @@ function countersignAdapters(options) {
525
529
  };
526
530
  }
527
531
  /**
528
- * Resume a previously admitted Countersign run (#599 / DK-3 / #637).
529
- * Restores role/ticket/session identity. Bound court re-entry runs the diarist
530
- * refresh station first (ADR 0075 每次过庭都跑); unbound skips refresh.
531
- * Same-ticket summons deliver this turn's instruction + frozen attachments on
532
- * the resume prompt; manual resume keeps package-envelope / caller-message
533
- * semantics and birth attachments. 起居录 path delivery remains post-admission's
534
- * single mount (#709).
532
+ * Resume a previously admitted Countersign run (#599 / DK-3 / #637 / #987).
533
+ * Restores role/ticket/session identity. Gate
534
+ * same-parent and explicit `ak-role resume <runId>` share this entry; summons
535
+ * may carry this turn's instruction. Manual resume forwards only the caller's
536
+ * message bytes. 起居录 path delivery remains
537
+ * post-admission's single mount (#709).
535
538
  */
536
539
  export async function runPublicCountersignResume(request, env, io) {
537
540
  return await runPostAdmissionSeatResume({
@@ -543,22 +546,7 @@ export async function runPublicCountersignResume(request, env, io) {
543
546
  const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
544
547
  return buildCountersignTurnRequest(admitted, resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared));
545
548
  },
546
- adapters: countersignAdapters({
547
- beforeDispatch: async (admitted) => {
548
- // #871 B7: durable set damage already identified at load — settle as
549
- // station-child exhausted → presentControlledFailure (not structural exit 2).
550
- if (admitted.courtTicketNumbersDamage !== undefined) {
551
- throw new StationChildExhaustedError(admitted.courtTicketNumbersDamage);
552
- }
553
- // #871: same-ticket re-summons may hand a fresh typed set from identity.
554
- // Whole-set replace onto this run fact; no new set → keep stored set.
555
- // Manual resume (no pending) keeps the durable set and never invents one.
556
- if (env.pendingCourtTicketNumbers !== undefined) {
557
- await bindCourtTicketNumbersOnAdmitted(admitted, env.pendingCourtTicketNumbers);
558
- }
559
- await runCountersignCourtDiaristStation(admitted, env, io);
560
- },
561
- }),
549
+ adapters: countersignAdapters(),
562
550
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
563
551
  });
564
552
  }
@@ -3,7 +3,6 @@ import { CliUsageError } from "./cli-errors.js";
3
3
  import { admitDiaristInvocation, bindAdmittedTicketNumber, buildInstructionTransportPrompt, relocateAdmittedRunToTicket, recordAdmittedCorrelation, } from "./invocation.js";
4
4
  import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
5
5
  import { loadResumableDiaristRun, markRunAdmitted, } from "./run-lifecycle.js";
6
- import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
7
6
  import { presentStructuralRejection, trySettleDiaristTerminalResult, } from "./settlement.js";
8
7
  import { projectRoleTurnRequest, } from "./turn-request.js";
9
8
  import { readBoardTicketNumber } from "../run-ticket-number.js";
@@ -53,32 +52,13 @@ export async function runPublicDiarist(argv, env, io, parseDiaristArgv) {
53
52
  }
54
53
  throw error;
55
54
  }
56
- // #637 / #771 / ADR 0075 / #779: ticket identity is the LLM's typed assertion
57
- // on this turn, OR a typed handoff key already held by the caller (countersign
58
- // refresh). Code never pre-judges the summons text and never freezes a
59
- // candidate catalog. First summons without handoff stays unbound until assert.
60
- const projectRoot = parsed.project ?? env.cwd;
55
+ // #637 / #771 / ADR 0075 / #779 / #987: ticket identity is the LLM's typed
56
+ // assertion on this turn, OR a typed handoff key already held by the caller
57
+ // (countersign refresh bind). Code never pre-judges the summons text and never
58
+ // freezes a candidate catalog. Public same-ticket resume-by-ticketNumber was
59
+ // deleted (#987 Result 7); callers continue an existing diarist run only via
60
+ // explicit `ak-role resume <runId>`. Handoff ticket remains a bind key only.
61
61
  const handoffTicket = env.boundTicketNumber;
62
- if (typeof handoffTicket === "number" &&
63
- Number.isSafeInteger(handoffTicket) &&
64
- handoffTicket >= 1) {
65
- const summons = {
66
- instruction: parsed.instruction,
67
- instructionEmpty: parsed.instruction.trim() === "",
68
- attachmentPaths: parsed.attachmentPaths,
69
- };
70
- const resumed = await tryResumeSameTicketSeatRun({
71
- home: env.home,
72
- projectRoot,
73
- role: "diarist",
74
- ticketNumber: handoffTicket,
75
- freshSummons: env.freshSummons,
76
- summons,
77
- resume: (runId, materials) => runPublicDiaristResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
78
- });
79
- if (resumed !== undefined)
80
- return resumed;
81
- }
82
62
  let admitted;
83
63
  try {
84
64
  admitted = await admitDiaristInvocation({
@@ -133,7 +133,7 @@ function inspectorAdapters(options) {
133
133
  /**
134
134
  * Resume a previously admitted Inspector run (#633 / #637); the session principal reopens.
135
135
  * Same-ticket summons deliver this turn's instruction + frozen attachments; manual
136
- * resume keeps package-envelope / caller-message semantics and birth attachments.
136
+ * resume forwards only caller-supplied message bytes.
137
137
  */
138
138
  export async function runPublicInspectorResume(request, env, io) {
139
139
  return await runPostAdmissionSeatResume({