@akagilnc/pi-workflow-roles 0.1.3565 → 0.1.3572

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 +4 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +755 -60
  4. package/dist/diarist-contracts.js +72 -0
  5. package/dist/package-contracts/terminating-tools.js +20 -2
  6. package/dist/packaged-role-registry.js +20 -0
  7. package/dist/public-cli/main.js +533 -634
  8. package/dist/public-cli/registry.js +4 -1
  9. package/extensions/role-runtime.ts +1 -0
  10. package/package.json +1 -1
  11. package/resources/diarist-collect.md +6 -10
  12. package/souls/diarist.md +9 -0
  13. package/src/acp-host/production-host.ts +1 -0
  14. package/src/acp-host/role-envelope.ts +2 -1
  15. package/src/diarist-contracts.ts +88 -0
  16. package/src/diarist-role.ts +60 -0
  17. package/src/diarist.ts +253 -179
  18. package/src/host-contracts.ts +6 -1
  19. package/src/package-contracts/terminating-tools.ts +22 -2
  20. package/src/packaged-role-registry.ts +20 -0
  21. package/src/pi/role-turn-host.ts +8 -0
  22. package/src/public-cli/cli.ts +29 -0
  23. package/src/public-cli/countersign-run.ts +9 -129
  24. package/src/public-cli/diarist-run.ts +312 -0
  25. package/src/public-cli/invocation.ts +23 -2
  26. package/src/public-cli/option-definitions.ts +18 -0
  27. package/src/public-cli/post-admission.ts +2 -2
  28. package/src/public-cli/registry.ts +3 -0
  29. package/src/public-cli/run-lifecycle.ts +31 -3
  30. package/src/public-cli/settlement.ts +50 -2
  31. package/src/public-cli/terminal.ts +2 -1
  32. package/src/role-runtime.ts +117 -6
  33. package/src/ticket-provenance-contracts.ts +1 -0
  34. package/src/ticket-provenance.ts +0 -23
  35. package/src/diarist-llm-collector.ts +0 -324
@@ -324,8 +324,8 @@ export async function dispatchPostAdmissionTurn<
324
324
 
325
325
  await markRunRunning(admitted.runDirectory, env.model, effectiveEngine, env.host);
326
326
  await clearTypedProviderHttpObservation(admitted.runDirectory);
327
- // beforeDispatch (e.g. countersign diarist station) runs after running is
328
- // marked — its failures must settle the run, not leave it permanently running.
327
+ // beforeDispatch (seat-owned pre-turn work) runs after running is marked —
328
+ // its failures must settle the run, not leave it permanently running.
329
329
  if (adapters.beforeDispatch !== undefined) {
330
330
  try {
331
331
  await adapters.beforeDispatch(admitted);
@@ -106,6 +106,9 @@ const STARTUP_CANDIDATES: Record<PublicConfigurableSeat, readonly ModelRef[]> =
106
106
  { provider: "openai-codex", model: "gpt-5.6-luna", thinking: "medium" },
107
107
  { provider: "xai", model: "grok-4.5", thinking: "high" },
108
108
  ],
109
+ // #708 `diarist-seat-default`: owner-set initial value; changed in the seat
110
+ // table like any other seat. No engine axis, package-default host.
111
+ diarist: [{ provider: "xai", model: "grok-4.5", thinking: "medium" }],
109
112
  };
110
113
 
