@akagilnc/pi-workflow-roles 0.1.4243 → 0.1.4274
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/README.md +27 -23
- package/README.zh-CN.md +24 -21
- package/dist/acp-host/production-host.js +4607 -3524
- package/dist/engine-detour-tool.js +64 -5
- package/dist/engine-detour-usage.js +285 -0
- package/dist/headless-host/production-host.js +6057 -4974
- package/dist/navigator-attendance.js +0 -2
- package/dist/navigator-public-session.js +2 -4
- package/dist/navigator-session-contracts.js +6 -13
- package/dist/pi/role-turn-host.js +10 -0
- package/dist/public-cli/config.js +16 -20
- package/dist/public-cli/main.js +935 -613
- package/dist/public-cli/post-admission.js +72 -43
- package/dist/public-cli/registry.js +0 -57
- package/dist/public-cli/settlement.js +80 -24
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/public-role-summons.js +121 -96
- package/package.json +1 -1
- package/src/engine-detour-tool.ts +106 -8
- package/src/engine-detour-usage.ts +373 -0
- package/src/host-contracts.ts +12 -1
- package/src/navigator-attendance.ts +0 -2
- package/src/navigator-public-session.ts +3 -3
- package/src/navigator-session-contracts.ts +8 -14
- package/src/pi/adapter.ts +6 -0
- package/src/pi/role-turn-host.ts +6 -0
- package/src/public-cli/cli.ts +115 -307
- package/src/public-cli/config.ts +35 -23
- package/src/public-cli/post-admission.ts +91 -49
- package/src/public-cli/registry.ts +2 -66
- package/src/public-cli/settlement.ts +148 -23
- package/src/public-cli/turn-request.ts +5 -0
- package/src/public-role-summons.ts +197 -125
- package/src/role-envelope.ts +2 -0
|
@@ -77,11 +77,17 @@ function hostSelectionFailureFromUnknown(error) {
|
|
|
77
77
|
const failure = error.failure;
|
|
78
78
|
return failure;
|
|
79
79
|
}
|
|
80
|
-
async function createSummonEnv(options) {
|
|
81
|
-
const [
|
|
80
|
+
async function createSummonEnv(options, afterHost) {
|
|
81
|
+
const [
|
|
82
|
+
{ piDurablePrincipalAuthority },
|
|
83
|
+
{ appendPiSessionCustomEntry },
|
|
84
|
+
{ resolveRoleTurnHost },
|
|
85
|
+
{ missingResolvedSeatModelMessage, resolvedSeatWithModel }
|
|
86
|
+
] = await Promise.all([
|
|
82
87
|
import("./pi/durable-principal.js"),
|
|
83
88
|
import("./pi/role-turn-host.js"),
|
|
84
|
-
import("./public-cli/role-turn-host-resolution.js")
|
|
89
|
+
import("./public-cli/role-turn-host-resolution.js"),
|
|
90
|
+
import("./public-cli/config.js")
|
|
85
91
|
]);
|
|
86
92
|
const principalAuthority = piDurablePrincipalAuthority;
|
|
87
93
|
const roleTurnHost = resolveRoleTurnHost(
|
|
@@ -93,26 +99,37 @@ async function createSummonEnv(options) {
|
|
|
93
99
|
},
|
|
94
100
|
{ role: options.role, seat: options.seat, principalAuthority }
|
|
95
101
|
);
|
|
96
|
-
|
|
102
|
+
afterHost?.();
|
|
103
|
+
const seatWithModel = resolvedSeatWithModel(options.seat);
|
|
104
|
+
if (seatWithModel === void 0) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
missingModelMessage: missingResolvedSeatModelMessage(options.role)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const hostName = seatWithModel.host ?? "pi";
|
|
97
111
|
const { loadHostProvidersTable, projectHostFacingProvider } = await import("./public-cli/host-providers.js");
|
|
98
|
-
const hostFacingSelection =
|
|
99
|
-
|
|
112
|
+
const hostFacingSelection = projectHostFacingProvider(
|
|
113
|
+
seatWithModel.selection,
|
|
100
114
|
hostName,
|
|
101
115
|
loadHostProvidersTable(options.home),
|
|
102
116
|
options.home
|
|
103
117
|
);
|
|
104
118
|
return {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
ok: true,
|
|
120
|
+
env: {
|
|
121
|
+
home: options.home,
|
|
122
|
+
principalAuthority,
|
|
123
|
+
agentDir: options.agentDir,
|
|
124
|
+
sessionAppender: appendPiSessionCustomEntry,
|
|
125
|
+
packageRoot: options.packageRoot,
|
|
126
|
+
roleTurnHost,
|
|
127
|
+
cwd: options.cwd,
|
|
128
|
+
credentials: options.credentials,
|
|
129
|
+
...hostFacingSelection === void 0 ? {} : { model: hostFacingSelection },
|
|
130
|
+
...projectSeatEngine(seatWithModel),
|
|
131
|
+
...projectSeatHost(seatWithModel)
|
|
132
|
+
}
|
|
116
133
|
};
|
|
117
134
|
}
|
|
118
135
|
async function summonPublicRole(options) {
|
|
@@ -127,46 +144,72 @@ async function summonPublicRole(options) {
|
|
|
127
144
|
const credentials = options.credentials ?? await loadCredentialProviders(agentDir);
|
|
128
145
|
const config = await loadPublicCliConfig(home);
|
|
129
146
|
const seat = resolveEffectiveSeat(config, options.role, credentials);
|
|
130
|
-
let summonEnv;
|
|
131
|
-
try {
|
|
132
|
-
summonEnv = await createSummonEnv({
|
|
133
|
-
role: options.role,
|
|
134
|
-
home,
|
|
135
|
-
agentDir,
|
|
136
|
-
cwd: options.cwd,
|
|
137
|
-
packageRoot,
|
|
138
|
-
credentials,
|
|
139
|
-
seat,
|
|
140
|
-
...options.extraPiArgs === void 0 ? {} : { extraPiArgs: options.extraPiArgs },
|
|
141
|
-
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
142
|
-
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters }
|
|
143
|
-
});
|
|
144
|
-
} catch (error) {
|
|
145
|
-
const failure = hostSelectionFailureFromUnknown(error);
|
|
146
|
-
if (failure !== void 0) {
|
|
147
|
-
const { formatHostSelectionFailure } = await import("./public-cli/role-turn-host-resolution.js");
|
|
148
|
-
return { exitCode: 1, stderr: formatHostSelectionFailure(failure) };
|
|
149
|
-
}
|
|
150
|
-
throw error;
|
|
151
|
-
}
|
|
152
|
-
const env = {
|
|
153
|
-
...summonEnv,
|
|
154
|
-
// Station child role run (#840): omit automatic navigator attendance.
|
|
155
|
-
stationChild: true,
|
|
156
|
-
// Host config passthrough only — same face as public CLI (#422 / #675).
|
|
157
|
-
...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit },
|
|
158
|
-
// Parent cancellation reaches the nested activation's own turn dispatch.
|
|
159
|
-
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
160
|
-
// #753: reask rides the existing notary same-ticket resume summons.instruction.
|
|
161
|
-
...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
|
|
162
|
-
// #879: verbatim submission body on officer dialogue content channel.
|
|
163
|
-
...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
|
|
164
|
-
...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
|
|
165
|
-
// createRunId is the only remaining env overlay — host is seat-selected above.
|
|
166
|
-
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
167
|
-
};
|
|
168
147
|
const captured = options.io === void 0 ? createCapturingIo() : void 0;
|
|
169
148
|
const io = options.io ?? captured.io;
|
|
149
|
+
const summonEnvInput = {
|
|
150
|
+
role: options.role,
|
|
151
|
+
home,
|
|
152
|
+
agentDir,
|
|
153
|
+
cwd: options.cwd,
|
|
154
|
+
packageRoot,
|
|
155
|
+
credentials,
|
|
156
|
+
seat,
|
|
157
|
+
...options.extraPiArgs === void 0 ? {} : { extraPiArgs: options.extraPiArgs },
|
|
158
|
+
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
159
|
+
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters }
|
|
160
|
+
};
|
|
161
|
+
async function prepareSummonEnv(afterHost) {
|
|
162
|
+
try {
|
|
163
|
+
const built = await createSummonEnv(summonEnvInput, afterHost);
|
|
164
|
+
if (!built.ok) {
|
|
165
|
+
return { ok: false, exitCode: 1, stderr: built.missingModelMessage };
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
ok: true,
|
|
169
|
+
env: {
|
|
170
|
+
...built.env,
|
|
171
|
+
stationChild: true,
|
|
172
|
+
...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit },
|
|
173
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
174
|
+
...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
|
|
175
|
+
...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
|
|
176
|
+
...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
|
|
177
|
+
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const failure = hostSelectionFailureFromUnknown(error);
|
|
182
|
+
if (failure !== void 0) {
|
|
183
|
+
const { formatHostSelectionFailure } = await import("./public-cli/role-turn-host-resolution.js");
|
|
184
|
+
return { ok: false, exitCode: 1, stderr: formatHostSelectionFailure(failure) };
|
|
185
|
+
}
|
|
186
|
+
const { CliUsageError } = await import("./public-cli/cli-errors.js");
|
|
187
|
+
if (error instanceof CliUsageError) {
|
|
188
|
+
const { presentStructuralRejection } = await import("./public-cli/settlement.js");
|
|
189
|
+
presentStructuralRejection(error, io);
|
|
190
|
+
return { ok: false, exitCode: 2 };
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function projectPrepareFailure(prepared) {
|
|
196
|
+
const parts = [prepared.stderr, captured?.stderrText()].filter(
|
|
197
|
+
(s) => typeof s === "string" && s !== ""
|
|
198
|
+
);
|
|
199
|
+
const stderr2 = parts.length === 0 ? void 0 : parts.join("");
|
|
200
|
+
return {
|
|
201
|
+
exitCode: prepared.exitCode,
|
|
202
|
+
...stderr2 === void 0 ? {} : { stderr: stderr2 }
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function runPrepared(parse, run) {
|
|
206
|
+
let parsedArgv;
|
|
207
|
+
const prepared = await prepareSummonEnv(() => {
|
|
208
|
+
parsedArgv = parse(options.argv);
|
|
209
|
+
});
|
|
210
|
+
if (!prepared.ok) return { fail: projectPrepareFailure(prepared) };
|
|
211
|
+
return { ok: await run(prepared.env, () => parsedArgv) };
|
|
212
|
+
}
|
|
170
213
|
let result;
|
|
171
214
|
switch (options.role) {
|
|
172
215
|
case "notary": {
|
|
@@ -174,7 +217,9 @@ async function summonPublicRole(options) {
|
|
|
174
217
|
import("./public-cli/notary-run.js"),
|
|
175
218
|
import("./public-cli/invocation.js")
|
|
176
219
|
]);
|
|
177
|
-
|
|
220
|
+
const stepped = await runPrepared(parseNotaryArgv, (env, once) => runPublicNotary(options.argv, env, io, once));
|
|
221
|
+
if ("fail" in stepped) return stepped.fail;
|
|
222
|
+
result = stepped.ok;
|
|
178
223
|
break;
|
|
179
224
|
}
|
|
180
225
|
case "inspector": {
|
|
@@ -182,49 +227,23 @@ async function summonPublicRole(options) {
|
|
|
182
227
|
import("./public-cli/inspector-run.js"),
|
|
183
228
|
import("./public-cli/invocation.js")
|
|
184
229
|
]);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
case "auditor": {
|
|
189
|
-
const [{ runPublicInstructionSeat }, { parseAuditorArgv }] = await Promise.all([
|
|
190
|
-
import("./public-cli/instruction-seat-run.js"),
|
|
191
|
-
import("./public-cli/invocation.js")
|
|
192
|
-
]);
|
|
193
|
-
result = await runPublicInstructionSeat(
|
|
194
|
-
options.argv,
|
|
195
|
-
env,
|
|
196
|
-
io,
|
|
197
|
-
"auditor",
|
|
198
|
-
parseAuditorArgv
|
|
199
|
-
);
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
case "navigator": {
|
|
203
|
-
const [{ runPublicInstructionSeat }, { parseNavigatorArgv }] = await Promise.all([
|
|
204
|
-
import("./public-cli/instruction-seat-run.js"),
|
|
205
|
-
import("./public-cli/invocation.js")
|
|
206
|
-
]);
|
|
207
|
-
result = await runPublicInstructionSeat(
|
|
208
|
-
options.argv,
|
|
209
|
-
env,
|
|
210
|
-
io,
|
|
211
|
-
"navigator",
|
|
212
|
-
parseNavigatorArgv
|
|
213
|
-
);
|
|
230
|
+
const stepped = await runPrepared(parseInspectorArgv, (env, once) => runPublicInspector(options.argv, env, io, once));
|
|
231
|
+
if ("fail" in stepped) return stepped.fail;
|
|
232
|
+
result = stepped.ok;
|
|
214
233
|
break;
|
|
215
234
|
}
|
|
235
|
+
case "auditor":
|
|
236
|
+
case "navigator":
|
|
216
237
|
case "gatekeeper": {
|
|
217
|
-
const
|
|
238
|
+
const seat2 = options.role;
|
|
239
|
+
const [{ runPublicInstructionSeat }, invocation] = await Promise.all([
|
|
218
240
|
import("./public-cli/instruction-seat-run.js"),
|
|
219
241
|
import("./public-cli/invocation.js")
|
|
220
242
|
]);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"gatekeeper",
|
|
226
|
-
parseGatekeeperArgv
|
|
227
|
-
);
|
|
243
|
+
const parse = seat2 === "auditor" ? invocation.parseAuditorArgv : seat2 === "navigator" ? invocation.parseNavigatorArgv : invocation.parseGatekeeperArgv;
|
|
244
|
+
const stepped = await runPrepared(parse, (env, once) => runPublicInstructionSeat(options.argv, env, io, seat2, once));
|
|
245
|
+
if ("fail" in stepped) return stepped.fail;
|
|
246
|
+
result = stepped.ok;
|
|
228
247
|
break;
|
|
229
248
|
}
|
|
230
249
|
case "judge": {
|
|
@@ -232,7 +251,9 @@ async function summonPublicRole(options) {
|
|
|
232
251
|
import("./public-cli/judge-run.js"),
|
|
233
252
|
import("./public-cli/invocation.js")
|
|
234
253
|
]);
|
|
235
|
-
|
|
254
|
+
const stepped = await runPrepared(parseJudgeArgv, (env, once) => runPublicJudge(options.argv, env, io, once));
|
|
255
|
+
if ("fail" in stepped) return stepped.fail;
|
|
256
|
+
result = stepped.ok;
|
|
236
257
|
break;
|
|
237
258
|
}
|
|
238
259
|
case "doctor": {
|
|
@@ -240,7 +261,9 @@ async function summonPublicRole(options) {
|
|
|
240
261
|
import("./public-cli/doctor-run.js"),
|
|
241
262
|
import("./public-cli/invocation.js")
|
|
242
263
|
]);
|
|
243
|
-
|
|
264
|
+
const stepped = await runPrepared(parseDoctorArgv, (env, once) => runPublicDoctor(options.argv, env, io, once));
|
|
265
|
+
if ("fail" in stepped) return stepped.fail;
|
|
266
|
+
result = stepped.ok;
|
|
244
267
|
break;
|
|
245
268
|
}
|
|
246
269
|
case "diarist": {
|
|
@@ -248,7 +271,9 @@ async function summonPublicRole(options) {
|
|
|
248
271
|
import("./public-cli/diarist-run.js"),
|
|
249
272
|
import("./public-cli/invocation.js")
|
|
250
273
|
]);
|
|
251
|
-
|
|
274
|
+
const stepped = await runPrepared(parseDiaristArgv, (env, once) => runPublicDiarist(options.argv, env, io, once));
|
|
275
|
+
if ("fail" in stepped) return stepped.fail;
|
|
276
|
+
result = stepped.ok;
|
|
252
277
|
break;
|
|
253
278
|
}
|
|
254
279
|
}
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Engine process failures stop through the host infrastructure-failure seam.
|
|
6
6
|
* Caller AbortSignal cancellation propagates unchanged.
|
|
7
7
|
*/
|
|
8
|
+
import { basename } from "node:path";
|
|
8
9
|
import { Type, type Static } from "typebox";
|
|
9
10
|
import type { HostContext, HostToolDefinition, HostToolResult, RoleHost } from "./host-contracts.ts";
|
|
10
11
|
|
|
@@ -16,6 +17,18 @@ import {
|
|
|
16
17
|
resolveEngineName,
|
|
17
18
|
runEngineDetourOnce,
|
|
18
19
|
} from "./engine-detour.ts";
|
|
20
|
+
import {
|
|
21
|
+
engineDetourStdoutByteLength,
|
|
22
|
+
reportEngineDetourCall,
|
|
23
|
+
} from "./engine-detour-usage.ts";
|
|
24
|
+
|
|
25
|
+
/** runDirectory leaf is `<runId>@<role>`; subject is optional. */
|
|
26
|
+
function basenameRunId(runDirectory: string): string | undefined {
|
|
27
|
+
const leaf = basename(runDirectory);
|
|
28
|
+
const at = leaf.indexOf("@");
|
|
29
|
+
if (at <= 0) return undefined;
|
|
30
|
+
return leaf.slice(0, at);
|
|
31
|
+
}
|
|
19
32
|
|
|
20
33
|
// #836 r16 class 3: argv required/minItems/element-minLength stay — execute()
|
|
21
34
|
// must obtain the first item as the executable and spawn it (below; #82-98).
|
|
@@ -32,7 +45,14 @@ const engineDetourArgsSchema = Type.Object(
|
|
|
32
45
|
|
|
33
46
|
type EngineDetourArgs = Static<typeof engineDetourArgsSchema>;
|
|
34
47
|
|
|
35
|
-
type EngineDetourContext = Pick<HostContext, "cwd" | "mode" | "abort"
|
|
48
|
+
type EngineDetourContext = Pick<HostContext, "cwd" | "mode" | "abort"> & {
|
|
49
|
+
sessionManager?: Pick<HostContext["sessionManager"], "getSessionFile">;
|
|
50
|
+
runDirectory?: string;
|
|
51
|
+
/** Public-invocation scope from the shared Host envelope (#537). */
|
|
52
|
+
invocationScopeId?: string;
|
|
53
|
+
/** Selected host from the shared Host envelope (#537 / ADR 0082). */
|
|
54
|
+
host?: string;
|
|
55
|
+
};
|
|
36
56
|
|
|
37
57
|
export type EngineDetourHostActions = {
|
|
38
58
|
failInfrastructure(
|
|
@@ -58,6 +78,12 @@ function isCallerCancellation(
|
|
|
58
78
|
return false;
|
|
59
79
|
}
|
|
60
80
|
|
|
81
|
+
function asError(error: unknown, fallback: string): Error {
|
|
82
|
+
return error instanceof Error
|
|
83
|
+
? error
|
|
84
|
+
: new Error(String(error).trim() || fallback);
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
/**
|
|
62
88
|
* Build one detour tool definition for a configured engine name.
|
|
63
89
|
* `fail` owns host abort (parent) vs throw (evidence child) for tool misuse only.
|
|
@@ -100,6 +126,68 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
100
126
|
);
|
|
101
127
|
}
|
|
102
128
|
|
|
129
|
+
const sessionParent = ctx.sessionManager?.getSessionFile?.();
|
|
130
|
+
const startedAt = Date.now();
|
|
131
|
+
const runDirectory = typeof ctx.runDirectory === "string" && ctx.runDirectory.length > 0
|
|
132
|
+
? ctx.runDirectory
|
|
133
|
+
: undefined;
|
|
134
|
+
const runId = runDirectory === undefined ? undefined : basenameRunId(runDirectory);
|
|
135
|
+
// Public-invocation scope + selected host from Host envelope only — never
|
|
136
|
+
// courtAttemptId, never sidecar file, never pre-spawn invocation.json I/O.
|
|
137
|
+
const invocationScopeId =
|
|
138
|
+
typeof ctx.invocationScopeId === "string" && ctx.invocationScopeId.trim() !== ""
|
|
139
|
+
? ctx.invocationScopeId.trim()
|
|
140
|
+
: undefined;
|
|
141
|
+
const host =
|
|
142
|
+
typeof ctx.host === "string" && ctx.host.trim() !== ""
|
|
143
|
+
? ctx.host.trim()
|
|
144
|
+
: undefined;
|
|
145
|
+
|
|
146
|
+
const recordCall = (observed: {
|
|
147
|
+
code?: number;
|
|
148
|
+
stdoutByteLength?: number;
|
|
149
|
+
}): void => {
|
|
150
|
+
if (typeof sessionParent !== "string" || sessionParent.length === 0) return;
|
|
151
|
+
reportEngineDetourCall({
|
|
152
|
+
toolCallId,
|
|
153
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
154
|
+
cwd: ctx.cwd,
|
|
155
|
+
sessionParent,
|
|
156
|
+
...(runId === undefined ? {} : { runId }),
|
|
157
|
+
...(invocationScopeId === undefined ? {} : { invocationScopeId }),
|
|
158
|
+
...(host === undefined ? {} : { host }),
|
|
159
|
+
...(observed.code === undefined ? {} : { code: observed.code }),
|
|
160
|
+
...(observed.stdoutByteLength === undefined
|
|
161
|
+
? {}
|
|
162
|
+
: { stdoutByteLength: observed.stdoutByteLength }),
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** Preserve engine cause; if ledger write also fails, keep both (失败诚实). */
|
|
167
|
+
const failAfterLedger = (
|
|
168
|
+
engineCause: Error,
|
|
169
|
+
observed: { code?: number; stdoutByteLength?: number },
|
|
170
|
+
aggregateMessage: string,
|
|
171
|
+
): never => {
|
|
172
|
+
try {
|
|
173
|
+
recordCall(observed);
|
|
174
|
+
} catch (recordError) {
|
|
175
|
+
input.fail(
|
|
176
|
+
new AggregateError(
|
|
177
|
+
[
|
|
178
|
+
engineCause,
|
|
179
|
+
asError(recordError, "engine detour usage ledger write failed"),
|
|
180
|
+
],
|
|
181
|
+
aggregateMessage,
|
|
182
|
+
{ cause: engineCause },
|
|
183
|
+
),
|
|
184
|
+
toolCallId,
|
|
185
|
+
ctx,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
input.fail(engineCause, toolCallId, ctx);
|
|
189
|
+
};
|
|
190
|
+
|
|
103
191
|
let result: Awaited<ReturnType<typeof runEngineDetourOnce>>;
|
|
104
192
|
try {
|
|
105
193
|
result = await runEngineDetourOnce({
|
|
@@ -109,20 +197,30 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
109
197
|
});
|
|
110
198
|
} catch (error) {
|
|
111
199
|
if (isCallerCancellation(error, signal)) throw error;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
200
|
+
// Spawn path: duration only — code/stdout bytes absent (not forged 0).
|
|
201
|
+
return failAfterLedger(
|
|
202
|
+
asError(error, "劳务引擎 spawn 失败"),
|
|
203
|
+
{},
|
|
204
|
+
"engine detour spawn and usage ledger both failed",
|
|
205
|
+
);
|
|
116
206
|
}
|
|
117
207
|
|
|
208
|
+
const stdoutByteLength = engineDetourStdoutByteLength(result.stdout);
|
|
209
|
+
const observed = { code: result.code, stdoutByteLength };
|
|
210
|
+
|
|
211
|
+
// Classify closed-child failure before ledger write so a sitian failure
|
|
212
|
+
// cannot erase nonzero/empty engine facts.
|
|
118
213
|
if (isEngineDetourFailure(result)) {
|
|
119
|
-
|
|
214
|
+
return failAfterLedger(
|
|
120
215
|
new Error(engineDetourFailureDiagnostic(result)),
|
|
121
|
-
|
|
122
|
-
|
|
216
|
+
observed,
|
|
217
|
+
"engine detour child-close and usage ledger both failed",
|
|
123
218
|
);
|
|
124
219
|
}
|
|
125
220
|
|
|
221
|
+
recordCall(observed);
|
|
222
|
+
|
|
223
|
+
// Usage ledger lives in sitian + decisiveFacts only (#537) — not tool details.
|
|
126
224
|
return {
|
|
127
225
|
content: [{ type: "text" as const, text: result.stdout }],
|
|
128
226
|
details: {
|