@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
@@ -2,25 +2,29 @@
2
2
  * Public Countersign Role run: admit ticket materials → court-pipeline prior
3
3
  * station (起居郎) → shared post-admission coordinator → settle Terminal result
4
4
  * (#572 / ADR 0074 / ADR 0075 / #742 / #771). #599: manual resume continues the
5
- * exact session. ADR 0079: same-ticket re-summons resume the seat's previous run;
6
- * explicit `ak-role new` mints fresh (显式派新腿入口与显式 resume 并列).
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.
7
9
  *
8
10
  * Court admission auto-runs 起居郎 so the 起居郎 LLM asserts the court target;
9
11
  * mechanical layer only verifies; countersign reuses that typed identity for bind
10
- * and same-ticket resume lookup (ADR 0075 / 0081 / 0079). Code never matches
11
- * instruction text against book-known numbers. Who may call 起居郎 and in what
12
- * order is not written into law (ADR 0075 不规定谁调用起居郎、顺序归调用者); the present admission
13
- * effect is what this seat currently does. 起居录 path delivery is owned once by
14
- * post-admission (#709 / ADR 0081).
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).
15
18
  *
16
- * Wiring (#771 / #863): resolve typed identity before materializing the admitted
17
- * run; same-ticket resume writes only to the retained run. 起居郎 escalate (认不出)
18
- * and typed failure terminals
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
19
22
  * (incl. verification failure) settle as countersign controlled failure — never
20
23
  * wash into 真无票. Only a true missing lawful typed terminal stays unbound and
21
24
  * continues the body (r5 unbound-continue). Bound refresh hands the typed key to
22
25
  * 起居郎 so freeze loads issue face (ADR 0075: 每次过庭都跑是调用者用法 / typed handoff).
23
26
  */
27
+ import { resolve } from "node:path";
24
28
  import type {
25
29
  DurablePrincipalAuthority,
26
30
  RoleTurnRequest,
@@ -38,8 +42,8 @@ import {
38
42
  bindAdmittedTicketNumber,
39
43
  bindCourtTicketNumbersOnAdmitted,
40
44
  buildCountersignTransportPrompt,
41
- freezePreparedAttachmentsIntoRun,
42
45
  materializeCountersignInvocation,
46
+ persistAdmittedSourceRunPath,
43
47
  relocateAdmittedRunToTicket,
44
48
  withPreparedAttachments,
45
49
  type AdmittedCountersignInvocation,
@@ -59,7 +63,6 @@ import {
59
63
  markRunAdmitted,
60
64
  type PublicResumeRequest,
61
65
  type RunWriterLease,
62
- type SameTicketSummonsMaterials,
63
66
  } from "./run-lifecycle.ts";
64
67
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
65
68
  import {
@@ -83,12 +86,6 @@ export type CountersignRunEnv = PostAdmissionEnv & {
83
86
  runCourtDiaristStation?: (
84
87
  admitted: AdmittedCountersignInvocation,
85
88
  ) => Promise<void>;
86
- /**
87
- * #871: typed co-review set from the identity 起居郎 turn, applied onto the
88
- * resumed run before bound refresh (same-ticket re-summons path). Whole-set
89
- * replace — never union. Production leaves unset outside that handoff.
90
- */
91
- pendingCourtTicketNumbers?: readonly number[];
92
89
  /**
93
90
  * #969 gate path: plain-language re-ask when prior 给事中 reply was not three-state.
94
91
  * Wins over gateReviewInstruction when both present (notary/inspector precedent).
@@ -100,11 +97,15 @@ export type CountersignRunEnv = PostAdmissionEnv & {
100
97
  */
101
98
  gateReviewInstruction?: string;
102
99
  /**
103
- * #969 gate path: parent run durable board ticket handoff. Same-ticket resume
104
- * key and identity 起居郎 bind under this key — never re-derived from receipt
105
- * ticketNumber (optional / omit-able payload field).
100
+ * #969 / #987 gate path: parent run durable board ticket handoff for bind only.
101
+ * Resume lookup uses parentRunPath — never this ticket number (#987 Result 7).
106
102
  */
107
103
  boundTicketNumber?: number;
104
+ /**
105
+ * #747 / #987 gate same-parent resume key (Secretariat source run directory).
106
+ * When set, re-summons resume the prior 给事中 under this parent before mint.
107
+ */
108
+ parentRunPath?: string;
108
109
  };
109
110
 
110
111
  /** Project admitted invocation onto the host-neutral turn request. */
@@ -474,6 +475,33 @@ export async function runPublicCountersign(
474
475
  }
475
476
 
476
477
  let admitted: AdmittedCountersignInvocation;
478
+ const gateParentRunPath =
479
+ typeof env.parentRunPath === "string" && env.parentRunPath.trim() !== ""
480
+ ? env.parentRunPath
481
+ : undefined;
482
+ if (gateParentRunPath !== undefined) {
483
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction ?? parsed.instruction;
484
+ const resumed = await tryResumeSameTicketSeatRun({
485
+ home: env.home,
486
+ projectRoot: resolve(parsed.project ?? env.cwd),
487
+ role: "countersign",
488
+ parentRunPath: gateParentRunPath,
489
+ freshSummons: env.freshSummons,
490
+ summons: {
491
+ sourceRunPath: gateParentRunPath,
492
+ instruction: resumeInstruction,
493
+ instructionEmpty: resumeInstruction.trim() === "",
494
+ },
495
+ resume: (runId, materials) =>
496
+ runPublicCountersignResume(
497
+ { runId, ...(materials === undefined ? {} : { summons: materials }) },
498
+ env,
499
+ io,
500
+ ),
501
+ });
502
+ if (resumed !== undefined) return resumed;
503
+ }
504
+
477
505
  try {
478
506
  admitted = await admitCountersignInvocation({
479
507
  home: env.home,
@@ -514,13 +542,15 @@ export async function runPublicCountersign(
514
542
  });
515
543
  };
516
544
 
517
- // #637 / #771 / ADR 0079: ticket identity is the 起居郎 LLM typed assertion
518
- // (never mechanical matching of summons text). Resolve that typed key before
519
- // materializing a run: same-ticket re-summons write only to the retained run.
520
- // Controlled failures materialize below so they still have a durable page. The test
521
- // seam `runCourtDiaristStation` defers identity to beforeDispatch; generic
522
- // hook failures stay on the parent call-local budget, exhausted nested
523
- // station children still skip parent auto-resume (#840 父子不层叠).
545
+ // #747 / #987: gate same-parent resume before identity mint. Public entry
546
+ // without parentRunPath never selects a prior run by ticket number.
547
+ // #637 / #771: ticket identity is the 起居郎 LLM typed assertion (never
548
+ // mechanical matching of summons text). Resolve that typed key before
549
+ // materializing a first-mint run. Controlled failures materialize below so
550
+ // they still have a durable page. The test seam `runCourtDiaristStation`
551
+ // defers identity to beforeDispatch; generic hook failures stay on the
552
+ // parent call-local budget, exhausted nested station children still skip
553
+ // parent auto-resume (#840 父子不层叠).
524
554
  let typedTicket: number | undefined;
525
555
  let typedCourtTicketNumbers: readonly number[] | undefined;
526
556
  let identityDiaristRan = false;
@@ -528,15 +558,12 @@ export async function runPublicCountersign(
528
558
  if (env.runCourtDiaristStation === undefined) {
529
559
  let outcome: CourtDiaristInvocationResult;
530
560
  try {
531
- // The identity child cannot name the newly minted countersign id as parent:
532
- // same-ticket lookup may intentionally never materialize that run. A selected
533
- // retained run receives its normally correlated refresh child during resume.
534
561
  outcome = await invokeCourtDiarist(
535
562
  {
536
563
  instruction: parsed.instruction,
537
564
  projectRoot: admitted.projectRoot,
538
565
  failureLabel: "unbound summons",
539
- // #969: parent durable ticket is the resume/bind key (ADR 0079).
566
+ // #969: parent durable ticket is the bind key (not a resume lookup).
540
567
  ...(env.boundTicketNumber === undefined
541
568
  ? {}
542
569
  : { boundTicketNumber: env.boundTicketNumber }),
@@ -602,7 +629,7 @@ export async function runPublicCountersign(
602
629
  // failure above; bound refresh still fails honest via station.
603
630
  if (outcome.identity.kind === "ticket") {
604
631
  // #969: parent durable handoff wins over 起居郎 re-assert when both
605
- // present (receipt/assert mismatch → parent resume key).
632
+ // present (receipt/assert mismatch → parent bind key).
606
633
  typedTicket = isSafePositiveTicketNumber(env.boundTicketNumber)
607
634
  ? env.boundTicketNumber
608
635
  : outcome.identity.ticketNumber;
@@ -610,81 +637,22 @@ export async function runPublicCountersign(
610
637
  typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
611
638
  } else if (isSafePositiveTicketNumber(env.boundTicketNumber)) {
612
639
  // Parent board handoff present; 起居郎 returned true-unbound — still
613
- // resume/bind under the parent key (omitted receipt ticketNumber path).
640
+ // bind under the parent key (omitted receipt ticketNumber path).
614
641
  typedTicket = env.boundTicketNumber;
615
642
  }
616
- if (typedTicket !== undefined) {
617
- // #969/#879: gate re-ask / parent submission body share summons.instruction
618
- // (reask wins; argv instruction remains the 起居郎 identity face above).
619
- const resumeInstruction =
620
- env.reviewReask ?? env.gateReviewInstruction ?? parsed.instruction;
621
- const summons: SameTicketSummonsMaterials = {
622
- instruction: resumeInstruction,
623
- instructionEmpty: resumeInstruction.trim() === "",
624
- };
625
- const resumed = await tryResumeSameTicketSeatRun({
626
- home: env.home,
627
- projectRoot: admitted.projectRoot,
628
- role: "countersign",
629
- ticketNumber: typedTicket,
630
- freshSummons: env.freshSummons,
631
- summons,
632
- resume: async (runId, materials) => {
633
- // Consume the same pre-identity snapshot into the retained run; never
634
- // reopen caller paths after identity has run.
635
- const retained = await loadResumableCountersignRun(
636
- env.home,
637
- runId,
638
- env.principalAuthority,
639
- );
640
- if (retained.admitted === undefined) {
641
- throw new Error(
642
- `retained countersign run disappeared before resume: ${runId}`,
643
- );
644
- }
645
- const frozenPaths = (
646
- await freezePreparedAttachmentsIntoRun(
647
- preparedAttachments,
648
- retained.admitted.runDirectory,
649
- `s-${Date.now().toString(36)}`,
650
- )
651
- ).map((attachment) => attachment.frozenPath);
652
- const preparedMaterials: SameTicketSummonsMaterials = {
653
- ...(materials ?? {}),
654
- ...(frozenPaths.length === 0
655
- ? {}
656
- : { attachmentPaths: frozenPaths }),
657
- };
658
- // Identity 起居郎 asserted unbound (no issue face). Resume still runs
659
- // the bound refresh station under the typed key (ADR 0075: 每次过庭都跑是调用者用法).
660
- // #871: hand the identity set so resume can whole-replace the run fact
661
- // when this summons produced a new typed set (never union).
662
- return await runPublicCountersignResume(
663
- {
664
- runId,
665
- summons: preparedMaterials,
666
- },
667
- {
668
- ...env,
669
- ...(typedCourtTicketNumbers === undefined
670
- ? {}
671
- : { pendingCourtTicketNumbers: typedCourtTicketNumbers }),
672
- },
673
- io,
674
- );
675
- },
676
- });
677
- if (resumed !== undefined) {
678
- return resumed;
679
- }
680
- }
681
643
  }
682
644
 
683
- // No prior run was selected. Materialize this invocation now; true-unbound,
684
- // first-ticket and deferred test-seam paths all retain their own durable page.
645
+ // No prior same-parent run was selected. Materialize this invocation now;
646
+ // true-unbound, first-ticket and deferred test-seam paths all retain their
647
+ // own durable page. Public re-summons without parentRunPath always mint.
685
648
  await materializeAdmission();
686
649
  await markRunAdmitted(admitted, env.principalAuthority);
687
650
 
651
+ if (gateParentRunPath !== undefined) {
652
+ await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
653
+ admitted = { ...admitted, sourceRunPath: gateParentRunPath };
654
+ }
655
+
688
656
  if (identityDiaristRan && typedTicket !== undefined) {
689
657
  try {
690
658
  await bindAdmittedTicketNumber(admitted, typedTicket);
@@ -803,13 +771,12 @@ function countersignAdapters(options?: {
803
771
  }
804
772
 
805
773
  /**
806
- * Resume a previously admitted Countersign run (#599 / DK-3 / #637).
807
- * Restores role/ticket/session identity. Bound court re-entry runs the diarist
808
- * refresh station first (ADR 0075 每次过庭都跑); unbound skips refresh.
809
- * Same-ticket summons deliver this turn's instruction + frozen attachments on
810
- * the resume prompt; manual resume keeps package-envelope / caller-message
811
- * semantics and birth attachments. 起居录 path delivery remains post-admission's
812
- * single mount (#709).
774
+ * Resume a previously admitted Countersign run (#599 / DK-3 / #637 / #987).
775
+ * Restores role/ticket/session identity. Gate
776
+ * same-parent and explicit `ak-role resume <runId>` share this entry; summons
777
+ * may carry this turn's instruction. Manual resume forwards only the caller's
778
+ * message bytes. 起居录 path delivery remains
779
+ * post-admission's single mount (#709).
813
780
  */
814
781
  export async function runPublicCountersignResume(
815
782
  request: PublicResumeRequest,
@@ -845,27 +812,7 @@ export async function runPublicCountersignResume(
845
812
  ),
846
813
  );
847
814
  },
848
- adapters: countersignAdapters({
849
- beforeDispatch: async (admitted) => {
850
- // #871 B7: durable set damage already identified at load — settle as
851
- // station-child exhausted → presentControlledFailure (not structural exit 2).
852
- if (admitted.courtTicketNumbersDamage !== undefined) {
853
- throw new StationChildExhaustedError(
854
- admitted.courtTicketNumbersDamage,
855
- );
856
- }
857
- // #871: same-ticket re-summons may hand a fresh typed set from identity.
858
- // Whole-set replace onto this run fact; no new set → keep stored set.
859
- // Manual resume (no pending) keeps the durable set and never invents one.
860
- if (env.pendingCourtTicketNumbers !== undefined) {
861
- await bindCourtTicketNumbersOnAdmitted(
862
- admitted,
863
- env.pendingCourtTicketNumbers,
864
- );
865
- }
866
- await runCountersignCourtDiaristStation(admitted, env, io);
867
- },
868
- }),
815
+ adapters: countersignAdapters(),
869
816
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
870
817
  });
871
818
  }
@@ -30,9 +30,7 @@ import {
30
30
  markRunAdmitted,
31
31
  type PublicResumeRequest,
32
32
  type RunWriterLease,
33
- type SameTicketSummonsMaterials,
34
33
  } from "./run-lifecycle.ts";
35
- import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
36
34
  import {
37
35
  presentStructuralRejection,
38
36
  trySettleDiaristTerminalResult,
@@ -128,39 +126,14 @@ export async function runPublicDiarist(
128
126
  throw error;
129
127
  }
130
128
 
131
- // #637 / #771 / ADR 0075 / #779: ticket identity is the LLM's typed assertion
132
- // on this turn, OR a typed handoff key already held by the caller (countersign
133
- // refresh). Code never pre-judges the summons text and never freezes a
134
- // candidate catalog. First summons without handoff stays unbound until assert.
129
+ // #637 / #771 / ADR 0075 / #779 / #987: ticket identity is the LLM's typed
130
+ // assertion on this turn, OR a typed handoff key already held by the caller
131
+ // (countersign refresh bind). Code never pre-judges the summons text and never
132
+ // freezes a candidate catalog. Public same-ticket resume-by-ticketNumber was
133
+ // deleted (#987 Result 7); callers continue an existing diarist run only via
134
+ // explicit `ak-role resume <runId>`. Handoff ticket remains a bind key only.
135
135
 
136
- const projectRoot = parsed.project ?? env.cwd;
137
136
  const handoffTicket = env.boundTicketNumber;
138
- if (
139
- typeof handoffTicket === "number" &&
140
- Number.isSafeInteger(handoffTicket) &&
141
- handoffTicket >= 1
142
- ) {
143
- const summons: SameTicketSummonsMaterials = {
144
- instruction: parsed.instruction,
145
- instructionEmpty: parsed.instruction.trim() === "",
146
- attachmentPaths: parsed.attachmentPaths,
147
- };
148
- const resumed = await tryResumeSameTicketSeatRun({
149
- home: env.home,
150
- projectRoot,
151
- role: "diarist",
152
- ticketNumber: handoffTicket,
153
- freshSummons: env.freshSummons,
154
- summons,
155
- resume: (runId, materials) =>
156
- runPublicDiaristResume(
157
- { runId, ...(materials === undefined ? {} : { summons: materials }) },
158
- env,
159
- io,
160
- ),
161
- });
162
- if (resumed !== undefined) return resumed;
163
- }
164
137
 
165
138
  let admitted: AdmittedDiaristInvocation;
166
139
  try {
@@ -219,7 +219,7 @@ function inspectorAdapters(options?: {
219
219
  /**
220
220
  * Resume a previously admitted Inspector run (#633 / #637); the session principal reopens.
221
221
  * Same-ticket summons deliver this turn's instruction + frozen attachments; manual
222
- * resume keeps package-envelope / caller-message semantics and birth attachments.
222
+ * resume forwards only caller-supplied message bytes.
223
223
  */
224
224
  export async function runPublicInspectorResume(
225
225
  request: PublicResumeRequest,
@@ -153,6 +153,11 @@ export type AdmittedCountersignInvocation = AdmittedRoleInvocationBase & {
153
153
  * countersign controlled failure with this text — never structural exit 2 or main-only.
154
154
  */
155
155
  courtTicketNumbersDamage?: string;
156
+ /**
157
+ * Parent run directory (#747 / #987 gate same-parent resume). Persisted on first
158
+ * mint when gate supplies parentRunPath; independent of ticket-number lookup.
159
+ */
160
+ readonly sourceRunPath?: string;
156
161
  };
157
162
 
158
163
  export type AdmittedGleanerLeftInvocation = AdmittedRoleInvocationBase & {
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Public Notary Role run: admit source-run locator → shared post-admission coordinator
3
- * → settle Terminal result (#448 / #517). Zero caller prompt/attachment. Lifecycle is
4
- * the shared post-admission seam; this module keeps only Notary adapters.
3
+ * → settle Terminal result (#448 / #517). Explicit new has zero caller
4
+ * prompt/attachment; explicit resume accepts the shared optional caller message.
5
+ * Lifecycle is the shared post-admission seam; this module keeps only Notary adapters.
5
6
  * #637 / #747: same-parent (--source-run) re-summons resume the seat's previous run.
6
7
  */
7
8
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
@@ -231,11 +232,6 @@ export async function runPublicNotaryResume(
231
232
  env,
232
233
  io,
233
234
  load: async (effective) => {
234
- if (effective.message !== undefined) {
235
- throw new CliUsageError(
236
- "notary rejects caller prompt/instruction; only zero caller-prompt continuation admitted",
237
- );
238
- }
239
235
  const loaded = await loadResumableNotaryRun(
240
236
  env.home,
241
237
  effective.runId,
@@ -1379,7 +1379,7 @@ const SUPPORT_COMMAND_HELP = {
1379
1379
  resume: {
1380
1380
  command: "resume",
1381
1381
  summary:
1382
- "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/符宝郎 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).",
1382
+ "Resume a role run under the live seat table (model/host/engine); session principal must still exist. Every callable seat, including Notary/符宝郎, 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).",
1383
1383
  usage: ["ak-role resume <runId> [message]"],
1384
1384
  examples: [
1385
1385
  "ak-role resume 01abc…",