@akagilnc/pi-workflow-roles 0.1.3708 → 0.1.3716

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 (37) hide show
  1. package/README.md +4 -0
  2. package/README.zh-CN.md +4 -0
  3. package/dist/acp-host/production-host.js +262 -518
  4. package/dist/adr-path-refs.js +1 -1
  5. package/dist/diarist-contracts.js +31 -21
  6. package/dist/diarist-role.js +17 -8
  7. package/dist/diarist.js +39 -388
  8. package/dist/package-contracts/terminating-tools.js +1 -1
  9. package/dist/packaged-role-registry.js +4 -8
  10. package/dist/pi/role-turn-host.js +1 -7
  11. package/dist/public-cli/config.js +106 -2
  12. package/dist/public-cli/main.js +263 -1488
  13. package/dist/public-cli/option-definitions.js +3 -0
  14. package/dist/role-runtime.js +60 -88
  15. package/dist/ticket-provenance-contracts.js +0 -5
  16. package/dist/ticket-provenance.js +2 -149
  17. package/package.json +1 -1
  18. package/resources/diarist-collect.md +21 -11
  19. package/src/acp-host/role-envelope.ts +1 -1
  20. package/src/adr-path-refs.ts +1 -1
  21. package/src/diarist-contracts.ts +48 -32
  22. package/src/diarist-role.ts +31 -16
  23. package/src/diarist.ts +54 -635
  24. package/src/host-contracts.ts +1 -5
  25. package/src/package-contracts/terminating-tools.ts +1 -1
  26. package/src/packaged-role-registry.ts +4 -8
  27. package/src/pi/role-turn-host.ts +1 -7
  28. package/src/public-cli/cli.ts +54 -0
  29. package/src/public-cli/config.ts +157 -1
  30. package/src/public-cli/countersign-run.ts +2 -3
  31. package/src/public-cli/diarist-run.ts +15 -117
  32. package/src/public-cli/option-definitions.ts +3 -0
  33. package/src/role-runtime.ts +73 -105
  34. package/src/ticket-provenance-contracts.ts +6 -12
  35. package/src/ticket-provenance.ts +1 -207
  36. package/dist/diarist-mechanical.js +0 -411
  37. package/src/diarist-mechanical.ts +0 -571
@@ -127,11 +127,7 @@ export type RoleTurnActivation =
127
127
  | { readonly role: "gatekeeper" }
128
128
  | { readonly role: "navigator" }
129
129
  | { readonly role: "auditor" }
130
- | {
131
- readonly role: "diarist";
132
- /** Frozen source-catalog path; absent for a true-unbound summons (#708). */
133
- readonly sourcesPath?: string;
134
- };
130
+ | { readonly role: "diarist" };
135
131
 
136
132
  export type RoleTurnContinuation =
137
133
  | { readonly kind: "initial"; readonly prompt: string }
