@akagilnc/pi-workflow-roles 0.1.5873 → 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.
@@ -5597,7 +5597,8 @@ var init_registry = __esm({
5597
5597
  "help",
5598
5598
  "resume",
5599
5599
  "new",
5600
- "setup"
5600
+ "setup",
5601
+ "dialogue"
5601
5602
  ];
5602
5603
  PUBLIC_DETERMINISTIC_COMMANDS = ["analyst"];
5603
5604
  }
@@ -6664,6 +6665,14 @@ var init_option_definitions = __esm({
6664
6665
  "ak-role new notary --source-run 01abc\u2026@countersign",
6665
6666
  "ak-role --host grok-build new inspector --attach ./change.patch"
6666
6667
  ]
6668
+ },
6669
+ dialogue: {
6670
+ command: "dialogue",
6671
+ summary: "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.",
6672
+ usage: ["ak-role dialogue <session.jsonl>"],
6673
+ examples: [
6674
+ "ak-role dialogue ~/.claude/projects/-Users-me-repo/0b1c\u2026.jsonl > /tmp/dialogue.jsonl"
6675
+ ]
6667
6676
  }
6668
6677
  };
6669
6678
  PUBLIC_COMMAND_HELP = {
@@ -15798,6 +15807,44 @@ var init_instruction_seat_run = __esm({
15798
15807
  }
15799
15808
  });
15800
15809
 
15810
+ // src/public-cli/dialogue-command.ts
15811
+ async function readSessionDialogue(path, home) {
15812
+ assertDialogueSessionSourcePath(path, home);
15813
+ const sessionLines = await readLedgerSessionJsonlLines(path);
15814
+ const dialogue = adaptSessionDialogue(sessionLines.map((entry) => entry.row));
15815
+ const seenIds = /* @__PURE__ */ new Set();
15816
+ const out = [];
15817
+ sessionLines.forEach((entry, index) => {
15818
+ if (entry.row === void 0) {
15819
+ out.push({ line: entry.line, raw: entry.raw });
15820
+ return;
15821
+ }
15822
+ for (const event of dialogue[index] ?? []) {
15823
+ const firstId = event.id !== void 0 && !seenIds.has(event.id);
15824
+ if (event.id !== void 0) seenIds.add(event.id);
15825
+ out.push({
15826
+ line: entry.line,
15827
+ ...firstId ? { id: event.id } : {},
15828
+ speaker: event.speaker,
15829
+ text: event.text
15830
+ });
15831
+ }
15832
+ });
15833
+ return out;
15834
+ }
15835
+ function renderSessionDialogue(lines) {
15836
+ return lines.map((line2) => `${JSON.stringify(line2)}
15837
+ `).join("");
15838
+ }
15839
+ var init_dialogue_command = __esm({
15840
+ "src/public-cli/dialogue-command.ts"() {
15841
+ "use strict";
15842
+ init_ledger_session_read();
15843
+ init_session_dialogue();
15844
+ init_ticket_provenance();
15845
+ }
15846
+ });
15847
+
15801
15848
  // src/analyst-book-key.ts
15802
15849
  import { statSync as statSync6 } from "node:fs";
15803
15850
  import { isAbsolute as isAbsolute7 } from "node:path";
@@ -18933,6 +18980,11 @@ async function runAkRole(argv, env) {
18933
18980
  return { exitCode: 0 };
18934
18981
  }
18935
18982
  const home = resolveHome(env);
