@akagilnc/pi-workflow-roles 0.1.3718 → 0.1.3733
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/dist/acp-host/description.js +19 -5
- package/dist/acp-host/production-host.js +210 -85
- package/dist/acp-host/seat-profile-soul.js +78 -0
- package/dist/auditor-dossier-tool.js +15 -9
- package/dist/gatekeeper-role.js +13 -10
- package/dist/host-descriptions.js +26 -0
- package/dist/public-cli/inspector-run.js +2 -2
- package/dist/public-cli/instruction-seat-run.js +1 -1
- package/dist/public-cli/main.js +27 -0
- package/dist/public-cli/notary-run.js +2 -2
- package/dist/public-role-summons.js +10 -6
- package/package.json +1 -1
- package/src/acp-host/description.ts +39 -4
- package/src/acp-host/production-host.ts +16 -1
- package/src/acp-host/role-envelope.ts +2 -1
- package/src/acp-host/role-turn-host.ts +47 -23
- package/src/acp-host/seat-profile-soul.ts +97 -0
- package/src/auditor-dossier-tool.ts +26 -14
- package/src/gatekeeper-role.ts +32 -12
- package/src/host-descriptions.ts +26 -0
- package/src/public-cli/inspector-run.ts +3 -3
- package/src/public-cli/instruction-seat-run.ts +2 -2
- package/src/public-cli/notary-run.ts +3 -3
- package/src/public-role-summons.ts +26 -14
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seat-scoped host profile soul delivery (#644 / 拍 2).
|
|
3
|
+
*
|
|
4
|
+
* Hermes has no per-session systemPrompt channel; identity is the profile's
|
|
5
|
+
* SOUL.md. Each seat owns `profiles/<namePrefix><role>/`, and SOUL.md is a
|
|
6
|
+
* symlink to the packaged `souls/<role>.md` (package is the sole soul source).
|
|
7
|
+
*/
|
|
8
|
+
import { constants } from "node:fs";
|
|
9
|
+
import { access, copyFile, lstat, mkdir, readlink, symlink, unlink } from "node:fs/promises";
|
|
10
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
export type SeatProfileSoul = Readonly<{
|
|
13
|
+
/** Argv flag selecting the profile (hermes pre-argparse `-p`). */
|
|
14
|
+
flag: string;
|
|
15
|
+
/** Profile id = `${namePrefix}${role}` (e.g. `ak-judge`). */
|
|
16
|
+
namePrefix: string;
|
|
17
|
+
/** Profile directory root relative to the operator home (`.hermes/profiles`). */
|
|
18
|
+
profilesRootFromHome: readonly string[];
|
|
19
|
+
/** Soul filename inside the profile directory. */
|
|
20
|
+
soulFileName: string;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
/** Profile id for one seat role. */
|
|
24
|
+
export function seatProfileName(spec: SeatProfileSoul, role: string): string {
|
|
25
|
+
return `${spec.namePrefix}${role}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Packaged soul path for one role (`souls/<role>.md`). */
|
|
29
|
+
export function packageRoleSoulPath(packageRoot: string, role: string): string {
|
|
30
|
+
return join(packageRoot, "souls", `${role}.md`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
await access(path, constants.F_OK);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Ensure the seat profile directory exists and SOUL.md is a symlink to the
|
|
44
|
+
* packaged role soul. On first create, copy credential surfaces from the host
|
|
45
|
+
* root (parent of the profiles root) so the profile can authenticate without
|
|
46
|
+
* touching the default profile in place. Returns the profile id for argv.
|
|
47
|
+
*/
|
|
48
|
+
export async function ensureSeatProfileSoul(options: {
|
|
49
|
+
readonly spec: SeatProfileSoul;
|
|
50
|
+
readonly operatorHome: string;
|
|
51
|
+
readonly packageRoot: string;
|
|
52
|
+
readonly role: string;
|
|
53
|
+
}): Promise<string> {
|
|
54
|
+
const { spec, operatorHome, packageRoot, role } = options;
|
|
55
|
+
const profileName = seatProfileName(spec, role);
|
|
56
|
+
const soulTarget = resolve(packageRoleSoulPath(packageRoot, role));
|
|
57
|
+
if (!(await pathExists(soulTarget))) {
|
|
58
|
+
throw new Error(`packaged role soul missing: ${soulTarget}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const profilesRoot = join(operatorHome, ...spec.profilesRootFromHome);
|
|
62
|
+
const profileDir = join(profilesRoot, profileName);
|
|
63
|
+
const hostRoot = dirname(profilesRoot);
|
|
64
|
+
const soulPath = join(profileDir, spec.soulFileName);
|
|
65
|
+
|
|
66
|
+
if (!(await pathExists(profileDir))) {
|
|
67
|
+
await mkdir(profileDir, { recursive: true });
|
|
68
|
+
// First-create credential bootstrap only. Never rewrite an existing profile's
|
|
69
|
+
// auth/config; never write into the host root / default profile.
|
|
70
|
+
for (const name of ["auth.json", ".env", "config.yaml"] as const) {
|
|
71
|
+
const source = join(hostRoot, name);
|
|
72
|
+
if (!(await pathExists(source))) continue;
|
|
73
|
+
await copyFile(source, join(profileDir, name));
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
await mkdir(profileDir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const desiredLink = relative(profileDir, soulTarget);
|
|
80
|
+
let current: string | undefined;
|
|
81
|
+
try {
|
|
82
|
+
const st = await lstat(soulPath);
|
|
83
|
+
if (st.isSymbolicLink()) {
|
|
84
|
+
current = await readlink(soulPath);
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
current = undefined;
|
|
88
|
+
}
|
|
89
|
+
if (current === desiredLink || current === soulTarget) {
|
|
90
|
+
return profileName;
|
|
91
|
+
}
|
|
92
|
+
if (await pathExists(soulPath) || current !== undefined) {
|
|
93
|
+
await unlink(soulPath);
|
|
94
|
+
}
|
|
95
|
+
await symlink(desiredLink, soulPath);
|
|
96
|
+
return profileName;
|
|
97
|
+
}
|
|
@@ -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
|
-
*
|
|
67
|
-
*
|
|
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
|
|
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
|
|
73
|
-
readonly submissionCandidatePath?: string;
|
|
93
|
+
readonly submission: unknown;
|
|
74
94
|
}): string {
|
|
75
|
-
|
|
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
|
/**
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -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
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
...(
|
|
322
|
-
|
|
323
|
-
|
|
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(
|
|
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,
|
package/src/host-descriptions.ts
CHANGED
|
@@ -24,6 +24,7 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
24
24
|
suffix: Object.freeze(["stdio"]),
|
|
25
25
|
modelFlag: "--model",
|
|
26
26
|
}),
|
|
27
|
+
modelPassing: "argv",
|
|
27
28
|
boundResume: "session/load",
|
|
28
29
|
sessionBindingFile: "grok-acp-session.json",
|
|
29
30
|
childEnv: Object.freeze({
|
|
@@ -32,6 +33,31 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
32
33
|
GROK_SUBAGENTS: "0",
|
|
33
34
|
}),
|
|
34
35
|
}),
|
|
36
|
+
/**
|
|
37
|
+
* Operator home `~/.hermes`, native session/load resume, `acp` subcommand.
|
|
38
|
+
* Model arrives as an ACP `session/set_model` RPC with modelId `provider:model`
|
|
39
|
+
* (seat table provider + model concatenated). Reasoning is the global
|
|
40
|
+
* `--reasoning` flag before `acp`. Soul is the seat profile SOUL.md symlink
|
|
41
|
+
* (`hermes -p ak-<role> …`); package `souls/<role>.md` is the sole source.
|
|
42
|
+
*/
|
|
43
|
+
"hermes": Object.freeze({
|
|
44
|
+
binaryFromHome: Object.freeze([".local", "bin", "hermes"]),
|
|
45
|
+
argv: Object.freeze({
|
|
46
|
+
prefix: Object.freeze(["acp"]),
|
|
47
|
+
suffix: Object.freeze([]),
|
|
48
|
+
thinkingFlag: "--reasoning",
|
|
49
|
+
}),
|
|
50
|
+
modelPassing: "set_model",
|
|
51
|
+
boundResume: "session/load",
|
|
52
|
+
sessionBindingFile: "hermes-acp-session.json",
|
|
53
|
+
childEnv: Object.freeze({}),
|
|
54
|
+
seatProfileSoul: Object.freeze({
|
|
55
|
+
flag: "-p",
|
|
56
|
+
namePrefix: "ak-",
|
|
57
|
+
profilesRootFromHome: Object.freeze([".hermes", "profiles"]),
|
|
58
|
+
soulFileName: "SOUL.md",
|
|
59
|
+
}),
|
|
60
|
+
}),
|
|
35
61
|
});
|
|
36
62
|
|
|
37
63
|
export function lookupHostDescription(host: string): AcpHostDescription | undefined {
|
|
@@ -51,7 +51,7 @@ export type InspectorRunEnv = PostAdmissionEnv & {
|
|
|
51
51
|
*/
|
|
52
52
|
reviewReask?: string;
|
|
53
53
|
/**
|
|
54
|
-
* #
|
|
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 /
|
|
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
|
-
* #
|
|
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 /
|
|
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
|
-
* #
|
|
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
|
|
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
|
|
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
|
-
* #
|
|
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
|
-
// #
|
|
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
|
-
*
|
|
416
|
-
*
|
|
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
|
|
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
|
|
427
|
-
//
|
|
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
|
-
|
|
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
|