@akagilnc/pi-workflow-roles 0.1.2054 → 0.1.2060
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/public-cli/main.js +55 -8
- package/package.json +1 -1
- package/resources/engines/kimi.md +4 -0
- package/src/engine-detour-tool.ts +142 -0
- package/src/engine-detour.ts +103 -0
- package/src/public-cli/judge-run.ts +26 -1
- package/src/public-cli/settlement.ts +80 -10
- package/src/role-runtime.ts +13 -0
package/dist/public-cli/main.js
CHANGED
|
@@ -19417,6 +19417,16 @@ var init_collector_ledger = __esm({
|
|
|
19417
19417
|
}
|
|
19418
19418
|
});
|
|
19419
19419
|
|
|
19420
|
+
// src/engine-detour.ts
|
|
19421
|
+
var ENGINE_DETOUR_TOOL_NAME, AK_ROLE_ENGINE_ENV;
|
|
19422
|
+
var init_engine_detour = __esm({
|
|
19423
|
+
"src/engine-detour.ts"() {
|
|
19424
|
+
"use strict";
|
|
19425
|
+
ENGINE_DETOUR_TOOL_NAME = "ak_engine_detour";
|
|
19426
|
+
AK_ROLE_ENGINE_ENV = "AK_ROLE_ENGINE";
|
|
19427
|
+
}
|
|
19428
|
+
});
|
|
19429
|
+
|
|
19420
19430
|
// src/work-subject-identity.ts
|
|
19421
19431
|
import { resolve as resolve6 } from "node:path";
|
|
19422
19432
|
function issueRoot(value) {
|
|
@@ -20570,34 +20580,46 @@ function boundErroredToolCandidate(entries, resultIndex, message, toolName) {
|
|
|
20570
20580
|
const diagnostic = toolResultText(message);
|
|
20571
20581
|
return bound === void 0 || diagnostic === "" ? void 0 : { candidate: bound.candidate, diagnostic, callIndex: bound.callIndex };
|
|
20572
20582
|
}
|
|
20573
|
-
function
|
|
20583
|
+
function extractInfrastructureToolFailure(entries, spec) {
|
|
20574
20584
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
20575
20585
|
const entry = entries[i];
|
|
20576
20586
|
if (entry?.type !== "message") continue;
|
|
20577
20587
|
const message = entry.message;
|
|
20578
20588
|
if (message?.role !== "toolResult") continue;
|
|
20579
20589
|
if (message.isError !== true) continue;
|
|
20580
|
-
if (typeof message.toolName !== "string" || !
|
|
20590
|
+
if (typeof message.toolName !== "string" || !spec.matchTool(message.toolName)) {
|
|
20581
20591
|
continue;
|
|
20582
20592
|
}
|
|
20583
20593
|
const diagnostic = toolResultText(message);
|
|
20584
20594
|
if (diagnostic.length === 0) continue;
|
|
20585
20595
|
return {
|
|
20586
|
-
cause:
|
|
20596
|
+
cause: spec.cause,
|
|
20587
20597
|
diagnostic,
|
|
20588
|
-
identity: { name:
|
|
20598
|
+
identity: { name: spec.identityName }
|
|
20589
20599
|
};
|
|
20590
20600
|
}
|
|
20591
20601
|
return void 0;
|
|
20592
20602
|
}
|
|
20593
|
-
async function
|
|
20603
|
+
async function readInfrastructureToolFailure(sessionFile, spec) {
|
|
20594
20604
|
try {
|
|
20595
20605
|
const entries = await readBoundSessionEntries(sessionFile);
|
|
20596
|
-
return
|
|
20606
|
+
return extractInfrastructureToolFailure(entries, spec);
|
|
20597
20607
|
} catch {
|
|
20598
20608
|
return void 0;
|
|
20599
20609
|
}
|
|
20600
20610
|
}
|
|
20611
|
+
async function readCollectorInfrastructureFailure(sessionFile) {
|
|
20612
|
+
return readInfrastructureToolFailure(
|
|
20613
|
+
sessionFile,
|
|
20614
|
+
COLLECTOR_INFRASTRUCTURE_FAILURE_SPEC
|
|
20615
|
+
);
|
|
20616
|
+
}
|
|
20617
|
+
async function readEngineDetourInfrastructureFailure(sessionFile) {
|
|
20618
|
+
return readInfrastructureToolFailure(
|
|
20619
|
+
sessionFile,
|
|
20620
|
+
ENGINE_DETOUR_INFRASTRUCTURE_FAILURE_SPEC
|
|
20621
|
+
);
|
|
20622
|
+
}
|
|
20601
20623
|
function assertCollectorReceiptMatchesAdmitted(receipt, admitted) {
|
|
20602
20624
|
if (receipt.repository !== admitted.repository.canonical) {
|
|
20603
20625
|
throw collectorReceiptBindingFailure(
|
|
@@ -22393,7 +22415,7 @@ function presentFailureTerminal(terminal, io) {
|
|
|
22393
22415
|
}));
|
|
22394
22416
|
}
|
|
22395
22417
|
}
|
|
22396
|
-
var CONCISE_DIAGNOSTIC_MAX_CHARS, COLLECTOR_INFRASTRUCTURE_TOOLS;
|
|
22418
|
+
var CONCISE_DIAGNOSTIC_MAX_CHARS, COLLECTOR_INFRASTRUCTURE_TOOLS, COLLECTOR_INFRASTRUCTURE_FAILURE_SPEC, ENGINE_DETOUR_INFRASTRUCTURE_FAILURE_SPEC;
|
|
22397
22419
|
var init_settlement = __esm({
|
|
22398
22420
|
"src/public-cli/settlement.ts"() {
|
|
22399
22421
|
"use strict";
|
|
@@ -22406,6 +22428,7 @@ var init_settlement = __esm({
|
|
|
22406
22428
|
init_run_lifecycle();
|
|
22407
22429
|
init_compliance_transport();
|
|
22408
22430
|
init_collector_ledger();
|
|
22431
|
+
init_engine_detour();
|
|
22409
22432
|
init_judge_output();
|
|
22410
22433
|
init_collector_output();
|
|
22411
22434
|
init_worker_output();
|
|
@@ -22426,6 +22449,16 @@ var init_settlement = __esm({
|
|
|
22426
22449
|
COLLECTOR_REQUEST_TOOL,
|
|
22427
22450
|
COLLECTOR_WAIT_TOOL
|
|
22428
22451
|
]);
|
|
22452
|
+
COLLECTOR_INFRASTRUCTURE_FAILURE_SPEC = {
|
|
22453
|
+
matchTool: (toolName) => COLLECTOR_INFRASTRUCTURE_TOOLS.has(toolName),
|
|
22454
|
+
cause: "activation",
|
|
22455
|
+
identityName: "CollectorInfrastructureError"
|
|
22456
|
+
};
|
|
22457
|
+
ENGINE_DETOUR_INFRASTRUCTURE_FAILURE_SPEC = {
|
|
22458
|
+
matchTool: (toolName) => toolName === ENGINE_DETOUR_TOOL_NAME,
|
|
22459
|
+
cause: "output",
|
|
22460
|
+
identityName: "EngineDetourInfrastructureError"
|
|
22461
|
+
};
|
|
22429
22462
|
}
|
|
22430
22463
|
});
|
|
22431
22464
|
|
|
@@ -23776,6 +23809,12 @@ async function dispatchAdmittedJudge(input) {
|
|
|
23776
23809
|
// and role-runtime can record typed provider HTTP observations.
|
|
23777
23810
|
AK_ROLE_RUN_DIR: admitted.runDirectory
|
|
23778
23811
|
};
|
|
23812
|
+
delete childEnv[AK_ROLE_ENGINE_ENV];
|
|
23813
|
+
if (env.engine !== void 0 && env.engine.trim() !== "") {
|
|
23814
|
+
childEnv[AK_ROLE_ENGINE_ENV] = env.engine.trim();
|
|
23815
|
+
} else {
|
|
23816
|
+
childEnv[AK_ROLE_ENGINE_ENV] = void 0;
|
|
23817
|
+
}
|
|
23779
23818
|
const correlationId = admitted.correlationId ?? env.correlationId;
|
|
23780
23819
|
if (correlationId !== void 0 && correlationId.trim() !== "") {
|
|
23781
23820
|
childEnv.AK_CORRELATION_ID = correlationId;
|
|
@@ -23850,13 +23889,20 @@ async function dispatchAdmittedJudge(input) {
|
|
|
23850
23889
|
terminal: auditIncomplete
|
|
23851
23890
|
};
|
|
23852
23891
|
}
|
|
23892
|
+
const infrastructureFailure = await readEngineDetourInfrastructureFailure(
|
|
23893
|
+
admitted.sessionFile
|
|
23894
|
+
);
|
|
23853
23895
|
const credentialFailure = postRunMissingCredentialFailure(
|
|
23854
23896
|
result2,
|
|
23855
23897
|
env.model,
|
|
23856
23898
|
env.credentials
|
|
23857
23899
|
);
|
|
23858
23900
|
const resolution = await resolveAuditedRunnerFailureResolution({
|
|
23859
|
-
runner: result2.knownFailure
|
|
23901
|
+
runner: result2.knownFailure ?? (infrastructureFailure === void 0 ? void 0 : {
|
|
23902
|
+
cause: infrastructureFailure.cause,
|
|
23903
|
+
diagnostic: infrastructureFailure.diagnostic,
|
|
23904
|
+
...infrastructureFailure.identity === void 0 ? {} : { identity: infrastructureFailure.identity }
|
|
23905
|
+
}),
|
|
23860
23906
|
sessionFile: admitted.sessionFile,
|
|
23861
23907
|
credential: credentialFailure,
|
|
23862
23908
|
runDirectory: admitted.runDirectory
|
|
@@ -23980,6 +24026,7 @@ async function runPublicResume(argv, env, io) {
|
|
|
23980
24026
|
var init_judge_run = __esm({
|
|
23981
24027
|
"src/public-cli/judge-run.ts"() {
|
|
23982
24028
|
"use strict";
|
|
24029
|
+
init_engine_detour();
|
|
23983
24030
|
init_engine_material();
|
|
23984
24031
|
init_explicit_internal();
|
|
23985
24032
|
init_cli_errors();
|
package/package.json
CHANGED
|
@@ -29,3 +29,7 @@ kimi --yolo --output-format text -p "YOUR_LABOR_PROMPT"
|
|
|
29
29
|
|
|
30
30
|
Prefer `kimi --help` on the host over any remembered flag set. Do not wrap this
|
|
31
31
|
engine behind `ak-role` flags.
|
|
32
|
+
|
|
33
|
+
When the package detour tool is available, start exactly one subprocess through
|
|
34
|
+
it with argv assembled from this material and the local CLI; return the stdout
|
|
35
|
+
labor content to the same session for the existing typed submission path.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned engine detour tool (#357 T2).
|
|
3
|
+
* Registered by shared role-runtime when Judge + engine activation signal is present.
|
|
4
|
+
* Forbidden in judge-role.ts (lifecycle ban — no spawn in role modules).
|
|
5
|
+
*/
|
|
6
|
+
import type {
|
|
7
|
+
AgentToolResult,
|
|
8
|
+
ExtensionAPI,
|
|
9
|
+
ExtensionContext,
|
|
10
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { Type, type Static } from "typebox";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
ENGINE_DETOUR_ALREADY_USED_DIAGNOSTIC,
|
|
15
|
+
ENGINE_DETOUR_TOOL_NAME,
|
|
16
|
+
engineDetourFailureDiagnostic,
|
|
17
|
+
engineNameFromEnv,
|
|
18
|
+
isEngineDetourFailure,
|
|
19
|
+
runEngineDetourOnce,
|
|
20
|
+
} from "./engine-detour.ts";
|
|
21
|
+
|
|
22
|
+
const engineDetourArgsSchema = Type.Object(
|
|
23
|
+
{
|
|
24
|
+
argv: Type.Array(Type.String({ minLength: 1 }), {
|
|
25
|
+
minItems: 1,
|
|
26
|
+
description:
|
|
27
|
+
"Executable argv for one engine subprocess. First element is the command (PATH lookup); remaining elements are arguments. Assemble from engine method material — do not invent package flags.",
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
{ additionalProperties: false },
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
type EngineDetourArgs = Static<typeof engineDetourArgsSchema>;
|
|
34
|
+
|
|
35
|
+
export type EngineDetourHostActions = {
|
|
36
|
+
failInfrastructure(
|
|
37
|
+
error: unknown,
|
|
38
|
+
ctx: ExtensionContext,
|
|
39
|
+
toolCallId?: string,
|
|
40
|
+
): never;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type EngineDetourToolRegistration = {
|
|
44
|
+
/** True when the tool definition was installed on this ExtensionAPI. */
|
|
45
|
+
readonly registered: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register the engine-generic detour tool once for this process when Judge has
|
|
50
|
+
* an engine activation signal. Returns whether registration occurred.
|
|
51
|
+
* Once-latch is activation-scoped via the returned reset handle.
|
|
52
|
+
*/
|
|
53
|
+
export function registerEngineDetourTool(
|
|
54
|
+
pi: ExtensionAPI,
|
|
55
|
+
hostActions: EngineDetourHostActions,
|
|
56
|
+
): EngineDetourToolRegistration & { resetLatch(): void } {
|
|
57
|
+
const engineName = engineNameFromEnv();
|
|
58
|
+
if (engineName === undefined) {
|
|
59
|
+
return {
|
|
60
|
+
registered: false,
|
|
61
|
+
resetLatch() {
|
|
62
|
+
/* no-op when unregistered */
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let used = false;
|
|
68
|
+
|
|
69
|
+
pi.registerTool({
|
|
70
|
+
name: ENGINE_DETOUR_TOOL_NAME,
|
|
71
|
+
label: "Engine Detour",
|
|
72
|
+
description:
|
|
73
|
+
`Run one labor-engine subprocess (engine=${engineName}) and return its stdout to this session. Call at most once per activation. Assemble argv from the engine method material path delivered in the session prompt.`,
|
|
74
|
+
promptSnippet: "Run the configured labor engine once and return its stdout",
|
|
75
|
+
promptGuidelines: [
|
|
76
|
+
`Use ${ENGINE_DETOUR_TOOL_NAME} exactly once when engine method material is present.`,
|
|
77
|
+
"Pass argv assembled from the material and the host CLI — first element is the executable name on PATH.",
|
|
78
|
+
"On success, use the returned stdout as labor content for the existing typed submission tool.",
|
|
79
|
+
],
|
|
80
|
+
parameters: engineDetourArgsSchema,
|
|
81
|
+
async execute(
|
|
82
|
+
toolCallId,
|
|
83
|
+
params,
|
|
84
|
+
signal,
|
|
85
|
+
_onUpdate,
|
|
86
|
+
ctx,
|
|
87
|
+
): Promise<AgentToolResult<unknown>> {
|
|
88
|
+
if (used) {
|
|
89
|
+
hostActions.failInfrastructure(
|
|
90
|
+
new Error(ENGINE_DETOUR_ALREADY_USED_DIAGNOSTIC),
|
|
91
|
+
ctx,
|
|
92
|
+
toolCallId,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
used = true;
|
|
96
|
+
|
|
97
|
+
const args = params as EngineDetourArgs;
|
|
98
|
+
const argv = Array.isArray(args.argv) ? args.argv : [];
|
|
99
|
+
if (argv.length === 0 || argv.some((part) => typeof part !== "string" || part.length === 0)) {
|
|
100
|
+
hostActions.failInfrastructure(
|
|
101
|
+
new Error("engine detour argv must be a non-empty string array"),
|
|
102
|
+
ctx,
|
|
103
|
+
toolCallId,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let result: Awaited<ReturnType<typeof runEngineDetourOnce>>;
|
|
108
|
+
try {
|
|
109
|
+
result = await runEngineDetourOnce({
|
|
110
|
+
argv,
|
|
111
|
+
cwd: ctx.cwd,
|
|
112
|
+
...(signal === undefined ? {} : { signal }),
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isEngineDetourFailure(result)) {
|
|
119
|
+
hostActions.failInfrastructure(
|
|
120
|
+
new Error(engineDetourFailureDiagnostic(result)),
|
|
121
|
+
ctx,
|
|
122
|
+
toolCallId,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: "text" as const, text: result.stdout }],
|
|
128
|
+
details: {
|
|
129
|
+
tool: ENGINE_DETOUR_TOOL_NAME,
|
|
130
|
+
code: result.code,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
registered: true,
|
|
138
|
+
resetLatch() {
|
|
139
|
+
used = false;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine-generic one-shot subprocess detour (#357 T2 / ADR 0069).
|
|
3
|
+
* Spawn once; no retry, hang surface, or per-engine branch.
|
|
4
|
+
* Material body is LLM data — this module only executes argv the model assembled.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
/** Package-owned detour tool name (settlement whitelist + session principal). */
|
|
9
|
+
export const ENGINE_DETOUR_TOOL_NAME = "ak_engine_detour" as const;
|
|
10
|
+
|
|
11
|
+
/** Env presence/name signal injected by public Judge run (registration gate only). */
|
|
12
|
+
export const AK_ROLE_ENGINE_ENV = "AK_ROLE_ENGINE" as const;
|
|
13
|
+
|
|
14
|
+
export const ENGINE_DETOUR_EMPTY_STDOUT_DIAGNOSTIC =
|
|
15
|
+
"engine detour produced empty stdout" as const;
|
|
16
|
+
|
|
17
|
+
export const ENGINE_DETOUR_ALREADY_USED_DIAGNOSTIC =
|
|
18
|
+
"engine detour already used in this activation" as const;
|
|
19
|
+
|
|
20
|
+
export type EngineDetourResult = Readonly<{
|
|
21
|
+
code: number;
|
|
22
|
+
stdout: string;
|
|
23
|
+
stderr: string;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
export type EngineDetourRunInput = Readonly<{
|
|
27
|
+
argv: readonly string[];
|
|
28
|
+
cwd: string;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Run one engine subprocess. First argv element is the executable (PATH lookup).
|
|
35
|
+
* stdio: ignore stdin, pipe stdout+stderr. No shell, no retry, no hang timer.
|
|
36
|
+
*/
|
|
37
|
+
export async function runEngineDetourOnce(
|
|
38
|
+
input: EngineDetourRunInput,
|
|
39
|
+
): Promise<EngineDetourResult> {
|
|
40
|
+
if (input.argv.length === 0) {
|
|
41
|
+
throw new Error("engine detour argv must be non-empty");
|
|
42
|
+
}
|
|
43
|
+
const command = input.argv[0]!;
|
|
44
|
+
const args = input.argv.slice(1);
|
|
45
|
+
return await new Promise<EngineDetourResult>((resolve, reject) => {
|
|
46
|
+
let settled = false;
|
|
47
|
+
const child = spawn(command, args, {
|
|
48
|
+
cwd: input.cwd,
|
|
49
|
+
env: input.env ?? process.env,
|
|
50
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
51
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
52
|
+
});
|
|
53
|
+
let stdout = "";
|
|
54
|
+
let stderr = "";
|
|
55
|
+
child.stdout.setEncoding("utf8").on("data", (chunk: string) => {
|
|
56
|
+
stdout += chunk;
|
|
57
|
+
});
|
|
58
|
+
child.stderr.setEncoding("utf8").on("data", (chunk: string) => {
|
|
59
|
+
stderr += chunk;
|
|
60
|
+
});
|
|
61
|
+
const fail = (error: unknown): void => {
|
|
62
|
+
if (settled) return;
|
|
63
|
+
settled = true;
|
|
64
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
65
|
+
};
|
|
66
|
+
child.on("error", (error) => fail(error));
|
|
67
|
+
child.on("close", (code) => {
|
|
68
|
+
if (settled) return;
|
|
69
|
+
settled = true;
|
|
70
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Failure predicate: nonzero exit OR stdout trim-empty (including whitespace-only). */
|
|
76
|
+
export function isEngineDetourFailure(result: {
|
|
77
|
+
code: number;
|
|
78
|
+
stdout: string;
|
|
79
|
+
}): boolean {
|
|
80
|
+
return result.code !== 0 || result.stdout.trim() === "";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Diagnostic string for shared settlement / Terminal Error Artifact.
|
|
85
|
+
* Prefer engine stderr 原样; whitespace-only/empty stderr is absent → stable fallback.
|
|
86
|
+
*/
|
|
87
|
+
export function engineDetourFailureDiagnostic(result: {
|
|
88
|
+
stderr: string;
|
|
89
|
+
code: number;
|
|
90
|
+
stdout: string;
|
|
91
|
+
}): string {
|
|
92
|
+
if (result.stderr.trim().length > 0) return result.stderr;
|
|
93
|
+
if (result.stdout.trim() === "") return ENGINE_DETOUR_EMPTY_STDOUT_DIAGNOSTIC;
|
|
94
|
+
return `engine detour exited with code ${result.code}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Non-empty trimmed engine name from process.env, else undefined. */
|
|
98
|
+
export function engineNameFromEnv(): string | undefined {
|
|
99
|
+
const raw = process.env[AK_ROLE_ENGINE_ENV];
|
|
100
|
+
if (typeof raw !== "string") return undefined;
|
|
101
|
+
const trimmed = raw.trim();
|
|
102
|
+
return trimmed === "" ? undefined : trimmed;
|
|
103
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { writeFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
|
+
import { AK_ROLE_ENGINE_ENV } from "../engine-detour.ts";
|
|
9
10
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
|
|
10
11
|
import {
|
|
11
12
|
runExplicitInternalActivation,
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
resolveControlledFailureResumeObservation,
|
|
59
60
|
controlledFailureInputFromResolution,
|
|
60
61
|
explicitInternalKnownFailureClassificationInput,
|
|
62
|
+
readEngineDetourInfrastructureFailure,
|
|
61
63
|
settleJudgeFailureTerminalResult,
|
|
62
64
|
trySettleJudgeTerminalResult,
|
|
63
65
|
trySettleComplianceAuditIncompleteTerminalResult,
|
|
@@ -296,6 +298,14 @@ async function dispatchAdmittedJudge(input: {
|
|
|
296
298
|
// and role-runtime can record typed provider HTTP observations.
|
|
297
299
|
AK_ROLE_RUN_DIR: admitted.runDirectory,
|
|
298
300
|
};
|
|
301
|
+
// Engine presence/name signal: registration gate + label only (no per-engine branch).
|
|
302
|
+
// Delete ambient inheritance first; own-key undefined mask survives process.env re-merge.
|
|
303
|
+
delete childEnv[AK_ROLE_ENGINE_ENV];
|
|
304
|
+
if (env.engine !== undefined && env.engine.trim() !== "") {
|
|
305
|
+
childEnv[AK_ROLE_ENGINE_ENV] = env.engine.trim();
|
|
306
|
+
} else {
|
|
307
|
+
childEnv[AK_ROLE_ENGINE_ENV] = undefined;
|
|
308
|
+
}
|
|
299
309
|
const correlationId = admitted.correlationId ?? env.correlationId;
|
|
300
310
|
if (correlationId !== undefined && correlationId.trim() !== "") {
|
|
301
311
|
childEnv.AK_CORRELATION_ID = correlationId;
|
|
@@ -377,6 +387,11 @@ async function dispatchAdmittedJudge(input: {
|
|
|
377
387
|
};
|
|
378
388
|
}
|
|
379
389
|
|
|
390
|
+
// Prefer engine-detour infrastructure failure already on the session principal
|
|
391
|
+
// over a later secondary provider-stop after abort (#357 T2 / collector-isomorphic).
|
|
392
|
+
const infrastructureFailure = await readEngineDetourInfrastructureFailure(
|
|
393
|
+
admitted.sessionFile,
|
|
394
|
+
);
|
|
380
395
|
// Production-owned typed cause channel — never inferred from stderr wording.
|
|
381
396
|
const credentialFailure = postRunMissingCredentialFailure(
|
|
382
397
|
result,
|
|
@@ -384,7 +399,17 @@ async function dispatchAdmittedJudge(input: {
|
|
|
384
399
|
env.credentials,
|
|
385
400
|
);
|
|
386
401
|
const resolution = await resolveAuditedRunnerFailureResolution({
|
|
387
|
-
runner:
|
|
402
|
+
runner:
|
|
403
|
+
result.knownFailure ??
|
|
404
|
+
(infrastructureFailure === undefined
|
|
405
|
+
? undefined
|
|
406
|
+
: {
|
|
407
|
+
cause: infrastructureFailure.cause,
|
|
408
|
+
diagnostic: infrastructureFailure.diagnostic,
|
|
409
|
+
...(infrastructureFailure.identity === undefined
|
|
410
|
+
? {}
|
|
411
|
+
: { identity: infrastructureFailure.identity }),
|
|
412
|
+
}),
|
|
388
413
|
sessionFile: admitted.sessionFile,
|
|
389
414
|
credential: credentialFailure,
|
|
390
415
|
runDirectory: admitted.runDirectory,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
COLLECTOR_REQUEST_TOOL,
|
|
34
34
|
COLLECTOR_WAIT_TOOL,
|
|
35
35
|
} from "../collector-ledger.ts";
|
|
36
|
+
import { ENGINE_DETOUR_TOOL_NAME } from "../engine-detour.ts";
|
|
36
37
|
import {
|
|
37
38
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
38
39
|
type JudgeVerdict,
|
|
@@ -1348,13 +1349,33 @@ const COLLECTOR_INFRASTRUCTURE_TOOLS = new Set<string>([
|
|
|
1348
1349
|
COLLECTOR_WAIT_TOOL,
|
|
1349
1350
|
]);
|
|
1350
1351
|
|
|
1352
|
+
/** Shared match/cause/identity knobs for session-principal infrastructure failures. */
|
|
1353
|
+
type InfrastructureFailureSpec = Readonly<{
|
|
1354
|
+
matchTool: (toolName: string) => boolean;
|
|
1355
|
+
cause: ControlledFailureCause;
|
|
1356
|
+
identityName: string;
|
|
1357
|
+
}>;
|
|
1358
|
+
|
|
1359
|
+
const COLLECTOR_INFRASTRUCTURE_FAILURE_SPEC: InfrastructureFailureSpec = {
|
|
1360
|
+
matchTool: (toolName) => COLLECTOR_INFRASTRUCTURE_TOOLS.has(toolName),
|
|
1361
|
+
cause: "activation",
|
|
1362
|
+
identityName: "CollectorInfrastructureError",
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
const ENGINE_DETOUR_INFRASTRUCTURE_FAILURE_SPEC: InfrastructureFailureSpec = {
|
|
1366
|
+
matchTool: (toolName) => toolName === ENGINE_DETOUR_TOOL_NAME,
|
|
1367
|
+
cause: "output",
|
|
1368
|
+
identityName: "EngineDetourInfrastructureError",
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1351
1371
|
/**
|
|
1352
|
-
* Prefer a real
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1372
|
+
* Prefer a real infrastructure tool failure already on the session principal
|
|
1373
|
+
* over a later secondary provider-stop (failure-honesty).
|
|
1374
|
+
* Tool match + cause + identity are call-site parameters — one extraction body.
|
|
1355
1375
|
*/
|
|
1356
|
-
|
|
1376
|
+
function extractInfrastructureToolFailure(
|
|
1357
1377
|
entries: readonly SessionEntry[],
|
|
1378
|
+
spec: InfrastructureFailureSpec,
|
|
1358
1379
|
): ControlledFailure | undefined {
|
|
1359
1380
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1360
1381
|
const entry = entries[i];
|
|
@@ -1364,33 +1385,82 @@ export function extractCollectorInfrastructureFailure(
|
|
|
1364
1385
|
if (message.isError !== true) continue;
|
|
1365
1386
|
if (
|
|
1366
1387
|
typeof message.toolName !== "string" ||
|
|
1367
|
-
!
|
|
1388
|
+
!spec.matchTool(message.toolName)
|
|
1368
1389
|
) {
|
|
1369
1390
|
continue;
|
|
1370
1391
|
}
|
|
1371
1392
|
const diagnostic = toolResultText(message);
|
|
1372
1393
|
if (diagnostic.length === 0) continue;
|
|
1373
1394
|
return {
|
|
1374
|
-
cause:
|
|
1395
|
+
cause: spec.cause,
|
|
1375
1396
|
diagnostic,
|
|
1376
|
-
identity: { name:
|
|
1397
|
+
identity: { name: spec.identityName },
|
|
1377
1398
|
};
|
|
1378
1399
|
}
|
|
1379
1400
|
return undefined;
|
|
1380
1401
|
}
|
|
1381
1402
|
|
|
1382
|
-
/** Read the bound session principal for a
|
|
1383
|
-
|
|
1403
|
+
/** Read the bound session principal for a parameterized infrastructure tool failure. */
|
|
1404
|
+
async function readInfrastructureToolFailure(
|
|
1384
1405
|
sessionFile: string,
|
|
1406
|
+
spec: InfrastructureFailureSpec,
|
|
1385
1407
|
): Promise<ControlledFailure | undefined> {
|
|
1386
1408
|
try {
|
|
1387
1409
|
const entries = await readBoundSessionEntries(sessionFile);
|
|
1388
|
-
return
|
|
1410
|
+
return extractInfrastructureToolFailure(entries, spec);
|
|
1389
1411
|
} catch {
|
|
1390
1412
|
return undefined;
|
|
1391
1413
|
}
|
|
1392
1414
|
}
|
|
1393
1415
|
|
|
1416
|
+
/**
|
|
1417
|
+
* Prefer a real Collector infrastructure tool failure already on the session
|
|
1418
|
+
* principal over a later secondary provider-stop (failure-honesty).
|
|
1419
|
+
* Observe/request/wait host failures keep their diagnostic identity (e.g. HTTP 404).
|
|
1420
|
+
*/
|
|
1421
|
+
export function extractCollectorInfrastructureFailure(
|
|
1422
|
+
entries: readonly SessionEntry[],
|
|
1423
|
+
): ControlledFailure | undefined {
|
|
1424
|
+
return extractInfrastructureToolFailure(
|
|
1425
|
+
entries,
|
|
1426
|
+
COLLECTOR_INFRASTRUCTURE_FAILURE_SPEC,
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/** Read the bound session principal for a Collector infrastructure tool failure. */
|
|
1431
|
+
export async function readCollectorInfrastructureFailure(
|
|
1432
|
+
sessionFile: string,
|
|
1433
|
+
): Promise<ControlledFailure | undefined> {
|
|
1434
|
+
return readInfrastructureToolFailure(
|
|
1435
|
+
sessionFile,
|
|
1436
|
+
COLLECTOR_INFRASTRUCTURE_FAILURE_SPEC,
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* Prefer a real engine-detour infrastructure tool failure already on the session
|
|
1442
|
+
* principal over a later secondary provider-stop (failure-honesty / #357 T2).
|
|
1443
|
+
* Cause stays `output` — labor leg failed before accepted typed Receipt.
|
|
1444
|
+
*/
|
|
1445
|
+
export function extractEngineDetourInfrastructureFailure(
|
|
1446
|
+
entries: readonly SessionEntry[],
|
|
1447
|
+
): ControlledFailure | undefined {
|
|
1448
|
+
return extractInfrastructureToolFailure(
|
|
1449
|
+
entries,
|
|
1450
|
+
ENGINE_DETOUR_INFRASTRUCTURE_FAILURE_SPEC,
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/** Read the bound session principal for an engine-detour infrastructure failure. */
|
|
1455
|
+
export async function readEngineDetourInfrastructureFailure(
|
|
1456
|
+
sessionFile: string,
|
|
1457
|
+
): Promise<ControlledFailure | undefined> {
|
|
1458
|
+
return readInfrastructureToolFailure(
|
|
1459
|
+
sessionFile,
|
|
1460
|
+
ENGINE_DETOUR_INFRASTRUCTURE_FAILURE_SPEC,
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1394
1464
|
/**
|
|
1395
1465
|
* Compare a validated receipt with the admitted Collector invocation identity.
|
|
1396
1466
|
* Throws a typed output failure when any identity field mismatches.
|
package/src/role-runtime.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
writeToolExecutionObservationRecord,
|
|
22
22
|
type ToolExecutionObservationWriter,
|
|
23
23
|
} from "./tool-execution-observation.ts";
|
|
24
|
+
import { registerEngineDetourTool } from "./engine-detour-tool.ts";
|
|
24
25
|
import { installPackageOwnedToolRegistration } from "./package-owned-tool-idle.ts";
|
|
25
26
|
import { installWorkerGitHooks } from "./worker-submission-gates.ts";
|
|
26
27
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
@@ -284,6 +285,7 @@ export {
|
|
|
284
285
|
type JudgeVerdict,
|
|
285
286
|
type SoulAuditResult,
|
|
286
287
|
} from "./judge-role.ts";
|
|
288
|
+
export { ENGINE_DETOUR_TOOL_NAME, AK_ROLE_ENGINE_ENV } from "./engine-detour.ts";
|
|
287
289
|
export {
|
|
288
290
|
AGENT_TOOL_NAME,
|
|
289
291
|
REVIEWER_OUTPUT_TOOL_NAME,
|
|
@@ -590,6 +592,8 @@ export function createRoleRuntimeExtension(
|
|
|
590
592
|
let pendingNavigatorSettlement: Promise<void> | undefined;
|
|
591
593
|
let navigatorWorkContext: NavigatorWorkContext | undefined;
|
|
592
594
|
const pendingInfrastructureToolCallIds = new Set<string>();
|
|
595
|
+
// #357 T2: engine detour once-latch + registration (Judge+engine only).
|
|
596
|
+
let engineDetourRegistration: ReturnType<typeof registerEngineDetourTool> | undefined;
|
|
593
597
|
// #288 primary-session thin adapter. The policy is the sole budget owner;
|
|
594
598
|
// terminating-tool rejections and mechanical delivery requests share two turns.
|
|
595
599
|
let receiptDelivery = createReceiptDeliveryPolicy();
|
|
@@ -1053,6 +1057,7 @@ export function createRoleRuntimeExtension(
|
|
|
1053
1057
|
pendingNavigatorPresentation = undefined;
|
|
1054
1058
|
pendingNavigatorSettlement = undefined;
|
|
1055
1059
|
pendingInfrastructureToolCallIds.clear();
|
|
1060
|
+
engineDetourRegistration?.resetLatch();
|
|
1056
1061
|
navigatorWorkContext = undefined;
|
|
1057
1062
|
// #351: OAuth keepalive is orthogonal to --ak-role; start before role early-return
|
|
1058
1063
|
// so role-less sessions (and reload after shutdown stop) still keep tokens alive.
|
|
@@ -1164,6 +1169,14 @@ export function createRoleRuntimeExtension(
|
|
|
1164
1169
|
}
|
|
1165
1170
|
|
|
1166
1171
|
await executeActivationStage(entry.role, activationStage(entry.role, runtime), { clock, writeTrace });
|
|
1172
|
+
// #357 T2: Judge+engine activation registers the package detour tool once.
|
|
1173
|
+
// Gate is env presence only — no per-engine execute branch; no judge-role spawn.
|
|
1174
|
+
if (entry.role === "judge" && engineDetourRegistration === undefined) {
|
|
1175
|
+
engineDetourRegistration = registerEngineDetourTool(pi, hostActions);
|
|
1176
|
+
if (!engineDetourRegistration.registered) {
|
|
1177
|
+
engineDetourRegistration = undefined;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1167
1180
|
// Worker gates ②④ + ① baseline: envelope arms the worktree after role install.
|
|
1168
1181
|
// Parent session feeds #216 createRecordSession so baseline/bounce survive resume.
|
|
1169
1182
|
if (entry.role === "coder" || entry.role === "fixer") {
|