@akagilnc/pi-workflow-roles 0.1.5614 → 0.1.5650
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 +960 -465
- package/dist/activation-ledger-topology.js +12 -0
- package/dist/external-host-turn-loop.js +281 -0
- package/dist/headless-host/production-host.js +900 -406
- package/dist/migrate-book-topology.js +8 -5
- package/dist/pi/role-turn-host.js +33 -3
- package/dist/public-cli/instruction-seat-run.js +177 -46
- package/dist/public-cli/invocation.js +5 -12
- package/dist/public-cli/main.js +541 -165
- package/dist/public-cli/post-admission.js +171 -18
- package/dist/public-cli/settlement.js +20 -10
- package/dist/report-ticket-tool.js +136 -0
- package/dist/role-run-placement.js +1 -2
- package/dist/role-run-relocation.js +24 -4
- package/package.json +1 -1
- package/resources/engine-dispatch.md +6 -3
- package/resources/engines/claude-code.md +4 -5
- package/resources/missing-ticket-reask.md +1 -0
- package/src/acp-host/role-turn-host.ts +41 -31
- package/src/activation-ledger-topology.ts +16 -0
- package/src/external-host-turn-loop.ts +103 -12
- package/src/headless-host/role-turn-host.ts +41 -25
- package/src/pi/role-turn-host.ts +37 -3
- package/src/public-cli/instruction-seat-run.ts +248 -50
- package/src/public-cli/invocation.ts +15 -27
- package/src/public-cli/post-admission.ts +250 -33
- package/src/public-cli/run-lifecycle.ts +5 -5
- package/src/public-cli/settlement.ts +38 -11
- package/src/public-cli/terminal.ts +6 -0
- package/src/report-ticket-tool.ts +176 -0
- package/src/role-envelope.ts +28 -3
- package/src/role-run-placement.ts +0 -2
- package/src/role-run-relocation.ts +45 -4
- package/src/role-runtime.ts +6 -0
- package/src/worker-submission-gates.ts +26 -3
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
|
|
4
4
|
import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
5
|
-
import { sessionDirectoryOf } from "../role-run-placement.ts";
|
|
5
|
+
import { sessionDirectoryOf, sessionFileOf } from "../role-run-placement.ts";
|
|
6
6
|
import {
|
|
7
7
|
createSerializedRoleTurnHost,
|
|
8
8
|
driveExternalRoleTurnRounds,
|
|
@@ -10,9 +10,12 @@ import {
|
|
|
10
10
|
externalHostFailure as failure,
|
|
11
11
|
raceAgainstHostAbort,
|
|
12
12
|
recordTurnDelivery,
|
|
13
|
+
retainPackageFaultBesideLivePlacement,
|
|
14
|
+
syncTurnRequestLivePlacement,
|
|
15
|
+
trySyncTurnRequestLivePlacement,
|
|
13
16
|
} from "../external-host-turn-loop.ts";
|
|
14
17
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
15
|
-
import { projectThrownFailureLeaf
|
|
18
|
+
import { projectThrownFailureLeaf } from "../public-cli/settlement.ts";
|
|
16
19
|
|
|
17
20
|
import {
|
|
18
21
|
copyAndRecordHostDossier,
|
|
@@ -260,7 +263,9 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
260
263
|
let sessionId: string | undefined;
|
|
261
264
|
let sessionOpened = false;
|
|
262
265
|
let accepted = false;
|
|
263
|
-
|
|
266
|
+
// Re-resolve after mid-turn report-ticket may have moved principal (#1171).
|
|
267
|
+
const liveSessionParent = (): string =>
|
|
268
|
+
config.sessionIdentity.resolveSessionFile(request.principal);
|
|
264
269
|
let outcome: RoleTurnResult = failure("session", "AcpNoOutcome", "no-outcome");
|
|
265
270
|
let reportedFailure: RoleTurnResult["knownFailure"];
|
|
266
271
|
let packageFailure: RoleTurnResult["knownFailure"];
|
|
@@ -321,7 +326,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
321
326
|
? loaded.sessionId
|
|
322
327
|
: bindSessionId;
|
|
323
328
|
if (config.hostName !== "hermes") recordNativeSessionPointer({
|
|
324
|
-
host: config.hostName, sessionId: loadedId, cwd: request.cwd, sessionParent, home: request.home,
|
|
329
|
+
host: config.hostName, sessionId: loadedId, cwd: request.cwd, sessionParent: liveSessionParent(), home: request.home,
|
|
325
330
|
});
|
|
326
331
|
return loadedId;
|
|
327
332
|
};
|
|
@@ -350,7 +355,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
350
355
|
sessionReady = false;
|
|
351
356
|
} else {
|
|
352
357
|
if (config.hostName !== "hermes") recordNativeSessionPointer({
|
|
353
|
-
host: config.hostName, sessionId, cwd: request.cwd, sessionParent, home: request.home,
|
|
358
|
+
host: config.hostName, sessionId, cwd: request.cwd, sessionParent: liveSessionParent(), home: request.home,
|
|
354
359
|
});
|
|
355
360
|
sessionOpened = true;
|
|
356
361
|
await config.sessionIdentity.bind(request.principal, sessionId);
|
|
@@ -377,7 +382,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
377
382
|
// MCP advertises tool.parameters; prepared.jsonSchema is the headless
|
|
378
383
|
// draft-07 stamp of that clone — record the delivered parameters face.
|
|
379
384
|
const { $schema: _headlessStamp, ...deliveredSchema } = prepared.jsonSchema;
|
|
380
|
-
await recordTurnDelivery(request
|
|
385
|
+
await recordTurnDelivery(await syncTurnRequestLivePlacement(request), {
|
|
381
386
|
systemPrompt: systemPromptOverride,
|
|
382
387
|
outputSchema: deliveredSchema,
|
|
383
388
|
}, "acp-host");
|
|
@@ -440,8 +445,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
440
445
|
const native = connection?.result?.();
|
|
441
446
|
reportedFailure = native?.knownFailure;
|
|
442
447
|
outcome = native ?? { code: null, stderr: connection?.stderr?.() ?? "", timedOut: false };
|
|
443
|
-
await
|
|
444
|
-
runDirectory: request.runDirectory,
|
|
448
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
445
449
|
diagnostic: `ACP turn exception beside native host facts: ${describeErrorIdentity(error)}`,
|
|
446
450
|
error,
|
|
447
451
|
});
|
|
@@ -458,8 +462,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
458
462
|
if (sessionId !== undefined && !accepted) {
|
|
459
463
|
try { connection.notify("session/cancel", { sessionId }); }
|
|
460
464
|
catch (error) {
|
|
461
|
-
await
|
|
462
|
-
runDirectory: request.runDirectory,
|
|
465
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
463
466
|
diagnostic: `session cancel failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
464
467
|
error,
|
|
465
468
|
});
|
|
@@ -467,8 +470,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
467
470
|
}
|
|
468
471
|
try { naturalTermination = await connection.close(); }
|
|
469
472
|
catch (error) {
|
|
470
|
-
await
|
|
471
|
-
runDirectory: request.runDirectory,
|
|
473
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
472
474
|
diagnostic: `connection close failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
473
475
|
error,
|
|
474
476
|
});
|
|
@@ -489,31 +491,39 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
489
491
|
|| (naturalTermination !== undefined && (native.timedOut || native.signal !== undefined));
|
|
490
492
|
let knownFailure = outcome.knownFailure;
|
|
491
493
|
if (nativeFailed && knownFailure !== undefined && knownFailure !== reportedFailure) {
|
|
492
|
-
if (knownFailure !== packageFailure)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
494
|
+
if (knownFailure !== packageFailure) {
|
|
495
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
496
|
+
diagnostic: knownFailure.diagnostic ?? "ACP package failure beside late native termination",
|
|
497
|
+
error: knownFailure,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
497
500
|
knownFailure = reportedFailure;
|
|
498
501
|
}
|
|
499
502
|
const { knownFailure: _nativeFailure, ...facts } = native;
|
|
500
503
|
outcome = { ...facts, ...(knownFailure === undefined ? {} : { knownFailure }) };
|
|
501
504
|
}
|
|
502
505
|
if (config.hostName !== "hermes" && sessionOpened && sessionId !== undefined) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
506
|
+
const live = await trySyncTurnRequestLivePlacement(request);
|
|
507
|
+
if ("runDirectory" in live) {
|
|
508
|
+
try {
|
|
509
|
+
copyAndRecordHostDossier({
|
|
510
|
+
host: config.hostName,
|
|
511
|
+
sessionId,
|
|
512
|
+
cwd: request.cwd,
|
|
513
|
+
sessionDirectory: sessionDirectoryOf(live.runDirectory),
|
|
514
|
+
sessionParent: sessionFileOf(live.runDirectory),
|
|
515
|
+
...(request.home !== undefined ? { home: request.home } : {}),
|
|
516
|
+
});
|
|
517
|
+
} catch (error) {
|
|
518
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
519
|
+
diagnostic: `host dossier copy failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
520
|
+
error,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
525
|
+
diagnostic: `host dossier copy skipped: live run path resolve failed beside host terminal: ${describeErrorIdentity(live.resolveError)}`,
|
|
526
|
+
error: live.resolveError,
|
|
517
527
|
});
|
|
518
528
|
}
|
|
519
529
|
}
|
|
@@ -45,6 +45,22 @@ export function tryHomeFromAkRolesPath(path: string): string | undefined {
|
|
|
45
45
|
return path.slice(0, match.index);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Soft derive: book key owning a path under `.ak-roles/books/<bookKey>/…`.
|
|
50
|
+
* Same topology family as tryHomeFromAkRolesPath — no cross-book invent.
|
|
51
|
+
*/
|
|
52
|
+
export function tryBookKeyFromAkRolesPath(path: string): string | undefined {
|
|
53
|
+
const home = tryHomeFromAkRolesPath(path);
|
|
54
|
+
if (home === undefined || home.length === 0) return undefined;
|
|
55
|
+
const booksRoot = join(resolveActivationLedgerHome(home), "books");
|
|
56
|
+
const rel = relative(booksRoot, resolve(path));
|
|
57
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const bookKey = rel.split(sep)[0];
|
|
61
|
+
return bookKey === undefined || bookKey === "" ? undefined : bookKey;
|
|
62
|
+
}
|
|
63
|
+
|
|
48
64
|
/**
|
|
49
65
|
* Hard derive: package home owning a run/session path under `.ak-roles/`.
|
|
50
66
|
* Fails typed when the path is not ledger topology (no HOME fallback).
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* One copy: serial, abort merge/race, closeRound re-ask rounds,
|
|
4
4
|
* host-aborted, round-limit. Last hop = ExternalHostTurnDriver (four verbs).
|
|
5
5
|
*/
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { basename } from "node:path";
|
|
8
|
+
|
|
6
9
|
import type {
|
|
7
10
|
RoleTurnHost,
|
|
8
11
|
RoleTurnKnownFailure,
|
|
@@ -10,12 +13,105 @@ import type {
|
|
|
10
13
|
RoleTurnResult,
|
|
11
14
|
} from "./host-contracts.ts";
|
|
12
15
|
import type { PreparedRoleTurn } from "./prepared-role-turn.ts";
|
|
16
|
+
import { tryBookKeyFromAkRolesPath } from "./activation-ledger-topology.ts";
|
|
13
17
|
import { projectThrownFailureLeaf, retainPackageFault } from "./public-cli/settlement.ts";
|
|
14
|
-
import { describeErrorIdentity } from "./public-cli/run-lifecycle.ts";
|
|
18
|
+
import { describeErrorIdentity, findRunDirectoryById } from "./public-cli/run-lifecycle.ts";
|
|
15
19
|
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.ts";
|
|
20
|
+
import { parseRunLeaf } from "./role-run-placement.ts";
|
|
21
|
+
import { projectTurnRequestLiveRunDirectory } from "./role-run-relocation.ts";
|
|
16
22
|
import { reportRunRecord } from "./sitian-facade.ts";
|
|
17
23
|
import { isOneShotWorkerReminderCode } from "./submission-errors.ts";
|
|
18
24
|
|
|
25
|
+
/**
|
|
26
|
+
* #1171: mid-turn report-ticket may have moved the leg. Prefer the recorded
|
|
27
|
+
* path when it still exists; otherwise re-locate by run id inside the book.
|
|
28
|
+
* Undefined means vanished and unlocatable — callers must not revive the dead leaf.
|
|
29
|
+
*/
|
|
30
|
+
export async function resolveLiveRunDirectoryPath(
|
|
31
|
+
recordedPath: string,
|
|
32
|
+
home: string,
|
|
33
|
+
): Promise<string | undefined> {
|
|
34
|
+
if (existsSync(recordedPath)) return recordedPath;
|
|
35
|
+
const parsed = parseRunLeaf(basename(recordedPath));
|
|
36
|
+
const bookKey = tryBookKeyFromAkRolesPath(recordedPath);
|
|
37
|
+
if (parsed === undefined || bookKey === undefined) return undefined;
|
|
38
|
+
return await findRunDirectoryById(
|
|
39
|
+
home,
|
|
40
|
+
parsed.runId,
|
|
41
|
+
bookKey,
|
|
42
|
+
parsed.role,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Prefer the request path when it still exists; otherwise re-locate by run id
|
|
48
|
+
* (resolveLiveRunDirectoryPath). Mutates the live request so later turn-record /
|
|
49
|
+
* exit-copy / fault notes stay on the new leaf.
|
|
50
|
+
*/
|
|
51
|
+
export async function syncTurnRequestLivePlacement(
|
|
52
|
+
request: RoleTurnRequest,
|
|
53
|
+
): Promise<string> {
|
|
54
|
+
const live = await resolveLiveRunDirectoryPath(request.runDirectory, request.home);
|
|
55
|
+
if (live === undefined) {
|
|
56
|
+
const parsed = parseRunLeaf(basename(request.runDirectory));
|
|
57
|
+
throw new Error(
|
|
58
|
+
`admitted run ${parsed?.runId ?? "(unparseable)"} missing after host turn; not found under ${request.home}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (live !== request.runDirectory) {
|
|
62
|
+
projectTurnRequestLiveRunDirectory(
|
|
63
|
+
request as {
|
|
64
|
+
runDirectory: string;
|
|
65
|
+
principal: { sessionDirectory?: string; sessionFile?: string };
|
|
66
|
+
},
|
|
67
|
+
live,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return live;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Soft live-path sync for cleanup / dossier work: lookup failure stays beside the
|
|
75
|
+
* caller and never revives a vanished leaf. Same authority as syncTurnRequestLivePlacement.
|
|
76
|
+
*/
|
|
77
|
+
export async function trySyncTurnRequestLivePlacement(
|
|
78
|
+
request: RoleTurnRequest,
|
|
79
|
+
): Promise<{ readonly runDirectory: string } | { readonly resolveError: unknown }> {
|
|
80
|
+
try {
|
|
81
|
+
return { runDirectory: await syncTurnRequestLivePlacement(request) };
|
|
82
|
+
} catch (resolveError) {
|
|
83
|
+
return { resolveError };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Retain a package fault on the live leaf when locatable. Lookup failure must not
|
|
89
|
+
* replace the original fault or block later cleanup — reuse retainPackageFault when
|
|
90
|
+
* the leaf exists; otherwise present both causes on stderr (Pi transport path shape).
|
|
91
|
+
*/
|
|
92
|
+
export async function retainPackageFaultBesideLivePlacement(
|
|
93
|
+
request: RoleTurnRequest,
|
|
94
|
+
input: { readonly diagnostic: string; readonly error?: unknown },
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const live = await trySyncTurnRequestLivePlacement(request);
|
|
97
|
+
if ("resolveError" in live) {
|
|
98
|
+
const resolveDiagnostic =
|
|
99
|
+
`live run path resolve failed beside fault: ${describeErrorIdentity(live.resolveError)}`;
|
|
100
|
+
try {
|
|
101
|
+
process.stderr.write(`${input.diagnostic}\n`);
|
|
102
|
+
process.stderr.write(`${resolveDiagnostic}\n`);
|
|
103
|
+
} catch {
|
|
104
|
+
// Best-effort presentation beside an already-chosen terminal.
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
await retainPackageFault({
|
|
109
|
+
runDirectory: live.runDirectory,
|
|
110
|
+
diagnostic: input.diagnostic,
|
|
111
|
+
...(Object.hasOwn(input, "error") ? { error: input.error } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
19
115
|
/**
|
|
20
116
|
* What one host start was given: one `turn-delivery` row of history.jsonl per start. History is
|
|
21
117
|
* the volume whose write failure does not stop a run (#833), so a row that cannot be
|
|
@@ -187,8 +283,7 @@ export async function driveExternalRoleTurnRounds(
|
|
|
187
283
|
try {
|
|
188
284
|
closure = await prepared.closeRound();
|
|
189
285
|
} catch (error) {
|
|
190
|
-
await
|
|
191
|
-
runDirectory: request.runDirectory,
|
|
286
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
192
287
|
diagnostic: `round closure failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
193
288
|
error,
|
|
194
289
|
});
|
|
@@ -199,8 +294,7 @@ export async function driveExternalRoleTurnRounds(
|
|
|
199
294
|
if (closure.accepted) {
|
|
200
295
|
try { await driver.afterAccepted?.(); }
|
|
201
296
|
catch (error) {
|
|
202
|
-
await
|
|
203
|
-
runDirectory: request.runDirectory,
|
|
297
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
204
298
|
diagnostic: `post-acceptance close failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
205
299
|
error,
|
|
206
300
|
});
|
|
@@ -208,8 +302,7 @@ export async function driveExternalRoleTurnRounds(
|
|
|
208
302
|
return result;
|
|
209
303
|
}
|
|
210
304
|
if ("failure" in closure) {
|
|
211
|
-
await
|
|
212
|
-
runDirectory: request.runDirectory,
|
|
305
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
213
306
|
diagnostic: `round closure failed beside host terminal: ${JSON.stringify(closure.failure)}`,
|
|
214
307
|
});
|
|
215
308
|
// This is an explicit envelope failure (e.g. required audit/ledger failure),
|
|
@@ -225,8 +318,7 @@ export async function driveExternalRoleTurnRounds(
|
|
|
225
318
|
else {
|
|
226
319
|
countedReasks += 1;
|
|
227
320
|
if (countedReasks > countedReaskLimit) {
|
|
228
|
-
await
|
|
229
|
-
runDirectory: request.runDirectory,
|
|
321
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
230
322
|
diagnostic: `${driver.roundLimitName}: round-retry-limit`,
|
|
231
323
|
});
|
|
232
324
|
return result;
|
|
@@ -242,14 +334,13 @@ export async function driveExternalRoleTurnRounds(
|
|
|
242
334
|
* failure stays primary. Ordinary cleanup is handled at its owning seam. */
|
|
243
335
|
export async function disposeExternalRoleTurn(
|
|
244
336
|
prepared: Pick<PreparedRoleTurn, "dispose">,
|
|
245
|
-
request:
|
|
337
|
+
request: RoleTurnRequest,
|
|
246
338
|
outcome: RoleTurnResult,
|
|
247
339
|
): Promise<RoleTurnResult> {
|
|
248
340
|
try {
|
|
249
341
|
await prepared.dispose?.();
|
|
250
342
|
} catch (error) {
|
|
251
|
-
await
|
|
252
|
-
runDirectory: request.runDirectory,
|
|
343
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
253
344
|
diagnostic: `required envelope shutdown failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
254
345
|
error,
|
|
255
346
|
});
|
|
@@ -11,7 +11,7 @@ import { tmpdir } from "node:os";
|
|
|
11
11
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
12
12
|
|
|
13
13
|
import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
14
|
-
import { sessionDirectoryOf } from "../role-run-placement.ts";
|
|
14
|
+
import { sessionDirectoryOf, sessionFileOf } from "../role-run-placement.ts";
|
|
15
15
|
import {
|
|
16
16
|
createSerializedRoleTurnHost,
|
|
17
17
|
driveExternalRoleTurnRounds,
|
|
@@ -20,9 +20,12 @@ import {
|
|
|
20
20
|
hostAbortedError,
|
|
21
21
|
isHostAbortedError,
|
|
22
22
|
recordTurnDelivery,
|
|
23
|
+
retainPackageFaultBesideLivePlacement,
|
|
24
|
+
syncTurnRequestLivePlacement,
|
|
25
|
+
trySyncTurnRequestLivePlacement,
|
|
23
26
|
} from "../external-host-turn-loop.ts";
|
|
24
27
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
25
|
-
import { projectThrownFailureLeaf
|
|
28
|
+
import { projectThrownFailureLeaf } from "../public-cli/settlement.ts";
|
|
26
29
|
import {
|
|
27
30
|
renderSystemPromptOverride,
|
|
28
31
|
resolveBoundHostSessionId,
|
|
@@ -467,7 +470,9 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
467
470
|
// CLI start-up inputs (system prompt / schema / MCP config files) are not
|
|
468
471
|
// dossier: they live in a throwaway directory, and what was delivered is
|
|
469
472
|
// recorded once per actual start as a history.jsonl `turn-delivery` row.
|
|
470
|
-
|
|
473
|
+
// Re-resolve after mid-turn report-ticket may have moved principal (#1171).
|
|
474
|
+
const liveSessionParent = (): string =>
|
|
475
|
+
config.sessionIdentity.resolveSessionFile(request.principal);
|
|
471
476
|
const inputsDirectory = await mkdtemp(join(tmpdir(), "ak-role-headless-"));
|
|
472
477
|
const systemPromptPath = join(inputsDirectory, "system-prompt.txt");
|
|
473
478
|
let mcpConfigPath: string | undefined;
|
|
@@ -500,8 +505,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
500
505
|
try {
|
|
501
506
|
await rm(inputsDirectory, { recursive: true, force: true });
|
|
502
507
|
} catch (cleanupError) {
|
|
503
|
-
await
|
|
504
|
-
runDirectory: request.runDirectory,
|
|
508
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
505
509
|
diagnostic: `headless turn inputs cleanup failed beside setup failure: ${describeErrorIdentity(cleanupError)}`,
|
|
506
510
|
error: cleanupError,
|
|
507
511
|
});
|
|
@@ -552,13 +556,13 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
552
556
|
host: config.hostName,
|
|
553
557
|
sessionId,
|
|
554
558
|
cwd: request.cwd,
|
|
555
|
-
sessionParent,
|
|
559
|
+
sessionParent: liveSessionParent(),
|
|
556
560
|
home: request.home,
|
|
557
561
|
}) !== undefined;
|
|
558
562
|
}
|
|
559
563
|
|
|
560
564
|
// What this CLI start was given: one history record per start.
|
|
561
|
-
await recordTurnDelivery(request
|
|
565
|
+
await recordTurnDelivery(await syncTurnRequestLivePlacement(request), {
|
|
562
566
|
systemPrompt,
|
|
563
567
|
...(startedWithSchema === undefined ? {} : { outputSchema: startedWithSchema }),
|
|
564
568
|
}, "headless-host");
|
|
@@ -587,7 +591,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
587
591
|
if (!codex && typeof event === "object" && event !== null && "session_id" in event
|
|
588
592
|
&& typeof event.session_id === "string" && event.session_id !== "" && event.session_id !== sessionId) {
|
|
589
593
|
sessionId = event.session_id;
|
|
590
|
-
recordNativeSessionPointer({ host: config.hostName, sessionId, cwd: request.cwd, sessionParent, home: request.home });
|
|
594
|
+
recordNativeSessionPointer({ host: config.hostName, sessionId, cwd: request.cwd, sessionParent: liveSessionParent(), home: request.home });
|
|
591
595
|
}
|
|
592
596
|
if (codex && !codexIdObserved) {
|
|
593
597
|
const tid = codexObserver?.result().threadId;
|
|
@@ -597,7 +601,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
597
601
|
host: config.hostName,
|
|
598
602
|
sessionId: tid,
|
|
599
603
|
cwd: request.cwd,
|
|
600
|
-
sessionParent,
|
|
604
|
+
sessionParent: liveSessionParent(),
|
|
601
605
|
home: request.home,
|
|
602
606
|
}) !== undefined;
|
|
603
607
|
}
|
|
@@ -608,7 +612,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
608
612
|
exitedSessionId = roundSessionId;
|
|
609
613
|
if (codex && roundSessionId !== undefined && roundSessionId !== "" && !pointerRecorded) {
|
|
610
614
|
try {
|
|
611
|
-
recordNativeSessionPointer({ host: config.hostName, sessionId: roundSessionId, cwd: request.cwd, sessionParent, home: request.home });
|
|
615
|
+
recordNativeSessionPointer({ host: config.hostName, sessionId: roundSessionId, cwd: request.cwd, sessionParent: liveSessionParent(), home: request.home });
|
|
612
616
|
} catch (error) {
|
|
613
617
|
spawned.packageErrors = [...spawned.packageErrors, error];
|
|
614
618
|
}
|
|
@@ -626,8 +630,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
626
630
|
}
|
|
627
631
|
|
|
628
632
|
for (const error of spawned.packageErrors) {
|
|
629
|
-
await
|
|
630
|
-
runDirectory: request.runDirectory,
|
|
633
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
631
634
|
diagnostic: `headless transport handling failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
632
635
|
error,
|
|
633
636
|
});
|
|
@@ -635,8 +638,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
635
638
|
|
|
636
639
|
try {
|
|
637
640
|
const noteBeside = async (error: unknown, what: string): Promise<void> => {
|
|
638
|
-
await
|
|
639
|
-
runDirectory: request.runDirectory,
|
|
641
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
640
642
|
diagnostic: `${what}: ${describeErrorIdentity(error)}`,
|
|
641
643
|
error,
|
|
642
644
|
});
|
|
@@ -729,8 +731,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
729
731
|
}
|
|
730
732
|
return deliveredFromSpawned(spawned);
|
|
731
733
|
} catch (error) {
|
|
732
|
-
await
|
|
733
|
-
runDirectory: request.runDirectory,
|
|
734
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
734
735
|
diagnostic: `required turn handling failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
735
736
|
error,
|
|
736
737
|
});
|
|
@@ -742,24 +743,39 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
742
743
|
},
|
|
743
744
|
});
|
|
744
745
|
} finally {
|
|
746
|
+
// Cleanup must not depend on live-path sync: lookup failure stays beside
|
|
747
|
+
// the original cleanup/copy work and never skips rm(inputsDirectory).
|
|
748
|
+
const live = await trySyncTurnRequestLivePlacement(request);
|
|
749
|
+
const liveRunDirectory = "runDirectory" in live ? live.runDirectory : undefined;
|
|
750
|
+
if (liveRunDirectory === undefined && "resolveError" in live) {
|
|
751
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
752
|
+
diagnostic: `headless cleanup live path resolve failed beside host terminal: ${describeErrorIdentity(live.resolveError)}`,
|
|
753
|
+
error: live.resolveError,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
745
756
|
try {
|
|
746
757
|
await rm(inputsDirectory, { recursive: true, force: true });
|
|
747
758
|
} catch (error) {
|
|
748
|
-
await
|
|
749
|
-
runDirectory: request.runDirectory,
|
|
759
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
750
760
|
diagnostic: `headless turn inputs cleanup failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
751
761
|
error,
|
|
752
762
|
});
|
|
753
763
|
}
|
|
754
764
|
try {
|
|
755
|
-
if (
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
765
|
+
if (
|
|
766
|
+
exitedSessionId !== undefined
|
|
767
|
+
&& exitedSessionId !== ""
|
|
768
|
+
&& liveRunDirectory !== undefined
|
|
769
|
+
) {
|
|
770
|
+
copyAndRecordHostDossier({
|
|
771
|
+
host: config.hostName, sessionId: exitedSessionId, cwd: request.cwd,
|
|
772
|
+
sessionDirectory: sessionDirectoryOf(liveRunDirectory),
|
|
773
|
+
sessionParent: sessionFileOf(liveRunDirectory),
|
|
774
|
+
...(request.home !== undefined ? { home: request.home } : {}),
|
|
775
|
+
});
|
|
776
|
+
}
|
|
760
777
|
} catch (error) {
|
|
761
|
-
await
|
|
762
|
-
runDirectory: request.runDirectory,
|
|
778
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
763
779
|
diagnostic: `host dossier copy failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
764
780
|
error,
|
|
765
781
|
});
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { execFile, spawn } from "node:child_process";
|
|
7
7
|
import { constants } from "node:fs";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
8
9
|
import { access, appendFile, realpath } from "node:fs/promises";
|
|
9
10
|
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
10
11
|
import { platform } from "node:process";
|
|
@@ -27,6 +28,12 @@ import { projectActivationFlags } from "../role-activation-flags.ts";
|
|
|
27
28
|
import { encodeUserDialogueStdin } from "../user-dialogue-stdin.ts";
|
|
28
29
|
import { readLedgerSessionJsonlLines, readStrictPiSessionJsonl } from "../ledger-session-read.ts";
|
|
29
30
|
import { copyAndRecordHostDossier } from "../host-session-record.ts";
|
|
31
|
+
import {
|
|
32
|
+
resolveLiveRunDirectoryPath,
|
|
33
|
+
retainPackageFaultBesideLivePlacement,
|
|
34
|
+
syncTurnRequestLivePlacement,
|
|
35
|
+
} from "../external-host-turn-loop.ts";
|
|
36
|
+
import { homeFromRunDirectory } from "../activation-ledger-topology.ts";
|
|
30
37
|
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.ts";
|
|
31
38
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
32
39
|
import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.ts";
|
|
@@ -358,7 +365,34 @@ export function createDefaultPiSpawnRunner(options: {
|
|
|
358
365
|
if (stdinDeliveryError !== undefined) packageErrors.push(stdinDeliveryError);
|
|
359
366
|
for (const error of packageErrors) {
|
|
360
367
|
const diagnostic = `Pi transport handling failed beside host terminal: ${describeErrorIdentity(error)}`;
|
|
361
|
-
|
|
368
|
+
// #1171 F4: spawn-time path may have been renamed; reuse
|
|
369
|
+
// resolveLiveRunDirectoryPath (same authority as syncTurnRequestLivePlacement).
|
|
370
|
+
const spawnDir = spawnOptions.env.AK_ROLE_RUN_DIR;
|
|
371
|
+
let runDirectory: string | undefined;
|
|
372
|
+
if (typeof spawnDir === "string" && spawnDir.trim() !== "") {
|
|
373
|
+
try {
|
|
374
|
+
runDirectory = await resolveLiveRunDirectoryPath(
|
|
375
|
+
spawnDir,
|
|
376
|
+
homeFromRunDirectory(spawnDir),
|
|
377
|
+
);
|
|
378
|
+
} catch (resolveError) {
|
|
379
|
+
// Resolve threw beside an already-closed child: keep both causes
|
|
380
|
+
// on the existing retain/stderr presentation path (失败诚实).
|
|
381
|
+
runDirectory = existsSync(spawnDir) ? spawnDir : undefined;
|
|
382
|
+
const resolveDiagnostic =
|
|
383
|
+
`live run path resolve failed beside Pi transport fault: ${describeErrorIdentity(resolveError)}`;
|
|
384
|
+
if (runDirectory !== undefined) {
|
|
385
|
+
await retainPackageFault({
|
|
386
|
+
runDirectory,
|
|
387
|
+
diagnostic: resolveDiagnostic,
|
|
388
|
+
error: resolveError,
|
|
389
|
+
});
|
|
390
|
+
} else {
|
|
391
|
+
try { process.stderr.write(`${resolveDiagnostic}\n`); }
|
|
392
|
+
catch { /* Best-effort presentation beside the already closed child. */ }
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
362
396
|
if (runDirectory !== undefined) {
|
|
363
397
|
await retainPackageFault({ runDirectory, diagnostic, error });
|
|
364
398
|
} else {
|
|
@@ -474,6 +508,7 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
474
508
|
// bind projects host.sessions; copy records the landing. Host adapters do
|
|
475
509
|
// not write current.json — public bind/settlement seams own that render.
|
|
476
510
|
try {
|
|
511
|
+
await syncTurnRequestLivePlacement(request);
|
|
477
512
|
const { sessionFile, sessionDirectory } = config.principalAuthority.decode(request.principal);
|
|
478
513
|
const sessionId = await readPiSessionHeaderId(sessionFile);
|
|
479
514
|
if (sessionId !== undefined) {
|
|
@@ -491,8 +526,7 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
491
526
|
});
|
|
492
527
|
}
|
|
493
528
|
} catch (error) {
|
|
494
|
-
await
|
|
495
|
-
runDirectory: request.runDirectory,
|
|
529
|
+
await retainPackageFaultBesideLivePlacement(request, {
|
|
496
530
|
diagnostic: `pi host dossier record failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
497
531
|
error,
|
|
498
532
|
});
|