@akagilnc/pi-workflow-roles 0.1.5864 → 0.1.5877

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public 起居郎 (diarist) terminating receipt contracts — ADR 0075 `diarist-is-role` / #901.
3
- * Lawful explicit releases: completed (入录) | escalate (认不出本庭对象上抛).
3
+ * Lawful explicit releases: completed (入录) | escalate (无法正常署时上呈,见 Soul).
4
4
  * Machine facts about the volume come from the mechanical reproject seam, never
5
5
  * from model self-report (锚定宪法); this module owns the receipt shape only.
6
6
  *
@@ -42,7 +42,7 @@ export type DiaristOutput =
42
42
  }[];
43
43
  }
44
44
  | {
45
- /** LLM cannot tell which ticket this summons is about — escalate, never wash into 无录. */
45
+ /** LLM cannot sign this round normally (Soul) — escalate, never wash into 无录. */
46
46
  readonly status: "escalate";
47
47
  readonly reason: string;
48
48
  };
@@ -100,8 +100,11 @@ export function projectDiaristTicketSessions(
100
100
  * sessions / ticketSessions 是投影所读的边界:卷路径或端点读不出时走既有 reask,不中止本轮。
101
101
  * 声明只留字段名和语义说明,不留类型、嵌套、长度、必填。
102
102
  */
103
+ export const DIARIST_BOUND_ENDPOINTS =
104
+ "起点与终点各写 id 或 line,二选一:id=会话卷里那一行 JSON 的 uuid 字段值(没有 uuid 取 id;顶层都没有再取 message 或 payload 里的 uuid / id),同一 id 出现多次时取第一次出现的那一行;line=那一行在文件里从 1 数起的行号。两端都含在区间内。" as const;
105
+
103
106
  const DIARIST_SESSIONS_DESCRIPTION =
104
- "对话边界。每卷含 path(会话卷绝对或可读路径)与 ranges(本卷本轮各区间)。每段区间含 from / to;起点与终点各写 id(原生 id)或 line(本轮行号),二选一。空列表=本轮无对话可划。端点无法指名时走 reask,不中止。" as const;
107
+ `对话边界。每卷含 path(会话卷绝对或可读路径)与 ranges(本卷本轮各区间)。每段区间含 from / to;${DIARIST_BOUND_ENDPOINTS}区间里只有说话人的原文进录:用户与助手消息里的文本、真人经队列的插话;思考、工具调用、工具结果和机器通知不进录,区间可以直接跨过它们,不必拆段。每次交卷的边界照投影追加进本票起居录,不去重,也不改已有的录。空列表=本轮无对话可划。端点无法指名时走 reask,不中止。` as const;
105
108
 
106
109
  export const diaristOutputSchema = withTerminatingOutputDeclarations(
107
110
  openToolObject(
@@ -116,7 +119,7 @@ export const diaristOutputSchema = withTerminatingOutputDeclarations(
116
119
  }),
117
120
  ),
118
121
  reason: Type.Unknown({
119
- description: "status 为 escalate 时:认不出本庭对象的原因",
122
+ description: "status 为 escalate 时:上呈的原因",
120
123
  }),
121
124
  sessions: Type.Optional(Type.Unknown({ description: DIARIST_SESSIONS_DESCRIPTION })),
122
125
  ticketSessions: Type.Unknown({
@@ -29,6 +29,8 @@ export {
29
29
  export type NotaryRoleDependencies = {
30
30
  loadSoul(): Promise<string>;
31
31
  loadSourceRunLocator(path: string): Promise<NotarySourceRunLocator>;
32
+ /** Soul of the audited seat (the verdict under review cites it); undefined when the package has none. */
33
+ loadAuditedSoul(role: string): Promise<string | undefined>;
32
34
  };
33
35
 
34
36
  export type NotaryRoleHostActions = {
@@ -95,14 +97,18 @@ export function projectNotaryBoundFromFlags(
95
97
  };
96
98
  }
97
99
 
98
- /** Assemble systemPrompt body from base + soul only (NO embedded bound JSON).
100
+ /** Assemble systemPrompt body from base + soul + the audited seat's Soul (NO embedded bound JSON).
99
101
  * Audited-run identity travels separately as `readingMaterial`; adapters fold
100
102
  * it into the provider-visible prompt at the send boundary. */
101
103
  export function assembleNotaryAgentStartPrompt(input: {
102
104
  readonly baseSystemPrompt: string;
103
105
  readonly soul: string;
106
+ readonly audited?: { readonly seat: string; readonly soul: string };
104
107
  }): string {
105
- return `${input.baseSystemPrompt}\n\n<notary_soul>\n${input.soul}\n</notary_soul>`;
108
+ const audited = input.audited === undefined
109
+ ? ""
110
+ : `\n\n<audited_seat_soul seat="${input.audited.seat}">\n${input.audited.soul}\n</audited_seat_soul>`;
111
+ return `${input.baseSystemPrompt}\n\n<notary_soul>\n${input.soul}\n</notary_soul>${audited}`;
106
112
  }
107
113
 
108
114
  /** Envelope-admitted ticket binding (flag read owned by shared envelope). */
@@ -119,6 +125,7 @@ export function createNotaryRoleRuntime(
119
125
  | {
120
126
  soul: string;
121
127
  sourceRun: NotarySourceRunLocator;
128
+ auditedSoul?: string;
122
129
  ticketNumber?: number;
123
130
  }
124
131
  | undefined;
@@ -142,9 +149,11 @@ export function createNotaryRoleRuntime(
142
149
  const soul = (await dependencies.loadSoul()).trim();
143
150
  if (soul.length === 0) throw new Error("Notary soul is empty");
144
151
  const sourceRun = await dependencies.loadSourceRunLocator(path);
152
+ const auditedSoul = (await dependencies.loadAuditedSoul(sourceRun.role))?.trim();
145
153
  activation = {
146
154
  soul,
147
155
  sourceRun,
156
+ ...(auditedSoul === undefined || auditedSoul === "" ? {} : { auditedSoul }),
148
157
  ...(admitted?.ticketNumber === undefined
149
158
  ? {}
150
159
  : { ticketNumber: admitted.ticketNumber }),
@@ -165,6 +174,9 @@ export function createNotaryRoleRuntime(
165
174
  systemPrompt: assembleNotaryAgentStartPrompt({
166
175
  baseSystemPrompt: event.systemPrompt,
167
176
  soul: activation.soul,
177
+ ...(activation.auditedSoul === undefined
178
+ ? {}
179
+ : { audited: { seat: activation.sourceRun.role, soul: activation.auditedSoul } }),
168
180
  }),
169
181
  readingMaterial: projectNotaryAuditedRunIdentity({
170
182
  sourceRun: activation.sourceRun,
@@ -37,6 +37,7 @@ export const NOTARY_SESSION_MATERIALS = [
37
37
  "souls/notary.md",
38
38
  "souls/audit-law.md",
39
39
  "souls/ticket-law.md",
40
+ "souls/quality-law.md",
40
41
  "souls/gate-output-guide.md",
41
42
  "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
42
43
  ] as const;
@@ -61,6 +62,7 @@ export const AUDITOR_PUBLIC_SESSION_MATERIALS = [
61
62
  "CLAUDE.md",
62
63
  "souls/judge-auditor.md",
63
64
  "souls/doctor-auditor.md",
65
+ "souls/judge.md",
64
66
  "souls/audit-law.md",
65
67
  "souls/quality-law.md",
66
68
  "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md",
@@ -319,7 +321,7 @@ export const PUBLIC_ROLE_RECORDS = [
319
321
  activationStage: "load-and-install",
320
322
  receiptStatusKey: "status",
321
323
  // #924: 公用《票面法》三席同装
322
- sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md", "resources/countersign-ticket-issue.md", "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md"],
324
+ sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md", "souls/audit-law.md", "souls/quality-law.md", "resources/countersign-ticket-issue.md", "docs/adr/0057-schema-narrowing-cuts-the-required-set-not-the-declared-set.md"],
323
325
  },
324
326
  {
325
327
  role: "secretariat",
@@ -79,6 +79,7 @@ import {
79
79
  type TypedOptionConsumer,
80
80
  } from "./option-definitions.ts";
81
81
  import { TurnDispatchedFailure } from "./auto-resume.ts";
82
+ import { readSessionDialogue, renderSessionDialogue } from "./dialogue-command.ts";
82
83
  import {
83
84
  continueParentAfterChild,
84
85
  presentAuditedOriginalOfUnsettledOfficer,
@@ -1157,6 +1158,12 @@ export async function runAkRole(
1157
1158
  // and settle through the outer catch — no $HOME fallback (#604).
1158
1159
  const home = resolveHome(env);
1159
1160
 
1161
+ if (parsed.command === "dialogue") {
1162
+ if (parsed.args.length !== 1) throw new CliUsageError("usage: ak-role dialogue <session.jsonl>");
1163
+ io.stdout(renderSessionDialogue(await readSessionDialogue(parsed.args[0]!, home)));
1164
+ return { exitCode: 0 };
1165
+ }
1166
+
1160
1167
  if (parsed.command === "roles") {
1161
1168
  if (parsed.args.length > 0) {
1162
1169
  throw new CliUsageError("roles takes no arguments");
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `ak-role dialogue <session>`: the speaker lines a diary range would project,
3
+ * one JSON object per line, so the diarist searches the dialogue instead of
4
+ * re-deriving the session format (owner 2026-10-21). Same read and adapter as
5
+ * the ticket-provenance projection — no second speaker rule.
6
+ */
7
+ import { readLedgerSessionJsonlLines } from "../ledger-session-read.ts";
8
+ import { adaptSessionDialogue, type DialogueSpeaker } from "../session-dialogue.ts";
9
+ import { assertDialogueSessionSourcePath } from "../ticket-provenance.ts";
10
+
11
+ export type SessionDialogueLine =
12
+ | {
13
+ /** 1-based file line, the same `line` a diary range endpoint names. */
14
+ readonly line: number;
15
+ /** Native id a range endpoint can name; only on its first line, since an id resolves there. */
16
+ readonly id?: string;
17
+ readonly speaker: DialogueSpeaker;
18
+ readonly text: string;
19
+ }
20
+ | {
21
+ /** Unparsable source line; a range covering it keeps these bytes in the record. */
22
+ readonly line: number;
23
+ readonly raw: string;
24
+ };
25
+
26
+ export async function readSessionDialogue(path: string, home?: string): Promise<SessionDialogueLine[]> {
27
+ assertDialogueSessionSourcePath(path, home);
28
+ const sessionLines = await readLedgerSessionJsonlLines(path);
29
+ const dialogue = adaptSessionDialogue(sessionLines.map((entry) => entry.row));
30
+ const seenIds = new Set<string>();
31
+ const out: SessionDialogueLine[] = [];
32
+ sessionLines.forEach((entry, index) => {
33
+ if (entry.row === undefined) {
34
+ out.push({ line: entry.line, raw: entry.raw });
35
+ return;
36
+ }
37
+ for (const event of dialogue[index] ?? []) {
38
+ const firstId = event.id !== undefined && !seenIds.has(event.id);
39
+ if (event.id !== undefined) seenIds.add(event.id);
40
+ out.push({
41
+ line: entry.line,
42
+ ...(firstId ? { id: event.id } : {}),
43
+ speaker: event.speaker,
44
+ text: event.text,
45
+ });
46
+ }
47
+ });
48
+ return out;
49
+ }
50
+
51
+ export function renderSessionDialogue(lines: readonly SessionDialogueLine[]): string {
52
+ return lines.map((line) => `${JSON.stringify(line)}\n`).join("");
53
+ }
@@ -1362,6 +1362,15 @@ const SUPPORT_COMMAND_HELP = {
1362
1362
  "ak-role --host grok-build new inspector --attach ./change.patch",
1363
1363
  ],
1364
1364
  },
1365
+ dialogue: {
1366
+ command: "dialogue",
1367
+ summary:
1368
+ "Print what a diary range projects from a session volume, one JSON object per line: speaker lines {line, id, speaker: owner|runner, text} and unparsable lines {line, raw}. line and id are what a range endpoint names; a repeated id is shown on its first line only, where it resolves. Same authorized session roots as the projection.",
1369
+ usage: ["ak-role dialogue <session.jsonl>"],
1370
+ examples: [
1371
+ "ak-role dialogue ~/.claude/projects/-Users-me-repo/0b1c….jsonl > /tmp/dialogue.jsonl",
1372
+ ],
1373
+ },
1365
1374
  } as const satisfies Record<string, PublicCommandHelpFacts>;
1366
1375
 
1367
1376
  /**
@@ -29,6 +29,7 @@ export const PUBLIC_CLI_SUPPORT_COMMANDS = [
29
29
  "resume",
30
30
  "new",
31
31
  "setup",
32
+ "dialogue",
32
33
  ] as const;
33
34
 
34
35
  export type PublicCliSupportCommand = (typeof PUBLIC_CLI_SUPPORT_COMMANDS)[number];
@@ -96,6 +96,7 @@ import {
96
96
  } from "./secretariat-role.ts";
97
97
  import { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
98
98
  import {
99
+ DIARIST_BOUND_ENDPOINTS,
99
100
  projectDiaristSessions,
100
101
  projectDiaristTicketSessions,
101
102
  } from "./diarist-contracts.ts";
@@ -785,7 +786,7 @@ function readDiaristRunCoordinates(ctx: HostContext): {
785
786
 
786
787
  /** Plain-language re-ask when diarist bounds cannot be used (#901 / reask-not-explode). */
787
788
  const DIARIST_BOUNDS_REASK =
788
- "边界无法使用。多票请逐票重交 ticketSessions,单票重交 sessions;每卷 path + ranges,每端以原生 id 或本轮行号二选一指名。" as const;
789
+ `边界无法使用。多票请逐票重交 ticketSessions,单票重交 sessions;每卷 path + ranges。${DIARIST_BOUND_ENDPOINTS}` as const;
789
790
  /**
790
791
  * #708 / #779 / #901: 起居郎 public seat on the shared filed-officer envelope.
791
792
  * LLM judges ticket + dialogue bounds; mechanical layer reprojects the unique
@@ -1383,6 +1384,9 @@ export function createRoleRuntimeExtension(
1383
1384
  if (!dependencies.loadNotarySourceRun) throw new Error("Notary runtime dependencies are not configured");
1384
1385
  return dependencies.loadNotarySourceRun(path);
1385
1386
  },
1387
+ async loadAuditedSoul(role) {
1388
+ return packagedRoleMetadata(role) === undefined ? undefined : requireRoleSoul(role as PackagedRole);
1389
+ },
1386
1390
  }, hostActions);
1387
1391
  const countersign = createCountersignRoleRuntime(roleHost, {
1388
1392
  loadSoul: () => requireRoleSoul("countersign"),
@@ -121,7 +121,7 @@ const SUBMISSION_FACE = {
121
121
  },
122
122
  diarist: {
123
123
  label: "起居郎输出",
124
- description: "起居郎交单票 sessions 或多票逐票 ticketSessions 边界;认不出本庭对象则 escalate。",
124
+ description: "起居郎交单票 sessions 或多票逐票 ticketSessions 边界,或 escalate。",
125
125
  promptSnippet: "起居郎交边界",
126
126
  parameters: diaristOutputSchema,
127
127
  },
@@ -83,7 +83,7 @@ function isLedgerRoleSessionFile(absolute: string, home?: string): boolean {
83
83
  }
84
84
 
85
85
  /** Real I/O seam gate: only host session stores or sitian role-run session.jsonl. */
86
- function assertDialogueSessionSourcePath(path: string, home?: string): void {
86
+ export function assertDialogueSessionSourcePath(path: string, home?: string): void {
87
87
  const absolute = resolve(path);
88
88
  for (const root of dialogueSessionSourceRoots(home)) {
89
89
  if (physicallyContainedIn(root, absolute)) return;