111
114
  export function publicStartupCandidates(
@@ -44,6 +44,7 @@ import {
44
44
  type AdmittedCoderInvocation,
45
45
  type AdmittedCountersignInvocation,
46
46
  type AdmittedCollectorInvocation,
47
+ type AdmittedDiaristInvocation,
47
48
  type AdmittedDoctorInvocation,
48
49
  type AdmittedInspectorInvocation,
49
50
  type AdmittedNotaryInvocation,
@@ -95,7 +96,8 @@ export type RoleRunRecord = {
95
96
  | "gleaner-left"
96
97
  | "inspector"
97
98
  | "gatekeeper"
98
- | "navigator";
99
+ | "navigator"
100
+ | "diarist";
99
101
  readonly state: RoleRunState;
100
102
  readonly bookKey: string;
101
103
  readonly projectRoot: string;
@@ -348,7 +350,8 @@ async function readRoleRunStateDisk(
348
350
  record.role !== "gleaner-left" &&
349
351
  record.role !== "inspector" &&
350
352
  record.role !== "gatekeeper" &&
351
- record.role !== "navigator"
353
+ record.role !== "navigator" &&
354
+ record.role !== "diarist"
352
355
  ) {
353
356
  return undefined;
354
357
  }
@@ -1444,6 +1447,12 @@ export type LoadedResumableGleanerLeftRun = {
1444
1447
  readonly observation?: TypedHttp429Observation;
1445
1448
  };
1446
1449
 
1450
+ export type LoadedResumableDiaristRun = {
1451
+ readonly admitted: AdmittedDiaristInvocation;
1452
+ readonly run: RoleRunRecord;
1453
+ readonly observation?: TypedHttp429Observation;
1454
+ };
1455
+
1447
1456
  export type LoadedResumableInstructionSeatRun = {
1448
1457
  readonly admitted: AdmittedGatekeeperInvocation | AdmittedNavigatorInvocation;
1449
1458
  readonly run: RoleRunRecord;
@@ -1637,7 +1646,7 @@ export async function loadResumableReviewerRun(
1637
1646
 
1638
1647
  /**
1639
1648
  * Load a resumable Countersign run for resume (#599). Ticket binding and
1640
- * attachments restore from the admitted request; diarist does not re-run.
1649
+ * attachments restore from the admitted request.
1641
1650
  */
1642
1651
  export async function loadResumableCountersignRun(
1643
1652
  home: string,
@@ -1704,6 +1713,24 @@ export async function loadResumableGleanerLeftRun(
1704
1713
  return seatLoadedResult(loaded, admitted);
1705
1714
  }
1706
1715
 
1716
+ export async function loadResumableDiaristRun(
1717
+ home: string,
1718
+ runId: string,
1719
+ authority: DurablePrincipalAuthority,
1720
+ ): Promise<LoadedResumableDiaristRun> {
1721
+ const loaded = await loadResumableRunRecord(home, runId, authority);
1722
+ if (loaded.run.role !== "diarist") {
1723
+ throw new CliUsageError(
1724
+ `role run ${runId} belongs to ${loaded.run.role}, not diarist`,
1725
+ );
1726
+ }
1727
+ const admitted: AdmittedDiaristInvocation = {
1728
+ role: "diarist",
1729
+ ...resumedBaseAdmitted(loaded),
1730
+ };
1731
+ return seatLoadedResult(loaded, admitted);
1732
+ }
1733
+
1707
1734
  export type LoadedResumableMergerRun = {
1708
1735
  readonly admitted: AdmittedMergerInvocation;
1709
1736
  readonly run: RoleRunRecord;
@@ -1929,6 +1956,7 @@ export async function peekRoleRunRole(
1929
1956
  | "inspector"
1930
1957
  | "gatekeeper"
1931
1958
  | "navigator"
1959
+ | "diarist"
1932
1960
  | undefined
1933
1961
  > {
1934
1962
  const runDirectory = await findRunDirectoryById(home, runId);
@@ -98,6 +98,11 @@ import {
98
98
  gleanerLeftDecisiveFacts,
99
99
  validateRecordedGleanerLeftOutput,
100
100
  } from "../gleaner-left-contracts.ts";
101
+ import {
102
+ DIARIST_OUTPUT_TOOL_NAME,
103
+ diaristDecisiveFacts,
104
+ validateRecordedDiaristOutput,
105
+ } from "../diarist-contracts.ts";
101
106
  import {
102
107
  INSPECTOR_OUTPUT_TOOL_NAME,
103
108
  inspectorDecisiveFacts,
@@ -145,6 +150,7 @@ import {
145
150
  type AdmittedJudgeInvocation,
146
151
  type AdmittedMergerInvocation,
147
152
  type AdmittedCountersignInvocation,
153
+ type AdmittedDiaristInvocation,
148
154
  type AdmittedGleanerLeftInvocation,
149
155
  type AdmittedInspectorInvocation,
150
156
  type AdmittedGatekeeperInvocation,
@@ -3360,7 +3366,8 @@ type SeatAcceptedSettlementSpec = {
3360
3366
  | "gleaner-left"
3361
3367
  | "inspector"
3362
3368
  | "gatekeeper"
3363
- | "navigator";
3369
+ | "navigator"
3370
+ | "diarist";
3364
3371
  readonly toolName: string;
3365
3372
  readonly nonUsableDiagnostic: string;
3366
3373
  readonly projectAccepted: (
@@ -3387,7 +3394,8 @@ async function settleLawfulSeatAcceptedTerminalResult(
3387
3394
  | AdmittedGleanerLeftInvocation
3388
3395
  | AdmittedInspectorInvocation
3389
3396
  | AdmittedGatekeeperInvocation
3390
- | AdmittedNavigatorInvocation,
3397
+ | AdmittedNavigatorInvocation
3398
+ | AdmittedDiaristInvocation,
3391
3399
  authority: DurablePrincipalAuthority,
3392
3400
  spec: SeatAcceptedSettlementSpec,
3393
3401
  scope?: SettlementCourtScope,
@@ -3651,6 +3659,46 @@ export async function trySettleGleanerLeftTerminalResult(
3651
3659
  return settleLawfulGleanerLeftTerminalResult(admitted, authority, scope);
3652
3660
  }
3653
3661
 
3662
+ /** Lawful Diarist accepted outcome (completed 入录选择, #708). */
3663
+ export type LawfulDiaristRoleOutcome = {
3664
+ kind: "accepted";
3665
+ role: "diarist";
3666
+ status: string;
3667
+ decisiveFacts: Readonly<Record<string, unknown>>;
3668
+ };
3669
+
3670
+ async function settleLawfulDiaristTerminalResult(
3671
+ admitted: AdmittedDiaristInvocation,
3672
+ authority: DurablePrincipalAuthority,
3673
+ scope?: SettlementCourtScope,
3674
+ ): Promise<TerminalResult | undefined> {
3675
+ return settleLawfulSeatAcceptedTerminalResult(admitted, authority, {
3676
+ role: "diarist",
3677
+ toolName: DIARIST_OUTPUT_TOOL_NAME,
3678
+ nonUsableDiagnostic: "起居郎回执无显式 completed",
3679
+ tryAcceptDetails: tryAcceptWithValidator(validateRecordedDiaristOutput),
3680
+ projectAccepted: (sealed) => {
3681
+ const output = validateRecordedDiaristOutput(sealed.decisiveFacts);
3682
+ const accepted: LawfulDiaristRoleOutcome = {
3683
+ kind: "accepted",
3684
+ role: "diarist",
3685
+ status: sealed.status,
3686
+ decisiveFacts: diaristDecisiveFacts(output),
3687
+ };
3688
+ return accepted;
3689
+ },
3690
+ }, scope);
3691
+ }
3692
+
3693
+ /** Try to settle a lawful Diarist Terminal; undefined only for genuine absence. */
3694
+ export async function trySettleDiaristTerminalResult(
3695
+ admitted: AdmittedDiaristInvocation,
3696
+ authority: DurablePrincipalAuthority,
3697
+ scope?: SettlementCourtScope,
3698
+ ): Promise<TerminalResult | undefined> {
3699
+ return settleLawfulDiaristTerminalResult(admitted, authority, scope);
3700
+ }
3701
+
3654
3702
  /** Lawful Inspector accepted outcome (pass/bounce/escalate). */
3655
3703
  export type LawfulInspectorRoleOutcome = {
3656
3704
  kind: "accepted";
@@ -40,7 +40,8 @@ export type TerminalRoleName =
40
40
  | "gleaner-left"
41
41
  | "inspector"
42
42
  | "gatekeeper"
43
- | "navigator";
43
+ | "navigator"
44
+ | "diarist";
44
45
 
45
46
  /** Merger/Collector residual only — Notary/audit residual abolished (#475). */
46
47
  export type ResidualIncompleteTerminalOutcome = {
@@ -70,6 +70,20 @@ import {
70
70
  type InspectorRuntimeDependencies,
71
71
  } from "./inspector-role.ts";
72
72
  import { INSPECTOR_ACCEPTED_TEXT } from "./inspector-contracts.ts";
73
+ import {
74
+ DIARIST_TOOL_SPEC,
75
+ type DiaristRuntimeDependencies,
76
+ } from "./diarist-role.ts";
77
+ import {
78
+ DIARIST_ACCEPTED_TEXT,
79
+ DIARIST_SOURCES_FLAG,
80
+ projectDiaristSelections,
81
+ } from "./diarist-contracts.ts";
82
+ import {
83
+ commitDiaristSelections,
84
+ loadDiaristSourceCatalog,
85
+ type DiaristSourceCatalog,
86
+ } from "./diarist.ts";
73
87
  import {
74
88
  GATEKEEPER_TOOL_SPEC,
75
89
  type GatekeeperRuntimeDependencies,
@@ -159,6 +173,14 @@ const GLEANER_LEFT_TRANSPORT_FLAGS = Object.freeze([
159
173
  }),
160
174
  ] as const);
161
175
 
176
+ /** Diarist private transport: frozen source catalog path (ADR 0018 / #708). */
177
+ const DIARIST_TRANSPORT_FLAGS = Object.freeze([
178
+ Object.freeze({
179
+ name: DIARIST_SOURCES_FLAG.name,
180
+ definition: DIARIST_SOURCES_FLAG.definition,
181
+ }),
182
+ ] as const);
183
+
162
184
  /**
163
185
  * Decode private transport flags into frozen admitted inputs.
164
186
  * Envelope-owned; necessary JSON decode only (public --authority-ref owns grammar).
@@ -439,6 +461,7 @@ type ActivationRuntime = {
439
461
  inspector: { activate(): Promise<void> };
440
462
  gatekeeper: { activate(): Promise<void> };
441
463
  navigator: { activate(): Promise<void> };
464
+ diarist: { activate(): Promise<void> };
442
465
  merger(): Promise<void>;
443
466
  };
444
467
 
@@ -487,6 +510,7 @@ function activationStage(role: PackagedRole, runtime: ActivationRuntime): { id:
487
510
  case "inspector": return { id: "load-and-install", run: async () => runtime.inspector.activate() };
488
511
  case "gatekeeper": return { id: "load-and-install", run: async () => runtime.gatekeeper.activate() };
489
512
  case "navigator": return { id: "load-and-install", run: async () => runtime.navigator.activate() };
513
+ case "diarist": return { id: "load-and-install", run: async () => runtime.diarist.activate() };
490
514
  case "merger": return { id: "prepare-git-and-install", run: async () => runtime.merger() };
491
515
  }
492
516
  }
@@ -594,6 +618,7 @@ export type RoleRuntimeDependencies = {
594
618
  loadInspectorSoul?(): Promise<string>;
595
619
  loadGatekeeperSoul?(): Promise<string>;
596
620
  loadNavigatorSoul?(): Promise<string>;
621
+ loadDiaristSoul?(): Promise<string>;
597
622
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
598
623
  loadMergerSoul?(): Promise<string>;
599
624
  loadMergerInput?(path: string): Promise<unknown>;
@@ -707,13 +732,17 @@ export async function projectClosedSubmissionLifecycle(
707
732
  await settle(publicNavigatorSettlement(projection.role, phase, closure));
708
733
  }
709
734
 
710
- /** Optional pre-accept hook on the shared filed-officer envelope (ADR 0075). */
735
+ /**
736
+ * Optional pre-accept hook on the shared filed-officer envelope (ADR 0075).
737
+ * May return a details projection (envelope-owned machine facts recorded next to
738
+ * the submitted parameters); undefined keeps the parameters as submitted.
739
+ */
711
740
  type FiledOfficerBeforeAccept = (input: {
712
741
  readonly toolCallId: string;
713
742
  readonly parameters: unknown;
714
743
  readonly signal: AbortSignal | undefined;
715
744
  readonly ctx: HostContext;
716
- }) => Promise<void>;
745
+ }) => Promise<unknown>;
717
746
 
718
747
  /**
719
748
  * Shared registration envelope for filed officers (ADR 0018 / #572):
@@ -749,14 +778,15 @@ function createFiledOfficerRuntime(
749
778
  parameters: spec.tool.parameters as never,
750
779
  async execute(toolCallId, parameters, signal, _onUpdate, ctx): Promise<HostToolResult<unknown>> {
751
780
  if (soul === undefined) throw new Error(`${spec.role} 职分未装载`);
752
- if (spec.beforeAccept !== undefined) {
753
- await spec.beforeAccept({ toolCallId, parameters, signal, ctx });
754
- }
781
+ const projected =
782
+ spec.beforeAccept === undefined
783
+ ? undefined
784
+ : await spec.beforeAccept({ toolCallId, parameters, signal, ctx });
755
785
  // Accept-as-is + terminate only. Shape is not an admission gate
756
786
  // (第 0 条 / ADR 0055); sole-final barrier is ledger-owned (#575).
757
787
  return {
758
788
  content: [{ type: "text" as const, text: spec.acceptedText }],
759
- details: parameters,
789
+ details: projected === undefined ? parameters : projected,
760
790
  terminate: true as const,
761
791
  };
762
792
  },
@@ -849,6 +879,73 @@ export function createNavigatorRoleRuntime(
849
879
  );
850
880
  }
851
881
 
882
+ /**
883
+ * #708: 起居郎 public seat on the shared filed-officer envelope.
884
+ * Semantic collection happened in this role's own turn; the accept hook runs the
885
+ * mechanical band (verbatim reverse-verify → idempotent sitian append →
886
+ * watermark) and records the resulting sitian facts next to the receipt.
887
+ * Machine facts never come from model self-report (锚定宪法).
888
+ */
889
+ export function createDiaristRoleRuntime(
890
+ roleHost: RoleHost,
891
+ dependencies: DiaristRuntimeDependencies,
892
+ getSourcesFlag: () => unknown,
893
+ ) {
894
+ // This turn's catalog, snapshotted at activate: the candidateIndexes the role
895
+ // saw and the rows accept commits are the same bytes. Accept never re-reads
896
+ // the file, so anything written to it mid-turn cannot become a diary entry.
897
+ let catalog: DiaristSourceCatalog | undefined;
898
+ const runtime = createFiledOfficerRuntime(
899
+ roleHost,
900
+ {
901
+ role: "diarist",
902
+ tool: DIARIST_TOOL_SPEC,
903
+ acceptedText: DIARIST_ACCEPTED_TEXT,
904
+ soulTag: "diarist",
905
+ beforeAccept: async ({ parameters }) => {
906
+ const submitted =
907
+ parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
908
+ ? (parameters as Record<string, unknown>)
909
+ : undefined;
910
+ // True-unbound summons: no ticket, no catalog, no diary committed — so
911
+ // this turn produced no sitian facts. A self-reported `sitian` is not
912
+ // one (锚定宪法); drop it rather than let it ride into decisiveFacts.
913
+ // Carrying the field is not a reason to bounce the receipt (第 0 条).
914
+ if (catalog === undefined) {
915
+ if (submitted === undefined || !("sitian" in submitted)) return undefined;
916
+ const stripped = { ...submitted };
917
+ delete stripped.sitian;
918
+ return stripped;
919
+ }
920
+ const facts = await commitDiaristSelections({
921
+ catalog,
922
+ selections: projectDiaristSelections(parameters),
923
+ });
924
+ return { ...(submitted ?? { receipt: parameters }), sitian: facts };
925
+ },
926
+ },
927
+ dependencies,
928
+ );
929
+ return {
930
+ async activate() {
931
+ catalog = loadFrozenDiaristCatalog(getSourcesFlag);
932
+ return runtime.activate();
933
+ },
934
+ };
935
+ }
936
+
937
+ /** Envelope-owned decode + one-shot load of the frozen catalog (ADR 0018). */
938
+ function loadFrozenDiaristCatalog(
939
+ getFlag: () => unknown,
940
+ ): DiaristSourceCatalog | undefined {
941
+ const raw = getFlag();
942
+ if (raw === undefined) return undefined;
943
+ if (typeof raw !== "string" || raw.trim() === "") {
944
+ throw new Error("Diarist source catalog flag must be a nonempty path");
945
+ }
946
+ return loadDiaristSourceCatalog(raw);
947
+ }
948
+
852
949
  export function createCountersignRoleRuntime(
853
950
  roleHost: RoleHost,
854
951
  dependencies: CountersignRuntimeDependencies,
@@ -905,6 +1002,9 @@ export function createRoleRuntimeExtension(
905
1002
  for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
906
1003
  roleHost.registerFlag(flag.name, flag.definition);
907
1004
  }
1005
+ for (const flag of DIARIST_TRANSPORT_FLAGS) {
1006
+ roleHost.registerFlag(flag.name, flag.definition);
1007
+ }
908
1008
 
909
1009
  let admitted = false;
910
1010
  let selectedRole: string | undefined;
@@ -1358,6 +1458,16 @@ export function createRoleRuntimeExtension(
1358
1458
  return dependencies.loadNavigatorSoul();
1359
1459
  },
1360
1460
  });
1461
+ const diarist = createDiaristRoleRuntime(
1462
+ roleHost,
1463
+ {
1464
+ async loadSoul() {
1465
+ if (!dependencies.loadDiaristSoul) throw new Error("Diarist runtime dependencies are not configured");
1466
+ return dependencies.loadDiaristSoul();
1467
+ },
1468
+ },
1469
+ () => envelopeHost.host.getFlag(DIARIST_SOURCES_FLAG.name),
1470
+ );
1361
1471
  let sessionMergerGitState = dependencies.mergerGitState;
1362
1472
  const merger = createMergerRoleRuntime(roleHost, {
1363
1473
  async loadSoul() { if (!dependencies.loadMergerSoul) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerSoul(); },
@@ -1524,6 +1634,7 @@ export function createRoleRuntimeExtension(
1524
1634
  inspector,
1525
1635
  gatekeeper,
1526
1636
  navigator,
1637
+ diarist,
1527
1638
  merger: async () => {
1528
1639
  if (dependencies.mergerGitState === undefined) {
1529
1640
  sessionMergerGitState = dependencies.createMergerGitState?.(ctx.cwd);
@@ -66,6 +66,7 @@ export type TicketProvenanceEntry = {
66
66
  * Separated by recordClass discriminator — never disguised as a source entry.
67
67
  */
68
68
  export type TicketProvenanceDiagnosticKind =
69
+ /** Historical rows only — the diarist turn's own failure settles the run (#708). */
69
70
  | "collector-failed"
70
71
  | "issue-source-failed"
71
72
  | "quote-verify-failed";
@@ -408,29 +408,6 @@ export function appendTicketProvenanceDiagnostic(input: {
408
408
  });
409
409
  }
410
410
 
411
- /** Collector/engine failure true cause — typed diagnostic, not a diary entry. */
412
- export function appendCollectorFailureDiagnostic(input: {
413
- readonly ticketNumber: number;
414
- readonly cwd: string;
415
- readonly home?: string;
416
- readonly collectorError: string;
417
- readonly recordedAt?: string;
418
- }): RecordPointer {
419
- const recordedAt = input.recordedAt ?? new Date().toISOString();
420
- return appendTicketProvenanceDiagnostic({
421
- ticketNumber: input.ticketNumber,
422
- cwd: input.cwd,
423
- ...(input.home === undefined ? {} : { home: input.home }),
424
- source: "diarist-collector-fail",
425
- diagnostic: {
426
- recordClass: TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC,
427
- diagnosticKind: "collector-failed",
428
- cause: input.collectorError,
429
- recordedAt,
430
- },
431
- });
432
- }
433
-
434
411
  /** Issue-face source acquisition failure — typed diagnostic on the ticket volume. */
435
412
  export function appendIssueSourceFailureDiagnostic(input: {
436
413
  readonly ticketNumber: number;