@akagilnc/pi-workflow-roles 0.1.4035 → 0.1.4062
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/production-host.js +476 -250
- package/dist/doctor-auditor.js +1 -1
- package/dist/dossier-resolution.js +21 -8
- package/dist/gatekeeper-role.js +34 -27
- package/dist/headless-host/description.js +7 -8
- package/dist/headless-host/production-host.js +503 -264
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +9 -6
- package/dist/public-cli/main.js +239 -112
- package/dist/public-cli/notary-run.js +7 -4
- package/dist/public-cli/post-admission.js +66 -59
- package/dist/public-cli/run-lifecycle.js +3 -0
- package/dist/public-cli/settlement.js +24 -7
- package/dist/public-role-summons.js +8 -0
- package/dist/submission-ledger.js +30 -8
- package/dist/user-dialogue-stdin.js +33 -0
- package/extensions/role-runtime.ts +1 -0
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +1 -1
- package/src/doctor-auditor.ts +1 -1
- package/src/dossier-resolution.ts +26 -8
- package/src/external-host-turn-loop.ts +9 -0
- package/src/gatekeeper-pass-envelope.ts +6 -0
- package/src/gatekeeper-role.ts +47 -28
- package/src/headless-host/description.ts +8 -11
- package/src/headless-host/role-turn-host.ts +20 -5
- package/src/host-contracts.ts +22 -3
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -0
- package/src/navigator-work-context.ts +3 -4
- package/src/notary-role.ts +1 -0
- package/src/packaged-role-registry.ts +1 -1
- package/src/pi/adapter.ts +19 -4
- package/src/pi/role-turn-host.ts +48 -14
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +17 -9
- package/src/public-cli/invocation.ts +2 -0
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/post-admission.ts +74 -65
- package/src/public-cli/run-lifecycle.ts +3 -0
- package/src/public-cli/settlement.ts +32 -6
- package/src/public-role-summons.ts +31 -7
- package/src/role-envelope.ts +9 -30
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +95 -19
- package/src/submission-ledger.ts +48 -13
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +4 -0
package/src/gatekeeper-role.ts
CHANGED
|
@@ -86,18 +86,30 @@ export type GateOfficerSummon = (
|
|
|
86
86
|
* conclusion (#753 / #756). Hosted as same-ticket resume instruction.
|
|
87
87
|
*/
|
|
88
88
|
reask?: string,
|
|
89
|
+
/**
|
|
90
|
+
* In-flight parent 交卷 body (tool-call arguments). Production default relays
|
|
91
|
+
* it verbatim on the officer dialogue content channel (#786 / #879).
|
|
92
|
+
* Identity-bound at the submit site — never recovered as latest toolCall.
|
|
93
|
+
*/
|
|
94
|
+
submission?: unknown,
|
|
89
95
|
) => Promise<PublicSummonResult>;
|
|
90
96
|
|
|
91
97
|
export type RunGatekeeperOptions = {
|
|
92
98
|
readonly context: ExtensionContext | HostContext;
|
|
93
99
|
readonly subject: GatekeeperSubject;
|
|
94
100
|
readonly signal?: AbortSignal;
|
|
95
|
-
/** Run directory of the parent role (pointer
|
|
101
|
+
/** Run directory of the parent role (binding pointer, ADR 0079 / #879). */
|
|
96
102
|
readonly runDirectory?: string;
|
|
97
103
|
/**
|
|
98
104
|
* Plain-language re-ask for this summon (resume speaker after non-three-state).
|
|
99
105
|
*/
|
|
100
106
|
readonly reask?: string;
|
|
107
|
+
/**
|
|
108
|
+
* In-flight parent typed payload for this gate turn. Relayed verbatim as
|
|
109
|
+
* officer dialogue content (#879). Call site passes the current tool args —
|
|
110
|
+
* code must not scan session latest/mtime to recover it.
|
|
111
|
+
*/
|
|
112
|
+
readonly submission?: unknown;
|
|
101
113
|
/**
|
|
102
114
|
* Test seam for public-role summons. Production calls the shared public
|
|
103
115
|
* activation path (#675); inject only in offline tracers.
|
|
@@ -198,15 +210,26 @@ function projectOfficerDecision(
|
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
213
|
+
* This-court officer payloads for the parent return path (#879).
|
|
214
|
+
* Only settlement-scoped roleOutcome.payloads (courtAttempt seal) carry this-court
|
|
215
|
+
* identity. Never guess from undivided submissions — sole row included — history
|
|
216
|
+
* stays on terminal.submissions (#836 presentation).
|
|
204
217
|
*/
|
|
205
|
-
function
|
|
218
|
+
function thisCourtOfficerPayloads(terminal: TerminalResult | undefined): readonly unknown[] {
|
|
206
219
|
const outcome = terminal?.roleOutcome;
|
|
207
220
|
if (outcome !== undefined && (outcome.kind === "accepted" || outcome.kind === "audit_escalation")) {
|
|
208
|
-
|
|
221
|
+
if (outcome.payloads !== undefined && outcome.payloads.length > 0) return outcome.payloads;
|
|
222
|
+
}
|
|
223
|
+
if (outcome?.kind === "failure" && outcome.payloads !== undefined && outcome.payloads.length > 0) {
|
|
224
|
+
return outcome.payloads;
|
|
209
225
|
}
|
|
226
|
+
// No sole-row identity guess: undivided submissions are not this-court (#879).
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Failure/transport channel may still surface every recorded payload beside the failure (#836 A.3). */
|
|
231
|
+
function officerFailurePayloads(terminal: TerminalResult | undefined): readonly unknown[] {
|
|
232
|
+
const outcome = terminal?.roleOutcome;
|
|
210
233
|
if (outcome?.kind === "failure") return outcome.payloads ?? terminal?.submissions ?? [];
|
|
211
234
|
return terminal?.submissions ?? [];
|
|
212
235
|
}
|
|
@@ -219,19 +242,9 @@ function projectOfficerPayloads(
|
|
|
219
242
|
if (payloads.length === 0) {
|
|
220
243
|
return projectOfficerDecision(officer, undefined, fallbackStatus);
|
|
221
244
|
}
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
if (payloads.length === 1) return queued;
|
|
226
|
-
if (
|
|
227
|
-
queued.status === "pass"
|
|
228
|
-
|| queued.status === "bounce"
|
|
229
|
-
|| queued.status === "escalate"
|
|
230
|
-
|| queued.status === "needs_reask"
|
|
231
|
-
) {
|
|
232
|
-
return { ...queued, receipt: payloads };
|
|
233
|
-
}
|
|
234
|
-
return queued;
|
|
245
|
+
// This-court multi-submit: queue the latest seal of THIS court only (#836 呈现≠排队).
|
|
246
|
+
// Single this-court seal is the common path. Never fold history into an array receipt.
|
|
247
|
+
return projectOfficerDecision(officer, payloads[payloads.length - 1], fallbackStatus);
|
|
235
248
|
}
|
|
236
249
|
|
|
237
250
|
function projectOfficerTerminal(
|
|
@@ -240,16 +253,17 @@ function projectOfficerTerminal(
|
|
|
240
253
|
): GatekeeperResult {
|
|
241
254
|
const terminal: TerminalResult | undefined = summoned.terminal;
|
|
242
255
|
const outcome = terminal?.roleOutcome;
|
|
243
|
-
const
|
|
256
|
+
const thisCourt = thisCourtOfficerPayloads(terminal);
|
|
244
257
|
if (outcome === undefined) {
|
|
245
258
|
const detail = summoned.stderr ?? "";
|
|
259
|
+
const failurePayloads = officerFailurePayloads(terminal);
|
|
246
260
|
return {
|
|
247
261
|
status: "transport_failure",
|
|
248
262
|
stage: officer,
|
|
249
263
|
reason: detail.length > 0
|
|
250
264
|
? `${gateSeatLabel(officer)} public summon exit ${summoned.exitCode}: ${detail}`
|
|
251
265
|
: `${gateSeatLabel(officer)} public summon produced no terminal (exit ${summoned.exitCode})`,
|
|
252
|
-
submission:
|
|
266
|
+
submission: failurePayloads.length > 0 ? failurePayloads : summoned,
|
|
253
267
|
};
|
|
254
268
|
}
|
|
255
269
|
if (outcome.kind === "no_receipt") {
|
|
@@ -263,30 +277,33 @@ function projectOfficerTerminal(
|
|
|
263
277
|
};
|
|
264
278
|
}
|
|
265
279
|
if (outcome.kind === "failure") {
|
|
280
|
+
const failurePayloads = officerFailurePayloads(terminal);
|
|
266
281
|
return {
|
|
267
282
|
status: "transport_failure",
|
|
268
283
|
stage: officer,
|
|
269
284
|
reason: outcome.diagnostic,
|
|
270
|
-
submission:
|
|
285
|
+
submission: failurePayloads.length > 0 ? failurePayloads : outcome.decisiveFacts,
|
|
271
286
|
};
|
|
272
287
|
}
|
|
273
288
|
if (outcome.kind === "audit_escalation") {
|
|
274
289
|
return {
|
|
275
290
|
status: "escalate",
|
|
276
291
|
officer,
|
|
277
|
-
receipt
|
|
292
|
+
// This-court receipt only (#879) — historical rows remain on terminal.submissions.
|
|
293
|
+
receipt: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
|
|
278
294
|
};
|
|
279
295
|
}
|
|
280
296
|
if (outcome.kind === "accepted") {
|
|
281
297
|
// outcome.status is the fixture/compat leaf: production settlement leaves
|
|
282
298
|
// it undefined once payloads are recorded, so this only matters when a
|
|
283
299
|
// caller still supplies status without any recorded payload (#836 hang).
|
|
284
|
-
return projectOfficerPayloads(officer,
|
|
300
|
+
return projectOfficerPayloads(officer, thisCourt, outcome.status);
|
|
285
301
|
}
|
|
286
302
|
return {
|
|
287
303
|
status: "needs_reask",
|
|
288
304
|
officer,
|
|
289
|
-
|
|
305
|
+
// This-court receipt only (#879).
|
|
306
|
+
receipt: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
|
|
290
307
|
};
|
|
291
308
|
}
|
|
292
309
|
|
|
@@ -324,13 +341,13 @@ export async function projectGatekeeperRun(
|
|
|
324
341
|
},
|
|
325
342
|
};
|
|
326
343
|
}
|
|
327
|
-
// #
|
|
328
|
-
//
|
|
344
|
+
// #879: binding pointer = parent run directory; dialogue content = this-turn
|
|
345
|
+
// typed payload passed explicitly (never latest-toolCall recovery, A7.1–A7.3).
|
|
329
346
|
let summoned: PublicSummonResult;
|
|
330
347
|
try {
|
|
331
348
|
const summon =
|
|
332
349
|
options.summonOfficer
|
|
333
|
-
?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask) => {
|
|
350
|
+
?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask, nextSubmission) => {
|
|
334
351
|
const { summonGateOfficer } = await import("./public-role-summons.ts");
|
|
335
352
|
return summonGateOfficer({
|
|
336
353
|
officer: nextOfficer,
|
|
@@ -338,6 +355,7 @@ export async function projectGatekeeperRun(
|
|
|
338
355
|
cwd: options.context.cwd ?? process.cwd(),
|
|
339
356
|
...(officerSignal === undefined ? {} : { signal: officerSignal }),
|
|
340
357
|
...(reask === undefined ? {} : { reask }),
|
|
358
|
+
...(nextSubmission === undefined ? {} : { submission: nextSubmission }),
|
|
341
359
|
...(options.home === undefined ? {} : { home: options.home }),
|
|
342
360
|
...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
|
|
343
361
|
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
@@ -349,6 +367,7 @@ export async function projectGatekeeperRun(
|
|
|
349
367
|
runDirectory,
|
|
350
368
|
options.signal,
|
|
351
369
|
options.reask,
|
|
370
|
+
options.submission,
|
|
352
371
|
);
|
|
353
372
|
} catch (error) {
|
|
354
373
|
return {
|
|
@@ -22,7 +22,7 @@ export type ClaudePrintHostDescription = HeadlessHostBase & Readonly<{
|
|
|
22
22
|
* are composed by the adapter from the turn request — not listed here.
|
|
23
23
|
*/
|
|
24
24
|
fixedArgs: readonly string[];
|
|
25
|
-
/** Print-mode flag
|
|
25
|
+
/** Print-mode flag (e.g. `-p`); user prompt rides stdin, not this flag's value (#879). */
|
|
26
26
|
promptFlag: string;
|
|
27
27
|
/** CLI flag for the seat model (e.g. `--model`). */
|
|
28
28
|
modelFlag: string;
|
|
@@ -76,11 +76,11 @@ export function resolveHeadlessBinary(
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Build one Claude print-mode argv for a single process turn.
|
|
79
|
-
* Shape: `<promptFlag> <
|
|
79
|
+
* Shape: `<promptFlag> <fixedArgs…> <system/schema/mcp/model/effort/session…>`.
|
|
80
|
+
* User dialogue rides stdin, not an argv element (#879 / ARG_MAX).
|
|
80
81
|
*/
|
|
81
82
|
export function headlessTurnArgs(options: {
|
|
82
83
|
readonly description: ClaudePrintHostDescription;
|
|
83
|
-
readonly prompt: string;
|
|
84
84
|
/** Absolute path written by the adapter; paired with `systemPromptFlag`. */
|
|
85
85
|
readonly systemPromptPath: string;
|
|
86
86
|
readonly jsonSchema: Readonly<Record<string, unknown>>;
|
|
@@ -94,7 +94,6 @@ export function headlessTurnArgs(options: {
|
|
|
94
94
|
const { description } = options;
|
|
95
95
|
const args: string[] = [
|
|
96
96
|
description.promptFlag,
|
|
97
|
-
options.prompt,
|
|
98
97
|
...description.fixedArgs,
|
|
99
98
|
description.systemPromptFlag,
|
|
100
99
|
options.systemPromptPath,
|
|
@@ -388,13 +387,12 @@ function codexMcpConfigArgs(
|
|
|
388
387
|
|
|
389
388
|
/**
|
|
390
389
|
* Build one `codex exec` / `codex exec resume` argv (#646).
|
|
391
|
-
* New: `exec --approve-for-me … prompt
|
|
392
|
-
* Resume: `exec --approve-for-me resume <thread_id> …
|
|
390
|
+
* New: `exec --approve-for-me … -- -` (prompt on stdin).
|
|
391
|
+
* Resume: `exec --approve-for-me resume <thread_id> … -- -`.
|
|
393
392
|
* Approval stays on the parent command so new and resumed turns use Codex's
|
|
394
393
|
* automatic review with its workspace-write sandbox.
|
|
395
394
|
*/
|
|
396
395
|
export function codexTurnArgs(options: {
|
|
397
|
-
readonly prompt: string;
|
|
398
396
|
/** Absolute path for `-c model_instructions_file=…`. */
|
|
399
397
|
readonly systemPromptPath: string;
|
|
400
398
|
/** Absolute path for `--output-schema` (closed transport schema). */
|
|
@@ -455,10 +453,9 @@ export function codexTurnArgs(options: {
|
|
|
455
453
|
args.push("--skip-git-repo-check");
|
|
456
454
|
}
|
|
457
455
|
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
args.push("--", options.prompt);
|
|
456
|
+
// `-` after `--` is Codex's stdin prompt sentinel. `--` still stops option
|
|
457
|
+
// parsing; the user body itself is not an argv element (#879 / ARG_MAX).
|
|
458
|
+
args.push("--", "-");
|
|
462
459
|
return args;
|
|
463
460
|
}
|
|
464
461
|
|
|
@@ -256,6 +256,8 @@ function spawnHeadlessTurn(options: {
|
|
|
256
256
|
readonly env: NodeJS.ProcessEnv;
|
|
257
257
|
readonly signal?: AbortSignal;
|
|
258
258
|
readonly timeoutMs?: number;
|
|
259
|
+
/** User dialogue body; omitted from argv so execve cannot E2BIG (#879). */
|
|
260
|
+
readonly stdin?: string;
|
|
259
261
|
/** Called for each complete stdout line as it arrives (live stream-json). */
|
|
260
262
|
readonly onStdoutLine?: (line: string) => void;
|
|
261
263
|
}): Promise<{ code: number | null; stdout: string; stderr: string; timedOut: boolean }> {
|
|
@@ -267,8 +269,20 @@ function spawnHeadlessTurn(options: {
|
|
|
267
269
|
const child = spawn(options.binary, [...options.args], {
|
|
268
270
|
cwd: options.cwd,
|
|
269
271
|
env: options.env,
|
|
270
|
-
stdio: ["
|
|
272
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
271
273
|
});
|
|
274
|
+
if (child.stdin === null) {
|
|
275
|
+
reject(new Error("headless child stdin pipe was not created"));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
let stdinDeliveryError: Error | undefined;
|
|
279
|
+
child.stdin.on("error", (error) => {
|
|
280
|
+
stdinDeliveryError ??= error;
|
|
281
|
+
});
|
|
282
|
+
if (options.stdin !== undefined) {
|
|
283
|
+
child.stdin.write(options.stdin);
|
|
284
|
+
}
|
|
285
|
+
child.stdin.end();
|
|
272
286
|
// Rolling retention only: last result-candidate line for final parse.
|
|
273
287
|
// Live events go to sitian via onStdoutLine — never accumulate the full stream.
|
|
274
288
|
let resultStdout = "";
|
|
@@ -318,6 +332,10 @@ function spawnHeadlessTurn(options: {
|
|
|
318
332
|
settled = true;
|
|
319
333
|
if (timer !== undefined) clearTimeout(timer);
|
|
320
334
|
options.signal?.removeEventListener("abort", onAbort);
|
|
335
|
+
if (stdinDeliveryError !== undefined) {
|
|
336
|
+
reject(stdinDeliveryError);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
321
339
|
resolve({ code, stdout: resultStdout, stderr, timedOut });
|
|
322
340
|
};
|
|
323
341
|
const onAbort = (): void => {
|
|
@@ -381,7 +399,6 @@ function terminalFromSpawned(
|
|
|
381
399
|
|
|
382
400
|
function buildTurnArgs(options: {
|
|
383
401
|
readonly description: HeadlessHostDescription;
|
|
384
|
-
readonly prompt: string;
|
|
385
402
|
readonly systemPromptPath: string;
|
|
386
403
|
readonly jsonSchema: Readonly<Record<string, unknown>>;
|
|
387
404
|
readonly mcpServers: readonly Readonly<Record<string, unknown>>[];
|
|
@@ -400,7 +417,6 @@ function buildTurnArgs(options: {
|
|
|
400
417
|
}
|
|
401
418
|
if (options.outputSchemaPath === undefined) throw new Error("codex requires an output schema path");
|
|
402
419
|
return codexTurnArgs({
|
|
403
|
-
prompt: options.prompt,
|
|
404
420
|
systemPromptPath: options.systemPromptPath,
|
|
405
421
|
outputSchemaPath: options.outputSchemaPath,
|
|
406
422
|
mcpServers: options.mcpServers,
|
|
@@ -419,7 +435,6 @@ function buildTurnArgs(options: {
|
|
|
419
435
|
if (options.mcpConfigPath === undefined) throw new Error("claude print-mode requires an MCP config path");
|
|
420
436
|
return headlessTurnArgs({
|
|
421
437
|
description: options.description,
|
|
422
|
-
prompt: options.prompt,
|
|
423
438
|
systemPromptPath: options.systemPromptPath,
|
|
424
439
|
jsonSchema: options.jsonSchema,
|
|
425
440
|
mcpConfigPath: options.mcpConfigPath,
|
|
@@ -479,7 +494,6 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
479
494
|
const gitCommonDir = codex ? resolveGitCommonDir(request.cwd) : undefined;
|
|
480
495
|
args = buildTurnArgs({
|
|
481
496
|
description: config.description,
|
|
482
|
-
prompt,
|
|
483
497
|
systemPromptPath,
|
|
484
498
|
jsonSchema: prepared.jsonSchema,
|
|
485
499
|
mcpServers: prepared.mcpServers,
|
|
@@ -508,6 +522,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
508
522
|
args,
|
|
509
523
|
cwd: request.cwd,
|
|
510
524
|
env,
|
|
525
|
+
stdin: prompt,
|
|
511
526
|
...(abortSignal === undefined ? {} : { signal: abortSignal }),
|
|
512
527
|
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
513
528
|
onStdoutLine(line) {
|
package/src/host-contracts.ts
CHANGED
|
@@ -130,7 +130,7 @@ export type RoleTurnActivation =
|
|
|
130
130
|
/** Required comparison-base revision for the unanchored merge-candidate diff. */
|
|
131
131
|
readonly baseRevision: string;
|
|
132
132
|
}
|
|
133
|
-
| { readonly role: "inspector" }
|
|
133
|
+
| { readonly role: "inspector"; readonly sourceRun?: string }
|
|
134
134
|
| { readonly role: "gatekeeper" }
|
|
135
135
|
| { readonly role: "navigator" }
|
|
136
136
|
| { readonly role: "auditor" }
|
|
@@ -160,6 +160,11 @@ export type RoleTurnHostTransition = {
|
|
|
160
160
|
readonly priorNativePaths: readonly string[];
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
+
/** Gate review officers (察院 / 符宝郎 / 审刑院). Single authority for seat checks. */
|
|
164
|
+
export function isOfficerReviewSeat(role: string): boolean {
|
|
165
|
+
return role === "notary" || role === "inspector" || role === "auditor";
|
|
166
|
+
}
|
|
167
|
+
|
|
163
168
|
/** One main-session turn request over the host-neutral execution seam. */
|
|
164
169
|
export type RoleTurnRequest = {
|
|
165
170
|
readonly principal: DurablePrincipal;
|
|
@@ -241,7 +246,21 @@ export interface DurablePrincipalAuthority {
|
|
|
241
246
|
type HostSessionManager = { getLeafEntry(): HostSessionEntry | undefined; getLeafId(): string | null | undefined; getEntries(): Iterable<HostSessionEntry>; getSessionDir(): string; getSessionFile(): string | undefined; getHeader?(): { readonly type: string; readonly id?: string } | null; setSessionFile?(path: string): void; appendCustomEntry?(customType: string, data?: unknown): unknown; };
|
|
242
247
|
|
|
243
248
|
/** Context supplied by a host for one activation and its interceptable events. */
|
|
244
|
-
export type HostContext = { cwd: string; mode: string; model: { readonly provider: string } | undefined; sessionManager: HostSessionManager; signal?: AbortSignal | undefined; ui?: { notify?(message: string, type?: "info" | "warning" | "error"): void }; transcript?(): string; abort(): void; };
|
|
249
|
+
export type HostContext = { cwd: string; mode: string; model: { readonly provider: string } | undefined; sessionManager: HostSessionManager; /** Per-turn admitted run directory (#879); never process-global env. */ runDirectory?: string; /** Per-turn court attempt (#879); never process-global env. */ courtAttemptId?: string; signal?: AbortSignal | undefined; ui?: { notify?(message: string, type?: "info" | "warning" | "error"): void }; transcript?(): string; abort(): void; };
|
|
250
|
+
|
|
251
|
+
/** Per-turn run directory; adapters must project any child-process identity. */
|
|
252
|
+
export function runDirectoryFromHostContext(context: HostContext): string | undefined {
|
|
253
|
+
return typeof context.runDirectory === "string" && context.runDirectory.trim() !== ""
|
|
254
|
+
? context.runDirectory
|
|
255
|
+
: undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Per-turn court attempt; absence never inherits ambient process identity. */
|
|
259
|
+
export function courtAttemptIdFromHostContext(context: HostContext): string | undefined {
|
|
260
|
+
return typeof context.courtAttemptId === "string" && context.courtAttemptId.trim() !== ""
|
|
261
|
+
? context.courtAttemptId
|
|
262
|
+
: undefined;
|
|
263
|
+
}
|
|
245
264
|
|
|
246
265
|
export type HostToolDefinition<S extends TSchema = TSchema, D = unknown, C = HostContext> = { name: string; label: string; description: string; promptSnippet?: string; parameters: S; execute( toolCallId: string, params: Static<S>, signal: AbortSignal | undefined, update: ((result: HostToolResult<D>) => void) | undefined, context: C, ): Promise<HostToolResult<D>>; /**
|
|
247
266
|
* #641 chain② opt-in: when the output params carry the shared infrastructure
|
|
@@ -356,7 +375,7 @@ export interface RoleHost {
|
|
|
356
375
|
getAllTools(): Array<{ name: string; sourceInfo?: { path?: string } }>;
|
|
357
376
|
setActiveTools(names: string[]): void;
|
|
358
377
|
getActiveTools(): string[];
|
|
359
|
-
requireGatekeeperPass?(options: { context: HostContext; subject: HostGatekeeperSubject; signal?: AbortSignal; hostActions: HostGatekeeperActions; toolCallId: string }): Promise<void>;
|
|
378
|
+
requireGatekeeperPass?(options: { context: HostContext; subject: HostGatekeeperSubject; signal?: AbortSignal; hostActions: HostGatekeeperActions; toolCallId: string; submission?: unknown }): Promise<void>;
|
|
360
379
|
on(event: "before_agent_start", handler: HostEventHandler<"before_agent_start">): void;
|
|
361
380
|
on(event: "input", handler: HostEventHandler<"input">): void;
|
|
362
381
|
on(event: "tool_call", handler: HostEventHandler<"tool_call">): void;
|
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
export const INSPECTOR_OUTPUT_TOOL_NAME = "ak_inspector_output" as const;
|
|
8
8
|
export const INSPECTOR_ACCEPTED_TEXT = "察院回执已接受";
|
|
9
|
+
export const INSPECTOR_SOURCE_RUN_FLAG = {
|
|
10
|
+
name: "ak-inspector-source-run",
|
|
11
|
+
definition: {
|
|
12
|
+
description: "察院初铸绑定的父 run 绝对路径",
|
|
13
|
+
type: "string" as const,
|
|
14
|
+
},
|
|
15
|
+
} as const;
|
|
9
16
|
|
|
10
17
|
export type InspectorOutput =
|
|
11
18
|
| { readonly status: "pass"; readonly findings?: unknown }
|
package/src/judge-role.ts
CHANGED
|
@@ -122,6 +122,8 @@ export function createJudgeRoleRuntime(
|
|
|
122
122
|
...(signal === undefined ? {} : { signal }),
|
|
123
123
|
hostActions,
|
|
124
124
|
toolCallId,
|
|
125
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
126
|
+
submission: parameters,
|
|
125
127
|
});
|
|
126
128
|
// #756: 审刑院合规路径 — same review-queue law as 符宝郎/察院.
|
|
127
129
|
// pass → accept; bounce|escalate → raw auditor receipt back to judge;
|
|
@@ -132,6 +134,8 @@ export function createJudgeRoleRuntime(
|
|
|
132
134
|
...(signal === undefined ? {} : { signal }),
|
|
133
135
|
hostActions,
|
|
134
136
|
toolCallId,
|
|
137
|
+
// #879: same parent payload for 审刑院; not recovered from session latest.
|
|
138
|
+
submission: parameters,
|
|
135
139
|
});
|
|
136
140
|
return {
|
|
137
141
|
content: [{ type: "text" as const, text: JUDGE_ACCEPTED_TEXT }],
|
|
@@ -6,7 +6,7 @@ import { readFile } from "node:fs/promises";
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
|
|
8
8
|
import { loadDoctorCase } from "./doctor-evidence.ts";
|
|
9
|
-
import type
|
|
9
|
+
import { runDirectoryFromHostContext, type HostContext } from "./host-contracts.ts";
|
|
10
10
|
import {
|
|
11
11
|
navigatorSubjectKey,
|
|
12
12
|
navigatorSubjectKeyForInput,
|
|
@@ -64,10 +64,9 @@ export async function loadNavigatorWorkContext(
|
|
|
64
64
|
subjectProvenance = "role_input";
|
|
65
65
|
}
|
|
66
66
|
// Public ak-role run: admitted request is the typed Navigator work-context source.
|
|
67
|
-
const publicRunDir =
|
|
67
|
+
const publicRunDir = runDirectoryFromHostContext(options.context);
|
|
68
68
|
const currentSessionDir = options.context.sessionManager.getSessionDir();
|
|
69
|
-
const isBoundPublicRun =
|
|
70
|
-
&& publicRunDir.trim() !== ""
|
|
69
|
+
const isBoundPublicRun = publicRunDir !== undefined
|
|
71
70
|
&& resolve(currentSessionDir) === resolve(publicRunDir, "session");
|
|
72
71
|
if (
|
|
73
72
|
options.role === "judge" &&
|
package/src/notary-role.ts
CHANGED
|
@@ -179,6 +179,7 @@ export function createNotaryRoleRuntime(
|
|
|
179
179
|
});
|
|
180
180
|
// Evidence assembly only (ADR 0018): soul + bound projection.
|
|
181
181
|
// Ticket value is envelope-admitted at activate — not re-read from flags here.
|
|
182
|
+
// #879 case dossier freeze→readingMaterial is envelope-owned (shared before_agent_start).
|
|
182
183
|
pi.on("before_agent_start", (event) => {
|
|
183
184
|
if (activation === undefined) {
|
|
184
185
|
throw new Error("符宝郎未激活");
|
|
@@ -174,7 +174,7 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
174
174
|
role: "inspector",
|
|
175
175
|
phases: [null],
|
|
176
176
|
outputTool: INSPECTOR_OUTPUT_TOOL_NAME,
|
|
177
|
-
inputFlag:
|
|
177
|
+
inputFlag: "ak-inspector-source-run",
|
|
178
178
|
phaseFlag: undefined,
|
|
179
179
|
activationStage: "load-and-install",
|
|
180
180
|
sessionMaterials: INSPECTOR_SESSION_MATERIALS,
|
package/src/pi/adapter.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
import { createOAuthKeepalive, type OAuthKeepaliveOptions } from "../oauth-keepalive.ts";
|
|
22
22
|
import { createRoleRuntimeExtension, type RoleRuntimeDependencies } from "../role-runtime.ts";
|
|
23
23
|
import { renderAgentStartMaterials } from "../agent-start-materials.ts";
|
|
24
|
+
import { readUserDialogueStdin } from "../user-dialogue-stdin.ts";
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
export type PiRoleHostAdapter = RoleEnvelopeHost;
|
|
@@ -45,6 +46,12 @@ function projectPiContext(context: ExtensionContext, transcriptFromContext?: (co
|
|
|
45
46
|
cwd: context.cwd,
|
|
46
47
|
mode: context.mode,
|
|
47
48
|
model: context.model === undefined ? undefined : { provider: context.model.provider },
|
|
49
|
+
...(typeof process.env.AK_ROLE_RUN_DIR === "string" && process.env.AK_ROLE_RUN_DIR.trim() !== ""
|
|
50
|
+
? { runDirectory: process.env.AK_ROLE_RUN_DIR }
|
|
51
|
+
: {}),
|
|
52
|
+
...(typeof process.env.AK_ROLE_COURT_ATTEMPT === "string" && process.env.AK_ROLE_COURT_ATTEMPT.trim() !== ""
|
|
53
|
+
? { courtAttemptId: process.env.AK_ROLE_COURT_ATTEMPT }
|
|
54
|
+
: {}),
|
|
48
55
|
sessionManager: {
|
|
49
56
|
getLeafEntry: () => context.sessionManager.getLeafEntry(),
|
|
50
57
|
getLeafId: () => context.sessionManager.getLeafId(),
|
|
@@ -78,6 +85,7 @@ function requirePiGatekeeperPass(options: {
|
|
|
78
85
|
signal?: AbortSignal;
|
|
79
86
|
hostActions: HostGatekeeperActions;
|
|
80
87
|
toolCallId: string;
|
|
88
|
+
submission?: unknown;
|
|
81
89
|
}): Promise<void> {
|
|
82
90
|
return requireGatekeeperPass({
|
|
83
91
|
context: options.context,
|
|
@@ -90,6 +98,7 @@ function requirePiGatekeeperPass(options: {
|
|
|
90
98
|
bindSubmissionNonPass: options.hostActions.bindSubmissionNonPass,
|
|
91
99
|
},
|
|
92
100
|
toolCallId: options.toolCallId,
|
|
101
|
+
...(options.submission === undefined ? {} : { submission: options.submission }),
|
|
93
102
|
});
|
|
94
103
|
}
|
|
95
104
|
|
|
@@ -99,13 +108,13 @@ function toPiResult<D>(result: HostToolResult<D>): HostToolResult<D> {
|
|
|
99
108
|
|
|
100
109
|
/** Fold a host before_agent_start return: push readingMaterial into the provider-visible
|
|
101
110
|
* systemPrompt via the shared renderer and strip the typed field before Pi sees it. */
|
|
102
|
-
function foldBeforeAgentStartReturn(result: unknown): BeforeAgentStartEventResult | void {
|
|
111
|
+
function foldBeforeAgentStartReturn(result: unknown, currentSystemPrompt: string): BeforeAgentStartEventResult | void {
|
|
103
112
|
if (result === undefined || result === null || typeof result !== "object") return undefined;
|
|
104
113
|
const record = result as { systemPrompt?: string; readingMaterial?: unknown };
|
|
105
114
|
const { systemPrompt, readingMaterial } = record;
|
|
106
115
|
const folded = readingMaterial === undefined
|
|
107
116
|
? systemPrompt
|
|
108
|
-
: renderAgentStartMaterials(systemPrompt ??
|
|
117
|
+
: renderAgentStartMaterials(systemPrompt ?? currentSystemPrompt, [readingMaterial]);
|
|
109
118
|
return folded === undefined ? {} : { systemPrompt: folded };
|
|
110
119
|
}
|
|
111
120
|
|
|
@@ -136,6 +145,12 @@ export function createPiRoleHostAdapter(
|
|
|
136
145
|
options: { transcriptFromContext?: (context: ExtensionContext) => string; oauthKeepalive?: OAuthKeepaliveOptions } = {},
|
|
137
146
|
): PiRoleHostAdapter {
|
|
138
147
|
const keepalive = createOAuthKeepalive(options.oauthKeepalive);
|
|
148
|
+
// Decode transport exactly once before Pi's native handler chain. Pi retains
|
|
149
|
+
// its own transform/image propagation and per-handler error isolation.
|
|
150
|
+
pi.on("input", (value) => {
|
|
151
|
+
const text = readUserDialogueStdin(value.text);
|
|
152
|
+
return text === value.text ? { action: "continue" } : { action: "transform", text };
|
|
153
|
+
});
|
|
139
154
|
const host: RoleHost = {
|
|
140
155
|
deliverSubmissionRejection(_rejection) {
|
|
141
156
|
// #836 删 1/A4.5: no package-authored non-sole resume sentence.
|
|
@@ -185,8 +200,8 @@ export function createPiRoleHostAdapter(
|
|
|
185
200
|
const [, handler] = registration;
|
|
186
201
|
pi.on("before_agent_start", (value, ctx) => {
|
|
187
202
|
const result = handler({ prompt: value.prompt, systemPrompt: value.systemPrompt, systemPromptOptions: value.systemPromptOptions }, context(ctx));
|
|
188
|
-
if (result instanceof Promise) return result.then(foldBeforeAgentStartReturn);
|
|
189
|
-
return foldBeforeAgentStartReturn(result);
|
|
203
|
+
if (result instanceof Promise) return result.then((settled) => foldBeforeAgentStartReturn(settled, value.systemPrompt));
|
|
204
|
+
return foldBeforeAgentStartReturn(result, value.systemPrompt);
|
|
190
205
|
});
|
|
191
206
|
} else if (registration[0] === "input") {
|
|
192
207
|
const [, handler] = registration;
|