@akagilnc/pi-workflow-roles 0.1.4021 → 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 +682 -355
- 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 +709 -369
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/navigator-attendance.js +2 -0
- package/dist/navigator-public-session.js +18 -34
- package/dist/navigator-session-contracts.js +19 -0
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/known-failure.js +3 -4
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/auto-resume.js +20 -7
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/diarist-run.js +9 -23
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +12 -7
- package/dist/public-cli/judge-run.js +3 -1
- package/dist/public-cli/main.js +440 -222
- 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 +78 -58
- package/dist/public-cli/terminal.js +1 -11
- package/dist/public-role-summons.js +8 -0
- package/dist/submission-ledger.js +139 -19
- 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 +30 -7
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -0
- package/src/navigator-attendance.ts +2 -0
- package/src/navigator-public-session.ts +19 -55
- package/src/navigator-session-contracts.ts +39 -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/known-failure.ts +3 -4
- package/src/pi/role-turn-host.ts +48 -14
- package/src/public-cli/auto-resume.ts +57 -20
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/cli.ts +2 -2
- package/src/public-cli/collector-run.ts +3 -1
- package/src/public-cli/countersign-run.ts +34 -27
- package/src/public-cli/diarist-run.ts +10 -27
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +20 -10
- package/src/public-cli/invocation.ts +2 -0
- package/src/public-cli/judge-run.ts +3 -1
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/post-admission.ts +74 -65
- package/src/public-cli/reviewer-run.ts +3 -1
- package/src/public-cli/run-lifecycle.ts +3 -0
- package/src/public-cli/settlement.ts +105 -80
- package/src/public-cli/terminal.ts +7 -16
- 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 +195 -26
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +4 -0
|
@@ -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
|
@@ -15,18 +15,22 @@ export type HostToolResult<T = unknown> = {
|
|
|
15
15
|
/** Opaque host-owned identity persisted with a Role run. */
|
|
16
16
|
export type DurablePrincipal = object & { readonly __durablePrincipal?: never };
|
|
17
17
|
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Controlled post-admission failure classes (ADR 0052 / #107). Owner = host contract.
|
|
20
|
+
* Closed set of typed facts only — never a fabricated "could not classify" label (#881).
|
|
21
|
+
* When no typed confirmation exists, omit cause and keep the original diagnostic / error pointer.
|
|
22
|
+
*/
|
|
19
23
|
export type ControlledFailureCause =
|
|
20
24
|
| "activation"
|
|
21
25
|
| "provider"
|
|
22
26
|
| "session"
|
|
23
27
|
| "output"
|
|
24
|
-
| "timeout"
|
|
25
|
-
| "unrecognized";
|
|
28
|
+
| "timeout";
|
|
26
29
|
|
|
27
30
|
/** Production-owned typed failure carried on a resolved turn result. */
|
|
28
31
|
export type RoleTurnKnownFailure = {
|
|
29
|
-
|
|
32
|
+
/** Present only when a typed fact confirms the class; omitted when unknown (#881). */
|
|
33
|
+
readonly cause?: ControlledFailureCause;
|
|
30
34
|
readonly identity?: {
|
|
31
35
|
readonly name?: string;
|
|
32
36
|
readonly code?: string | number;
|
|
@@ -126,7 +130,7 @@ export type RoleTurnActivation =
|
|
|
126
130
|
/** Required comparison-base revision for the unanchored merge-candidate diff. */
|
|
127
131
|
readonly baseRevision: string;
|
|
128
132
|
}
|
|
129
|
-
| { readonly role: "inspector" }
|
|
133
|
+
| { readonly role: "inspector"; readonly sourceRun?: string }
|
|
130
134
|
| { readonly role: "gatekeeper" }
|
|
131
135
|
| { readonly role: "navigator" }
|
|
132
136
|
| { readonly role: "auditor" }
|
|
@@ -156,6 +160,11 @@ export type RoleTurnHostTransition = {
|
|
|
156
160
|
readonly priorNativePaths: readonly string[];
|
|
157
161
|
};
|
|
158
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
|
+
|
|
159
168
|
/** One main-session turn request over the host-neutral execution seam. */
|
|
160
169
|
export type RoleTurnRequest = {
|
|
161
170
|
readonly principal: DurablePrincipal;
|
|
@@ -237,7 +246,21 @@ export interface DurablePrincipalAuthority {
|
|
|
237
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; };
|
|
238
247
|
|
|
239
248
|
/** Context supplied by a host for one activation and its interceptable events. */
|
|
240
|
-
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
|
+
}
|
|
241
264
|
|
|
242
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>>; /**
|
|
243
266
|
* #641 chain② opt-in: when the output params carry the shared infrastructure
|
|
@@ -352,7 +375,7 @@ export interface RoleHost {
|
|
|
352
375
|
getAllTools(): Array<{ name: string; sourceInfo?: { path?: string } }>;
|
|
353
376
|
setActiveTools(names: string[]): void;
|
|
354
377
|
getActiveTools(): string[];
|
|
355
|
-
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>;
|
|
356
379
|
on(event: "before_agent_start", handler: HostEventHandler<"before_agent_start">): void;
|
|
357
380
|
on(event: "input", handler: HostEventHandler<"input">): void;
|
|
358
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 }],
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
navigatorProviderFailure,
|
|
26
26
|
navigatorProviderFailureFromDiagnostics,
|
|
27
27
|
navigatorProviderFailureFromError,
|
|
28
|
+
navigatorProviderFailureFromPublicTerminal,
|
|
28
29
|
navigatorProviderFailureFromStatus,
|
|
29
30
|
navigatorUnavailableError,
|
|
30
31
|
parseNavigatorModelSetting,
|
|
@@ -46,6 +47,7 @@ export {
|
|
|
46
47
|
navigatorProviderFailure,
|
|
47
48
|
navigatorProviderFailureFromDiagnostics,
|
|
48
49
|
navigatorProviderFailureFromError,
|
|
50
|
+
navigatorProviderFailureFromPublicTerminal,
|
|
49
51
|
navigatorProviderFailureFromStatus,
|
|
50
52
|
navigatorUnavailableError,
|
|
51
53
|
parseNavigatorModelSetting,
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
import { sitianReport } from "./sitian-facade.ts";
|
|
9
9
|
import {
|
|
10
10
|
NavigatorUnavailableError,
|
|
11
|
-
|
|
11
|
+
navigatorProviderFailureFromPublicTerminal,
|
|
12
12
|
navigatorProviderFailureFromError,
|
|
13
|
-
navigatorProviderFailureFromStatus,
|
|
14
13
|
navigatorUnavailableError,
|
|
15
14
|
parseNavigatorModelSetting,
|
|
16
15
|
resolveNavigatorSeatSelection,
|
|
@@ -18,43 +17,6 @@ import {
|
|
|
18
17
|
type NavigatorSessionFactory,
|
|
19
18
|
} from "./navigator-session-contracts.ts";
|
|
20
19
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
21
|
-
import { lastRolePayloadRecord } from "./public-cli/terminal.ts";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Classify public-navigator failure terminal from structured decisiveFacts only
|
|
25
|
-
* (httpStatus / diagnostics / secondaryEvidence on the terminal) — same fields
|
|
26
|
-
* settlement already stamps from typed HTTP observation / provider stop.
|
|
27
|
-
*/
|
|
28
|
-
function providerFailureFromPublicTerminal(outcome: {
|
|
29
|
-
readonly cause: string;
|
|
30
|
-
readonly diagnostic: string;
|
|
31
|
-
readonly decisiveFacts: Readonly<Record<string, unknown>>;
|
|
32
|
-
}): NavigatorProviderFailureFact {
|
|
33
|
-
const facts = outcome.decisiveFacts;
|
|
34
|
-
const secondary =
|
|
35
|
-
typeof facts.secondaryEvidence === "object" && facts.secondaryEvidence !== null
|
|
36
|
-
? (facts.secondaryEvidence as Record<string, unknown>)
|
|
37
|
-
: undefined;
|
|
38
|
-
const httpStatus =
|
|
39
|
-
typeof secondary?.httpStatus === "number"
|
|
40
|
-
? secondary.httpStatus
|
|
41
|
-
: typeof facts.httpStatus === "number"
|
|
42
|
-
? facts.httpStatus
|
|
43
|
-
: typeof facts.errorCode === "number"
|
|
44
|
-
? facts.errorCode
|
|
45
|
-
: undefined;
|
|
46
|
-
const fromStatus = navigatorProviderFailureFromStatus(httpStatus);
|
|
47
|
-
if (fromStatus !== undefined) return fromStatus;
|
|
48
|
-
const diagnostics = secondary?.diagnostics ?? facts.diagnostics;
|
|
49
|
-
const fromDiagnostics = navigatorProviderFailureFromDiagnostics(diagnostics);
|
|
50
|
-
if (fromDiagnostics !== undefined) return fromDiagnostics;
|
|
51
|
-
const fromCode = navigatorProviderFailureFromError({
|
|
52
|
-
code: secondary?.code ?? facts.errorCode,
|
|
53
|
-
});
|
|
54
|
-
if (fromCode !== undefined) return fromCode;
|
|
55
|
-
if (outcome.cause === "provider") return { source: "transport", cause: "transport" };
|
|
56
|
-
return { source: "session", cause: "session" };
|
|
57
|
-
}
|
|
58
20
|
|
|
59
21
|
export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
|
|
60
22
|
return async ({ context, subject, tool }) => {
|
|
@@ -122,14 +84,16 @@ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
|
|
|
122
84
|
const outcome = summoned.terminal?.roleOutcome;
|
|
123
85
|
if (outcome === undefined) {
|
|
124
86
|
const detail = summoned.stderr?.trim() || `exit ${summoned.exitCode}`;
|
|
125
|
-
|
|
87
|
+
// Known source is the public-summon transport path; unconfirmed cause stays unknown.
|
|
88
|
+
providerFailure = { source: "transport", cause: "unknown" };
|
|
126
89
|
throw navigatorUnavailableError(
|
|
127
|
-
|
|
90
|
+
providerFailure.source,
|
|
128
91
|
new Error(`Navigator public summon produced no terminal (${detail})`),
|
|
92
|
+
providerFailure.cause,
|
|
129
93
|
);
|
|
130
94
|
}
|
|
131
95
|
if (outcome.kind === "failure") {
|
|
132
|
-
providerFailure =
|
|
96
|
+
providerFailure = navigatorProviderFailureFromPublicTerminal(outcome);
|
|
133
97
|
throw navigatorUnavailableError(
|
|
134
98
|
providerFailure.source,
|
|
135
99
|
new Error(outcome.diagnostic),
|
|
@@ -147,23 +111,23 @@ export function createNativeNavigatorSessionFactory(): NavigatorSessionFactory {
|
|
|
147
111
|
// Not a shape-unusable judgment on the navigator reply (#757).
|
|
148
112
|
return;
|
|
149
113
|
}
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
114
|
+
for (const payload of outcome.payloads ?? []) {
|
|
115
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) continue;
|
|
116
|
+
const candidates = (payload as { candidates?: unknown }).candidates;
|
|
117
|
+
if (!Array.isArray(candidates)) continue;
|
|
118
|
+
await tool.execute(
|
|
119
|
+
"navigator-public-prepare",
|
|
120
|
+
{ candidates },
|
|
121
|
+
undefined,
|
|
122
|
+
undefined,
|
|
123
|
+
context as never,
|
|
124
|
+
);
|
|
154
125
|
}
|
|
155
|
-
// Rejoin attendance prepare tool sink (same candidate shape as public advice).
|
|
156
|
-
await tool.execute(
|
|
157
|
-
"navigator-public-prepare",
|
|
158
|
-
{ candidates },
|
|
159
|
-
undefined,
|
|
160
|
-
undefined,
|
|
161
|
-
context as never,
|
|
162
|
-
);
|
|
163
126
|
} catch (error) {
|
|
164
127
|
if (error instanceof NavigatorUnavailableError) throw error;
|
|
165
128
|
const fact = navigatorProviderFailureFromError(error);
|
|
166
|
-
|
|
129
|
+
// Catch path is the public-summon seam (source transport); untyped cause stays unknown.
|
|
130
|
+
providerFailure = fact ?? { source: "transport", cause: "unknown" };
|
|
167
131
|
throw navigatorUnavailableError(providerFailure.source, error, providerFailure.cause);
|
|
168
132
|
}
|
|
169
133
|
})();
|
|
@@ -133,6 +133,45 @@ export function navigatorProviderFailureFromDiagnostics(diagnostics: unknown): N
|
|
|
133
133
|
return undefined;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Classify a public-navigator failure terminal from structured facts only.
|
|
138
|
+
* Untyped cause stays unknown — never relabeled session.
|
|
139
|
+
*/
|
|
140
|
+
export function navigatorProviderFailureFromPublicTerminal(outcome: {
|
|
141
|
+
readonly cause?: string;
|
|
142
|
+
readonly diagnostic: string;
|
|
143
|
+
readonly decisiveFacts: Readonly<Record<string, unknown>>;
|
|
144
|
+
}): NavigatorProviderFailureFact {
|
|
145
|
+
const facts = outcome.decisiveFacts;
|
|
146
|
+
const secondary =
|
|
147
|
+
typeof facts.secondaryEvidence === "object" && facts.secondaryEvidence !== null
|
|
148
|
+
? (facts.secondaryEvidence as Record<string, unknown>)
|
|
149
|
+
: undefined;
|
|
150
|
+
const httpStatus =
|
|
151
|
+
typeof secondary?.httpStatus === "number"
|
|
152
|
+
? secondary.httpStatus
|
|
153
|
+
: typeof facts.httpStatus === "number"
|
|
154
|
+
? facts.httpStatus
|
|
155
|
+
: typeof facts.errorCode === "number"
|
|
156
|
+
? facts.errorCode
|
|
157
|
+
: undefined;
|
|
158
|
+
const fromStatus = navigatorProviderFailureFromStatus(httpStatus);
|
|
159
|
+
if (fromStatus !== undefined) return fromStatus;
|
|
160
|
+
const diagnostics = secondary?.diagnostics ?? facts.diagnostics;
|
|
161
|
+
const fromDiagnostics = navigatorProviderFailureFromDiagnostics(diagnostics);
|
|
162
|
+
if (fromDiagnostics !== undefined) return fromDiagnostics;
|
|
163
|
+
const fromCode = navigatorProviderFailureFromError({
|
|
164
|
+
code: secondary?.code ?? facts.errorCode,
|
|
165
|
+
});
|
|
166
|
+
if (fromCode !== undefined) return fromCode;
|
|
167
|
+
// cause=provider names the known source region only; without status/diagnostics/code
|
|
168
|
+
// the specific cause stays unknown (#881 — never invent transport/session labels).
|
|
169
|
+
if (outcome.cause === "provider") return { source: "transport", cause: "unknown" };
|
|
170
|
+
const typed = navigatorUnavailableKey(outcome.cause);
|
|
171
|
+
if (typed !== undefined) return { source: typed, cause: typed };
|
|
172
|
+
return { source: "unknown", cause: "unknown" };
|
|
173
|
+
}
|
|
174
|
+
|
|
136
175
|
const navigatorProviderFailureSchema = Type.Object({
|
|
137
176
|
source: Type.Union([
|
|
138
177
|
Type.Literal("context"), Type.Literal("session"), Type.Literal("model"), Type.Literal("thinking"),
|
|
@@ -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;
|
package/src/pi/known-failure.ts
CHANGED
|
@@ -49,9 +49,8 @@ function sessionStopDetails(input: {
|
|
|
49
49
|
/**
|
|
50
50
|
* Project a native session assistant stop onto the existing knownFailure chain.
|
|
51
51
|
* Classification follows two-way testimony: typed HTTP status or SDK structure
|
|
52
|
-
* keeps provider; stopReason
|
|
53
|
-
*
|
|
54
|
-
* in details without rewriting; missing fields are omitted.
|
|
52
|
+
* keeps provider; stopReason / errorMessage prose alone never invents a class (#881).
|
|
53
|
+
* Present upstream payload is preserved in details without rewriting; missing fields are omitted.
|
|
55
54
|
*/
|
|
56
55
|
export function knownFailureFromProviderStop(input: {
|
|
57
56
|
readonly stopReason?: string;
|
|
@@ -70,7 +69,7 @@ export function knownFailureFromProviderStop(input: {
|
|
|
70
69
|
const diagnostic = nonEmptyString(input.errorMessage);
|
|
71
70
|
const details = sessionStopDetails(input);
|
|
72
71
|
return {
|
|
73
|
-
|
|
72
|
+
...(hasUpstreamErrorTestimony(input) ? { cause: "provider" as const } : {}),
|
|
74
73
|
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
75
74
|
...(Object.keys(details).length === 0 ? {} : { details }),
|
|
76
75
|
};
|