@akagilnc/pi-workflow-roles 0.1.5504 → 0.1.5575
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +5965 -5994
- package/dist/analyst-gate-cycles-read.js +23 -55
- package/dist/analyst-ledger.js +21 -35
- package/dist/analyst-metric-families/acceptance-success-rework.js +25 -1
- package/dist/analyst-metric-families/round-timeline.js +2 -5
- package/dist/archivist-record-pointer.js +38 -30
- package/dist/auditor-dossier-tool.js +4 -3
- package/dist/auditor-soul.js +3 -11
- package/dist/doctor-evidence.js +3 -3
- package/dist/engine-detour-usage.js +6 -14
- package/dist/headless-host/production-host.js +5316 -5324
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/migrate-book-topology.js +1314 -696
- package/dist/notary-source-run.js +1 -1
- package/dist/packaged-role-registry.js +5 -44
- package/dist/pi/role-turn-host.js +53 -3
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +5 -3
- package/dist/public-cli/invocation.js +51 -56
- package/dist/public-cli/main.js +1916 -1748
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +27 -53
- package/dist/public-cli/settlement.js +157 -302
- package/dist/role-run-placement.js +1 -11
- package/dist/role-run-relocation.js +72 -50
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +68 -197
- package/dist/run-ticket-number.js +16 -3
- package/dist/session-identity.js +49 -18
- package/dist/sitian-appender.js +13 -16
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/ticket-provenance.js +22 -7
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -0
- package/src/acp-host/production-host.ts +1 -1
- package/src/acp-host/role-turn-host.ts +9 -2
- package/src/analyst-gate-cycles-read.ts +26 -60
- package/src/analyst-ledger.ts +27 -42
- package/src/analyst-metric-families/acceptance-success-rework.ts +24 -1
- package/src/analyst-metric-families/gate-cycles.ts +1 -1
- package/src/analyst-metric-families/round-timeline.ts +2 -5
- package/src/analyst-page.ts +3 -3
- package/src/archivist-record-entry.ts +2 -2
- package/src/archivist-record-pointer.ts +50 -36
- package/src/auditor-dossier-tool.ts +7 -5
- package/src/auditor-soul.ts +3 -9
- package/src/book-topology-migration-placement.ts +16 -3
- package/src/book-topology-record-class-migrators.ts +4 -2
- package/src/book-topology-runs-migrator.ts +6 -3
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +2 -2
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +6 -16
- package/src/external-host-turn-loop.ts +25 -0
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +54 -20
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/notary-source-run.ts +1 -1
- package/src/packaged-role-registry.ts +1 -50
- package/src/pi/adapter.ts +50 -7
- package/src/pi/role-turn-host.ts +51 -3
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +9 -2
- package/src/public-cli/invocation.ts +61 -87
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +27 -72
- package/src/public-cli/settlement.ts +193 -418
- package/src/public-cli/terminal.ts +1 -1
- package/src/role-run-placement.ts +2 -13
- package/src/role-run-relocation.ts +71 -68
- package/src/role-runtime.ts +6 -10
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +83 -237
- package/src/run-ticket-number.ts +41 -11
- package/src/session-identity.ts +52 -20
- package/src/sitian-appender.ts +17 -18
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/ticket-provenance.ts +20 -7
- package/src/ticket-trajectory.ts +10 -10
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
|
+
import { isRecord } from "./unknown-value.ts";
|
|
3
|
+
import { latestOfficerPointersFromRecords, RUN_HISTORY_FILE } from "./run-dossier.ts";
|
|
4
|
+
import { OFFICER_POINTER_RECORD_KIND } from "./run-dossier-files.ts";
|
|
5
|
+
import { readSitianRecords, reportRunRecord } from "./sitian-facade.ts";
|
|
5
6
|
|
|
6
7
|
import { homeFromRunDirectory } from "./activation-ledger-topology.ts";
|
|
7
8
|
import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
|
|
8
9
|
import { rewriteRunDirectoryPathValue } from "./role-run-relocation.ts";
|
|
9
|
-
import { parseRunLeaf, runDirectoryOfSessionFile
|
|
10
|
-
import { sitianVolumeDirectory } from "./sitian-appender.ts";
|
|
10
|
+
import { parseRunLeaf, runDirectoryOfSessionFile } from "./role-run-placement.ts";
|
|
11
11
|
|
|
12
12
|
/** Typed parent-side pointer to an independent officer run 正本 (ADR 0079 / #675). */
|
|
13
13
|
export const DIRECT_OFFICER_RUN_POINTER_KIND = "direct-officer-run-pointer" as const;
|
|
@@ -27,21 +27,17 @@ export type DirectOfficerRunPointer = {
|
|
|
27
27
|
readonly submissionToolCallId?: string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
officer: DirectOfficerRunPointer["officer"],
|
|
33
|
-
): string {
|
|
34
|
-
return join(sitianVolumeDirectory(dirname(parentSessionFile), "auditor-roles"), `${officer}.pointer.json`);
|
|
35
|
-
}
|
|
30
|
+
/** History record kind of a booked officer pointer (appender routes it to history.jsonl). */
|
|
31
|
+
export { OFFICER_POINTER_RECORD_KIND };
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
|
-
* Book a typed pointer
|
|
39
|
-
*
|
|
40
|
-
*
|
|
34
|
+
* Book a typed pointer as one history record of the parent run; the settlement
|
|
35
|
+
* seam projects the latest per officer into current.json `officers`.
|
|
36
|
+
* Never fabricates user/assistant/toolResult rows (#675).
|
|
41
37
|
*
|
|
42
|
-
*
|
|
43
|
-
* same-parent re-summons
|
|
44
|
-
*
|
|
38
|
+
* Latest record wins per officer under one parent (#753 gate-round accounting):
|
|
39
|
+
* a same-parent re-summons supersedes the earlier pointer instead of adding a
|
|
40
|
+
* second one that re-scans the full officer session and multiplies gate-cycle counts.
|
|
45
41
|
*/
|
|
46
42
|
export function bookDirectOfficerRunPointer(options: {
|
|
47
43
|
readonly parentSessionFile: string;
|
|
@@ -50,8 +46,6 @@ export function bookDirectOfficerRunPointer(options: {
|
|
|
50
46
|
readonly runDirectory?: string;
|
|
51
47
|
readonly submissionToolCallId?: string;
|
|
52
48
|
}): DirectOfficerRunPointer {
|
|
53
|
-
const pointerFile = directOfficerRunPointerFile(options.parentSessionFile, options.officer);
|
|
54
|
-
mkdirSync(dirname(pointerFile), { recursive: true });
|
|
55
49
|
const submissionToolCallId = options.submissionToolCallId?.trim() ?? "";
|
|
56
50
|
const pointer: DirectOfficerRunPointer = {
|
|
57
51
|
version: 1,
|
|
@@ -63,32 +57,26 @@ export function bookDirectOfficerRunPointer(options: {
|
|
|
63
57
|
: {}),
|
|
64
58
|
...(submissionToolCallId === "" ? {} : { submissionToolCallId }),
|
|
65
59
|
};
|
|
66
|
-
|
|
67
|
-
pointerFile,
|
|
68
|
-
`${JSON.stringify(pointer)}\n`,
|
|
69
|
-
"utf8",
|
|
70
|
-
);
|
|
60
|
+
reportRunRecord(runDirectoryOfSessionFile(options.parentSessionFile), OFFICER_POINTER_RECORD_KIND, pointer, "submission-gate");
|
|
71
61
|
return pointer;
|
|
72
62
|
}
|
|
73
63
|
|
|
74
64
|
/** Shared strict pointer IO; internal audit wraps unreadability as a re-summons. */
|
|
75
|
-
export async function
|
|
65
|
+
export async function resolveOfficerPointer(
|
|
66
|
+
raw: unknown,
|
|
67
|
+
parentRunDirectory: string,
|
|
68
|
+
): Promise<{
|
|
76
69
|
sessionFile: string;
|
|
77
70
|
officer?: DirectOfficerRunPointer["officer"];
|
|
78
71
|
runDirectory?: string;
|
|
79
72
|
submissionToolCallId?: string;
|
|
80
73
|
}> {
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
raw = JSON.parse(await readFile(pointerPath, "utf8"));
|
|
84
|
-
} catch (error) {
|
|
85
|
-
throw new Error(`direct officer run pointer unreadable in ${pointerPath}: ${errorText(error)}`, { cause: error });
|
|
86
|
-
}
|
|
74
|
+
const where = `${parentRunDirectory} officers`;
|
|
87
75
|
if (!isRecord(raw) || raw.kind !== DIRECT_OFFICER_RUN_POINTER_KIND || raw.version !== 1) {
|
|
88
|
-
throw new Error(`direct officer run pointer has unknown shape in ${
|
|
76
|
+
throw new Error(`direct officer run pointer has unknown shape in ${where}`);
|
|
89
77
|
}
|
|
90
78
|
if (typeof raw.sessionFile !== "string" || raw.sessionFile.trim() === "") {
|
|
91
|
-
throw new Error(`direct officer run pointer missing sessionFile in ${
|
|
79
|
+
throw new Error(`direct officer run pointer missing sessionFile in ${where}`);
|
|
92
80
|
}
|
|
93
81
|
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
94
82
|
|| raw.officer === "auditor" || raw.officer === "countersign" ? raw.officer : undefined;
|
|
@@ -97,7 +85,6 @@ export async function resolveOfficerSessionFromPointerFile(pointerPath: string):
|
|
|
97
85
|
let sessionFile = raw.sessionFile;
|
|
98
86
|
const recordedRunDirectory = runDirectory ?? runDirectoryOfSessionFile(sessionFile);
|
|
99
87
|
const leaf = parseRunLeaf(basename(recordedRunDirectory));
|
|
100
|
-
const parentRunDirectory = runDirectoryFromSessionDirectory(dirname(dirname(pointerPath)));
|
|
101
88
|
const parentIdentity = await readRoleRunIdentity(parentRunDirectory);
|
|
102
89
|
if (leaf !== undefined && parentIdentity !== undefined) {
|
|
103
90
|
const currentRunDirectory = await findRunDirectoryById(
|
|
@@ -118,13 +105,40 @@ export async function resolveOfficerSessionFromPointerFile(pointerPath: string):
|
|
|
118
105
|
};
|
|
119
106
|
}
|
|
120
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The latest pointer booked per officer on one run, as stored. History absent → empty.
|
|
110
|
+
* Canonical malformed diagnostics are not discarded: a damaged officer source must
|
|
111
|
+
* not wash into lawful zero rounds (#1161 O1).
|
|
112
|
+
*/
|
|
113
|
+
export async function readBookedOfficerPointers(parentRunDirectory: string): Promise<Readonly<Record<string, unknown>>> {
|
|
114
|
+
const { records, diagnostics } = await readSitianRecords(join(parentRunDirectory, RUN_HISTORY_FILE));
|
|
115
|
+
if (diagnostics.length > 0) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`${RUN_HISTORY_FILE} has ${diagnostics.length} malformed row(s); officer pointers unreadable: ${parentRunDirectory}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return latestOfficerPointersFromRecords(records);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Every pointer booked on one run, in officer-name order. No pointers → []. */
|
|
124
|
+
export async function readOfficerPointers(parentRunDirectory: string): Promise<readonly Awaited<ReturnType<typeof resolveOfficerPointer>>[]> {
|
|
125
|
+
const officers = await readBookedOfficerPointers(parentRunDirectory);
|
|
126
|
+
return Promise.all(
|
|
127
|
+
Object.keys(officers).sort().map((name) => resolveOfficerPointer(officers[name], parentRunDirectory)),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
/** Missing or unreadable is not a pass: the internal audit caller re-runs the gate. */
|
|
122
132
|
export async function readDirectOfficerRunPointer(
|
|
123
133
|
parentSessionFile: string,
|
|
124
134
|
officer: DirectOfficerRunPointer["officer"],
|
|
125
135
|
): Promise<DirectOfficerRunPointer | undefined> {
|
|
126
136
|
try {
|
|
127
|
-
const
|
|
137
|
+
const parentRunDirectory = runDirectoryOfSessionFile(parentSessionFile);
|
|
138
|
+
const pointer = await resolveOfficerPointer(
|
|
139
|
+
(await readBookedOfficerPointers(parentRunDirectory))[officer],
|
|
140
|
+
parentRunDirectory,
|
|
141
|
+
);
|
|
128
142
|
if (pointer.officer !== officer) return undefined;
|
|
129
143
|
return { ...pointer, officer, version: 1, kind: DIRECT_OFFICER_RUN_POINTER_KIND };
|
|
130
144
|
} catch {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
2
|
|
|
3
|
+
import { RUN_HISTORY_FILE, RUN_LOG_FILE, runCurrentPath } from "./run-dossier.ts";
|
|
3
4
|
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import type { HostContext } from "./host-contracts.ts";
|
|
5
6
|
import {
|
|
6
|
-
roleRunArtifactsDirectory,
|
|
7
7
|
runDirectoryOfSessionFile,
|
|
8
8
|
sessionFileOf,
|
|
9
9
|
} from "./role-run-placement.ts";
|
|
@@ -13,10 +13,11 @@ export const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier" as const;
|
|
|
13
13
|
|
|
14
14
|
export type AuditorDossierLocation = {
|
|
15
15
|
readonly runDirectory: string;
|
|
16
|
-
readonly
|
|
16
|
+
readonly current: string;
|
|
17
17
|
readonly parentSessionCandidate: string;
|
|
18
18
|
readonly attachments: string;
|
|
19
|
-
readonly
|
|
19
|
+
readonly log: string;
|
|
20
|
+
readonly history: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
/** Resolve the exact run binding already carried by the parent record session. */
|
|
@@ -47,10 +48,11 @@ export function createAuditorDossierTool(
|
|
|
47
48
|
// #836: whole run directory pointer — officer finds materials; no leaf preference (A7.2).
|
|
48
49
|
const details: AuditorDossierLocation = {
|
|
49
50
|
runDirectory,
|
|
50
|
-
|
|
51
|
+
current: runCurrentPath(runDirectory),
|
|
51
52
|
parentSessionCandidate: sessionFileOf(runDirectory),
|
|
52
53
|
attachments: join(runDirectory, "attachments"),
|
|
53
|
-
|
|
54
|
+
log: join(runDirectory, RUN_LOG_FILE),
|
|
55
|
+
history: join(runDirectory, RUN_HISTORY_FILE),
|
|
54
56
|
};
|
|
55
57
|
return {
|
|
56
58
|
content: [{ type: "text", text: JSON.stringify(details) }],
|
package/src/auditor-soul.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { readPageSync } from "./run-dossier.ts";
|
|
2
3
|
import { basename, join } from "node:path";
|
|
3
4
|
|
|
4
5
|
import { parseRunLeaf } from "./role-run-placement.ts";
|
|
@@ -122,15 +123,8 @@ export async function readAuditorResumeBinding(runDirectory: string): Promise<
|
|
|
122
123
|
| { readonly subject: AuditorSoulRole; readonly sourceRunDirectory: string }
|
|
123
124
|
| undefined
|
|
124
125
|
> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
raw = JSON.parse(await readFile(join(runDirectory, "admitted-request.json"), "utf8")) as unknown;
|
|
128
|
-
} catch (error) {
|
|
129
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
132
|
-
if (!isRecord(raw)) return undefined;
|
|
133
|
-
const record = raw as { sourceRunPath?: unknown; auditorSubject?: unknown };
|
|
126
|
+
const record = readPageSync(runDirectory, "admitted");
|
|
127
|
+
if (record === undefined) return undefined;
|
|
134
128
|
if (typeof record.sourceRunPath !== "string" || record.sourceRunPath.trim() === "") return undefined;
|
|
135
129
|
const subject = isAuditorSoulRole(record.auditorSubject)
|
|
136
130
|
? record.auditorSubject
|
|
@@ -15,9 +15,10 @@ import { isUnboundRunDirectory, roleRunPlacement } from "./role-run-placement.ts
|
|
|
15
15
|
export { isUnboundRunDirectory };
|
|
16
16
|
import {
|
|
17
17
|
MIGRATION_TICKET_DERIVATION_PAGE,
|
|
18
|
-
|
|
18
|
+
readMigrationBoardTicketNumber,
|
|
19
19
|
readMigrationDerivedTicketNumber,
|
|
20
20
|
} from "./run-ticket-number.ts";
|
|
21
|
+
import { readPageSync } from "./run-dossier.ts";
|
|
21
22
|
|
|
22
23
|
import { isRecord, isEnoent } from "./unknown-value.ts";
|
|
23
24
|
|
|
@@ -83,11 +84,23 @@ export function ticketNumberFromWorktreeBasename(
|
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
86
|
* projectRoot from a retained run's durable pages.
|
|
86
|
-
*
|
|
87
|
+
* New落点 (state rows) first, then pre-#1161 page files at the migration boundary.
|
|
88
|
+
* ENOENT → try next / undefined; bad JSON and non-ENOENT IO propagate.
|
|
87
89
|
*/
|
|
88
90
|
async function readProjectRootFromRun(
|
|
89
91
|
runDirectory: string,
|
|
90
92
|
): Promise<{ readonly projectRoot: string; readonly sourcePage: string } | undefined> {
|
|
93
|
+
for (const section of ["admitted", "invocation", "runState"] as const) {
|
|
94
|
+
try {
|
|
95
|
+
const page = readPageSync(runDirectory, section);
|
|
96
|
+
const projectRoot = page?.projectRoot;
|
|
97
|
+
if (typeof projectRoot === "string" && projectRoot.length > 0) {
|
|
98
|
+
return { projectRoot, sourcePage: `state.jsonl:${section}` };
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!isEnoent(error)) throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
91
104
|
for (const page of ["admitted-request.json", "invocation.json", "run-state.json"] as const) {
|
|
92
105
|
const path = join(runDirectory, page);
|
|
93
106
|
try {
|
|
@@ -123,7 +136,7 @@ export async function resolveMigratingRunTicket(runDirectory: string): Promise<{
|
|
|
123
136
|
readonly ticketNumber: number | undefined;
|
|
124
137
|
readonly derivation: MigratingRunTicketDerivation | undefined;
|
|
125
138
|
}> {
|
|
126
|
-
const boardTicket = await
|
|
139
|
+
const boardTicket = await readMigrationBoardTicketNumber(runDirectory);
|
|
127
140
|
if (boardTicket !== undefined) {
|
|
128
141
|
return {
|
|
129
142
|
ticketNumber: boardTicket,
|
|
@@ -31,12 +31,14 @@ import {
|
|
|
31
31
|
type BookTopologyPartitionMigrator,
|
|
32
32
|
type MigrationItemOutcome,
|
|
33
33
|
} from "./book-topology-migration.ts";
|
|
34
|
-
import { S4_SUBMISSION_LEDGER_KINDS } from "./sitian-appender.ts";
|
|
35
34
|
import { projectTicketProvenanceHeader } from "./ticket-provenance-contracts.ts";
|
|
36
35
|
|
|
37
36
|
import { sha256Hex } from "./sha256.ts";
|
|
38
37
|
import { isRecord, isEnoent } from "./unknown-value.ts";
|
|
39
38
|
|
|
39
|
+
/** Kinds the retired #852-era submission-ledger volume held (migration of stored runs only). */
|
|
40
|
+
const LEGACY_SUBMISSION_LEDGER_KINDS = new Set(["candidate", "roundContext", "outcome", "sealed", "post-seal-anomaly"]);
|
|
41
|
+
|
|
40
42
|
const TICKET_PROVENANCE = "ticket-provenance";
|
|
41
43
|
const SUBMISSION_LEDGER = "submission-ledger";
|
|
42
44
|
const ATTEMPT_HISTORY = "attempt-history";
|
|
@@ -210,7 +212,7 @@ function runIdFromPayload(payload: unknown): string | undefined {
|
|
|
210
212
|
function recordClassOfKind(kind: unknown): RecordClass | undefined {
|
|
211
213
|
if (kind === TICKET_PROVENANCE) return TICKET_PROVENANCE;
|
|
212
214
|
if (kind === ATTEMPT_HISTORY) return ATTEMPT_HISTORY;
|
|
213
|
-
if (typeof kind === "string" &&
|
|
215
|
+
if (typeof kind === "string" && LEGACY_SUBMISSION_LEDGER_KINDS.has(kind)) return SUBMISSION_LEDGER;
|
|
214
216
|
return undefined;
|
|
215
217
|
}
|
|
216
218
|
|
|
@@ -26,9 +26,10 @@ import {
|
|
|
26
26
|
rewriteRoleRunDurablePages,
|
|
27
27
|
type RunDirectoryPathRewrite,
|
|
28
28
|
} from "./role-run-relocation.ts";
|
|
29
|
+
import { renderCurrentSync } from "./run-dossier.ts";
|
|
29
30
|
import {
|
|
30
31
|
MIGRATION_TICKET_DERIVATION_PAGE,
|
|
31
|
-
|
|
32
|
+
readMigrationBoardTicketNumber,
|
|
32
33
|
} from "./run-ticket-number.ts";
|
|
33
34
|
|
|
34
35
|
import { isEnoent } from "./unknown-value.ts";
|
|
@@ -151,7 +152,7 @@ async function planBookMoves(
|
|
|
151
152
|
// #863: board typed ticket on an unbound leaf → place under that ticket.
|
|
152
153
|
// No board ticket → stay unbound. Never invent from prose or derivation.
|
|
153
154
|
const boardTicket = leaf.isDirectory
|
|
154
|
-
? await
|
|
155
|
+
? await readMigrationBoardTicketNumber(leaf.sourcePath)
|
|
155
156
|
: undefined;
|
|
156
157
|
if (parsed !== undefined && boardTicket !== undefined) {
|
|
157
158
|
targetPath = destinationRunDirectory(
|
|
@@ -318,7 +319,7 @@ export async function relocateBoardBoundUnboundRunsInBook(
|
|
|
318
319
|
const parsed = parseRunLeaf(entry.name);
|
|
319
320
|
if (parsed === undefined) continue;
|
|
320
321
|
const sourcePath = join(unboundRuns, entry.name);
|
|
321
|
-
const boardTicket = await
|
|
322
|
+
const boardTicket = await readMigrationBoardTicketNumber(sourcePath);
|
|
322
323
|
if (boardTicket === undefined) continue;
|
|
323
324
|
const targetPath = destinationRunDirectory(
|
|
324
325
|
booksDirectory,
|
|
@@ -367,6 +368,8 @@ export async function relocateBoardBoundUnboundRunsInBook(
|
|
|
367
368
|
for (const move of planned) {
|
|
368
369
|
await mkdir(dirname(move.targetPath), { recursive: true });
|
|
369
370
|
await rename(move.sourcePath, move.targetPath);
|
|
371
|
+
// Same live relocate seam: derived host.original follows the new run directory.
|
|
372
|
+
renderCurrentSync(move.targetPath);
|
|
370
373
|
}
|
|
371
374
|
|
|
372
375
|
return planned.map((move) => ({
|
package/src/doctor-contracts.ts
CHANGED
|
@@ -49,7 +49,7 @@ export type DoctorSubmission =
|
|
|
49
49
|
// #836: runtime cost is a fact beside the role payload, never merged into it —
|
|
50
50
|
// DoctorOutput is the accepted payload itself, identical to DoctorSubmission.
|
|
51
51
|
export type DoctorOutput = DoctorSubmission;
|
|
52
|
-
export type DoctorEvidenceEntry = { id: string; kind: "session" | "
|
|
52
|
+
export type DoctorEvidenceEntry = { id: string; kind: "session" | "log"; byteLength: number; contentLength: number; sha256: string; content: string };
|
|
53
53
|
export type DoctorCase = { version: 1; identity: DoctorCaseIdentity; evidence: DoctorEvidenceEntry[]; cost: DoctorCaseCost };
|
|
54
54
|
|
|
55
55
|
// status 的合法词写在 description。交卷原样入账(validateDoctorSubmissionShape),
|
package/src/doctor-evidence.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { AcceptedDetailsContractError, acceptedFacts, isTerminatingToolName, val
|
|
|
7
7
|
|
|
8
8
|
import { isRecord, isMissingPathError } from "./unknown-value.ts";
|
|
9
9
|
|
|
10
|
-
async function discoverCaseFiles(root: string): Promise<string[]> { const found: string[] = []; async function walk(dir: string, depth: number) { for (const item of await readdir(dir, { withFileTypes: true })) { const path = resolve(dir, item.name); if (item.isDirectory()) { await walk(path, depth + 1); } else if (item.isFile() && (item.name.endsWith(".jsonl") || (item.name === "
|
|
10
|
+
async function discoverCaseFiles(root: string): Promise<string[]> { const found: string[] = []; async function walk(dir: string, depth: number) { for (const item of await readdir(dir, { withFileTypes: true })) { const path = resolve(dir, item.name); if (item.isDirectory()) { await walk(path, depth + 1); } else if (item.isFile() && ((item.name.endsWith(".jsonl") && item.name !== "state.jsonl" && !(item.name === "log.jsonl" && depth === 1)) || (item.name === "log.jsonl" && depth === 1))) found.push(path); } } await walk(root, 0); return found.sort(); }
|
|
11
11
|
function sourceList(count: number, sources: string[]) { return { count, sources: [...new Set(sources)].sort() }; }
|
|
12
12
|
function accumulate(metric: DoctorCount, value: number, source: string) { metric.count += value; if (value) metric.sources.push(source); }
|
|
13
13
|
function timestamp(row: Record<string, unknown>) { return typeof row.timestamp === "string" && Number.isFinite(Date.parse(row.timestamp)) ? row.timestamp : undefined; }
|
|
@@ -49,7 +49,7 @@ export async function loadDoctorCase(runsPath: string, projectRoot?: string): Pr
|
|
|
49
49
|
if (!match) throw new Error("Doctor case must be an .ak-roles/books/<book>/<n>/runs directory");
|
|
50
50
|
const evidence: DoctorEvidenceEntry[] = [], sessions: DoctorCaseCost["sessions"] = [], statuses: DoctorCaseCost["statuses"] = [], commits: DoctorCaseCost["commits"] = [];
|
|
51
51
|
const turns: DoctorCount = { count: 0, sources: [] }, calls: DoctorCount = { count: 0, sources: [] }, tokens: DoctorCount = { count: 0, sources: [] };
|
|
52
|
-
for (const path of await discoverCaseFiles(root)) { const id = relative(root, path).split(sep).join("/"); const bytes = await readFile(path); const content = bytes.toString("utf8"); const kind = id.endsWith(".jsonl") ? "
|
|
52
|
+
for (const path of await discoverCaseFiles(root)) { const id = relative(root, path).split(sep).join("/"); const bytes = await readFile(path); const content = bytes.toString("utf8"); const kind = id.split("/").length === 2 && id.endsWith("/log.jsonl") ? "log" : "session"; evidence.push({ id, kind, byteLength: bytes.byteLength, contentLength: content.length, sha256: sha256Hex(bytes), content }); if (kind === "log") continue; const result = deriveSession(content, id); sessions.push(result.session); statuses.push(...result.statuses); commits.push(...result.commits); accumulate(turns, result.turns, id); accumulate(calls, result.calls, id); accumulate(tokens, result.tokens, id); }
|
|
53
53
|
const runDirs = (await readdir(root, { withFileTypes: true })).filter((item) => item.isDirectory()).map((item) => item.name).sort(); const legs = evidence.filter((entry) => entry.kind === "session").map((entry) => entry.id); const retryDirs = runDirs.filter((name) => /(?:^|[-_])retry(?:[-_]|$)/i.test(name)); const rawBytes = evidence.filter((entry) => entry.kind === "session").reduce((sum, entry) => sum + entry.byteLength, 0);
|
|
54
54
|
const cost: DoctorCaseCost = { invocations: sourceList(runDirs.length, runDirs), legs: sourceList(legs.length, legs), modelApiTurns: sourceList(turns.count, turns.sources), outputTokens: sourceList(tokens.count, tokens.sources), toolCalls: sourceList(calls.count, calls.sources), retries: { ...sourceList(retryDirs.length, retryDirs), evidence: "literal run-dir naming" }, statuses, commits, sessions, outputBytes: { ...sourceList(rawBytes, legs), payload: "raw JSONL bytes", providerWireBytes: "unavailable" } };
|
|
55
55
|
return { version: 1, identity: { issueNumber: Number(match[1]), runsPath: await stableRunsIdentity(root) }, evidence, cost };
|
|
@@ -125,7 +125,7 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
125
125
|
: undefined;
|
|
126
126
|
const runId = runDirectory === undefined ? undefined : runIdFromRunDirectory(runDirectory);
|
|
127
127
|
// Public-invocation scope + selected host from Host envelope only — never
|
|
128
|
-
// courtAttemptId, never sidecar file, never pre-spawn
|
|
128
|
+
// courtAttemptId, never sidecar file, never pre-spawn current.json I/O.
|
|
129
129
|
const invocationScopeId =
|
|
130
130
|
typeof ctx.invocationScopeId === "string" && ctx.invocationScopeId.trim() !== ""
|
|
131
131
|
? ctx.invocationScopeId.trim()
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* `ak-role resume`) mints a new one. Never courtAttemptId, never Pi session
|
|
16
16
|
* toolResult join keys, and never a detour-owned sidecar file.
|
|
17
17
|
*/
|
|
18
|
+
import { readPageSync, RUN_LOG_FILE } from "./run-dossier.ts";
|
|
18
19
|
import { randomUUID } from "node:crypto";
|
|
19
20
|
import { readFileSync } from "node:fs";
|
|
20
21
|
import { join } from "node:path";
|
|
@@ -25,7 +26,6 @@ export {
|
|
|
25
26
|
} from "./role-run-placement.ts";
|
|
26
27
|
|
|
27
28
|
import { ENGINE_DETOUR_TOOL_NAME } from "./engine-detour.ts";
|
|
28
|
-
import { sitianRunVolumeDirectory, sitianVolumeRecordsFile } from "./sitian-appender.ts";
|
|
29
29
|
import {
|
|
30
30
|
readSitianRecords,
|
|
31
31
|
resolveSitianRecordPath,
|
|
@@ -42,13 +42,11 @@ export const ENGINE_DETOUR_CALL_KIND = "engine-detour-call" as const;
|
|
|
42
42
|
export const ENGINE_DETOUR_TOOL_USAGE_FACT_KEY = "engineDetourToolUsage" as const;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Run-relative
|
|
45
|
+
* Run-relative path of the log that holds one detour call's record.
|
|
46
46
|
* Openable once the run directory is known from the terminal's run id.
|
|
47
47
|
* Contains no runId bytes itself (#108 + #537).
|
|
48
48
|
*/
|
|
49
|
-
export const ENGINE_DETOUR_CALL_RECORD_FILE_RELATIVE =
|
|
50
|
-
sitianRunVolumeDirectory("", ENGINE_DETOUR_CALL_KIND),
|
|
51
|
-
);
|
|
49
|
+
export const ENGINE_DETOUR_CALL_RECORD_FILE_RELATIVE = RUN_LOG_FILE;
|
|
52
50
|
|
|
53
51
|
/** stdout UTF-8 byte length (ticket-frozen metric; empty stdout is real 0). */
|
|
54
52
|
export function engineDetourStdoutByteLength(stdout: string): number {
|
|
@@ -253,22 +251,14 @@ export async function readEngineDetourToolUsage(input: {
|
|
|
253
251
|
return { callCount: calls.length, calls };
|
|
254
252
|
}
|
|
255
253
|
|
|
256
|
-
/** One shared
|
|
254
|
+
/** One shared state.jsonl invocation-row reader (absent → undefined; other errors propagate). */
|
|
257
255
|
function readInvocationRecord(
|
|
258
256
|
runDirectory: string,
|
|
259
257
|
): Record<string, unknown> | undefined {
|
|
260
|
-
|
|
261
|
-
const raw = JSON.parse(
|
|
262
|
-
readFileSync(join(runDirectory, "invocation.json"), "utf8"),
|
|
263
|
-
) as unknown;
|
|
264
|
-
return isRecord(raw) ? raw : undefined;
|
|
265
|
-
} catch (error) {
|
|
266
|
-
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
|
|
267
|
-
throw error;
|
|
268
|
-
}
|
|
258
|
+
return readPageSync(runDirectory, "invocation");
|
|
269
259
|
}
|
|
270
260
|
|
|
271
|
-
/** True when invocation
|
|
261
|
+
/** True when the invocation section carries a non-empty engine axis. */
|
|
272
262
|
export async function readInvocationEngineMounted(
|
|
273
263
|
runDirectory: string,
|
|
274
264
|
): Promise<boolean> {
|
|
@@ -13,8 +13,33 @@ import type { PreparedRoleTurn } from "./prepared-role-turn.ts";
|
|
|
13
13
|
import { projectThrownFailureLeaf, retainPackageFault } from "./public-cli/settlement.ts";
|
|
14
14
|
import { describeErrorIdentity } from "./public-cli/run-lifecycle.ts";
|
|
15
15
|
import { deliveryLimitFromConfig } from "./receipt-delivery-policy.ts";
|
|
16
|
+
import { reportRunRecord } from "./sitian-facade.ts";
|
|
16
17
|
import { isOneShotWorkerReminderCode } from "./submission-errors.ts";
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* What one host start was given: one `turn-delivery` row of history.jsonl per start. History is
|
|
21
|
+
* the volume whose write failure does not stop a run (#833), so a row that cannot be
|
|
22
|
+
* written is noted and the host still starts.
|
|
23
|
+
*/
|
|
24
|
+
export async function recordTurnDelivery(
|
|
25
|
+
runDirectory: string,
|
|
26
|
+
delivered: { readonly systemPrompt: string; readonly outputSchema?: unknown },
|
|
27
|
+
source: string,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
try {
|
|
30
|
+
reportRunRecord(runDirectory, "turn-delivery", {
|
|
31
|
+
systemPrompt: delivered.systemPrompt,
|
|
32
|
+
...(delivered.outputSchema === undefined ? {} : { outputSchema: delivered.outputSchema }),
|
|
33
|
+
}, source);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
await retainPackageFault({
|
|
36
|
+
runDirectory,
|
|
37
|
+
diagnostic: `turn delivery record failed beside host start: ${describeErrorIdentity(error)}`,
|
|
38
|
+
error,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
18
43
|
export type ExternalPreparedTurn = Pick<PreparedRoleTurn, "prompt" | "abortSignal" | "closeRound">;
|
|
19
44
|
|
|
20
45
|
export type ExternalHostRoundOutcome =
|
|
@@ -36,10 +36,7 @@ export function createProductionHeadlessRoleTurnHost(
|
|
|
36
36
|
options: ProductionHeadlessHostOptions,
|
|
37
37
|
): RoleTurnHost {
|
|
38
38
|
const { packageRoot, principalAuthority, description, hostName } = options;
|
|
39
|
-
const sessionIdentity = createSessionIdentityAuthority(
|
|
40
|
-
principalAuthority,
|
|
41
|
-
description.sessionBindingFile,
|
|
42
|
-
);
|
|
39
|
+
const sessionIdentity = createSessionIdentityAuthority(principalAuthority, hostName);
|
|
43
40
|
const roleRuntimeDependencies = createRoleRuntimeDependencies(packageRoot);
|
|
44
41
|
|
|
45
42
|
const innerFor = (operatorHome: string): RoleTurnHost =>
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
|
-
import { writeFile } from "node:fs/promises";
|
|
9
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
10
11
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
11
12
|
|
|
12
13
|
import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
externalHostFailure as failure,
|
|
19
20
|
hostAbortedError,
|
|
20
21
|
isHostAbortedError,
|
|
22
|
+
recordTurnDelivery,
|
|
21
23
|
} from "../external-host-turn-loop.ts";
|
|
22
24
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
23
25
|
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.ts";
|
|
@@ -462,31 +464,51 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
462
464
|
|
|
463
465
|
// config.env owns package-root/child env; do not re-spread process.env over it.
|
|
464
466
|
const env: NodeJS.ProcessEnv = { ...process.env, ...(config.env ?? {}) };
|
|
465
|
-
|
|
466
|
-
|
|
467
|
+
// CLI start-up inputs (system prompt / schema / MCP config files) are not
|
|
468
|
+
// dossier: they live in a throwaway directory, and what was delivered is
|
|
469
|
+
// recorded once per actual start as a history.jsonl `turn-delivery` row.
|
|
470
|
+
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
471
|
+
const inputsDirectory = await mkdtemp(join(tmpdir(), "ak-role-headless-"));
|
|
472
|
+
const systemPromptPath = join(inputsDirectory, "system-prompt.txt");
|
|
467
473
|
let mcpConfigPath: string | undefined;
|
|
468
474
|
let outputSchemaPath: string | undefined;
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
475
|
+
let startedWithSchema: unknown;
|
|
476
|
+
// A setup failure after mkdtemp must not leave the prompt material behind.
|
|
477
|
+
try {
|
|
478
|
+
await writeFile(systemPromptPath, systemPrompt, "utf8");
|
|
479
|
+
let deliveredSchema: unknown;
|
|
480
|
+
if (codex) {
|
|
481
|
+
// #1148: Codex --output-schema requires a native strict transport schema.
|
|
482
|
+
// Project from the unique open declaration; do not alter package receipt rules.
|
|
483
|
+
if (prepared.terminatingToolName !== NAVIGATOR_OUTPUT_TOOL_NAME) {
|
|
484
|
+
outputSchemaPath = join(inputsDirectory, "output-schema.json");
|
|
485
|
+
deliveredSchema = closeJsonSchemaForCodex(prepared.jsonSchema);
|
|
486
|
+
await writeFile(outputSchemaPath, `${JSON.stringify(deliveredSchema, null, 2)}\n`, "utf8");
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
mcpConfigPath = join(inputsDirectory, "mcp-config.json");
|
|
474
490
|
await writeFile(
|
|
475
|
-
|
|
476
|
-
`${JSON.stringify(
|
|
491
|
+
mcpConfigPath,
|
|
492
|
+
`${JSON.stringify(headlessMcpConfigDocument(prepared.mcpServers), null, 2)}\n`,
|
|
477
493
|
"utf8",
|
|
478
494
|
);
|
|
495
|
+
// #959: navigator prose exit — no closed JSON schema on claude either.
|
|
496
|
+
if (prepared.terminatingToolName !== NAVIGATOR_OUTPUT_TOOL_NAME) deliveredSchema = prepared.jsonSchema;
|
|
479
497
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
498
|
+
startedWithSchema = deliveredSchema;
|
|
499
|
+
} catch (setupError) {
|
|
500
|
+
try {
|
|
501
|
+
await rm(inputsDirectory, { recursive: true, force: true });
|
|
502
|
+
} catch (cleanupError) {
|
|
503
|
+
await retainPackageFault({
|
|
504
|
+
runDirectory: request.runDirectory,
|
|
505
|
+
diagnostic: `headless turn inputs cleanup failed beside setup failure: ${describeErrorIdentity(cleanupError)}`,
|
|
506
|
+
error: cleanupError,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
throw setupError;
|
|
487
510
|
}
|
|
488
511
|
|
|
489
|
-
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
490
512
|
let exitedSessionId: string | undefined;
|
|
491
513
|
try {
|
|
492
514
|
outcome = await driveExternalRoleTurnRounds(prepared, request, {
|
|
@@ -535,6 +557,11 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
535
557
|
}) !== undefined;
|
|
536
558
|
}
|
|
537
559
|
|
|
560
|
+
// What this CLI start was given: one history record per start.
|
|
561
|
+
await recordTurnDelivery(request.runDirectory, {
|
|
562
|
+
systemPrompt,
|
|
563
|
+
...(startedWithSchema === undefined ? {} : { outputSchema: startedWithSchema }),
|
|
564
|
+
}, "headless-host");
|
|
538
565
|
let spawned: HeadlessSpawnResult;
|
|
539
566
|
try {
|
|
540
567
|
spawned = await spawnHeadlessTurn({
|
|
@@ -715,12 +742,19 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
715
742
|
},
|
|
716
743
|
});
|
|
717
744
|
} finally {
|
|
745
|
+
try {
|
|
746
|
+
await rm(inputsDirectory, { recursive: true, force: true });
|
|
747
|
+
} catch (error) {
|
|
748
|
+
await retainPackageFault({
|
|
749
|
+
runDirectory: request.runDirectory,
|
|
750
|
+
diagnostic: `headless turn inputs cleanup failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
751
|
+
error,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
718
754
|
try {
|
|
719
755
|
if (exitedSessionId !== undefined && exitedSessionId !== "") copyAndRecordHostDossier({
|
|
720
756
|
host: config.hostName, sessionId: exitedSessionId, cwd: request.cwd,
|
|
721
757
|
sessionDirectory: sessionDirectoryOf(request.runDirectory), sessionParent,
|
|
722
|
-
continuation: request.continuation,
|
|
723
|
-
...(request.model !== undefined ? { model: request.model } : {}),
|
|
724
758
|
...(request.home !== undefined ? { home: request.home } : {}),
|
|
725
759
|
});
|
|
726
760
|
} catch (error) {
|
package/src/host-descriptions.ts
CHANGED
|
@@ -15,8 +15,6 @@ import type { HeadlessHostDescription } from "./headless-host/description.ts";
|
|
|
15
15
|
export type HostIdentityDescription = Readonly<{
|
|
16
16
|
/** Binary path segments relative to the operator home. */
|
|
17
17
|
binaryFromHome: readonly string[];
|
|
18
|
-
/** Durable host session binding filename beside the session principal. */
|
|
19
|
-
sessionBindingFile: string;
|
|
20
18
|
}>;
|
|
21
19
|
|
|
22
20
|
/** Resolve a host description's agent binary under the operator home. */
|
|
@@ -45,7 +43,6 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
45
43
|
}),
|
|
46
44
|
modelPassing: "set_model",
|
|
47
45
|
setModelId: "bare",
|
|
48
|
-
sessionBindingFile: "grok-acp-session.json",
|
|
49
46
|
}),
|
|
50
47
|
/**
|
|
51
48
|
* Operator home `~/.hermes`, native session/load resume, `acp` subcommand.
|
|
@@ -63,7 +60,6 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
63
60
|
}),
|
|
64
61
|
modelPassing: "set_model",
|
|
65
62
|
setModelId: "provider:model",
|
|
66
|
-
sessionBindingFile: "hermes-acp-session.json",
|
|
67
63
|
seatProfileSoul: Object.freeze({
|
|
68
64
|
flag: "-p",
|
|
69
65
|
namePrefix: "ak-",
|
|
@@ -84,7 +80,6 @@ export const HEADLESS_HOST_DESCRIPTIONS: Readonly<Record<string, HeadlessHostDes
|
|
|
84
80
|
"claude": Object.freeze({
|
|
85
81
|
protocol: "claude-print",
|
|
86
82
|
binaryFromHome: Object.freeze([".local", "bin", "claude"]),
|
|
87
|
-
sessionBindingFile: "claude-headless-session.json",
|
|
88
83
|
fixedArgs: Object.freeze([
|
|
89
84
|
// Live NDJSON events → sitian host-session records (#811); last line is the result receipt.
|
|
90
85
|
"--output-format", "stream-json",
|
|
@@ -109,7 +104,6 @@ export const HEADLESS_HOST_DESCRIPTIONS: Readonly<Record<string, HeadlessHostDes
|
|
|
109
104
|
"codex": Object.freeze({
|
|
110
105
|
protocol: "codex-exec",
|
|
111
106
|
binaryFromHome: Object.freeze([".local", "bin", "codex"]),
|
|
112
|
-
sessionBindingFile: "codex-headless-session.json",
|
|
113
107
|
}),
|
|
114
108
|
});
|
|
115
109
|
|