@@ -303,7 +303,7 @@ export function validateAcceptedLifecycle(
303
303
  if (toolName === DIARIST_OUTPUT_TOOL_NAME) {
304
304
  // Envelope-owned mechanical sitian facts are runtime-bound on details only
305
305
  // (same shape as the Doctor runtime cost); the submitted arguments carry the
306
- // role's own selections. Machine facts never come from model self-report.
306
+ // role's own entries. Machine facts never come from model self-report.
307
307
  const { sitian: _mechanical, ...submitted } = details as DiaristOutput & { sitian?: unknown };
308
308
  const testimony = validateAcceptedDetails(toolName, argumentsValue);
309
309
  if (!deepEqual(testimony, submitted)) throw new Error("accepted tool lifecycle details mismatch");
@@ -12,10 +12,7 @@ import { COUNTERSIGN_OUTPUT_TOOL_NAME } from "./countersign-contracts.ts";
12
12
  import { GLEANER_LEFT_OUTPUT_TOOL_NAME } from "./gleaner-left-contracts.ts";
13
13
  import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
14
14
  import { AUDITOR_OUTPUT_TOOL_NAME } from "./package-contracts/auditor-output.ts";
15
- import {
16
- DIARIST_OUTPUT_TOOL_NAME,
17
- DIARIST_SOURCES_FLAG,
18
- } from "./diarist-contracts.ts";
15
+ import { DIARIST_OUTPUT_TOOL_NAME } from "./diarist-contracts.ts";
19
16
 
20
17
  /** Shared by public notary and gatekeeper-province notary. */
21
18
  export const NOTARY_SESSION_MATERIALS = [
@@ -214,14 +211,13 @@ export const PUBLIC_ROLE_RECORDS = [
214
211
  activationStage: "load-and-install",
215
212
  sessionMaterials: AUDITOR_PUBLIC_SESSION_MATERIALS,
216
213
  },
217
- // #708 / ADR 0075 `diarist-is-role`: 起居郎 is a seat like any other. The
218
- // frozen source catalog rides the shared input-flag seam; it is absent for a
219
- // true-unbound summons (no ticket → no diary).
214
+ // #708 / ADR 0075 `diarist-is-role` / #779: 起居郎 is a seat like any other.
215
+ // No input flag — LLM finds materials itself (caller-transparent).
220
216
  {
221
217
  role: "diarist",
222
218
  phases: [null],
223
219
  outputTool: DIARIST_OUTPUT_TOOL_NAME,
224
- inputFlag: DIARIST_SOURCES_FLAG.name,
220
+ inputFlag: undefined,
225
221
  phaseFlag: undefined,
226
222
  activationStage: "load-and-install",
227
223
  sessionMaterials: [
@@ -149,13 +149,7 @@ function buildActivationFlagArgs(activation: RoleTurnActivation): string[] {
149
149
  case "auditor":
150
150
  return ["--ak-role", "auditor"];
151
151
  case "diarist":
152
- return [
153
- "--ak-role",
154
- "diarist",
155
- ...(activation.sourcesPath === undefined
156
- ? []
157
- : ["--ak-diarist-sources", activation.sourcesPath]),
158
- ];
152
+ return ["--ak-role", "diarist"];
159
153
  default: {
160
154
  const _exhaustive: never = activation;
161
155
  return _exhaustive;
@@ -28,6 +28,8 @@ import {
28
28
  setPersistentSeatConfig,
29
29
  setPersistentSeatEngine,
30
30
  setPersistentSeatHost,
31
+ setProviderHostAlias,
32
+ unsetProviderHostAlias,
31
33
  validatePublicCliConfigAxes,
32
34
  type CredentialProviders,
33
35
  type EffectiveSeat,
@@ -769,6 +771,7 @@ function renderHelp(): string {
769
771
  "Persistent config: ak-role config set <seat> <provider/model[:thinking]> | unset <gatekeeper|inspector|notary>",
770
772
  "Persistent engine (callable roles): ak-role config set-engine <seat> <name> | unset-engine <seat>",
771
773
  "Persistent host (callable roles): ak-role config set-host <seat> <name> | unset-host <seat>",
774
+ "Provider host alias: ak-role config set-provider-alias <provider> <host> <alias> | unset-provider-alias <provider> <host>",
772
775
  "Host resolution: --host → persistent seat host → pi (resume uses the same order; #617)",
773
776
  "Effective seats: ak-role roles",
774
777
  );
@@ -909,6 +912,16 @@ function renderConfig(config: PublicCliConfig): string {
909
912
  }
910
913
  // #422: show the effective auto-resume ceiling (configured value or default).
911
914
  lines.push(`autoResumeLimit\t${config.autoResumeLimit ?? AUTO_RESUME_LIMIT}`);
915
+ // #778: owner-registered provider→host aliases (disk face; seats stay as written).
916
+ const aliases = config.providerAliases;
917
+ if (aliases !== undefined) {
918
+ for (const provider of Object.keys(aliases).sort()) {
919
+ const byHost = aliases[provider]!;
920
+ for (const host of Object.keys(byHost).sort()) {
921
+ lines.push(`providerAlias\t${provider}\t${host}\t${byHost[host]}`);
922
+ }
923
+ }
924
+ }
912
925
  return `${lines.join("\n")}\n`;
913
926
  }
914
927
 
@@ -1081,6 +1094,47 @@ async function runConfigCommand(
1081
1094
  return 0;
1082
1095
  }
1083
1096
 
1097
+ // #778: owner-registered provider→host alias (opaque strings; no host catalog).
1098
+ if (args[0] === "set-provider-alias") {
1099
+ if (args.length !== 4) {
1100
+ throw new CliUsageError(
1101
+ "usage: ak-role config set-provider-alias <provider> <host> <alias>",
1102
+ );
1103
+ }
1104
+ let config = await loadAndValidateConfig(home, packageRoot);
1105
+ try {
1106
+ config = setProviderHostAlias(config, args[1]!, args[2]!, args[3]!);
1107
+ } catch (error) {
1108
+ throw new CliUsageError(
1109
+ error instanceof Error ? error.message : String(error),
1110
+ { cause: error },
1111
+ );
1112
+ }
1113
+ await savePublicCliConfig(config, home);
1114
+ io.stdout(renderConfig(config));
1115
+ return 0;
1116
+ }
1117
+
1118
+ if (args[0] === "unset-provider-alias") {
1119
+ if (args.length !== 3) {
1120
+ throw new CliUsageError(
1121
+ "usage: ak-role config unset-provider-alias <provider> <host>",
1122
+ );
1123
+ }
1124
+ let config = await loadAndValidateConfig(home, packageRoot);
1125
+ try {
1126
+ config = unsetProviderHostAlias(config, args[1]!, args[2]!);
1127
+ } catch (error) {
1128
+ throw new CliUsageError(
1129
+ error instanceof Error ? error.message : String(error),
1130
+ { cause: error },
1131
+ );
1132
+ }
1133
+ await savePublicCliConfig(config, home);
1134
+ io.stdout(renderConfig(config));
1135
+ return 0;
1136
+ }
1137
+
1084
1138
  throw new CliUsageError(`unknown config subcommand: ${args[0]}`);
1085
1139
  }
1086
1140
 
@@ -54,6 +54,15 @@ export type PersistentSeatConfig = {
54
54
  host?: string;
55
55
  };
56
56
 
57
+ /**
58
+ * #778: owner-registered provider name by host. Keyed seat-table provider →
59
+ * host → host-facing provider. Package code carries the map only — no built-in
60
+ * pairs. Unregistered (provider, host) pairs pass the seat provider through.
61
+ */
62
+ export type ProviderHostAliases = Readonly<
63
+ Record<string, Readonly<Record<string, string>>>
64
+ >;
65
+
57
66
  export type PublicCliConfig = {
58
67
  seats: Partial<Record<PublicConfigurableSeat, PersistentSeatConfig>>;
59
68
  /**
@@ -63,6 +72,11 @@ export type PublicCliConfig = {
63
72
  * bound (ADR 0035).
64
73
  */
65
74
  autoResumeLimit?: number;
75
+ /**
76
+ * #778: owner-registered provider→host aliases. Sibling of `seats`; never
77
+ * invents pairs. Absent or empty means pure pass-through.
78
+ */
79
+ providerAliases?: ProviderHostAliases;
66
80
  /**
67
81
  * #592: opaque seat rows this build does not own. Carried through every
68
82
  * parse→save cycle so a write never silently erases neighboring-line rows
@@ -293,6 +307,81 @@ export function setAutoResumeLimit(
293
307
  return { ...config, autoResumeLimit: parseAutoResumeLimit(limit) };
294
308
  }
295
309
 
310
+ function requireNonEmptyToken(value: string, label: string): string {
311
+ const trimmed = value.trim();
312
+ if (trimmed === "") {
313
+ throw new Error(`${label} must be a non-empty string`);
314
+ }
315
+ return trimmed;
316
+ }
317
+
318
+ /**
319
+ * #778: register one owner provider→host alias. Opaque strings only — no host
320
+ * catalog check and no built-in provider pairs. Sibling keys (seats included)
321
+ * are preserved.
322
+ */
323
+ export function setProviderHostAlias(
324
+ config: PublicCliConfig,
325
+ provider: string,
326
+ host: string,
327
+ alias: string,
328
+ ): PublicCliConfig {
329
+ const from = requireNonEmptyToken(provider, "provider");
330
+ const hostName = requireNonEmptyToken(host, "host");
331
+ const to = requireNonEmptyToken(alias, "alias");
332
+ const previous = config.providerAliases ?? {};
333
+ return {
334
+ ...config,
335
+ providerAliases: {
336
+ ...previous,
337
+ [from]: {
338
+ ...(previous[from] ?? {}),
339
+ [hostName]: to,
340
+ },
341
+ },
342
+ };
343
+ }
344
+
345
+ /** #778: drop one owner provider→host alias. Empty map collapses to absent. */
346
+ export function unsetProviderHostAlias(
347
+ config: PublicCliConfig,
348
+ provider: string,
349
+ host: string,
350
+ ): PublicCliConfig {
351
+ const from = requireNonEmptyToken(provider, "provider");
352
+ const hostName = requireNonEmptyToken(host, "host");
353
+ const previous = config.providerAliases;
354
+ if (previous === undefined || previous[from] === undefined) return config;
355
+ const { [hostName]: _dropped, ...restHosts } = previous[from]!;
356
+ const nextForProvider =
357
+ Object.keys(restHosts).length === 0 ? undefined : restHosts;
358
+ const { [from]: _provider, ...restProviders } = previous;
359
+ const nextAliases =
360
+ nextForProvider === undefined
361
+ ? restProviders
362
+ : { ...restProviders, [from]: nextForProvider };
363
+ if (Object.keys(nextAliases).length === 0) {
364
+ const { providerAliases: _gone, ...rest } = config;
365
+ return rest;
366
+ }
367
+ return { ...config, providerAliases: nextAliases };
368
+ }
369
+
370
+ /**
371
+ * #778: project seat-table provider through owner aliases for one host.
372
+ * Unregistered pairs return the seat selection unchanged (pass-through).
373
+ */
374
+ export function applyProviderHostAlias(
375
+ selection: SeatModelConfig | undefined,
376
+ host: string,
377
+ aliases: ProviderHostAliases | undefined,
378
+ ): SeatModelConfig | undefined {
379
+ if (selection === undefined || aliases === undefined) return selection;
380
+ const mapped = aliases[selection.provider]?.[host];
381
+ if (mapped === undefined) return selection;
382
+ return { ...selection, provider: mapped };
383
+ }
384
+
296
385
  /**
297
386
  * Config-parse seam: persistent call axes belong to PUBLIC_CALLABLE_ROLES;
298
387
  * engine names need only path-safety syntax (no closed material catalog;
@@ -387,6 +476,7 @@ export function buildSeatModelCliArgs(model: SeatModelConfig | undefined): strin
387
476
  function serializePublicCliConfig(config: PublicCliConfig): {
388
477
  seats: Record<string, unknown>;
389
478
  autoResumeLimit?: number;
479
+ providerAliases?: ProviderHostAliases;
390
480
  } {
391
481
  return {
392
482
  // Known seats win on any key clash; by construction the two maps are
@@ -398,9 +488,48 @@ function serializePublicCliConfig(config: PublicCliConfig): {
398
488
  ...(config.autoResumeLimit === undefined
399
489
  ? {}
400
490
  : { autoResumeLimit: config.autoResumeLimit }),
491
+ ...(config.providerAliases === undefined ||
492
+ Object.keys(config.providerAliases).length === 0
493
+ ? {}
494
+ : { providerAliases: config.providerAliases }),
401
495
  };
402
496
  }
403
497
 
498
+ function parseProviderHostAliases(value: unknown): ProviderHostAliases {
499
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
500
+ throw new Error("public CLI config.providerAliases must be an object");
501
+ }
502
+ const out: Record<string, Record<string, string>> = {};
503
+ for (const [provider, byHost] of Object.entries(value as Record<string, unknown>)) {
504
+ if (provider.trim() === "") {
505
+ throw new Error("public CLI config.providerAliases provider key must be non-empty");
506
+ }
507
+ if (byHost === null || typeof byHost !== "object" || Array.isArray(byHost)) {
508
+ throw new Error(
509
+ `public CLI config.providerAliases[${provider}] must be an object`,
510
+ );
511
+ }
512
+ const hosts: Record<string, string> = {};
513
+ for (const [host, alias] of Object.entries(byHost as Record<string, unknown>)) {
514
+ if (host.trim() === "") {
515
+ throw new Error(
516
+ `public CLI config.providerAliases[${provider}] host key must be non-empty`,
517
+ );
518
+ }
519
+ if (typeof alias !== "string" || alias.trim() === "") {
520
+ throw new Error(
521
+ `public CLI config.providerAliases[${provider}][${host}] must be a non-empty string`,
522
+ );
523
+ }
524
+ hosts[host] = alias;
525
+ }
526
+ if (Object.keys(hosts).length > 0) {
527
+ out[provider] = hosts;
528
+ }
529
+ }
530
+ return out;
531
+ }
532
+
404
533
  function parsePublicCliConfig(value: unknown): PublicCliConfig {
405
534
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
406
535
  throw new Error("public CLI config must be an object");
@@ -408,6 +537,7 @@ function parsePublicCliConfig(value: unknown): PublicCliConfig {
408
537
  const record = value as {
409
538
  seats?: unknown;
410
539
  autoResumeLimit?: unknown;
540
+ providerAliases?: unknown;
411
541
  unknownSeats?: unknown;
412
542
  };
413
543
  // #422 round-trip preservation: the sibling top-level key must survive every
@@ -416,6 +546,15 @@ function parsePublicCliConfig(value: unknown): PublicCliConfig {
416
546
  if (record.autoResumeLimit !== undefined) {
417
547
  autoResumeLimit = parseAutoResumeLimit(record.autoResumeLimit);
418
548
  }
549
+ // #778: same survival duty as autoResumeLimit — owner aliases must not vanish
550
+ // on an unrelated seat write.
551
+ let providerAliases: ProviderHostAliases | undefined;
552
+ if (record.providerAliases !== undefined) {
553
+ const parsed = parseProviderHostAliases(record.providerAliases);
554
+ if (Object.keys(parsed).length > 0) {
555
+ providerAliases = parsed;
556
+ }
557
+ }
419
558
  // #592: opaque bucket may already be present on an in-memory round-trip
420
559
  // (load→set→save calls parse again). Carry it before seats iteration so a
421
560
  // disk-shaped seats map and a memory-shaped bucket both survive.
@@ -433,6 +572,7 @@ function parsePublicCliConfig(value: unknown): PublicCliConfig {
433
572
  const withOpaque = (seats: PublicCliConfig["seats"]): PublicCliConfig => ({
434
573
  seats,
435
574
  ...(autoResumeLimit === undefined ? {} : { autoResumeLimit }),
575
+ ...(providerAliases === undefined ? {} : { providerAliases }),
436
576
  ...(Object.keys(unknownSeats).length === 0 ? {} : { unknownSeats }),
437
577
  });
438
578
  if (record.seats === undefined) {
@@ -678,7 +818,23 @@ export function resolveEffectiveSeat(
678
818
  }
679
819
  }
680
820
 
681
- return attachHostAxis(attachEngineAxis(modelSeat, config, invocation), config, invocation);
821
+ const withAxes = attachHostAxis(
822
+ attachEngineAxis(modelSeat, config, invocation),
823
+ config,
824
+ invocation,
825
+ );
826
+ // #778: host-facing provider is the seat-table value after owner aliases.
827
+ // Disk seats stay as written; only the effective selection is projected.
828
+ const selection = applyProviderHostAlias(
829
+ withAxes.selection,
830
+ withAxes.host,
831
+ config.providerAliases,
832
+ );
833
+ if (selection === undefined) {
834
+ const { selection: _dropped, ...rest } = withAxes;
835
+ return rest;
836
+ }
837
+ return { ...withAxes, selection };
682
838
  }
683
839
 
684
840
  export function effectiveSeatConfigurations(
@@ -104,8 +104,7 @@ type CourtDiaristIdentity =
104
104
  * Non-zero exit without escalate status is not rethrown here — caller decides
105
105
  * whether refresh must fail or first-entry settles controlled failure.
106
106
  * When `boundTicketNumber` is set (typed handoff from countersign), diarist
107
- * freezes under that key so issue face enters the catalog — never mechanical
108
- * recognition from prose.
107
+ * binds under that key before the turn — never mechanical recognition from prose.
109
108
  */
110
109
  async function invokeCourtDiarist(input: {
111
110
  readonly instruction: string;
@@ -218,7 +217,7 @@ export async function runCountersignCourtDiaristStation(
218
217
  instruction: `整理 #${admitted.ticketNumber} 的本案依据。`,
219
218
  projectRoot: admitted.projectRoot,
220
219
  failureLabel: `ticket #${admitted.ticketNumber}`,
221
- // Refresh holds a typed key — hand it off so freeze loads issue face.
220
+ // Refresh holds a typed key — hand it off so identity is bound before turn.
222
221
  boundTicketNumber: admitted.ticketNumber,
223
222
  },
224
223
  env,
@@ -1,20 +1,11 @@
1
1
  /**
2
- * Public Diarist (起居郎) Role run — #708 / ADR 0075 `diarist-is-role`.
2
+ * Public Diarist (起居郎) Role run — #708 / ADR 0075 `diarist-is-role` / #779.
3
3
  * Same admit → post-admission → settle shape as the other instruction seats.
4
- * Semantic collection is the role's own turn; this seat only freezes the
5
- * mechanical source catalog the turn selects from (`diarist-collector-is-own-turn`).
4
+ * Semantic collection is the role's own turn: LLM finds materials itself
5
+ * (no frozen candidate catalog, no path/attachment burden on the caller).
6
6
  * Who calls it and in what order is the caller's business (ADR 0010 `no-call-rule`).
7
7
  */
8
- import { existsSync } from "node:fs";
9
- import { writeFile } from "node:fs/promises";
10
- import { join } from "node:path";
11
-
12
8
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
13
- import {
14
- loadDiaristIssueFace,
15
- prepareDiaristSourceCatalog,
16
- serializeDiaristSourceCatalog,
17
- } from "../diarist.ts";
18
9
  import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
19
10
  import { CliUsageError } from "./cli-errors.ts";
20
11
  import {
@@ -35,7 +26,6 @@ import {
35
26
  import {
36
27
  loadResumableDiaristRun,
37
28
  markRunAdmitted,
38
- readRoleRunIdentity,
39
29
  type PublicResumeRequest,
40
30
  type SameTicketSummonsMaterials,
41
31
  } from "./run-lifecycle.ts";
@@ -57,107 +47,29 @@ export type DiaristRunEnv = PostAdmissionEnv & {
57
47
  /**
58
48
  * Typed handoff from a caller that already holds a verified ticket key
59
49
  * (countersign refresh / post-assert). Never derived from summons prose.
60
- * When set: same-ticket resume under that key, else bind-before-freeze so
61
- * issue face enters the catalog (ADR 0075 / 0081).
50
+ * When set: same-ticket resume under that key, else bind before the turn
51
+ * so identity is already on the run pages (ADR 0075 / 0081).
62
52
  */
63
53
  boundTicketNumber?: number;
64
54
  };
65
55
 
66
- /** Frozen catalog filename inside the run dossier (durable material, not argv). */
67
- const DIARIST_SOURCE_CATALOG_FILE = "diarist-sources.json" as const;
68
-
69
- /** Neutral path identifier only (ADR 0073) — catalog bytes stay on disk. */
70
- function withDiaristCatalogPath(
71
- prompt: string,
72
- sourcesPath: string | undefined,
73
- ): string {
74
- if (sourcesPath === undefined || sourcesPath.trim() === "") return prompt;
75
- return `${prompt}\n\n已冻结来源文件(路径):\n- ${sourcesPath}`;
76
- }
77
-
78
56
  /** Project admitted invocation onto the host-neutral turn request. */
79
57
  export function buildDiaristTurnRequest(
80
58
  admitted: AdmittedDiaristInvocation,
81
59
  options: RoleTurnRequestProjectionOptions,
82
- sourcesPath?: string,
83
60
  ): RoleTurnRequest {
84
- const continuation =
85
- sourcesPath === undefined
86
- ? options.continuation
87
- : {
88
- ...options.continuation,
89
- prompt: withDiaristCatalogPath(options.continuation.prompt, sourcesPath),
90
- };
91
61
  return projectRoleTurnRequest(
92
62
  admitted,
93
- {
94
- activation: {
95
- role: "diarist" as const,
96
- ...(sourcesPath === undefined ? {} : { sourcesPath }),
97
- },
98
- },
99
- { ...options, continuation },
63
+ { activation: { role: "diarist" as const } },
64
+ options,
100
65
  );
101
66
  }
102
67
 
103
- /**
104
- * Mechanical source enumeration into a frozen catalog for this turn.
105
- * Bound summons: establish the per-ticket volume and load the issue face.
106
- * First-summons (unbound): freeze session candidates + summons text so the LLM
107
- * turn can assert ticketNumber; accept verifies and mints the volume (ADR 0075).
108
- * True-unbound is decided by the LLM assertion (null/absent ticketNumber), not
109
- * by skipping the freeze here.
110
- *
111
- * A run that has not settled yet (crash-resume, open-court continuation) keeps
112
- * the catalog its candidateIndexes were minted against — re-enumerating there
113
- * would rebind the role's indexes to bytes it never saw. Only a settled run
114
- * re-enumerates on the next same-ticket summons (ADR 0075 增量).
115
- */
116
- async function freezeDiaristSourceCatalog(
117
- admitted: AdmittedDiaristInvocation,
118
- env: Pick<DiaristRunEnv, "cwd" | "home">,
119
- ): Promise<string> {
120
- const path = join(admitted.runDirectory, DIARIST_SOURCE_CATALOG_FILE);
121
- if (existsSync(path)) {
122
- const identity = await readRoleRunIdentity(admitted.runDirectory);
123
- if (identity !== undefined && identity.state !== "terminal") return path;
124
- }
125
- const issueFace =
126
- admitted.ticketNumber === undefined
127
- ? undefined
128
- : await loadDiaristIssueFace({
129
- ticketNumber: admitted.ticketNumber,
130
- projectRoot: admitted.projectRoot,
131
- home: env.home,
132
- });
133
- const catalog = await prepareDiaristSourceCatalog({
134
- ...(admitted.ticketNumber === undefined
135
- ? {}
136
- : { ticketNumber: admitted.ticketNumber }),
137
- instruction: admitted.instruction,
138
- runDirectory: admitted.runDirectory,
139
- projectRoot: admitted.projectRoot,
140
- cwd: admitted.projectRoot,
141
- home: env.home,
142
- ...(issueFace === undefined ? {} : { issueFace }),
143
- sessionCwds: [admitted.projectRoot, env.cwd],
144
- });
145
- await writeFile(path, serializeDiaristSourceCatalog(catalog), "utf8");
146
- return path;
147
- }
148
-
149
- function diaristAdapters(options?: {
150
- beforeDispatch?: (
151
- admitted: AdmittedDiaristInvocation,
152
- ) => void | Promise<void>;
153
- }): PostAdmissionAdapters<AdmittedDiaristInvocation> {
68
+ function diaristAdapters(): PostAdmissionAdapters<AdmittedDiaristInvocation> {
154
69
  return {
155
70
  trySettle: (admitted, authority, scope) =>
156
71
  trySettleDiaristTerminalResult(admitted, authority, scope),
157
72
  shouldPresentSettled: () => true,
158
- ...(options?.beforeDispatch === undefined
159
- ? {}
160
- : { beforeDispatch: options.beforeDispatch }),
161
73
  };
162
74
  }
163
75
 
@@ -182,11 +94,10 @@ export async function runPublicDiarist(
182
94
  throw error;
183
95
  }
184
96
 
185
- // #637 / #771 / ADR 0075: ticket identity is the LLM's typed assertion on this
186
- // turn (mechanical verify on accept), OR a typed handoff key already held by
187
- // the caller (countersign refresh). Code never pre-judges the summons text
188
- // against book-known numbers. First summons without handoff stays unbound
189
- // until assert.
97
+ // #637 / #771 / ADR 0075 / #779: ticket identity is the LLM's typed assertion
98
+ // on this turn, OR a typed handoff key already held by the caller (countersign
99
+ // refresh). Code never pre-judges the summons text and never freezes a
100
+ // candidate catalog. First summons without handoff stays unbound until assert.
190
101
 
191
102
  const projectRoot = parsed.project ?? env.cwd;
192
103
  const handoffTicket = env.boundTicketNumber;
@@ -240,7 +151,7 @@ export async function runPublicDiarist(
240
151
 
241
152
  await markRunAdmitted(admitted, env.principalAuthority);
242
153
 
243
- // Typed handoff: bind before freeze so issue face enters the catalog.
154
+ // Typed handoff: bind before the turn so identity is on durable pages.
244
155
  if (
245
156
  typeof handoffTicket === "number" &&
246
157
  Number.isSafeInteger(handoffTicket) &&
@@ -270,7 +181,6 @@ export async function runPublicDiarist(
270
181
  ),
271
182
  },
272
183
  };
273
- // Mutable shell: ticket bind + catalog freeze re-project activation before executeTurn.
274
184
  const turnRequest = buildDiaristTurnRequest(admitted, turnProjection);
275
185
 
276
186
  return await runPostAdmissionOneShot({
@@ -278,15 +188,7 @@ export async function runPublicDiarist(
278
188
  env,
279
189
  io,
280
190
  request: turnRequest,
281
- adapters: diaristAdapters({
282
- beforeDispatch: async (admittedSeat) => {
283
- const sourcesPath = await freezeDiaristSourceCatalog(admittedSeat, env);
284
- Object.assign(
285
- turnRequest,
286
- buildDiaristTurnRequest(admittedSeat, turnProjection, sourcesPath),
287
- );
288
- },
289
- }),
191
+ adapters: diaristAdapters(),
290
192
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
291
193
  }).then(async (result) => {
292
194
  // Accept may have bound ticket onto durable pages after LLM assertion.
@@ -323,9 +225,7 @@ export async function runPublicDiarist(
323
225
 
324
226
  /**
325
227
  * Resume a previously admitted Diarist run (#708 / ADR 0079 同票传召 = resume).
326
- * A re-entry into a settled run re-enumerates fresh sources; the offered
327
- * watermark keeps the pass incremental so already-seen blocks are not
328
- * re-offered. An unfinished run keeps its frozen catalog (see freeze above).
228
+ * LLM re-finds materials; entry identity keeps the volume idempotent.
329
229
  */
330
230
  export async function runPublicDiaristResume(
331
231
  request: PublicResumeRequest,
@@ -347,7 +247,6 @@ export async function runPublicDiaristResume(
347
247
  admitted.runDirectory,
348
248
  effective.summons,
349
249
  );
350
- const sourcesPath = await freezeDiaristSourceCatalog(admitted, env);
351
250
  return buildDiaristTurnRequest(
352
251
  admitted,
353
252
  resumeTurnRequestProjectionOptions(
@@ -356,7 +255,6 @@ export async function runPublicDiaristResume(
356
255
  env,
357
256
  summonsPrepared,
358
257
  ),
359
- sourcesPath,
360
258
  );
361
259
  },
362
260
  adapters: diaristAdapters(),
@@ -1307,6 +1307,8 @@ const SUPPORT_COMMAND_HELP = {
1307
1307
  "ak-role config set-host <seat> <name>",
1308
1308
  "ak-role config unset-host <seat>",
1309
1309
  "ak-role config set-auto-resume-limit <N>",
1310
+ "ak-role config set-provider-alias <provider> <host> <alias>",
1311
+ "ak-role config unset-provider-alias <provider> <host>",
1310
1312
  ],
1311
1313
  examples: [
1312
1314
  "ak-role config set judge openai-codex/gpt-5.6-sol:high",
@@ -1314,6 +1316,7 @@ const SUPPORT_COMMAND_HELP = {
1314
1316
  "ak-role config set-engine judge opus",
1315
1317
  "ak-role config set-host judge grok-build",
1316
1318
  "ak-role config set-auto-resume-limit 3",
1319
+ "ak-role config set-provider-alias xai hermes xai-oauth",
1317
1320
  ],
1318
1321
  },
1319
1322
  help: {