18983
+ if (parsed.command === "dialogue") {
18984
+ if (parsed.args.length !== 1) throw new CliUsageError("usage: ak-role dialogue <session.jsonl>");
18985
+ io.stdout(renderSessionDialogue(await readSessionDialogue(parsed.args[0], home)));
18986
+ return { exitCode: 0 };
18987
+ }
18936
18988
  if (parsed.command === "roles") {
18937
18989
  if (parsed.args.length > 0) {
18938
18990
  throw new CliUsageError("roles takes no arguments");
@@ -19123,6 +19175,7 @@ var init_cli = __esm({
19123
19175
  init_invocation();
19124
19176
  init_option_definitions();
19125
19177
  init_auto_resume();
19178
+ init_dialogue_command();
19126
19179
  init_instruction_seat_run();
19127
19180
  init_countersign_run();
19128
19181
  init_analyst_run();
@@ -4906,7 +4906,8 @@ var init_registry = __esm({
4906
4906
  "help",
4907
4907
  "resume",
4908
4908
  "new",
4909
- "setup"
4909
+ "setup",
4910
+ "dialogue"
4910
4911
  ];
4911
4912
  PUBLIC_DETERMINISTIC_COMMANDS = ["analyst"];
4912
4913
  }
@@ -7898,6 +7899,14 @@ var init_option_definitions = __esm({
7898
7899
  "ak-role new notary --source-run 01abc\u2026@countersign",
7899
7900
  "ak-role --host grok-build new inspector --attach ./change.patch"
7900
7901
  ]
7902
+ },
7903
+ dialogue: {
7904
+ command: "dialogue",
7905
+ summary: "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.",
7906
+ usage: ["ak-role dialogue <session.jsonl>"],
7907
+ examples: [
7908
+ "ak-role dialogue ~/.claude/projects/-Users-me-repo/0b1c\u2026.jsonl > /tmp/dialogue.jsonl"
7909
+ ]
7901
7910
  }
7902
7911
  };
7903
7912
  PUBLIC_COMMAND_HELP = {
@@ -17145,6 +17154,44 @@ var init_instruction_seat_run = __esm({
17145
17154
  }
17146
17155
  });
17147
17156
 
17157
+ // src/public-cli/dialogue-command.ts
17158
+ async function readSessionDialogue(path, home) {
17159
+ assertDialogueSessionSourcePath(path, home);
17160
+ const sessionLines = await readLedgerSessionJsonlLines(path);
17161
+ const dialogue = adaptSessionDialogue(sessionLines.map((entry) => entry.row));
17162
+ const seenIds = /* @__PURE__ */ new Set();
17163
+ const out = [];
17164
+ sessionLines.forEach((entry, index) => {
17165
+ if (entry.row === void 0) {
17166
+ out.push({ line: entry.line, raw: entry.raw });
17167
+ return;
17168
+ }
17169
+ for (const event of dialogue[index] ?? []) {
17170
+ const firstId = event.id !== void 0 && !seenIds.has(event.id);
17171
+ if (event.id !== void 0) seenIds.add(event.id);
17172
+ out.push({
17173
+ line: entry.line,
17174
+ ...firstId ? { id: event.id } : {},
17175
+ speaker: event.speaker,
17176
+ text: event.text
17177
+ });
17178
+ }
17179
+ });
17180
+ return out;
17181
+ }
17182
+ function renderSessionDialogue(lines) {
17183
+ return lines.map((line2) => `${JSON.stringify(line2)}
17184
+ `).join("");
17185
+ }
17186
+ var init_dialogue_command = __esm({
17187
+ "src/public-cli/dialogue-command.ts"() {
17188
+ "use strict";
17189
+ init_ledger_session_read();
17190
+ init_session_dialogue();
17191
+ init_ticket_provenance();
17192
+ }
17193
+ });
17194
+
17148
17195
  // src/analyst-book-key.ts
17149
17196
  import { statSync as statSync6 } from "node:fs";
17150
17197
  import { isAbsolute as isAbsolute8 } from "node:path";
@@ -20280,6 +20327,11 @@ async function runAkRole(argv, env) {
20280
20327
  return { exitCode: 0 };
20281
20328
  }
20282
20329
  const home = resolveHome(env);
20330
+ if (parsed.command === "dialogue") {
20331
+ if (parsed.args.length !== 1) throw new CliUsageError("usage: ak-role dialogue <session.jsonl>");
20332
+ io.stdout(renderSessionDialogue(await readSessionDialogue(parsed.args[0], home)));
20333
+ return { exitCode: 0 };
20334
+ }
20283
20335
  if (parsed.command === "roles") {
20284
20336
  if (parsed.args.length > 0) {
20285
20337
  throw new CliUsageError("roles takes no arguments");
@@ -20470,6 +20522,7 @@ var init_cli = __esm({
20470
20522
  init_invocation();
20471
20523
  init_option_definitions();
20472
20524
  init_auto_resume();
20525
+ init_dialogue_command();
20473
20526
  init_instruction_seat_run();
20474
20527
  init_countersign_run();
20475
20528
  init_analyst_run();
@@ -11261,6 +11261,14 @@ var init_option_definitions = __esm({
11261
11261
  "ak-role new notary --source-run 01abc\u2026@countersign",
11262
11262
  "ak-role --host grok-build new inspector --attach ./change.patch"
11263
11263
  ]
11264
+ },
11265
+ dialogue: {
11266
+ command: "dialogue",
11267
+ summary: "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.",
11268
+ usage: ["ak-role dialogue <session.jsonl>"],
11269
+ examples: [
11270
+ "ak-role dialogue ~/.claude/projects/-Users-me-repo/0b1c\u2026.jsonl > /tmp/dialogue.jsonl"
11271
+ ]
11264
11272
  }
11265
11273
  };
11266
11274
  PUBLIC_COMMAND_HELP = {
@@ -19,6 +19,7 @@ import { formatHostSelectionFailure, HostSelectionError, } from "./role-turn-hos
19
19
  import { admittedSourceRunPath, parseAnalystArgv, parsePublicSeatArgv, } from "./invocation.js";
20
20
  import { createTypedOptionConsumer, optionsForOwner, projectCommandHelp, projectOwnerOptions, PUBLIC_NAVIGATOR_HELP_NOTE, PUBLIC_ROLE_OPTION_OWNERS, renderHumanOwnerOptionLines, } from "./option-definitions.js";
21
21
  import { TurnDispatchedFailure } from "./auto-resume.js";
22
+ import { readSessionDialogue, renderSessionDialogue } from "./dialogue-command.js";
22
23
  import { continueParentAfterChild, presentAuditedOriginalOfUnsettledOfficer, runPublicInstructionSeat, runPublicInstructionSeatResume, } from "./instruction-seat-run.js";
23
24
  import { latestPayloadEscalated } from "./countersign-run.js";
24
25
  import { runPublicAnalyst } from "./analyst-run.js";
@@ -780,6 +781,12 @@ export async function runAkRole(argv, env) {
780
781
  // Profile home only after home-free paths return. Failures keep real identity
781
782
  // and settle through the outer catch — no $HOME fallback (#604).
782
783
  const home = resolveHome(env);
784
+ if (parsed.command === "dialogue") {
785
+ if (parsed.args.length !== 1)
786
+ throw new CliUsageError("usage: ak-role dialogue <session.jsonl>");
787
+ io.stdout(renderSessionDialogue(await readSessionDialogue(parsed.args[0], home)));
788
+ return { exitCode: 0 };
789
+ }
783
790
  if (parsed.command === "roles") {
784
791
  if (parsed.args.length > 0) {
785
792
  throw new CliUsageError("roles takes no arguments");
@@ -0,0 +1,37 @@
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.js";
8
+ import { adaptSessionDialogue } from "../session-dialogue.js";
9
+ import { assertDialogueSessionSourcePath } from "../ticket-provenance.js";
10
+ export async function readSessionDialogue(path, home) {
11
+ assertDialogueSessionSourcePath(path, home);
12
+ const sessionLines = await readLedgerSessionJsonlLines(path);
13
+ const dialogue = adaptSessionDialogue(sessionLines.map((entry) => entry.row));
14
+ const seenIds = new Set();
15
+ const out = [];
16
+ sessionLines.forEach((entry, index) => {
17
+ if (entry.row === undefined) {
18
+ out.push({ line: entry.line, raw: entry.raw });
19
+ return;
20
+ }
21
+ for (const event of dialogue[index] ?? []) {
22
+ const firstId = event.id !== undefined && !seenIds.has(event.id);
23
+ if (event.id !== undefined)
24
+ seenIds.add(event.id);
25
+ out.push({
26
+ line: entry.line,
27
+ ...(firstId ? { id: event.id } : {}),
28
+ speaker: event.speaker,
29
+ text: event.text,
30
+ });
31
+ }
32
+ });
33
+ return out;
34
+ }
35
+ export function renderSessionDialogue(lines) {
36
+ return lines.map((line) => `${JSON.stringify(line)}\n`).join("");
37
+ }
@@ -10381,7 +10381,8 @@ var init_registry = __esm({
10381
10381
  "help",
10382
10382
  "resume",
10383
10383
  "new",
10384
- "setup"
10384
+ "setup",
10385
+ "dialogue"
10385
10386
  ];
10386
10387
  PUBLIC_DETERMINISTIC_COMMANDS = ["analyst"];
10387
10388
  }
@@ -13461,16 +13462,151 @@ var init_merger_git_state = __esm({
13461
13462
  });
13462
13463
 
13463
13464
  // src/session-dialogue.ts
13465
+ function nativeEventId(row) {
13466
+ for (const key of ["uuid", "id"]) {
13467
+ const value = row[key];
13468
+ if (typeof value === "string" && value !== "") return value;
13469
+ }
13470
+ for (const nestKey of ["message", "payload"]) {
13471
+ const nested = row[nestKey];
13472
+ if (!isRecord(nested)) continue;
13473
+ for (const key of ["uuid", "id"]) {
13474
+ const value = nested[key];
13475
+ if (typeof value === "string" && value !== "") return value;
13476
+ }
13477
+ }
13478
+ return void 0;
13479
+ }
13480
+ function textParts(content) {
13481
+ if (typeof content === "string") return content === "" ? [] : [content];
13482
+ if (!Array.isArray(content)) return [];
13483
+ const out = [];
13484
+ for (const part of content) {
13485
+ if (!isRecord(part) || !SPEAKER_TEXT_PART_TYPES.has(String(part.type))) continue;
13486
+ const text = part.text;
13487
+ if (typeof text === "string" && text !== "") out.push(text);
13488
+ }
13489
+ return out;
13490
+ }
13491
+ function responseItemMessage(row) {
13492
+ if (row.type !== "response_item" || !isRecord(row.payload)) return void 0;
13493
+ if (row.payload.type !== "message") return void 0;
13494
+ return row.payload;
13495
+ }
13496
+ function messageBody(row) {
13497
+ if (isRecord(row.message)) return row.message;
13498
+ return responseItemMessage(row);
13499
+ }
13500
+ function speakerOf(message) {
13501
+ if (message.role === "assistant") return "runner";
13502
+ if (message.role === "user") return "owner";
13503
+ return void 0;
13504
+ }
13505
+ function codexContentItemKinds(message) {
13506
+ const pass = message.internal_chat_message_metadata_passthrough;
13507
+ if (!isRecord(pass) || !Array.isArray(pass.content_item_kinds)) return void 0;
13508
+ const kinds = [];
13509
+ for (const kind of pass.content_item_kinds) {
13510
+ if (typeof kind === "string" && kind !== "") kinds.push(kind);
13511
+ }
13512
+ return kinds;
13513
+ }
13514
+ function isCodexOwnerResponseItemUser(message) {
13515
+ if (message.role !== "user") return false;
13516
+ const kinds = codexContentItemKinds(message);
13517
+ if (kinds === void 0) return false;
13518
+ return kinds.some((kind) => kind.startsWith("user."));
13519
+ }
13520
+ function fromQueueEvent(row) {
13521
+ if (row.type !== "queue-operation" || row.operation !== "enqueue") return [];
13522
+ const content = row.content;
13523
+ if (typeof content !== "string" || content === "") return [];
13524
+ const id = nativeEventId(row);
13525
+ return [{ speaker: "owner", text: content, ...id === void 0 ? {} : { id } }];
13526
+ }
13527
+ function hasFixedOpenTagPrefix(content, tag) {
13528
+ if (!content.startsWith(tag)) return false;
13529
+ const next = content.charAt(tag.length);
13530
+ return next === "" || next === ">" || next === " " || next === " " || next === "\n" || next === "\r";
13531
+ }
13532
+ function isFixedNonOwnerEnqueueShape(content) {
13533
+ return hasFixedOpenTagPrefix(content, TASK_NOTIFICATION_OPEN_TAG);
13534
+ }
13535
+ function materializationOriginKind(row) {
13536
+ if (!isRecord(row.origin)) return void 0;
13537
+ const kind = row.origin.kind;
13538
+ return typeof kind === "string" && kind !== "" ? kind : void 0;
13539
+ }
13540
+ function isNonOwnerOriginKind(kind) {
13541
+ if (kind === void 0) return false;
13542
+ return kind !== "human";
13543
+ }
13544
+ function fromMessageEvent(row) {
13545
+ const message = messageBody(row);
13546
+ if (message === void 0) return [];
13547
+ if (message.role === "toolResult") return [];
13548
+ const speaker = speakerOf(message);
13549
+ if (speaker === void 0) return [];
13550
+ if (row.type === "response_item" && speaker === "owner") {
13551
+ if (!isCodexOwnerResponseItemUser(message)) return [];
13552
+ }
13553
+ const parts = textParts(message.content);
13554
+ if (parts.length === 0) return [];
13555
+ const text = parts.join("");
13556
+ if (text === "") return [];
13557
+ const id = nativeEventId(row);
13558
+ return [{ speaker, text, ...id === void 0 ? {} : { id } }];
13559
+ }
13560
+ function adaptSessionDialogue(rows) {
13561
+ return rows.map((row) => {
13562
+ if (row === void 0) return [];
13563
+ const originKind = materializationOriginKind(row);
13564
+ if (isNonOwnerOriginKind(originKind)) return [];
13565
+ if (row.type === "queue-operation" && row.operation === "enqueue" && typeof row.content === "string" && isFixedNonOwnerEnqueueShape(row.content)) {
13566
+ return [];
13567
+ }
13568
+ const queued = fromQueueEvent(row);
13569
+ if (queued.length > 0) return queued;
13570
+ return fromMessageEvent(row);
13571
+ });
13572
+ }
13573
+ var SPEAKER_TEXT_PART_TYPES, TASK_NOTIFICATION_OPEN_TAG;
13464
13574
  var init_session_dialogue = __esm({
13465
13575
  "src/session-dialogue.ts"() {
13466
13576
  "use strict";
13467
13577
  init_unknown_value();
13578
+ SPEAKER_TEXT_PART_TYPES = /* @__PURE__ */ new Set(["text", "input_text", "output_text"]);
13579
+ TASK_NOTIFICATION_OPEN_TAG = "<task-notification";
13468
13580
  }
13469
13581
  });
13470
13582
 
13471
13583
  // src/ticket-provenance.ts
13472
13584
  import { readFile as readFile7 } from "node:fs/promises";
13473
13585
  import { join as join12, resolve as resolve6 } from "node:path";
13586
+ function dialogueSessionSourceRoots(home) {
13587
+ const machineHome = typeof home === "string" && home.trim() !== "" ? home : packageMachineHome();
13588
+ return [
13589
+ join12(machineHome, ".claude", "projects"),
13590
+ join12(machineHome, ".codex", "sessions"),
13591
+ join12(machineHome, ".pi", "agent", "sessions")
13592
+ ];
13593
+ }
13594
+ function isLedgerRoleSessionFile(absolute, home) {
13595
+ const machineHome = typeof home === "string" && home.trim() !== "" ? home : packageMachineHome();
13596
+ const ledgerHome = resolveActivationLedgerHome(machineHome);
13597
+ if (!physicallyContainedIn(ledgerHome, absolute)) return false;
13598
+ return sessionFileOf(runDirectoryOfSessionFile(absolute)) === absolute;
13599
+ }
13600
+ function assertDialogueSessionSourcePath(path, home) {
13601
+ const absolute = resolve6(path);
13602
+ for (const root of dialogueSessionSourceRoots(home)) {
13603
+ if (physicallyContainedIn(root, absolute)) return;
13604
+ }
13605
+ if (isLedgerRoleSessionFile(absolute, home)) return;
13606
+ throw new TicketProvenanceInputError(
13607
+ `session unreadable: ${path} (outside authorized source roots)`
13608
+ );
13609
+ }
13474
13610
  function ticketProvenanceSubject(ticketNumber) {
13475
13611
  if (!isSafePositiveTicketNumber(ticketNumber)) {
13476
13612
  throw new Error(
@@ -13507,6 +13643,7 @@ async function rehomeUnboundTicketProvenance(runDirectory, ticketNumber, cwd, ho
13507
13643
  }).join("");
13508
13644
  if (block !== "") appendSitianRecordBlock(ticketInput, block);
13509
13645
  }
13646
+ var TicketProvenanceInputError;
13510
13647
  var init_ticket_provenance = __esm({
13511
13648
  "src/ticket-provenance.ts"() {
13512
13649
  "use strict";
@@ -13520,6 +13657,16 @@ var init_ticket_provenance = __esm({
13520
13657
  init_sitian_reader();
13521
13658
  init_ticket_provenance_contracts();
13522
13659
  init_unknown_value();
13660
+ TicketProvenanceInputError = class extends Error {
13661
+ code = "ticket-provenance-input";
13662
+ constructor(message, options) {
13663
+ super(
13664
+ message,
13665
+ options?.cause === void 0 ? void 0 : { cause: options.cause }
13666
+ );
13667
+ this.name = "TicketProvenanceInputError";
13668
+ }
13669
+ };
13523
13670
  }
13524
13671
  });
13525
13672
 
@@ -14730,6 +14877,14 @@ var init_option_definitions = __esm({
14730
14877
  "ak-role new notary --source-run 01abc\u2026@countersign",
14731
14878
  "ak-role --host grok-build new inspector --attach ./change.patch"
14732
14879
  ]
14880
+ },
14881
+ dialogue: {
14882
+ command: "dialogue",
14883
+ summary: "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.",
14884
+ usage: ["ak-role dialogue <session.jsonl>"],
14885
+ examples: [
14886
+ "ak-role dialogue ~/.claude/projects/-Users-me-repo/0b1c\u2026.jsonl > /tmp/dialogue.jsonl"
14887
+ ]
14733
14888
  }
14734
14889
  };
14735
14890
  PUBLIC_COMMAND_HELP = {
@@ -23900,6 +24055,44 @@ var init_post_admission = __esm({
23900
24055
  }
23901
24056
  });
23902
24057
 
24058
+ // src/public-cli/dialogue-command.ts
24059
+ async function readSessionDialogue(path, home) {
24060
+ assertDialogueSessionSourcePath(path, home);
24061
+ const sessionLines = await readLedgerSessionJsonlLines(path);
24062
+ const dialogue = adaptSessionDialogue(sessionLines.map((entry) => entry.row));
24063
+ const seenIds = /* @__PURE__ */ new Set();
24064
+ const out = [];
24065
+ sessionLines.forEach((entry, index) => {
24066
+ if (entry.row === void 0) {
24067
+ out.push({ line: entry.line, raw: entry.raw });
24068
+ return;
24069
+ }
24070
+ for (const event of dialogue[index] ?? []) {
24071
+ const firstId = event.id !== void 0 && !seenIds.has(event.id);
24072
+ if (event.id !== void 0) seenIds.add(event.id);
24073
+ out.push({
24074
+ line: entry.line,
24075
+ ...firstId ? { id: event.id } : {},
24076
+ speaker: event.speaker,
24077
+ text: event.text
24078
+ });
24079
+ }
24080
+ });
24081
+ return out;
24082
+ }
24083
+ function renderSessionDialogue(lines) {
24084
+ return lines.map((line2) => `${JSON.stringify(line2)}
24085
+ `).join("");
24086
+ }
24087
+ var init_dialogue_command = __esm({
24088
+ "src/public-cli/dialogue-command.ts"() {
24089
+ "use strict";
24090
+ init_ledger_session_read();
24091
+ init_session_dialogue();
24092
+ init_ticket_provenance();
24093
+ }
24094
+ });
24095
+
23903
24096
  // node_modules/.pnpm/typebox@1.3.8/node_modules/typebox/build/schema/types/_refine.mjs
23904
24097
  function IsRefine2(value) {
23905
24098
  return guard_exports.HasPropertyKey(value, "~refine") && guard_exports.IsArray(value["~refine"]) && guard_exports.Every(value["~refine"], 0, (value2) => guard_exports.IsObject(value2) && guard_exports.HasPropertyKey(value2, "check") && guard_exports.HasPropertyKey(value2, "error") && guard_exports.IsFunction(value2.check) && guard_exports.IsFunction(value2.error));
@@ -32946,6 +33139,11 @@ async function runAkRole(argv2, env) {
32946
33139
  return { exitCode: 0 };
32947
33140
  }
32948
33141
  const home = resolveHome(env);
33142
+ if (parsed.command === "dialogue") {
33143
+ if (parsed.args.length !== 1) throw new CliUsageError("usage: ak-role dialogue <session.jsonl>");
33144
+ io.stdout(renderSessionDialogue(await readSessionDialogue(parsed.args[0], home)));
33145
+ return { exitCode: 0 };
33146
+ }
32949
33147
  if (parsed.command === "roles") {
32950
33148
  if (parsed.args.length > 0) {
32951
33149
  throw new CliUsageError("roles takes no arguments");
@@ -33136,6 +33334,7 @@ var init_cli = __esm({
33136
33334
  init_invocation();
33137
33335
  init_option_definitions();
33138
33336
  init_auto_resume();
33337
+ init_dialogue_command();
33139
33338
  init_instruction_seat_run();
33140
33339
  init_countersign_run();
33141
33340
  init_analyst_run();
@@ -1141,6 +1141,14 @@ const SUPPORT_COMMAND_HELP = {
1141
1141
  "ak-role --host grok-build new inspector --attach ./change.patch",
1142
1142
  ],
1143
1143
  },
1144
+ dialogue: {
1145
+ command: "dialogue",
1146
+ summary: "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.",
1147
+ usage: ["ak-role dialogue <session.jsonl>"],
1148
+ examples: [
1149
+ "ak-role dialogue ~/.claude/projects/-Users-me-repo/0b1c….jsonl > /tmp/dialogue.jsonl",
1150
+ ],
1151
+ },
1144
1152
  };
1145
1153
  /**
1146
1154
  * Sole public help-copy owner keyed by command/topic id.
@@ -14,7 +14,8 @@ const PUBLIC_CLI_SUPPORT_COMMANDS = [
14
14
  "help",
15
15
  "resume",
16
16
  "new",
17
- "setup"
17
+ "setup",
18
+ "dialogue"
18
19
  ];
19
20
  function seatModelOnly(seat) {
20
21
  if (seat?.provider === void 0 || seat.model === void 0) return void 0;
@@ -54,7 +54,7 @@ function isLedgerRoleSessionFile(absolute, home) {
54
54
  return sessionFileOf(runDirectoryOfSessionFile(absolute)) === absolute;
55
55
  }
56
56
  /** Real I/O seam gate: only host session stores or sitian role-run session.jsonl. */
57
- function assertDialogueSessionSourcePath(path, home) {
57
+ export function assertDialogueSessionSourcePath(path, home) {
58
58
  const absolute = resolve(path);
59
59
  for (const root of dialogueSessionSourceRoots(home)) {
60
60
  if (physicallyContainedIn(root, absolute))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5873",
3
+ "version": "0.1.5877",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -10,7 +10,7 @@
10
10
 
11
11
  你是 LLM,自己读以判断边界:
12
12
 
13
- - 本仓相关会话卷(路径里的 `_` 在目录名里常写作 `-`;主 clone 与 worktree 可能共用或分列,自行辨认)
13
+ - 本仓相关会话卷(路径里的 `_` 在目录名里常写作 `-`;主 clone 与 worktree 可能共用或分列,自行辨认)。读卷用 `ak-role dialogue <会话卷>`:每行一句说话(`line`、`id`、`speaker`、`text`),正是区间投影会收进录的那些行;`line` 与 `id` 就是交卷区间端点的写法,给了 `id` 的行才能用 id 指名
14
14
  - 本票票面与评论:用 `gh` 或等价方式读 GitHub issue
15
15
 
16
16
  指令明确列出多张要办的票时,逐票读卷判断各自相关边界;共同对话可以同时归入相关各票,无关他票对话不得混入。单票/多票交卷字段及不可用边界的处置见交卷工具的声明。
@@ -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];
@@ -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;