@akagilnc/pi-workflow-roles 0.1.3718 → 0.1.3722

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.
@@ -502,6 +502,19 @@ var init_collector_github = __esm({
502
502
  }
503
503
  });
504
504
 
505
+ // src/readable-gate-item.ts
506
+ function readableGateItem(value) {
507
+ return typeof value === "string" ? value : JSON.stringify(value);
508
+ }
509
+ function joinReadableGateItems(items, separator = "; ") {
510
+ return items.map(readableGateItem).join(separator);
511
+ }
512
+ var init_readable_gate_item = __esm({
513
+ "src/readable-gate-item.ts"() {
514
+ "use strict";
515
+ }
516
+ });
517
+
505
518
  // src/auditor-dossier-tool.ts
506
519
  var auditor_dossier_tool_exports = {};
507
520
  __export(auditor_dossier_tool_exports, {
@@ -512,6 +525,7 @@ __export(auditor_dossier_tool_exports, {
512
525
  createAuditorDossierTool: () => createAuditorDossierTool,
513
526
  gateSubmissionCandidatePath: () => gateSubmissionCandidatePath,
514
527
  persistGateSubmissionCandidate: () => persistGateSubmissionCandidate,
528
+ readLatestSubmissionArguments: () => readLatestSubmissionArguments,
515
529
  readLatestToolCallLeaf: () => readLatestToolCallLeaf
516
530
  });
517
531
  import { mkdirSync, writeFileSync } from "node:fs";
@@ -543,16 +557,20 @@ function readLatestToolCallLeaf(context) {
543
557
  function gateSubmissionCandidatePath(runDirectory) {
544
558
  return join(runDirectory, "artifacts", GATE_SUBMISSION_CANDIDATE_FILE);
545
559
  }
546
- function buildGateOfficerReviewInstruction(input) {
547
- const lines = [
548
- "\u672C\u8F6E\u7236\u5E2D\u4EA4\u5377\u5F85\u5BA1\u3002",
549
- `\u6765\u6E90 run\uFF1A${input.sourceRunDirectory}`
550
- ];
551
- const candidate = input.submissionCandidatePath?.trim();
552
- if (candidate !== void 0 && candidate.length > 0) {
553
- lines.push(`\u4EA4\u5377\u5019\u9009\uFF08\u51BB\u7ED3\u5FEB\u7167\uFF09\uFF1A${candidate}`);
560
+ function readLatestSubmissionArguments(context) {
561
+ const leaf = readLatestToolCallLeaf(context);
562
+ if (!isRecord2(leaf) || !isRecord2(leaf.message) || !Array.isArray(leaf.message.content)) {
563
+ return void 0;
554
564
  }
555
- return lines.join("\n");
565
+ for (const part of leaf.message.content) {
566
+ if (isRecord2(part) && part.type === "toolCall" && "arguments" in part) {
567
+ return part.arguments;
568
+ }
569
+ }
570
+ return void 0;
571
+ }
572
+ function buildGateOfficerReviewInstruction(input) {
573
+ return readableGateItem(input.submission);
556
574
  }
557
575
  function persistGateSubmissionCandidate(runDirectory, context) {
558
576
  const leaf = readLatestToolCallLeaf(context);
@@ -596,6 +614,7 @@ var AUDITOR_DOSSIER_TOOL_NAME, GATE_SUBMISSION_CANDIDATE_FILE;
596
614
  var init_auditor_dossier_tool = __esm({
597
615
  "src/auditor-dossier-tool.ts"() {
598
616
  "use strict";
617
+ init_readable_gate_item();
599
618
  AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier";
600
619
  GATE_SUBMISSION_CANDIDATE_FILE = "gate-submission-candidate.json";
601
620
  }
@@ -2235,19 +2254,6 @@ var init_reviewer_output = __esm({
2235
2254
  }
2236
2255
  });
2237
2256
 
2238
- // src/readable-gate-item.ts
2239
- function readableGateItem(value) {
2240
- return typeof value === "string" ? value : JSON.stringify(value);
2241
- }
2242
- function joinReadableGateItems(items, separator = "; ") {
2243
- return items.map(readableGateItem).join(separator);
2244
- }
2245
- var init_readable_gate_item = __esm({
2246
- "src/readable-gate-item.ts"() {
2247
- "use strict";
2248
- }
2249
- });
2250
-
2251
2257
  // src/audit-escalation.ts
2252
2258
  function buildAuditEscalationResult(decision, deliveredOutput) {
2253
2259
  const auditOwned = {
@@ -16929,8 +16935,11 @@ async function summonPublicRole(options) {
16929
16935
  ...options.signal === void 0 ? {} : { signal: options.signal },
16930
16936
  // #753: reask rides the existing notary same-ticket resume summons.instruction.
16931
16937
  ...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
16932
- // #753/#750: new-submission pointers on same-parent resume (not conclusion re-ask).
16933
- ...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction }
16938
+ // #786: verbatim submission body on same-parent resume (not conclusion re-ask).
16939
+ ...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
16940
+ // Offline test injects — same faces as public CLI env (production leaves unset).
16941
+ ...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
16942
+ ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
16934
16943
  };
16935
16944
  const captured = options.io === void 0 ? createCapturingIo() : void 0;
16936
16945
  const io = options.io ?? captured.io;
@@ -17027,11 +17036,10 @@ async function summonGateOfficer(options) {
17027
17036
  home = homeFromRunDirectory2(options.sourceRunDirectory);
17028
17037
  }
17029
17038
  let gateReviewInstruction;
17030
- if (options.reask === void 0) {
17039
+ if (options.reask === void 0 && options.submission !== void 0) {
17031
17040
  const { buildGateOfficerReviewInstruction: buildGateOfficerReviewInstruction2 } = await Promise.resolve().then(() => (init_auditor_dossier_tool(), auditor_dossier_tool_exports));
17032
17041
  gateReviewInstruction = buildGateOfficerReviewInstruction2({
17033
- sourceRunDirectory: options.sourceRunDirectory,
17034
- ...options.submissionCandidatePath === void 0 ? {} : { submissionCandidatePath: options.submissionCandidatePath }
17042
+ submission: options.submission
17035
17043
  });
17036
17044
  }
17037
17045
  const common = {
@@ -17041,7 +17049,9 @@ async function summonGateOfficer(options) {
17041
17049
  ...options.io === void 0 ? {} : { io: options.io },
17042
17050
  ...options.signal === void 0 ? {} : { signal: options.signal },
17043
17051
  ...options.reask === void 0 ? {} : { reviewReask: options.reask },
17044
- ...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction }
17052
+ ...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction },
17053
+ ...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
17054
+ ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
17045
17055
  };
17046
17056
  if (options.officer === "notary") {
17047
17057
  return summonPublicRole({
@@ -18670,13 +18680,11 @@ async function projectGatekeeperRun(options) {
18670
18680
  }
18671
18681
  };
18672
18682
  }
18673
- const submissionCandidatePath = persistGateSubmissionCandidate(
18674
- runDirectory,
18675
- options.context
18676
- );
18683
+ persistGateSubmissionCandidate(runDirectory, options.context);
18684
+ const submission = readLatestSubmissionArguments(options.context);
18677
18685
  let summoned;
18678
18686
  try {
18679
- const summon = options.summonOfficer ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask) => {
18687
+ const summon = options.summonOfficer ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask, nextSubmission) => {
18680
18688
  const { summonGateOfficer: summonGateOfficer2 } = await Promise.resolve().then(() => (init_public_role_summons(), public_role_summons_exports));
18681
18689
  return summonGateOfficer2({
18682
18690
  officer: nextOfficer,
@@ -18684,10 +18692,20 @@ async function projectGatekeeperRun(options) {
18684
18692
  cwd: options.context.cwd ?? process.cwd(),
18685
18693
  ...officerSignal === void 0 ? {} : { signal: officerSignal },
18686
18694
  ...reask === void 0 ? {} : { reask },
18687
- ...submissionCandidatePath === void 0 ? {} : { submissionCandidatePath }
18695
+ ...nextSubmission === void 0 ? {} : { submission: nextSubmission },
18696
+ ...options.home === void 0 ? {} : { home: options.home },
18697
+ ...options.packageRoot === void 0 ? {} : { packageRoot: options.packageRoot },
18698
+ ...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
18699
+ ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
18688
18700
  });
18689
18701
  });
18690
- summoned = await summon(officer, runDirectory, options.signal, options.reask);
18702
+ summoned = await summon(
18703
+ officer,
18704
+ runDirectory,
18705
+ options.signal,
18706
+ options.reask,
18707
+ submission
18708
+ );
18691
18709
  } catch (error) {
18692
18710
  return {
18693
18711
  officer,
@@ -1,5 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
+ import { readableGateItem } from "./readable-gate-item.js";
3
4
  import { Type } from "typebox";
4
5
  const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier";
5
6
  const GATE_SUBMISSION_CANDIDATE_FILE = "gate-submission-candidate.json";
@@ -29,16 +30,20 @@ function readLatestToolCallLeaf(context) {
29
30
  function gateSubmissionCandidatePath(runDirectory) {
30
31
  return join(runDirectory, "artifacts", GATE_SUBMISSION_CANDIDATE_FILE);
31
32
  }
32
- function buildGateOfficerReviewInstruction(input) {
33
- const lines = [
34
- "\u672C\u8F6E\u7236\u5E2D\u4EA4\u5377\u5F85\u5BA1\u3002",
35
- `\u6765\u6E90 run\uFF1A${input.sourceRunDirectory}`
36
- ];
37
- const candidate = input.submissionCandidatePath?.trim();
38
- if (candidate !== void 0 && candidate.length > 0) {
39
- lines.push(`\u4EA4\u5377\u5019\u9009\uFF08\u51BB\u7ED3\u5FEB\u7167\uFF09\uFF1A${candidate}`);
33
+ function readLatestSubmissionArguments(context) {
34
+ const leaf = readLatestToolCallLeaf(context);
35
+ if (!isRecord(leaf) || !isRecord(leaf.message) || !Array.isArray(leaf.message.content)) {
36
+ return void 0;
37
+ }
38
+ for (const part of leaf.message.content) {
39
+ if (isRecord(part) && part.type === "toolCall" && "arguments" in part) {
40
+ return part.arguments;
41
+ }
40
42
  }
41
- return lines.join("\n");
43
+ return void 0;
44
+ }
45
+ function buildGateOfficerReviewInstruction(input) {
46
+ return readableGateItem(input.submission);
42
47
  }
43
48
  function persistGateSubmissionCandidate(runDirectory, context) {
44
49
  const leaf = readLatestToolCallLeaf(context);
@@ -86,5 +91,6 @@ export {
86
91
  createAuditorDossierTool,
87
92
  gateSubmissionCandidatePath,
88
93
  persistGateSubmissionCandidate,
94
+ readLatestSubmissionArguments,
89
95
  readLatestToolCallLeaf
90
96
  };
@@ -1,4 +1,4 @@
1
- import { auditorRunDirectory, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
1
+ import { auditorRunDirectory, persistGateSubmissionCandidate, readLatestSubmissionArguments, } from "./auditor-dossier-tool.js";
2
2
  import { GatekeeperDecisionError } from "./submission-errors.js";
3
3
  import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
4
4
  import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
@@ -173,14 +173,15 @@ export async function projectGatekeeperRun(options) {
173
173
  },
174
174
  };
175
175
  }
176
- // Pointer-only summons need a resolvable leaf: Grok session.jsonl is header-only
177
- // (#617 DK-4); write the in-memory tool-call candidate as a run artifact first (#632).
178
- // Candidate path also rides same-parent officer resume as 人读材料 (#753 / #750).
179
- const submissionCandidatePath = persistGateSubmissionCandidate(runDirectory, options.context);
176
+ // #632: Grok session.jsonl is header-only — freeze the in-memory tool-call leaf
177
+ // as a run artifact so dossier exploration still resolves. LLM→LLM resume does
178
+ // not ride this path: submission body goes verbatim on gateReviewInstruction (#786).
179
+ persistGateSubmissionCandidate(runDirectory, options.context);
180
+ const submission = readLatestSubmissionArguments(options.context);
180
181
  let summoned;
181
182
  try {
182
183
  const summon = options.summonOfficer
183
- ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask) => {
184
+ ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask, nextSubmission) => {
184
185
  const { summonGateOfficer } = await import("./public-role-summons.js");
185
186
  return summonGateOfficer({
186
187
  officer: nextOfficer,
@@ -188,12 +189,14 @@ export async function projectGatekeeperRun(options) {
188
189
  cwd: options.context.cwd ?? process.cwd(),
189
190
  ...(officerSignal === undefined ? {} : { signal: officerSignal }),
190
191
  ...(reask === undefined ? {} : { reask }),
191
- ...(submissionCandidatePath === undefined
192
- ? {}
193
- : { submissionCandidatePath }),
192
+ ...(nextSubmission === undefined ? {} : { submission: nextSubmission }),
193
+ ...(options.home === undefined ? {} : { home: options.home }),
194
+ ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
195
+ ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
196
+ ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
194
197
  });
195
198
  });
196
- summoned = await summon(officer, runDirectory, options.signal, options.reask);
199
+ summoned = await summon(officer, runDirectory, options.signal, options.reask, submission);
197
200
  }
198
201
  catch (error) {
199
202
  return {
@@ -31,7 +31,7 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
31
31
  // No bare catch→fresh: lookup/resume failures surface; only true absence mints new.
32
32
  const projectRoot = parsed.project ?? env.cwd;
33
33
  // #747: parentRunPath is the pure 卷宗指针 path only — never reask/materials text.
34
- // #753: gate re-ask / new-submission pointers ride summons.instruction on resume.
34
+ // #753/#786: gate re-ask / verbatim submission body ride summons.instruction on resume.
35
35
  const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
36
36
  if (parentRunPath !== undefined) {
37
37
  const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
@@ -57,7 +57,7 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
57
57
  return resumed;
58
58
  // Reask without a prior same-parent run cannot deliver the plain-language ask
59
59
  // on a fresh mint without inventing a second prompt path — fail loud (#753).
60
- // gateReviewInstruction alone is resume-only; fresh mint keeps argv 卷宗指针.
60
+ // gateReviewInstruction (verbatim body) alone is resume-only; fresh mint keeps argv 卷宗指针.
61
61
  if (env.reviewReask !== undefined) {
62
62
  presentStructuralRejection(new CliUsageError("inspector review reask requires a prior same-parent run to resume"), io);
63
63
  return { exitCode: 2 };
@@ -150,7 +150,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
150
150
  return { exitCode: 2 };
151
151
  }
152
152
  }
153
- // #756: auditor reask / new-submission pointers ride summons.instruction on resume.
153
+ // #756/#786: auditor reask / verbatim submission body ride summons.instruction on resume.
154
154
  const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
155
155
  const summons = {
156
156
  ...(resumeInstruction === undefined
@@ -21973,6 +21973,7 @@ var init_method_skill = __esm({
21973
21973
  var init_auditor_dossier_tool = __esm({
21974
21974
  "src/auditor-dossier-tool.ts"() {
21975
21975
  "use strict";
21976
+ init_readable_gate_item();
21976
21977
  init_build();
21977
21978
  }
21978
21979
  });
@@ -52,7 +52,7 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
52
52
  throw error;
53
53
  }
54
54
  // #747: officer resume key is this parent source-run path (not ticket number).
55
- // #753: gate re-ask and new-submission pointers share summons.instruction
55
+ // #753/#786: gate re-ask and verbatim submission body share summons.instruction
56
56
  // (reask wins when both present; no parallel stack).
57
57
  {
58
58
  const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
@@ -76,7 +76,7 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
76
76
  return resumed;
77
77
  // Reask without a prior same-parent run cannot deliver the plain-language ask
78
78
  // on a fresh mint without inventing a second prompt path — fail loud (#753).
79
- // gateReviewInstruction alone is resume-only materials; fresh mint ignores it.
79
+ // gateReviewInstruction (verbatim body) alone is resume-only; fresh mint ignores it.
80
80
  if (env.reviewReask !== undefined) {
81
81
  presentStructuralRejection(new CliUsageError("notary review reask requires a prior same-parent run to resume"), io);
82
82
  return { exitCode: 2 };
@@ -165,8 +165,11 @@ async function summonPublicRole(options) {
165
165
  ...options.signal === void 0 ? {} : { signal: options.signal },
166
166
  // #753: reask rides the existing notary same-ticket resume summons.instruction.
167
167
  ...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
168
- // #753/#750: new-submission pointers on same-parent resume (not conclusion re-ask).
169
- ...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction }
168
+ // #786: verbatim submission body on same-parent resume (not conclusion re-ask).
169
+ ...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
170
+ // Offline test injects — same faces as public CLI env (production leaves unset).
171
+ ...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
172
+ ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
170
173
  };
171
174
  const captured = options.io === void 0 ? createCapturingIo() : void 0;
172
175
  const io = options.io ?? captured.io;
@@ -263,11 +266,10 @@ async function summonGateOfficer(options) {
263
266
  home = homeFromRunDirectory(options.sourceRunDirectory);
264
267
  }
265
268
  let gateReviewInstruction;
266
- if (options.reask === void 0) {
269
+ if (options.reask === void 0 && options.submission !== void 0) {
267
270
  const { buildGateOfficerReviewInstruction } = await import("./auditor-dossier-tool.js");
268
271
  gateReviewInstruction = buildGateOfficerReviewInstruction({
269
- sourceRunDirectory: options.sourceRunDirectory,
270
- ...options.submissionCandidatePath === void 0 ? {} : { submissionCandidatePath: options.submissionCandidatePath }
272
+ submission: options.submission
271
273
  });
272
274
  }
273
275
  const common = {
@@ -277,7 +279,9 @@ async function summonGateOfficer(options) {
277
279
  ...options.io === void 0 ? {} : { io: options.io },
278
280
  ...options.signal === void 0 ? {} : { signal: options.signal },
279
281
  ...options.reask === void 0 ? {} : { reviewReask: options.reask },
280
- ...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction }
282
+ ...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction },
283
+ ...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
284
+ ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
281
285
  };
282
286
  if (options.officer === "notary") {
283
287
  return summonPublicRole({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3718",
3
+ "version": "0.1.3722",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -3,6 +3,7 @@ import { dirname, join, resolve } from "node:path";
3
3
 
4
4
  import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
  import type { HostContext } from "./host-contracts.ts";
6
+ import { readableGateItem } from "./readable-gate-item.ts";
6
7
  import { Type } from "typebox";
7
8
 
8
9
  export const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier" as const;
@@ -63,24 +64,35 @@ export function gateSubmissionCandidatePath(runDirectory: string): string {
63
64
  }
64
65
 
65
66
  /**
66
- * Human-readable materials for gate-officer same-parent resume (#753 / #750).
67
- * Pointers only — no body embed (ADR 0079/0081); no duty/handbook lines (#755).
67
+ * Arguments of the latest assistant toolCall leaf — the in-flight 交卷 body.
68
+ * Transport assembly only: does not interpret field contents (#786).
69
+ */
70
+ export function readLatestSubmissionArguments(
71
+ context: GateSubmissionSessionContext,
72
+ ): unknown | undefined {
73
+ const leaf = readLatestToolCallLeaf(context);
74
+ if (!isRecord(leaf) || !isRecord(leaf.message) || !Array.isArray(leaf.message.content)) {
75
+ return undefined;
76
+ }
77
+ for (const part of leaf.message.content) {
78
+ if (isRecord(part) && part.type === "toolCall" && "arguments" in part) {
79
+ return part.arguments;
80
+ }
81
+ }
82
+ return undefined;
83
+ }
84
+
85
+ /**
86
+ * Verbatim parent-submission body for gate-officer same-parent resume (#786).
68
87
  * Three pairs share this face: countersign↔notary, judge↔auditor, worker↔inspector.
69
- * Code delivers the pointers; it does not judge whether the officer read them.
88
+ * Code relays bytes; it does not parse, judge, or rewrite the submission.
89
+ * Symmetric with bounce direction (officer receipt → parent via readableGateItem).
90
+ * No path/pointer substitute — argv 卷宗指针 stays on the code-summons face (ADR 0079).
70
91
  */
71
92
  export function buildGateOfficerReviewInstruction(input: {
72
- readonly sourceRunDirectory: string;
73
- readonly submissionCandidatePath?: string;
93
+ readonly submission: unknown;
74
94
  }): string {
75
- const lines = [
76
- "本轮父席交卷待审。",
77
- `来源 run:${input.sourceRunDirectory}`,
78
- ];
79
- const candidate = input.submissionCandidatePath?.trim();
80
- if (candidate !== undefined && candidate.length > 0) {
81
- lines.push(`交卷候选(冻结快照):${candidate}`);
82
- }
83
- return lines.join("\n");
95
+ return readableGateItem(input.submission);
84
96
  }
85
97
 
86
98
  /**
@@ -4,6 +4,7 @@ import type { HostContext } from "./host-contracts.ts";
4
4
  import {
5
5
  auditorRunDirectory,
6
6
  persistGateSubmissionCandidate,
7
+ readLatestSubmissionArguments,
7
8
  } from "./auditor-dossier-tool.ts";
8
9
  import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
9
10
  import { GatekeeperDecisionError } from "./submission-errors.ts";
@@ -89,6 +90,11 @@ export type GateOfficerSummon = (
89
90
  * conclusion (#753 / #756). Hosted as same-ticket resume instruction.
90
91
  */
91
92
  reask?: string,
93
+ /**
94
+ * In-flight parent 交卷 body (tool-call arguments). Production default relays
95
+ * it verbatim on same-parent officer resume (#786).
96
+ */
97
+ submission?: unknown,
92
98
  ) => Promise<PublicSummonResult>;
93
99
 
94
100
  export type RunGatekeeperOptions = {
@@ -106,6 +112,14 @@ export type RunGatekeeperOptions = {
106
112
  * activation path (#675); inject only in offline tracers.
107
113
  */
108
114
  readonly summonOfficer?: GateOfficerSummon;
115
+ /**
116
+ * Offline test injects forwarded through the production default summonGateOfficer
117
+ * path (same faces as public CLI). Production leaves these unset.
118
+ */
119
+ readonly home?: string;
120
+ readonly packageRoot?: string;
121
+ readonly roleTurnHost?: import("./host-contracts.ts").RoleTurnHost;
122
+ readonly createRunId?: () => string;
109
123
  };
110
124
 
111
125
  export type GatekeeperPassHostActions = {
@@ -299,18 +313,16 @@ export async function projectGatekeeperRun(
299
313
  },
300
314
  };
301
315
  }
302
- // Pointer-only summons need a resolvable leaf: Grok session.jsonl is header-only
303
- // (#617 DK-4); write the in-memory tool-call candidate as a run artifact first (#632).
304
- // Candidate path also rides same-parent officer resume as 人读材料 (#753 / #750).
305
- const submissionCandidatePath = persistGateSubmissionCandidate(
306
- runDirectory,
307
- options.context,
308
- );
316
+ // #632: Grok session.jsonl is header-only — freeze the in-memory tool-call leaf
317
+ // as a run artifact so dossier exploration still resolves. LLM→LLM resume does
318
+ // not ride this path: submission body goes verbatim on gateReviewInstruction (#786).
319
+ persistGateSubmissionCandidate(runDirectory, options.context);
320
+ const submission = readLatestSubmissionArguments(options.context);
309
321
  let summoned: PublicSummonResult;
310
322
  try {
311
323
  const summon =
312
324
  options.summonOfficer
313
- ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask) => {
325
+ ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask, nextSubmission) => {
314
326
  const { summonGateOfficer } = await import("./public-role-summons.ts");
315
327
  return summonGateOfficer({
316
328
  officer: nextOfficer,
@@ -318,12 +330,20 @@ export async function projectGatekeeperRun(
318
330
  cwd: options.context.cwd ?? process.cwd(),
319
331
  ...(officerSignal === undefined ? {} : { signal: officerSignal }),
320
332
  ...(reask === undefined ? {} : { reask }),
321
- ...(submissionCandidatePath === undefined
322
- ? {}
323
- : { submissionCandidatePath }),
333
+ ...(nextSubmission === undefined ? {} : { submission: nextSubmission }),
334
+ ...(options.home === undefined ? {} : { home: options.home }),
335
+ ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
336
+ ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
337
+ ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
324
338
  });
325
339
  });
326
- summoned = await summon(officer, runDirectory, options.signal, options.reask);
340
+ summoned = await summon(
341
+ officer,
342
+ runDirectory,
343
+ options.signal,
344
+ options.reask,
345
+ submission,
346
+ );
327
347
  } catch (error) {
328
348
  return {
329
349
  officer,
@@ -51,7 +51,7 @@ export type InspectorRunEnv = PostAdmissionEnv & {
51
51
  */
52
52
  reviewReask?: string;
53
53
  /**
54
- * #753/#750 same-parent re-summons: human-readable new-submission pointers.
54
+ * #786 same-parent re-summons: verbatim parent-submission body.
55
55
  * Rides summons.instruction when reviewReask is absent. Fresh mint keeps argv 卷宗指针.
56
56
  */
57
57
  gateReviewInstruction?: string;
@@ -99,7 +99,7 @@ export async function runPublicInspector(
99
99
  // No bare catch→fresh: lookup/resume failures surface; only true absence mints new.
100
100
  const projectRoot = parsed.project ?? env.cwd;
101
101
  // #747: parentRunPath is the pure 卷宗指针 path only — never reask/materials text.
102
- // #753: gate re-ask / new-submission pointers ride summons.instruction on resume.
102
+ // #753/#786: gate re-ask / verbatim submission body ride summons.instruction on resume.
103
103
  const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
104
104
  if (parentRunPath !== undefined) {
105
105
  const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
@@ -129,7 +129,7 @@ export async function runPublicInspector(
129
129
  if (resumed !== undefined) return resumed;
130
130
  // Reask without a prior same-parent run cannot deliver the plain-language ask
131
131
  // on a fresh mint without inventing a second prompt path — fail loud (#753).
132
- // gateReviewInstruction alone is resume-only; fresh mint keeps argv 卷宗指针.
132
+ // gateReviewInstruction (verbatim body) alone is resume-only; fresh mint keeps argv 卷宗指针.
133
133
  if (env.reviewReask !== undefined) {
134
134
  presentStructuralRejection(
135
135
  new CliUsageError(
@@ -70,7 +70,7 @@ export type InstructionSeatRunEnv = PostAdmissionEnv & {
70
70
  */
71
71
  reviewReask?: string;
72
72
  /**
73
- * #753/#750 same-parent re-summons: human-readable new-submission pointers.
73
+ * #786 same-parent re-summons: verbatim parent-submission body.
74
74
  * Rides summons.instruction when reviewReask is absent. Fresh mint keeps argv instruction.
75
75
  */
76
76
  gateReviewInstruction?: string;
@@ -294,7 +294,7 @@ export async function runPublicInstructionSeat(
294
294
  }
295
295
  }
296
296
 
297
- // #756: auditor reask / new-submission pointers ride summons.instruction on resume.
297
+ // #756/#786: auditor reask / verbatim submission body ride summons.instruction on resume.
298
298
  const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
299
299
  const summons: SameTicketSummonsMaterials = {
300
300
  ...(resumeInstruction === undefined
@@ -53,7 +53,7 @@ export type NotaryRunEnv = PostAdmissionEnv & {
53
53
  */
54
54
  reviewReask?: string;
55
55
  /**
56
- * #753/#750 same-parent re-summons: human-readable new-submission pointers.
56
+ * #786 same-parent re-summons: verbatim parent-submission body.
57
57
  * Rides summons.instruction when reviewReask is absent. Fresh mint ignores it.
58
58
  */
59
59
  gateReviewInstruction?: string;
@@ -120,7 +120,7 @@ export async function runPublicNotary(
120
120
  throw error;
121
121
  }
122
122
  // #747: officer resume key is this parent source-run path (not ticket number).
123
- // #753: gate re-ask and new-submission pointers share summons.instruction
123
+ // #753/#786: gate re-ask and verbatim submission body share summons.instruction
124
124
  // (reask wins when both present; no parallel stack).
125
125
  {
126
126
  const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
@@ -148,7 +148,7 @@ export async function runPublicNotary(
148
148
  if (resumed !== undefined) return resumed;
149
149
  // Reask without a prior same-parent run cannot deliver the plain-language ask
150
150
  // on a fresh mint without inventing a second prompt path — fail loud (#753).
151
- // gateReviewInstruction alone is resume-only materials; fresh mint ignores it.
151
+ // gateReviewInstruction (verbatim body) alone is resume-only; fresh mint ignores it.
152
152
  if (env.reviewReask !== undefined) {
153
153
  presentStructuralRejection(
154
154
  new CliUsageError(
@@ -60,12 +60,18 @@ export type PublicSummonRequest = {
60
60
  */
61
61
  readonly reviewReask?: string;
62
62
  /**
63
- * #753/#750 same-parent re-summons: human-readable pointer materials for the
64
- * new parent submission (source run + optional gate-submission-candidate).
63
+ * #786 same-parent re-summons: verbatim parent-submission body for the gate officer.
65
64
  * Rides summons.instruction when reviewReask is absent. Fresh mint ignores it.
66
- * Never folded into argv / parent-run lookup keys.
65
+ * Never folded into argv / parent-run lookup keys (ADR 0079 卷宗指针 stays pure).
67
66
  */
68
67
  readonly gateReviewInstruction?: string;
68
+ /**
69
+ * Offline test inject — same face as public CLI env.roleTurnHost. Production
70
+ * summons leave this unset and use the seat-resolved host.
71
+ */
72
+ readonly roleTurnHost?: RoleTurnHost;
73
+ /** Offline test inject for deterministic run ids (same face as public CLI). */
74
+ readonly createRunId?: () => string;
69
75
  };
70
76
 
71
77
  export type PublicSummonResult = {
@@ -293,10 +299,13 @@ export async function summonPublicRole(
293
299
  ...(options.signal === undefined ? {} : { signal: options.signal }),
294
300
  // #753: reask rides the existing notary same-ticket resume summons.instruction.
295
301
  ...(options.reviewReask === undefined ? {} : { reviewReask: options.reviewReask }),
296
- // #753/#750: new-submission pointers on same-parent resume (not conclusion re-ask).
302
+ // #786: verbatim submission body on same-parent resume (not conclusion re-ask).
297
303
  ...(options.gateReviewInstruction === undefined
298
304
  ? {}
299
305
  : { gateReviewInstruction: options.gateReviewInstruction }),
306
+ // Offline test injects — same faces as public CLI env (production leaves unset).
307
+ ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
308
+ ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
300
309
  };
301
310
  const captured = options.io === undefined ? createCapturingIo() : undefined;
302
311
  const io = options.io ?? captured!.io;
@@ -412,10 +421,14 @@ export async function summonGateOfficer(options: {
412
421
  */
413
422
  readonly reask?: string;
414
423
  /**
415
- * Path of this turn's gate-submission-candidate when written (#632 / #753).
416
- * Becomes human-readable resume materials when reask is absent.
424
+ * In-flight parent 交卷 body (tool-call arguments). Relayed verbatim on resume
425
+ * when reask is absent (#786). Never a path pointer substitute.
417
426
  */
418
- readonly submissionCandidatePath?: string;
427
+ readonly submission?: unknown;
428
+ /** Offline test inject — forwarded to summonPublicRole. */
429
+ readonly roleTurnHost?: RoleTurnHost;
430
+ /** Offline test inject — forwarded to summonPublicRole. */
431
+ readonly createRunId?: () => string;
419
432
  }): Promise<PublicSummonResult> {
420
433
  let home = options.home;
421
434
  if (home === undefined) {
@@ -423,16 +436,13 @@ export async function summonGateOfficer(options: {
423
436
  // Hard path resolve: fail loud — never fall through to packageMachineHome (#604 / #675).
424
437
  home = homeFromRunDirectory(options.sourceRunDirectory);
425
438
  }
426
- // Conclusion re-ask keeps sole ownership of reviewReask. New-submission materials
427
- // ride a separate field so first mint never fails the "reask requires prior" gate.
439
+ // Conclusion re-ask keeps sole ownership of reviewReask. New-submission body
440
+ // rides a separate field so first mint never fails the "reask requires prior" gate.
428
441
  let gateReviewInstruction: string | undefined;
429
- if (options.reask === undefined) {
442
+ if (options.reask === undefined && options.submission !== undefined) {
430
443
  const { buildGateOfficerReviewInstruction } = await import("./auditor-dossier-tool.ts");
431
444
  gateReviewInstruction = buildGateOfficerReviewInstruction({
432
- sourceRunDirectory: options.sourceRunDirectory,
433
- ...(options.submissionCandidatePath === undefined
434
- ? {}
435
- : { submissionCandidatePath: options.submissionCandidatePath }),
445
+ submission: options.submission,
436
446
  });
437
447
  }
438
448
  const common = {
@@ -445,6 +455,8 @@ export async function summonGateOfficer(options: {
445
455
  ...(gateReviewInstruction === undefined
446
456
  ? {}
447
457
  : { gateReviewInstruction }),
458
+ ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
459
+ ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
448
460
  } as const;
449
461
  if (options.officer === "notary") {
450
462
  // #753: reask rides summonPublicRole → runPublicNotary summons.instruction