@akagilnc/pi-workflow-roles 0.1.2052 → 0.1.2058
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 +84 -24
- 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/invocation.ts +19 -12
- package/src/public-cli/judge-run.ts +39 -3
- package/src/public-cli/run-lifecycle.ts +11 -3
- package/src/public-cli/settlement.ts +80 -10
- package/src/role-runtime.ts +13 -0
package/dist/public-cli/main.js
CHANGED
|
@@ -16380,18 +16380,21 @@ async function writeRoleInvocationLedger(source, role, effectiveModel) {
|
|
|
16380
16380
|
"utf8"
|
|
16381
16381
|
);
|
|
16382
16382
|
}
|
|
16383
|
-
async function recordEffectiveInvocationModel(runDirectory, model) {
|
|
16383
|
+
async function recordEffectiveInvocationModel(runDirectory, model, engine) {
|
|
16384
16384
|
const ledgerPath = join6(runDirectory, "invocation.json");
|
|
16385
16385
|
const current = JSON.parse(await readFile4(ledgerPath, "utf8"));
|
|
16386
|
-
const next = {
|
|
16387
|
-
|
|
16388
|
-
provider
|
|
16389
|
-
model
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16386
|
+
const next = { ...current };
|
|
16387
|
+
if (model !== void 0) {
|
|
16388
|
+
next.provider = model.provider;
|
|
16389
|
+
next.model = model.model;
|
|
16390
|
+
if (model.thinking === void 0) {
|
|
16391
|
+
delete next.thinking;
|
|
16392
|
+
} else {
|
|
16393
|
+
next.thinking = model.thinking;
|
|
16394
|
+
}
|
|
16395
|
+
}
|
|
16396
|
+
if (engine !== void 0) {
|
|
16397
|
+
next.engine = engine;
|
|
16395
16398
|
}
|
|
16396
16399
|
await writeFile2(
|
|
16397
16400
|
ledgerPath,
|
|
@@ -18629,9 +18632,13 @@ async function markRunAdmitted(admitted) {
|
|
|
18629
18632
|
...admitted.role === "coder" || admitted.role === "fixer" ? { phase: admitted.phase } : {}
|
|
18630
18633
|
});
|
|
18631
18634
|
}
|
|
18632
|
-
async function markRunRunning(runDirectory, effectiveModel) {
|
|
18633
|
-
if (effectiveModel !== void 0) {
|
|
18634
|
-
await recordEffectiveInvocationModel(
|
|
18635
|
+
async function markRunRunning(runDirectory, effectiveModel, effectiveEngine) {
|
|
18636
|
+
if (effectiveModel !== void 0 || effectiveEngine !== void 0) {
|
|
18637
|
+
await recordEffectiveInvocationModel(
|
|
18638
|
+
runDirectory,
|
|
18639
|
+
effectiveModel,
|
|
18640
|
+
effectiveEngine
|
|
18641
|
+
);
|
|
18635
18642
|
}
|
|
18636
18643
|
const current = await readRoleRunState(runDirectory);
|
|
18637
18644
|
if (current === void 0) {
|
|
@@ -19410,6 +19417,16 @@ var init_collector_ledger = __esm({
|
|
|
19410
19417
|
}
|
|
19411
19418
|
});
|
|
19412
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
|
+
|
|
19413
19430
|
// src/work-subject-identity.ts
|
|
19414
19431
|
import { resolve as resolve6 } from "node:path";
|
|
19415
19432
|
function issueRoot(value) {
|
|
@@ -20563,34 +20580,46 @@ function boundErroredToolCandidate(entries, resultIndex, message, toolName) {
|
|
|
20563
20580
|
const diagnostic = toolResultText(message);
|
|
20564
20581
|
return bound === void 0 || diagnostic === "" ? void 0 : { candidate: bound.candidate, diagnostic, callIndex: bound.callIndex };
|
|
20565
20582
|
}
|
|
20566
|
-
function
|
|
20583
|
+
function extractInfrastructureToolFailure(entries, spec) {
|
|
20567
20584
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
20568
20585
|
const entry = entries[i];
|
|
20569
20586
|
if (entry?.type !== "message") continue;
|
|
20570
20587
|
const message = entry.message;
|
|
20571
20588
|
if (message?.role !== "toolResult") continue;
|
|
20572
20589
|
if (message.isError !== true) continue;
|
|
20573
|
-
if (typeof message.toolName !== "string" || !
|
|
20590
|
+
if (typeof message.toolName !== "string" || !spec.matchTool(message.toolName)) {
|
|
20574
20591
|
continue;
|
|
20575
20592
|
}
|
|
20576
20593
|
const diagnostic = toolResultText(message);
|
|
20577
20594
|
if (diagnostic.length === 0) continue;
|
|
20578
20595
|
return {
|
|
20579
|
-
cause:
|
|
20596
|
+
cause: spec.cause,
|
|
20580
20597
|
diagnostic,
|
|
20581
|
-
identity: { name:
|
|
20598
|
+
identity: { name: spec.identityName }
|
|
20582
20599
|
};
|
|
20583
20600
|
}
|
|
20584
20601
|
return void 0;
|
|
20585
20602
|
}
|
|
20586
|
-
async function
|
|
20603
|
+
async function readInfrastructureToolFailure(sessionFile, spec) {
|
|
20587
20604
|
try {
|
|
20588
20605
|
const entries = await readBoundSessionEntries(sessionFile);
|
|
20589
|
-
return
|
|
20606
|
+
return extractInfrastructureToolFailure(entries, spec);
|
|
20590
20607
|
} catch {
|
|
20591
20608
|
return void 0;
|
|
20592
20609
|
}
|
|
20593
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
|
+
}
|
|
20594
20623
|
function assertCollectorReceiptMatchesAdmitted(receipt, admitted) {
|
|
20595
20624
|
if (receipt.repository !== admitted.repository.canonical) {
|
|
20596
20625
|
throw collectorReceiptBindingFailure(
|
|
@@ -22386,7 +22415,7 @@ function presentFailureTerminal(terminal, io) {
|
|
|
22386
22415
|
}));
|
|
22387
22416
|
}
|
|
22388
22417
|
}
|
|
22389
|
-
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;
|
|
22390
22419
|
var init_settlement = __esm({
|
|
22391
22420
|
"src/public-cli/settlement.ts"() {
|
|
22392
22421
|
"use strict";
|
|
@@ -22399,6 +22428,7 @@ var init_settlement = __esm({
|
|
|
22399
22428
|
init_run_lifecycle();
|
|
22400
22429
|
init_compliance_transport();
|
|
22401
22430
|
init_collector_ledger();
|
|
22431
|
+
init_engine_detour();
|
|
22402
22432
|
init_judge_output();
|
|
22403
22433
|
init_collector_output();
|
|
22404
22434
|
init_worker_output();
|
|
@@ -22419,6 +22449,16 @@ var init_settlement = __esm({
|
|
|
22419
22449
|
COLLECTOR_REQUEST_TOOL,
|
|
22420
22450
|
COLLECTOR_WAIT_TOOL
|
|
22421
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
|
+
};
|
|
22422
22462
|
}
|
|
22423
22463
|
});
|
|
22424
22464
|
|
|
@@ -23742,7 +23782,7 @@ async function presentControlledFailure6(admitted, failureInput, io) {
|
|
|
23742
23782
|
};
|
|
23743
23783
|
}
|
|
23744
23784
|
async function dispatchAdmittedJudge(input) {
|
|
23745
|
-
const { admitted, env, io, extraArgs, lease } = input;
|
|
23785
|
+
const { admitted, env, io, extraArgs, lease, effectiveEngine } = input;
|
|
23746
23786
|
try {
|
|
23747
23787
|
const missingCredential = missingCredentialPreDispatchFailure(
|
|
23748
23788
|
env.model,
|
|
@@ -23755,7 +23795,11 @@ async function dispatchAdmittedJudge(input) {
|
|
|
23755
23795
|
io
|
|
23756
23796
|
);
|
|
23757
23797
|
}
|
|
23758
|
-
await markRunRunning(
|
|
23798
|
+
await markRunRunning(
|
|
23799
|
+
admitted.runDirectory,
|
|
23800
|
+
env.model,
|
|
23801
|
+
effectiveEngine
|
|
23802
|
+
);
|
|
23759
23803
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
23760
23804
|
const childEnv = {
|
|
23761
23805
|
...process.env,
|
|
@@ -23765,6 +23809,12 @@ async function dispatchAdmittedJudge(input) {
|
|
|
23765
23809
|
// and role-runtime can record typed provider HTTP observations.
|
|
23766
23810
|
AK_ROLE_RUN_DIR: admitted.runDirectory
|
|
23767
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
|
+
}
|
|
23768
23818
|
const correlationId = admitted.correlationId ?? env.correlationId;
|
|
23769
23819
|
if (correlationId !== void 0 && correlationId.trim() !== "") {
|
|
23770
23820
|
childEnv.AK_CORRELATION_ID = correlationId;
|
|
@@ -23839,13 +23889,20 @@ async function dispatchAdmittedJudge(input) {
|
|
|
23839
23889
|
terminal: auditIncomplete
|
|
23840
23890
|
};
|
|
23841
23891
|
}
|
|
23892
|
+
const infrastructureFailure = await readEngineDetourInfrastructureFailure(
|
|
23893
|
+
admitted.sessionFile
|
|
23894
|
+
);
|
|
23842
23895
|
const credentialFailure = postRunMissingCredentialFailure(
|
|
23843
23896
|
result2,
|
|
23844
23897
|
env.model,
|
|
23845
23898
|
env.credentials
|
|
23846
23899
|
);
|
|
23847
23900
|
const resolution = await resolveAuditedRunnerFailureResolution({
|
|
23848
|
-
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
|
+
}),
|
|
23849
23906
|
sessionFile: admitted.sessionFile,
|
|
23850
23907
|
credential: credentialFailure,
|
|
23851
23908
|
runDirectory: admitted.runDirectory
|
|
@@ -23909,7 +23966,9 @@ async function runPublicJudge(argv, env, io, parseJudgeArgv2) {
|
|
|
23909
23966
|
},
|
|
23910
23967
|
io,
|
|
23911
23968
|
extraArgs,
|
|
23912
|
-
lease
|
|
23969
|
+
lease,
|
|
23970
|
+
// #358: only initial Judge dispatch records mechanical engine provenance.
|
|
23971
|
+
...env.engine === void 0 ? {} : { effectiveEngine: env.engine }
|
|
23913
23972
|
});
|
|
23914
23973
|
}
|
|
23915
23974
|
async function runPublicResume(argv, env, io) {
|
|
@@ -23967,6 +24026,7 @@ async function runPublicResume(argv, env, io) {
|
|
|
23967
24026
|
var init_judge_run = __esm({
|
|
23968
24027
|
"src/public-cli/judge-run.ts"() {
|
|
23969
24028
|
"use strict";
|
|
24029
|
+
init_engine_detour();
|
|
23970
24030
|
init_engine_material();
|
|
23971
24031
|
init_explicit_internal();
|
|
23972
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
|
+
}
|
|
@@ -239,28 +239,35 @@ async function writeRoleInvocationLedger(
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
/**
|
|
242
|
-
* Merge the effective launch model
|
|
243
|
-
* (resume / temporary override path — same
|
|
242
|
+
* Merge the effective launch model (and optional initial engine) onto the
|
|
243
|
+
* existing invocation identity page (resume / temporary override path — same
|
|
244
|
+
* field shape as admission write).
|
|
244
245
|
* Bare model clears any prior thinking key so absence stays honest.
|
|
246
|
+
* Engine is write-if-present only: undefined leaves any existing key untouched
|
|
247
|
+
* (resume model merge must not erase initial mechanical provenance).
|
|
245
248
|
*/
|
|
246
249
|
export async function recordEffectiveInvocationModel(
|
|
247
250
|
runDirectory: string,
|
|
248
|
-
model
|
|
251
|
+
model?: InvocationEffectiveModel,
|
|
252
|
+
engine?: string,
|
|
249
253
|
): Promise<void> {
|
|
250
254
|
const ledgerPath = join(runDirectory, "invocation.json");
|
|
251
255
|
const current = JSON.parse(await readFile(ledgerPath, "utf8")) as Record<
|
|
252
256
|
string,
|
|
253
257
|
unknown
|
|
254
258
|
>;
|
|
255
|
-
const next: Record<string, unknown> = {
|
|
256
|
-
|
|
257
|
-
provider
|
|
258
|
-
model
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
const next: Record<string, unknown> = { ...current };
|
|
260
|
+
if (model !== undefined) {
|
|
261
|
+
next.provider = model.provider;
|
|
262
|
+
next.model = model.model;
|
|
263
|
+
if (model.thinking === undefined) {
|
|
264
|
+
delete next.thinking;
|
|
265
|
+
} else {
|
|
266
|
+
next.thinking = model.thinking;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (engine !== undefined) {
|
|
270
|
+
next.engine = engine;
|
|
264
271
|
}
|
|
265
272
|
await writeFile(
|
|
266
273
|
ledgerPath,
|
|
@@ -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,
|
|
@@ -254,12 +256,17 @@ async function dispatchAdmittedJudge(input: {
|
|
|
254
256
|
io: CliIo;
|
|
255
257
|
extraArgs: string[];
|
|
256
258
|
lease: RunWriterLease;
|
|
259
|
+
/**
|
|
260
|
+
* Mechanical engine provenance for initial Judge dispatch only.
|
|
261
|
+
* Explicit — never read from env.engine here, so resume cannot rewrite it.
|
|
262
|
+
*/
|
|
263
|
+
effectiveEngine?: string;
|
|
257
264
|
}): Promise<{
|
|
258
265
|
exitCode: number;
|
|
259
266
|
admitted: AdmittedJudgeInvocation;
|
|
260
267
|
terminal?: TerminalResult;
|
|
261
268
|
}> {
|
|
262
|
-
const { admitted, env, io, extraArgs, lease } = input;
|
|
269
|
+
const { admitted, env, io, extraArgs, lease, effectiveEngine } = input;
|
|
263
270
|
try {
|
|
264
271
|
// Fail closed at the public credential seam before model dispatch: missing
|
|
265
272
|
// selected-provider auth must not be washed by ambient keys or zero-exit runs.
|
|
@@ -274,7 +281,11 @@ async function dispatchAdmittedJudge(input: {
|
|
|
274
281
|
io,
|
|
275
282
|
);
|
|
276
283
|
}
|
|
277
|
-
await markRunRunning(
|
|
284
|
+
await markRunRunning(
|
|
285
|
+
admitted.runDirectory,
|
|
286
|
+
env.model,
|
|
287
|
+
effectiveEngine,
|
|
288
|
+
);
|
|
278
289
|
// Attempt-scoped observation: drop any prior dispatch's 429 evidence so only
|
|
279
290
|
// the current initial/resume attempt can qualify v1 resume.
|
|
280
291
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
@@ -287,6 +298,14 @@ async function dispatchAdmittedJudge(input: {
|
|
|
287
298
|
// and role-runtime can record typed provider HTTP observations.
|
|
288
299
|
AK_ROLE_RUN_DIR: admitted.runDirectory,
|
|
289
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
|
+
}
|
|
290
309
|
const correlationId = admitted.correlationId ?? env.correlationId;
|
|
291
310
|
if (correlationId !== undefined && correlationId.trim() !== "") {
|
|
292
311
|
childEnv.AK_CORRELATION_ID = correlationId;
|
|
@@ -368,6 +387,11 @@ async function dispatchAdmittedJudge(input: {
|
|
|
368
387
|
};
|
|
369
388
|
}
|
|
370
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
|
+
);
|
|
371
395
|
// Production-owned typed cause channel — never inferred from stderr wording.
|
|
372
396
|
const credentialFailure = postRunMissingCredentialFailure(
|
|
373
397
|
result,
|
|
@@ -375,7 +399,17 @@ async function dispatchAdmittedJudge(input: {
|
|
|
375
399
|
env.credentials,
|
|
376
400
|
);
|
|
377
401
|
const resolution = await resolveAuditedRunnerFailureResolution({
|
|
378
|
-
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
|
+
}),
|
|
379
413
|
sessionFile: admitted.sessionFile,
|
|
380
414
|
credential: credentialFailure,
|
|
381
415
|
runDirectory: admitted.runDirectory,
|
|
@@ -459,6 +493,8 @@ export async function runPublicJudge(
|
|
|
459
493
|
io,
|
|
460
494
|
extraArgs,
|
|
461
495
|
lease,
|
|
496
|
+
// #358: only initial Judge dispatch records mechanical engine provenance.
|
|
497
|
+
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
462
498
|
});
|
|
463
499
|
}
|
|
464
500
|
|
|
@@ -230,15 +230,23 @@ export async function markRunAdmitted(
|
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
232
|
* Shared dispatch execution seam: record the effective launch model (initial or
|
|
233
|
-
* resume override)
|
|
233
|
+
* resume override) and optional initial effective engine onto invocation.json
|
|
234
|
+
* when known, then transition to running.
|
|
234
235
|
* Role runners must not coordinate lifecycle ledger writes themselves.
|
|
236
|
+
* Engine is write-if-present only — callers that omit it (resume / non-Judge)
|
|
237
|
+
* never touch the engine key.
|
|
235
238
|
*/
|
|
236
239
|
export async function markRunRunning(
|
|
237
240
|
runDirectory: string,
|
|
238
241
|
effectiveModel?: InvocationEffectiveModel,
|
|
242
|
+
effectiveEngine?: string,
|
|
239
243
|
): Promise<void> {
|
|
240
|
-
if (effectiveModel !== undefined) {
|
|
241
|
-
await recordEffectiveInvocationModel(
|
|
244
|
+
if (effectiveModel !== undefined || effectiveEngine !== undefined) {
|
|
245
|
+
await recordEffectiveInvocationModel(
|
|
246
|
+
runDirectory,
|
|
247
|
+
effectiveModel,
|
|
248
|
+
effectiveEngine,
|
|
249
|
+
);
|
|
242
250
|
}
|
|
243
251
|
const current = await readRoleRunState(runDirectory);
|
|
244
252
|
if (current === undefined) {
|
|
@@ -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") {
|