@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.
- package/README.md +4 -0
- package/README.zh-CN.md +4 -0
- package/dist/acp-host/production-host.js +262 -518
- package/dist/adr-path-refs.js +1 -1
- package/dist/diarist-contracts.js +31 -21
- package/dist/diarist-role.js +17 -8
- package/dist/diarist.js +39 -388
- package/dist/package-contracts/terminating-tools.js +1 -1
- package/dist/packaged-role-registry.js +4 -8
- package/dist/pi/role-turn-host.js +1 -7
- package/dist/public-cli/config.js +106 -2
- package/dist/public-cli/main.js +263 -1488
- package/dist/public-cli/option-definitions.js +3 -0
- package/dist/role-runtime.js +60 -88
- package/dist/ticket-provenance-contracts.js +0 -5
- package/dist/ticket-provenance.js +2 -149
- package/package.json +1 -1
- package/resources/diarist-collect.md +21 -11
- package/src/acp-host/role-envelope.ts +1 -1
- package/src/adr-path-refs.ts +1 -1
- package/src/diarist-contracts.ts +48 -32
- package/src/diarist-role.ts +31 -16
- package/src/diarist.ts +54 -635
- package/src/host-contracts.ts +1 -5
- package/src/package-contracts/terminating-tools.ts +1 -1
- package/src/packaged-role-registry.ts +4 -8
- package/src/pi/role-turn-host.ts +1 -7
- package/src/public-cli/cli.ts +54 -0
- package/src/public-cli/config.ts +157 -1
- package/src/public-cli/countersign-run.ts +2 -3
- package/src/public-cli/diarist-run.ts +15 -117
- package/src/public-cli/option-definitions.ts +3 -0
- package/src/role-runtime.ts +73 -105
- package/src/ticket-provenance-contracts.ts +6 -12
- package/src/ticket-provenance.ts +1 -207
- package/dist/diarist-mechanical.js +0 -411
- package/src/diarist-mechanical.ts +0 -571
package/src/host-contracts.ts
CHANGED
|
@@ -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
|
|
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
|
|
218
|
-
//
|
|
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:
|
|
220
|
+
inputFlag: undefined,
|
|
225
221
|
phaseFlag: undefined,
|
|
226
222
|
activationStage: "load-and-install",
|
|
227
223
|
sessionMaterials: [
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -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;
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -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
|
|
package/src/public-cli/config.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
5
|
-
*
|
|
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
|
|
61
|
-
*
|
|
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
|
-
|
|
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
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
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
|
|
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
|
-
*
|
|
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: {
|