@akagilnc/pi-workflow-roles 0.1.5216 → 0.1.5241
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/CLAUDE.md +3 -0
- package/README.md +1 -1
- package/dist/acp-host/production-host.js +658 -829
- package/dist/engine-detour-usage.js +4 -16
- package/dist/headless-host/production-host.js +777 -862
- package/dist/host-session-record.js +303 -0
- package/dist/pi/role-turn-host.js +1 -21
- package/dist/public-cli/main.js +200 -487
- package/dist/public-cli/post-admission.js +11 -60
- package/dist/sitian-appender.js +10 -155
- package/package.json +1 -1
- package/src/acp-host/role-turn-host.ts +36 -108
- package/src/engine-detour-usage.ts +4 -16
- package/src/external-host-turn-loop.ts +2 -18
- package/src/headless-host/role-turn-host.ts +67 -31
- package/src/host-contracts.ts +0 -15
- package/src/host-session-record.ts +325 -25
- package/src/notary-role.ts +0 -1
- package/src/pi/role-turn-host.ts +1 -25
- package/src/public-cli/countersign-run.ts +4 -3
- package/src/public-cli/post-admission.ts +10 -69
- package/src/public-role-summons.ts +1 -2
- package/src/role-envelope.ts +2 -2
- package/src/role-runtime.ts +0 -14
- package/src/sitian-appender.ts +16 -205
- package/dist/host-transition-prior-native.js +0 -78
- package/dist/public-cli/case-dossier-delivery.js +0 -109
- package/src/host-transition-prior-native.ts +0 -80
- package/src/pi/pi-normalization.ts +0 -291
- package/src/public-cli/case-dossier-delivery.ts +0 -147
|
@@ -18,7 +18,6 @@ import { pickEngineAxis } from "../package-resources/engine-material.js";
|
|
|
18
18
|
import { readStoredHostSessionId, resolveHostAwareSessionAvailability, } from "../session-identity.js";
|
|
19
19
|
import { rewriteRunDirectoryPathValue } from "../role-run-relocation.js";
|
|
20
20
|
import { isOfficerReviewSeat } from "../packaged-role-registry.js";
|
|
21
|
-
import { deliverCaseDossierAsAttachment } from "./case-dossier-delivery.js";
|
|
22
21
|
/** Original error bytes, never relabeled — a secondary fact riding beside a classified cause. */
|
|
23
22
|
function describeCaughtError(error) {
|
|
24
23
|
if (error instanceof Error) {
|
|
@@ -29,17 +28,17 @@ function describeCaughtError(error) {
|
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
31
30
|
* Nested gate summons (station child) on an officer seat: dialogue content is
|
|
32
|
-
* peer words only (#879).
|
|
33
|
-
*
|
|
31
|
+
* peer words only (#879). Never RoleTurnRequest.materials; #1092 drops code-side
|
|
32
|
+
* 起居录 path pointer freeze — roles locate records by ticket number.
|
|
34
33
|
*/
|
|
35
34
|
function isStationChildOfficerDialogue(role, env) {
|
|
36
35
|
return env.stationChild === true && isOfficerReviewSeat(role);
|
|
37
36
|
}
|
|
38
|
-
import { mintEngineDetourInvocationScope,
|
|
39
|
-
import { projectHostTransitionPriorNative } from "../host-transition-prior-native.js";
|
|
37
|
+
import { mintEngineDetourInvocationScope, withEngineDetourInvocationScope, } from "../engine-detour-usage.js";
|
|
40
38
|
import { clearCurrentCourt, clearTypedProviderHttpObservation, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, renderResumeCommand, } from "./run-lifecycle.js";
|
|
41
39
|
import { processCancelDiagnostic, processCancelSignalName, } from "./process-cancel.js";
|
|
42
40
|
import { homeFromRunDirectory } from "../activation-ledger-topology.js";
|
|
41
|
+
import { recordRunStart } from "../host-session-record.js";
|
|
43
42
|
import { attemptProducedFreshSubmission, classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatErrorCauseDetail, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, publishedFailureErrorPath, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, settleHostEndedNoReceipt, attachRecordedSubmissions, rewritePublishedFailureErrorPath, } from "./settlement.js";
|
|
44
43
|
import {} from "./terminal.js";
|
|
45
44
|
import { ensureRealArtifactsDirectory, persistReturnedRunState, runWithAutoResumeLoop, TurnDispatchedFailure, } from "./auto-resume.js";
|
|
@@ -407,45 +406,11 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
407
406
|
try {
|
|
408
407
|
// #987: do not pre-block or classify host dispatch from AK credential
|
|
409
408
|
// catalog facts. Per-invocation runner/host evidence owns provider identity.
|
|
410
|
-
// #617 DK-4: capture previous invocation host before markRunRunning overwrites it.
|
|
411
|
-
// Single authority projectHostTransitionPriorNative classifies the prior native volume.
|
|
412
|
-
// Same-run resume (#637) keeps host identity on the run's invocation page.
|
|
413
|
-
let previousHost;
|
|
414
|
-
const liveHost = env.host;
|
|
415
|
-
const principalCoordinates = admitted.principal === undefined
|
|
416
|
-
? undefined
|
|
417
|
-
: env.principalAuthority.decode(admitted.principal);
|
|
418
|
-
let hostTransition;
|
|
419
|
-
try {
|
|
420
|
-
previousHost = readInvocationSelectedHost(admitted.runDirectory);
|
|
421
|
-
hostTransition =
|
|
422
|
-
previousHost !== undefined && liveHost !== undefined && principalCoordinates !== undefined
|
|
423
|
-
? await projectHostTransitionPriorNative({
|
|
424
|
-
previousHost,
|
|
425
|
-
liveHost,
|
|
426
|
-
piSessionFile: principalCoordinates.sessionFile,
|
|
427
|
-
})
|
|
428
|
-
: undefined;
|
|
429
|
-
}
|
|
430
|
-
catch (error) {
|
|
431
|
-
// prior-native IO is on the public one-shot path — controlled failure, not bare throw.
|
|
432
|
-
return {
|
|
433
|
-
...(await presentControlledFailure(admitted, withEngineDetourInvocationScope({
|
|
434
|
-
timedOut: false,
|
|
435
|
-
code: null,
|
|
436
|
-
stderr: "",
|
|
437
|
-
thrown: error,
|
|
438
|
-
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState)),
|
|
439
|
-
...deferredPersist,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
409
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
443
410
|
// The authoritative host write (markRunRunning) is delayed to just before
|
|
444
411
|
// executeTurn — not merely past beforeDispatch (#840 r9 判词 class 2). Any
|
|
445
|
-
// pre-turn retry (beforeDispatch,
|
|
446
|
-
//
|
|
447
|
-
// committing env.host earlier would make readInvocationSelectedHost read back the
|
|
448
|
-
// new host on the retry and silently drop hostTransition.
|
|
412
|
+
// pre-turn retry (beforeDispatch, continuation assembly) must not commit a host
|
|
413
|
+
// identity until the turn is ready.
|
|
449
414
|
// Failures settle the run (presentControlledFailure); they must not leave it
|
|
450
415
|
// permanently running. Call-local auto-resume retries this hook until it
|
|
451
416
|
// succeeds; only an exhausted nested station child skips the parent loop
|
|
@@ -472,10 +437,8 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
472
437
|
}
|
|
473
438
|
// Turn request is assembled after beforeDispatch so this turn sees whatever it
|
|
474
439
|
// settled — the seat's ticket bind re-projection and any court diarist station
|
|
475
|
-
// writes (#742).
|
|
476
|
-
//
|
|
477
|
-
// (station-child and ordinary share one seam). Dialogue continuation stays
|
|
478
|
-
// caller/peer opaque — never splice system path sections into user dialogue.
|
|
440
|
+
// writes (#742). Dialogue continuation stays caller/peer opaque. #1092: no
|
|
441
|
+
// code-side 起居录 path freeze or readingMaterial inject — roles read by ticket.
|
|
479
442
|
// No package-resume parallel face or typed resume identity.
|
|
480
443
|
// #990: one-shot / auto-resume may freeze principal+runDirectory before
|
|
481
444
|
// relocate; always take durable identity from the live admitted object.
|
|
@@ -487,24 +450,11 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
487
450
|
if (env.stationChild !== undefined) {
|
|
488
451
|
turnRequest = { ...turnRequest, stationChild: env.stationChild };
|
|
489
452
|
}
|
|
490
|
-
if (hostTransition !== undefined) {
|
|
491
|
-
turnRequest = { ...turnRequest, hostTransition };
|
|
492
|
-
}
|
|
493
453
|
// Selected host axis rides the shared Host envelope for in-turn tools
|
|
494
454
|
// (detour usage ledger) — never a pre-spawn invocation.json reread.
|
|
495
|
-
if (typeof
|
|
496
|
-
turnRequest = { ...turnRequest, host:
|
|
455
|
+
if (typeof env.host === "string" && env.host.trim() !== "") {
|
|
456
|
+
turnRequest = { ...turnRequest, host: env.host.trim() };
|
|
497
457
|
}
|
|
498
|
-
// 0081 non-dialogue face: freeze pointer section under run/attachments/.
|
|
499
|
-
// Seat consumes via loadCaseDossierReadingMaterial → existing agent-start
|
|
500
|
-
// readingMaterial / systemPrompt.materials fold. Caller instruction, empty
|
|
501
|
-
// request, and resume --message stay verbatim.
|
|
502
|
-
await deliverCaseDossierAsAttachment({
|
|
503
|
-
ticketNumber: admitted.ticketNumber,
|
|
504
|
-
projectRoot: admitted.projectRoot,
|
|
505
|
-
home: env.home,
|
|
506
|
-
runDirectory: admitted.runDirectory,
|
|
507
|
-
});
|
|
508
458
|
// Authoritative host write happens here, at the real dispatch boundary —
|
|
509
459
|
// immediately before the turn actually starts, after every retryable
|
|
510
460
|
// pre-turn step above has succeeded on this attempt (#840 r9 判词 class 2).
|
|
@@ -514,6 +464,7 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
514
464
|
// auto-resume re-enters this dispatch and must reuse the same scope.
|
|
515
465
|
let result;
|
|
516
466
|
try {
|
|
467
|
+
recordRunStart(admitted.runDirectory);
|
|
517
468
|
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
518
469
|
}
|
|
519
470
|
catch (error) {
|
package/dist/sitian-appender.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
appendFileSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
unlinkSync,
|
|
7
|
-
writeFileSync
|
|
8
|
-
} from "node:fs";
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
9
3
|
import { dirname, join } from "node:path";
|
|
10
4
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
|
|
11
5
|
import {
|
|
@@ -19,48 +13,12 @@ import {
|
|
|
19
13
|
import {
|
|
20
14
|
SitianInfrastructureError
|
|
21
15
|
} from "./sitian-contracts.js";
|
|
22
|
-
function isRecord(value) {
|
|
23
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
-
}
|
|
25
|
-
function errorCodeOf(error) {
|
|
26
|
-
return error.code;
|
|
27
|
-
}
|
|
28
|
-
function identityClaimPath(recordFile, identity) {
|
|
29
|
-
const digest = createHash("sha256").update(identity, "utf8").digest("hex");
|
|
30
|
-
return `${recordFile}.id-${digest}`;
|
|
31
|
-
}
|
|
32
|
-
function createExclusiveFile(path, contents) {
|
|
33
|
-
writeFileSync(path, contents, { encoding: "utf8", flag: "wx" });
|
|
34
|
-
}
|
|
35
|
-
function sealTornTail(recordFile) {
|
|
36
|
-
if (!existsSync(recordFile)) return;
|
|
37
|
-
const buffer = readFileSync(recordFile);
|
|
38
|
-
if (buffer.length > 0 && buffer[buffer.length - 1] !== 10) {
|
|
39
|
-
appendFileSync(recordFile, "\n", "utf8");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
16
|
function appendSitianRecordBlock(input, block) {
|
|
43
17
|
if (block === "") return;
|
|
44
18
|
try {
|
|
45
19
|
const { sessionDir, recordFile, ledgerHome } = resolveSitianRecordPath(input);
|
|
46
20
|
ensureRealDirectoryTree(ledgerHome, sessionDir);
|
|
47
|
-
appendFileSync(recordFile,
|
|
48
|
-
sealTornTail(recordFile);
|
|
49
|
-
const identityOf = (line) => {
|
|
50
|
-
try {
|
|
51
|
-
const row = JSON.parse(line);
|
|
52
|
-
return isRecord(row) && typeof row.identity === "string" ? row.identity : void 0;
|
|
53
|
-
} catch {
|
|
54
|
-
return void 0;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
const identities = new Set(readFileSync(recordFile, "utf8").split("\n").map(identityOf).filter((id) => id !== void 0));
|
|
58
|
-
for (const line of block.match(/[^\n]*\n|[^\n]+$/g) ?? []) {
|
|
59
|
-
const identity = identityOf(line);
|
|
60
|
-
if (identity !== void 0 && identities.has(identity)) continue;
|
|
61
|
-
appendFileSync(recordFile, line, "utf8");
|
|
62
|
-
if (identity !== void 0) identities.add(identity);
|
|
63
|
-
}
|
|
21
|
+
appendFileSync(recordFile, block, "utf8");
|
|
64
22
|
} catch (error) {
|
|
65
23
|
if (error instanceof SitianInfrastructureError) throw error;
|
|
66
24
|
throw new SitianInfrastructureError(
|
|
@@ -69,106 +27,6 @@ function appendSitianRecordBlock(input, block) {
|
|
|
69
27
|
);
|
|
70
28
|
}
|
|
71
29
|
}
|
|
72
|
-
function findIdentityPointer(recordFile, identity, kind, level) {
|
|
73
|
-
if (!existsSync(recordFile)) return void 0;
|
|
74
|
-
const text = readFileSync(recordFile, "utf8");
|
|
75
|
-
for (const line of text.split("\n")) {
|
|
76
|
-
const trimmed = line.trim();
|
|
77
|
-
if (!trimmed) continue;
|
|
78
|
-
try {
|
|
79
|
-
const parsed = JSON.parse(trimmed);
|
|
80
|
-
if (isRecord(parsed) && parsed.identity === identity) {
|
|
81
|
-
return { identity, recordFile, kind, level };
|
|
82
|
-
}
|
|
83
|
-
} catch {
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return void 0;
|
|
87
|
-
}
|
|
88
|
-
function unlinkAbsentOk(path) {
|
|
89
|
-
try {
|
|
90
|
-
unlinkSync(path);
|
|
91
|
-
return void 0;
|
|
92
|
-
} catch (error) {
|
|
93
|
-
if (errorCodeOf(error) === "ENOENT") return void 0;
|
|
94
|
-
return error instanceof Error ? error : new Error(errorText(error));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function appendWithIdentityClaim(recordFile, record, row) {
|
|
98
|
-
sealTornTail(recordFile);
|
|
99
|
-
const claimPath = identityClaimPath(recordFile, record.identity);
|
|
100
|
-
const existing = findIdentityPointer(
|
|
101
|
-
recordFile,
|
|
102
|
-
record.identity,
|
|
103
|
-
record.kind,
|
|
104
|
-
record.level
|
|
105
|
-
);
|
|
106
|
-
if (existing !== void 0) {
|
|
107
|
-
unlinkAbsentOk(claimPath);
|
|
108
|
-
return existing;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
createExclusiveFile(claimPath, "");
|
|
112
|
-
} catch (error) {
|
|
113
|
-
if (errorCodeOf(error) !== "EEXIST") throw error;
|
|
114
|
-
sealTornTail(recordFile);
|
|
115
|
-
const published = findIdentityPointer(
|
|
116
|
-
recordFile,
|
|
117
|
-
record.identity,
|
|
118
|
-
record.kind,
|
|
119
|
-
record.level
|
|
120
|
-
);
|
|
121
|
-
if (published !== void 0) {
|
|
122
|
-
unlinkAbsentOk(claimPath);
|
|
123
|
-
return published;
|
|
124
|
-
}
|
|
125
|
-
throw new SitianInfrastructureError(
|
|
126
|
-
`Sitian identity claim at ${claimPath} already exists for identity ${record.identity}`,
|
|
127
|
-
{ cause: error }
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
let primaryFailure;
|
|
131
|
-
let result;
|
|
132
|
-
let cleanupFailure;
|
|
133
|
-
try {
|
|
134
|
-
sealTornTail(recordFile);
|
|
135
|
-
const raced = findIdentityPointer(
|
|
136
|
-
recordFile,
|
|
137
|
-
record.identity,
|
|
138
|
-
record.kind,
|
|
139
|
-
record.level
|
|
140
|
-
);
|
|
141
|
-
if (raced !== void 0) {
|
|
142
|
-
result = raced;
|
|
143
|
-
} else {
|
|
144
|
-
appendFileSync(recordFile, row, "utf8");
|
|
145
|
-
result = {
|
|
146
|
-
identity: record.identity,
|
|
147
|
-
recordFile,
|
|
148
|
-
kind: record.kind,
|
|
149
|
-
level: record.level
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
} catch (error) {
|
|
153
|
-
primaryFailure = error;
|
|
154
|
-
} finally {
|
|
155
|
-
cleanupFailure = unlinkAbsentOk(claimPath);
|
|
156
|
-
}
|
|
157
|
-
if (primaryFailure !== void 0) {
|
|
158
|
-
if (primaryFailure instanceof SitianInfrastructureError) throw primaryFailure;
|
|
159
|
-
throw new SitianInfrastructureError(
|
|
160
|
-
`Sitian appender persistence failure: ${errorText(primaryFailure)}`,
|
|
161
|
-
{ cause: primaryFailure }
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
if (cleanupFailure !== void 0) {
|
|
165
|
-
throw new SitianInfrastructureError(
|
|
166
|
-
`Sitian identity claim at ${claimPath} could not be released after append for identity ${record.identity}: ${errorText(cleanupFailure)}`,
|
|
167
|
-
{ cause: cleanupFailure }
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
30
|
const S4_SUBMISSION_LEDGER_KINDS = /* @__PURE__ */ new Set([
|
|
173
31
|
"candidate",
|
|
174
32
|
"roundContext",
|
|
@@ -215,14 +73,6 @@ function resolveSitianRecordPathInLedger(input, ledgerHome) {
|
|
|
215
73
|
}
|
|
216
74
|
return { sessionDir, recordFile, ledgerHome };
|
|
217
75
|
}
|
|
218
|
-
function projectTicketRecordsPathShape() {
|
|
219
|
-
const { recordFile } = ticketProvenanceUnderBookPaths(
|
|
220
|
-
join("~", ".ak-roles"),
|
|
221
|
-
"<\u7C3F>",
|
|
222
|
-
"<\u7968\u53F7>"
|
|
223
|
-
);
|
|
224
|
-
return recordFile.replace(/\\/g, "/");
|
|
225
|
-
}
|
|
226
76
|
function resolveSitianRecordPath(input) {
|
|
227
77
|
const ledgerHome = input.home !== void 0 && input.home.length > 0 ? resolveActivationLedgerHome(input.home) : resolveActivationLedgerHomeForPath(input.runDirectory ?? input.sessionParent);
|
|
228
78
|
return resolveSitianRecordPathInLedger(input, ledgerHome);
|
|
@@ -249,7 +99,13 @@ function appendSitianRecord(input) {
|
|
|
249
99
|
};
|
|
250
100
|
const row = `${JSON.stringify(record)}
|
|
251
101
|
`;
|
|
252
|
-
|
|
102
|
+
appendFileSync(recordFile, row, "utf8");
|
|
103
|
+
return {
|
|
104
|
+
identity: record.identity,
|
|
105
|
+
recordFile,
|
|
106
|
+
kind: record.kind,
|
|
107
|
+
level: record.level
|
|
108
|
+
};
|
|
253
109
|
} catch (error) {
|
|
254
110
|
if (error instanceof SitianInfrastructureError) throw error;
|
|
255
111
|
throw new SitianInfrastructureError(
|
|
@@ -262,7 +118,6 @@ export {
|
|
|
262
118
|
S4_SUBMISSION_LEDGER_KINDS,
|
|
263
119
|
appendSitianRecord,
|
|
264
120
|
appendSitianRecordBlock,
|
|
265
|
-
projectTicketRecordsPathShape,
|
|
266
121
|
resolveSitianRecordPath,
|
|
267
122
|
resolveSitianRecordPathInLedger,
|
|
268
123
|
resolveSitianVolumeCategory
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import { spawn } from "node:child_process";
|
|
2
3
|
import { createInterface } from "node:readline";
|
|
3
4
|
|
|
@@ -8,7 +9,10 @@ import {
|
|
|
8
9
|
raceAgainstHostAbort,
|
|
9
10
|
} from "../external-host-turn-loop.ts";
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
copyAndRecordHostDossier,
|
|
14
|
+
recordNativeSessionPointer,
|
|
15
|
+
} from "../host-session-record.ts";
|
|
12
16
|
import {
|
|
13
17
|
NAVIGATOR_OUTPUT_TOOL_NAME,
|
|
14
18
|
navigatorProseFromUnknown,
|
|
@@ -20,30 +24,6 @@ import {
|
|
|
20
24
|
} from "../prepared-role-turn.ts";
|
|
21
25
|
import { acpModelId, type AcpHostDescription } from "./description.ts";
|
|
22
26
|
|
|
23
|
-
/** #971: authorized host for usage + auto_compact ledgering only. */
|
|
24
|
-
const GROK_BUILD_HOST = "grok-build";
|
|
25
|
-
|
|
26
|
-
/** #971: grok-build prompt result carries host usage under `_meta.usage`. */
|
|
27
|
-
function acpPromptResultHasUsage(result: Readonly<Record<string, unknown>>): boolean {
|
|
28
|
-
const meta = result._meta;
|
|
29
|
-
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return false;
|
|
30
|
-
return Object.prototype.hasOwnProperty.call(meta, "usage");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* #971: ledger only the authorized vendor compaction notice on grok-build.
|
|
35
|
-
* Other `_x.ai/*` notifications stay out of host-session records.
|
|
36
|
-
*/
|
|
37
|
-
function isAcpAutoCompactCompletedNotification(
|
|
38
|
-
method: string,
|
|
39
|
-
params: Readonly<Record<string, unknown>>,
|
|
40
|
-
): boolean {
|
|
41
|
-
if (method !== "_x.ai/session_notification") return false;
|
|
42
|
-
const update = params.update;
|
|
43
|
-
if (typeof update !== "object" || update === null || Array.isArray(update)) return false;
|
|
44
|
-
return (update as { sessionUpdate?: unknown }).sessionUpdate === "auto_compact_completed";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
27
|
/**
|
|
48
28
|
* #959: collect free-form agent text from ACP session/update stream.
|
|
49
29
|
* Used only when the navigator seat spoke prose without calling the output tool.
|
|
@@ -278,7 +258,9 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
278
258
|
const systemPromptOverride = renderSystemPromptOverride(prepared.systemPrompt);
|
|
279
259
|
let connection: AcpConnection | undefined;
|
|
280
260
|
let sessionId: string | undefined;
|
|
261
|
+
let sessionOpened = false;
|
|
281
262
|
let accepted = false;
|
|
263
|
+
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
282
264
|
// Mutable so dispose failure can outrank a clean turn (headless withCleanupFailure face).
|
|
283
265
|
let outcome: RoleTurnResult = failure("session", "AcpNoOutcome", "no-outcome");
|
|
284
266
|
try {
|
|
@@ -286,66 +268,26 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
286
268
|
outcome = failure("activation", "UncontrolledAcpSession", "ak-config-missing");
|
|
287
269
|
} else {
|
|
288
270
|
connection = await config.connect(request);
|
|
289
|
-
// Live host-session records: ACP session/update → sitian sole entry (#811).
|
|
290
|
-
// One abort + one race helper + one outer projection — write failure ends
|
|
291
|
-
// any in-flight RPC as typed session infrastructure failure.
|
|
292
|
-
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
293
|
-
const recordAbort = new AbortController();
|
|
294
|
-
let hostSessionRecordFailure: RoleTurnKnownFailure | undefined;
|
|
295
|
-
const hostSessionRecordResult = (): RoleTurnResult => ({
|
|
296
|
-
code: null,
|
|
297
|
-
stderr: "",
|
|
298
|
-
timedOut: false,
|
|
299
|
-
knownFailure: hostSessionRecordFailure!,
|
|
300
|
-
});
|
|
301
|
-
const noteHostSessionRecordFailure = (error: unknown): void => {
|
|
302
|
-
if (hostSessionRecordFailure !== undefined) return;
|
|
303
|
-
hostSessionRecordFailure = {
|
|
304
|
-
cause: "session",
|
|
305
|
-
identity: { name: "HostSessionRecordFailure", code: "host-session-record-failed" },
|
|
306
|
-
diagnostic: error instanceof Error ? error.message : String(error),
|
|
307
|
-
};
|
|
308
|
-
try { recordAbort.abort(); } catch { /* already aborted */ }
|
|
309
|
-
};
|
|
310
271
|
const rpc = (
|
|
311
272
|
method: string,
|
|
312
273
|
params: Readonly<Record<string, unknown>>,
|
|
313
|
-
): Promise<Readonly<Record<string, unknown>>> =>
|
|
314
|
-
raceAgainstHostAbort(connection!.request(method, params), recordAbort.signal, "host-session-record-failed");
|
|
274
|
+
): Promise<Readonly<Record<string, unknown>>> => connection!.request(method, params);
|
|
315
275
|
|
|
316
276
|
// #959: navigator free-form agent text — only while session/prompt is in flight.
|
|
317
277
|
// session/load replays history via session/update; those must not enter the bucket
|
|
318
278
|
// (resume / set_model load would otherwise prepend prior turns as "this turn" prose).
|
|
319
279
|
const agentProseChunks: string[] = [];
|
|
320
280
|
let collectAgentProse = false;
|
|
321
|
-
// #971: usage + auto_compact are grok-build-only; same ACP adapter serves Hermes too.
|
|
322
|
-
const ledgerGrokBuildObservability = config.hostName === GROK_BUILD_HOST;
|
|
323
281
|
connection.onNotification?.((method, params) => {
|
|
324
|
-
if (hostSessionRecordFailure !== undefined) return;
|
|
325
282
|
const isSessionUpdate = method === "session/update";
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// #971: session/update stays; auto_compact_completed is the only vendor notice.
|
|
329
|
-
if (!isSessionUpdate && !isAutoCompact) return;
|
|
330
|
-
if (isSessionUpdate && collectAgentProse) {
|
|
283
|
+
if (!isSessionUpdate) return;
|
|
284
|
+
if (collectAgentProse) {
|
|
331
285
|
const chunk = acpAgentTextChunk(params);
|
|
332
286
|
if (chunk !== undefined) agentProseChunks.push(chunk);
|
|
333
287
|
}
|
|
334
|
-
try {
|
|
335
|
-
reportHostSessionEvent({
|
|
336
|
-
host: config.hostName,
|
|
337
|
-
cwd: request.cwd,
|
|
338
|
-
sessionParent,
|
|
339
|
-
source: "acp-host",
|
|
340
|
-
event: { method, params },
|
|
341
|
-
});
|
|
342
|
-
} catch (error) {
|
|
343
|
-
noteHostSessionRecordFailure(error);
|
|
344
|
-
}
|
|
345
288
|
});
|
|
346
289
|
|
|
347
|
-
|
|
348
|
-
const initialized = await rpc("initialize", {
|
|
290
|
+
const initialized = await rpc("initialize", {
|
|
349
291
|
protocolVersion: 1,
|
|
350
292
|
clientCapabilities: {},
|
|
351
293
|
});
|
|
@@ -374,9 +316,13 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
374
316
|
sessionId: bindSessionId,
|
|
375
317
|
...sessionBindParams,
|
|
376
318
|
});
|
|
377
|
-
|
|
319
|
+
const loadedId = typeof loaded.sessionId === "string" && loaded.sessionId !== ""
|
|
378
320
|
? loaded.sessionId
|
|
379
321
|
: bindSessionId;
|
|
322
|
+
if (config.hostName !== "hermes") recordNativeSessionPointer({
|
|
323
|
+
host: config.hostName, sessionId: loadedId, cwd: request.cwd, sessionParent, home: request.home,
|
|
324
|
+
});
|
|
325
|
+
return loadedId;
|
|
380
326
|
};
|
|
381
327
|
if (request.continuation.kind === "resume" && config.boundResume === "session/load") {
|
|
382
328
|
const explicitHostSessionId = request.continuation.hostSessionId;
|
|
@@ -384,7 +330,9 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
384
330
|
? explicitHostSessionId
|
|
385
331
|
: await config.sessionIdentity.load(request.principal);
|
|
386
332
|
if (boundSessionId !== undefined && boundSessionId !== "") {
|
|
333
|
+
sessionId = boundSessionId;
|
|
387
334
|
sessionId = await loadSession(boundSessionId);
|
|
335
|
+
sessionOpened = true;
|
|
388
336
|
}
|
|
389
337
|
}
|
|
390
338
|
let sessionReady = true;
|
|
@@ -395,6 +343,10 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
395
343
|
outcome = failure("session", "AcpSessionFailure", "session-id-missing");
|
|
396
344
|
sessionReady = false;
|
|
397
345
|
} else {
|
|
346
|
+
if (config.hostName !== "hermes") recordNativeSessionPointer({
|
|
347
|
+
host: config.hostName, sessionId, cwd: request.cwd, sessionParent, home: request.home,
|
|
348
|
+
});
|
|
349
|
+
sessionOpened = true;
|
|
398
350
|
await config.sessionIdentity.bind(request.principal, sessionId);
|
|
399
351
|
}
|
|
400
352
|
}
|
|
@@ -415,12 +367,6 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
415
367
|
roundLimitName: "AcpRoundLimit",
|
|
416
368
|
currentSessionId: () => sessionId,
|
|
417
369
|
async runRound({ prompt, abortSignal }) {
|
|
418
|
-
if (hostSessionRecordFailure !== undefined) return { status: "terminal", result: hostSessionRecordResult() };
|
|
419
|
-
// Envelope infra abort, parent cancellation (#675), and host-session
|
|
420
|
-
// record abort (#811) share one race face for in-flight prompt.
|
|
421
|
-
const abortParts: AbortSignal[] = [recordAbort.signal];
|
|
422
|
-
if (abortSignal !== undefined) abortParts.push(abortSignal);
|
|
423
|
-
const combinedAbort = AbortSignal.any(abortParts);
|
|
424
370
|
let result: Readonly<Record<string, unknown>>;
|
|
425
371
|
// Open the prose gate only for this prompt round; clear any stale chunks first.
|
|
426
372
|
agentProseChunks.length = 0;
|
|
@@ -431,36 +377,15 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
431
377
|
sessionId: activeSessionId,
|
|
432
378
|
prompt: [{ type: "text", text: prompt }],
|
|
433
379
|
}),
|
|
434
|
-
|
|
380
|
+
abortSignal,
|
|
435
381
|
"ACP host aborted",
|
|
436
382
|
);
|
|
437
383
|
} catch (error) {
|
|
438
384
|
collectAgentProse = false;
|
|
439
385
|
agentProseChunks.length = 0;
|
|
440
|
-
if (hostSessionRecordFailure !== undefined) {
|
|
441
|
-
return { status: "terminal", result: hostSessionRecordResult() };
|
|
442
|
-
}
|
|
443
386
|
throw error;
|
|
444
387
|
}
|
|
445
388
|
collectAgentProse = false;
|
|
446
|
-
// #971: per-round grok-build usage is on the prompt JSON-RPC result, not a notice.
|
|
447
|
-
if (ledgerGrokBuildObservability && acpPromptResultHasUsage(result)) {
|
|
448
|
-
try {
|
|
449
|
-
reportHostSessionEvent({
|
|
450
|
-
host: config.hostName,
|
|
451
|
-
cwd: request.cwd,
|
|
452
|
-
sessionParent,
|
|
453
|
-
source: "acp-host",
|
|
454
|
-
event: { method: "session/prompt", result },
|
|
455
|
-
});
|
|
456
|
-
} catch (error) {
|
|
457
|
-
noteHostSessionRecordFailure(error);
|
|
458
|
-
return { status: "terminal", result: hostSessionRecordResult() };
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (hostSessionRecordFailure !== undefined) {
|
|
462
|
-
return { status: "terminal", result: hostSessionRecordResult() };
|
|
463
|
-
}
|
|
464
389
|
if (result.stopReason === "refusal") {
|
|
465
390
|
agentProseChunks.length = 0;
|
|
466
391
|
return {
|
|
@@ -487,18 +412,9 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
487
412
|
accepted = true;
|
|
488
413
|
},
|
|
489
414
|
});
|
|
490
|
-
outcome =
|
|
415
|
+
outcome = turnResult;
|
|
491
416
|
} // sessionReady
|
|
492
417
|
} // model match else
|
|
493
|
-
} catch (error) {
|
|
494
|
-
// recordAbort races setup RPCs via raceAgainstHostAbort (host-aborted code);
|
|
495
|
-
// noteHostSessionRecordFailure always sets the typed failure before aborting.
|
|
496
|
-
if (hostSessionRecordFailure !== undefined) {
|
|
497
|
-
outcome = hostSessionRecordResult();
|
|
498
|
-
} else {
|
|
499
|
-
throw error;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
418
|
} // mcpServers else
|
|
503
419
|
} finally {
|
|
504
420
|
if (connection !== undefined) {
|
|
@@ -509,6 +425,18 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
509
425
|
try { await connection.close(); }
|
|
510
426
|
catch { /* keep turn result */ }
|
|
511
427
|
}
|
|
428
|
+
if (config.hostName !== "hermes" && sessionOpened && sessionId !== undefined) {
|
|
429
|
+
copyAndRecordHostDossier({
|
|
430
|
+
host: config.hostName,
|
|
431
|
+
sessionId,
|
|
432
|
+
cwd: request.cwd,
|
|
433
|
+
sessionDirectory: join(request.runDirectory, "session"),
|
|
434
|
+
sessionParent,
|
|
435
|
+
continuation: request.continuation,
|
|
436
|
+
...(request.model !== undefined ? { model: request.model } : {}),
|
|
437
|
+
...(request.home !== undefined ? { home: request.home } : {}),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
512
440
|
try {
|
|
513
441
|
await prepared.dispose?.();
|
|
514
442
|
} catch (cleanupError) {
|
|
@@ -222,7 +222,7 @@ export async function readEngineDetourToolUsage(input: {
|
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
const { records } = await readSitianRecords(recordFile);
|
|
225
|
-
const
|
|
225
|
+
const callsByToolCallId = new Map<string, EngineDetourCallFact>();
|
|
226
226
|
for (const record of records) {
|
|
227
227
|
if (record.kind !== ENGINE_DETOUR_CALL_KIND) continue;
|
|
228
228
|
const boundScope = invocationScopeIdOfRecord(record);
|
|
@@ -240,9 +240,11 @@ export async function readEngineDetourToolUsage(input: {
|
|
|
240
240
|
};
|
|
241
241
|
const fact = callFactFromSitianPayload(record.payload, pointer);
|
|
242
242
|
if (fact === undefined) continue;
|
|
243
|
-
|
|
243
|
+
// ADR 0086: Reader deduplicates by toolCallId within the invocation scope
|
|
244
|
+
callsByToolCallId.set(fact.toolCallId, fact);
|
|
244
245
|
}
|
|
245
246
|
|
|
247
|
+
const calls = Array.from(callsByToolCallId.values());
|
|
246
248
|
return { callCount: calls.length, calls };
|
|
247
249
|
}
|
|
248
250
|
|
|
@@ -300,20 +302,6 @@ export async function readInvocationEngineMounted(
|
|
|
300
302
|
return typeof raw.engine === "string" && raw.engine.trim() !== "";
|
|
301
303
|
}
|
|
302
304
|
|
|
303
|
-
/**
|
|
304
|
-
* Selected host from the admission invocation page — for post-admission
|
|
305
|
-
* host-transition classification only. In-turn tools take host from the shared
|
|
306
|
-
* Host envelope (RoleTurnRequest / HostContext), never this reader.
|
|
307
|
-
* Undefined when absent — callers must not invent "pi".
|
|
308
|
-
*/
|
|
309
|
-
export function readInvocationSelectedHost(runDirectory: string): string | undefined {
|
|
310
|
-
const raw = readInvocationRecord(runDirectory);
|
|
311
|
-
if (raw === undefined) return undefined;
|
|
312
|
-
return typeof raw.host === "string" && raw.host.trim() !== ""
|
|
313
|
-
? raw.host.trim()
|
|
314
|
-
: undefined;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
305
|
/** Merge usage into decisiveFacts without touching role payloads. */
|
|
318
306
|
export function withEngineDetourToolUsageFact<
|
|
319
307
|
T extends { readonly decisiveFacts?: Readonly<Record<string, unknown>> },
|