@akagilnc/pi-workflow-roles 0.1.5483 → 0.1.5571
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 +6312 -6376
- package/dist/analyst-gate-cycles-read.js +28 -78
- package/dist/analyst-ledger.js +26 -42
- 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-entry.js +4 -3
- package/dist/archivist-record-pointer.js +71 -36
- package/dist/atomic-write.js +2 -2
- package/dist/auditor-dossier-tool.js +11 -6
- package/dist/auditor-soul.js +5 -12
- package/dist/collector-config.js +6 -5
- package/dist/doctor-evidence.js +8 -4
- package/dist/engine-detour-usage.js +8 -22
- package/dist/headless-host/production-host.js +5969 -6011
- package/dist/host-descriptions.js +0 -4
- package/dist/host-session-record.js +266 -267
- package/dist/ledger-session-read.js +5 -0
- package/dist/migrate-book-topology.js +1369 -722
- package/dist/navigator-public-session.js +13 -17
- package/dist/notary-source-run.js +8 -16
- package/dist/packaged-role-registry.js +5 -56
- package/dist/pi/durable-principal.js +2 -3
- package/dist/pi/role-turn-host.js +79 -15
- package/dist/public-cli/auto-resume.js +8 -47
- package/dist/public-cli/instruction-seat-run.js +9 -7
- package/dist/public-cli/invocation.js +67 -100
- package/dist/public-cli/main.js +2689 -2532
- package/dist/public-cli/post-admission.js +96 -55
- package/dist/public-cli/run-lifecycle.js +44 -100
- package/dist/public-cli/settlement.js +184 -429
- package/dist/public-cli/terminal.js +0 -12
- package/dist/role-run-placement.js +53 -16
- package/dist/role-run-relocation.js +111 -52
- package/dist/run-dossier-files.js +30 -0
- package/dist/run-dossier.js +322 -0
- package/dist/run-terminal-artifacts.js +69 -202
- package/dist/run-ticket-number.js +49 -7
- package/dist/session-assistant-usage.js +9 -18
- package/dist/session-identity.js +49 -19
- package/dist/sitian-appender.js +29 -18
- package/dist/sitian-facade.js +11 -0
- package/dist/sitian-reader.js +44 -29
- package/dist/submission-gate.js +1 -1
- package/dist/submission-ledger.js +4 -4
- package/dist/ticket-provenance-contracts.js +2 -8
- package/dist/ticket-provenance.js +25 -9
- 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 +11 -4
- package/src/analyst-gate-cycles-read.ts +31 -88
- package/src/analyst-ledger.ts +36 -51
- 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 +6 -5
- package/src/archivist-record-pointer.ts +92 -42
- package/src/atomic-write.ts +3 -1
- package/src/auditor-dossier-tool.ts +14 -8
- package/src/auditor-soul.ts +5 -10
- 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/collector-config.ts +5 -3
- package/src/doctor-contracts.ts +1 -1
- package/src/doctor-evidence.ts +7 -3
- package/src/engine-detour-tool.ts +1 -1
- package/src/engine-detour-usage.ts +12 -25
- package/src/external-host-turn-loop.ts +25 -0
- package/src/factory-board.ts +16 -81
- package/src/headless-host/production-host.ts +1 -4
- package/src/headless-host/role-turn-host.ts +56 -21
- package/src/host-descriptions.ts +0 -6
- package/src/host-session-record.ts +224 -140
- package/src/ledger-session-read.ts +6 -0
- package/src/navigator-public-session.ts +13 -18
- package/src/navigator-work-context.ts +3 -1
- package/src/notary-role.ts +2 -1
- package/src/notary-source-run.ts +8 -18
- package/src/packaged-role-registry.ts +1 -65
- package/src/pi/adapter.ts +50 -7
- package/src/pi/durable-principal.ts +2 -4
- package/src/pi/role-turn-host.ts +85 -14
- package/src/public-cli/auto-resume.ts +7 -43
- package/src/public-cli/instruction-seat-run.ts +13 -6
- package/src/public-cli/invocation.ts +90 -144
- package/src/public-cli/post-admission.ts +111 -72
- package/src/public-cli/run-lifecycle.ts +47 -115
- package/src/public-cli/settlement.ts +221 -589
- package/src/public-cli/terminal.ts +1 -61
- package/src/role-envelope.ts +4 -2
- package/src/role-run-placement.ts +65 -17
- package/src/role-run-relocation.ts +110 -70
- package/src/role-runtime.ts +46 -69
- package/src/run-dossier-files.ts +33 -0
- package/src/run-dossier.ts +357 -0
- package/src/run-terminal-artifacts.ts +84 -240
- package/src/run-ticket-number.ts +82 -14
- package/src/session-assistant-usage.ts +8 -20
- package/src/session-identity.ts +52 -21
- package/src/sitian-appender.ts +33 -26
- package/src/sitian-facade.ts +22 -0
- package/src/sitian-reader.ts +62 -32
- package/src/submission-gate.ts +1 -1
- package/src/submission-ledger.ts +4 -4
- package/src/ticket-provenance-contracts.ts +3 -6
- package/src/ticket-provenance.ts +23 -11
- package/src/ticket-snapshot.ts +3 -2
- package/src/ticket-trajectory.ts +95 -109
|
@@ -1,9 +1,35 @@
|
|
|
1
|
+
import { readPageSync } from "./run-dossier.js";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { isEnoent, isRecord } from "./unknown-value.js";
|
|
4
5
|
const MIGRATION_TICKET_DERIVATION_PAGE = "migration-ticket-derivation.json";
|
|
6
|
+
function isPositiveTicketNumber(value) {
|
|
7
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
8
|
+
}
|
|
5
9
|
function isSafePositiveTicketNumber(value) {
|
|
6
|
-
return
|
|
10
|
+
return isPositiveTicketNumber(value) && Number.isSafeInteger(value);
|
|
11
|
+
}
|
|
12
|
+
function isTicketNumberString(value) {
|
|
13
|
+
return typeof value === "string" && /^[1-9][0-9]*$/.test(value);
|
|
14
|
+
}
|
|
15
|
+
function parseTicketNumber(value) {
|
|
16
|
+
if (isSafePositiveTicketNumber(value)) return value;
|
|
17
|
+
if (!isTicketNumberString(value)) return void 0;
|
|
18
|
+
const parsed = Number(value);
|
|
19
|
+
return isSafePositiveTicketNumber(parsed) ? parsed : void 0;
|
|
20
|
+
}
|
|
21
|
+
function sitianSubjectTicketNumber(subject) {
|
|
22
|
+
if (typeof subject === "string" || typeof subject === "number") {
|
|
23
|
+
return parseTicketNumber(subject);
|
|
24
|
+
}
|
|
25
|
+
if (subject !== null && typeof subject === "object" && !Array.isArray(subject)) {
|
|
26
|
+
return parseTicketNumber(subject.ticketNumber);
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
function ticketNumberFromSitianSubject(subject) {
|
|
31
|
+
const parsed = sitianSubjectTicketNumber(subject);
|
|
32
|
+
return parsed === void 0 ? void 0 : String(parsed);
|
|
7
33
|
}
|
|
8
34
|
function readDeclaredTicketNumber(value) {
|
|
9
35
|
if (isSafePositiveTicketNumber(value)) return value;
|
|
@@ -11,8 +37,7 @@ function readDeclaredTicketNumber(value) {
|
|
|
11
37
|
const trimmed = value.trim();
|
|
12
38
|
const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
|
|
13
39
|
if (match === null) return void 0;
|
|
14
|
-
|
|
15
|
-
return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : void 0;
|
|
40
|
+
return parseTicketNumber(match[1]);
|
|
16
41
|
}
|
|
17
42
|
function requireSafePositiveTicketNumber(ticketNumber, context = "ticketNumber") {
|
|
18
43
|
if (!isSafePositiveTicketNumber(ticketNumber)) {
|
|
@@ -37,13 +62,24 @@ async function readJsonObject(path) {
|
|
|
37
62
|
throw error;
|
|
38
63
|
}
|
|
39
64
|
}
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
const LEGACY_BOARD_PAGE = {
|
|
66
|
+
admitted: "admitted-request.json",
|
|
67
|
+
invocation: "invocation.json"
|
|
68
|
+
};
|
|
69
|
+
function readBoardPageTicketNumberFromRows(runDirectory, section) {
|
|
70
|
+
const fromRows = readPageSync(runDirectory, section);
|
|
71
|
+
return fromRows === void 0 ? void 0 : ticketFromRecord(fromRows);
|
|
72
|
+
}
|
|
73
|
+
async function readBoardPageTicketNumberFromLegacy(runDirectory, section) {
|
|
74
|
+
const record = await readJsonObject(join(runDirectory, LEGACY_BOARD_PAGE[section]));
|
|
42
75
|
if (record === void 0) return void 0;
|
|
43
76
|
return ticketFromRecord(record);
|
|
44
77
|
}
|
|
45
78
|
async function readBoardTicketNumber(runDirectory) {
|
|
46
|
-
return
|
|
79
|
+
return readBoardPageTicketNumberFromRows(runDirectory, "admitted") ?? readBoardPageTicketNumberFromRows(runDirectory, "invocation");
|
|
80
|
+
}
|
|
81
|
+
async function readMigrationBoardTicketNumber(runDirectory) {
|
|
82
|
+
return await readBoardTicketNumber(runDirectory) ?? await readBoardPageTicketNumberFromLegacy(runDirectory, "admitted") ?? await readBoardPageTicketNumberFromLegacy(runDirectory, "invocation");
|
|
47
83
|
}
|
|
48
84
|
async function readMigrationDerivedTicketNumber(runDirectory) {
|
|
49
85
|
const record = await readJsonObject(
|
|
@@ -58,10 +94,16 @@ async function readRunTicketNumber(runDirectory) {
|
|
|
58
94
|
}
|
|
59
95
|
export {
|
|
60
96
|
MIGRATION_TICKET_DERIVATION_PAGE,
|
|
97
|
+
isPositiveTicketNumber,
|
|
61
98
|
isSafePositiveTicketNumber,
|
|
99
|
+
isTicketNumberString,
|
|
100
|
+
parseTicketNumber,
|
|
62
101
|
readBoardTicketNumber,
|
|
63
102
|
readDeclaredTicketNumber,
|
|
103
|
+
readMigrationBoardTicketNumber,
|
|
64
104
|
readMigrationDerivedTicketNumber,
|
|
65
105
|
readRunTicketNumber,
|
|
66
|
-
requireSafePositiveTicketNumber
|
|
106
|
+
requireSafePositiveTicketNumber,
|
|
107
|
+
sitianSubjectTicketNumber,
|
|
108
|
+
ticketNumberFromSitianSubject
|
|
67
109
|
};
|
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ROLE_RUN_SESSION_FILENAME, sessionFileOf } from "./role-run-placement.js";
|
|
2
|
+
import { readStrictPiSessionJsonl } from "./ledger-session-read.js";
|
|
2
3
|
import { errorText } from "./unknown-value.js";
|
|
3
4
|
export async function readAssistantUsageFromSessionFile(sessionFile) {
|
|
4
|
-
|
|
5
|
-
let text;
|
|
5
|
+
let rows;
|
|
6
6
|
try {
|
|
7
|
-
|
|
7
|
+
rows = await readStrictPiSessionJsonl(sessionFile);
|
|
8
8
|
}
|
|
9
9
|
catch (error) {
|
|
10
10
|
if (error?.code === "ENOENT")
|
|
11
11
|
return undefined;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let rows;
|
|
15
|
-
try {
|
|
16
|
-
rows = text
|
|
17
|
-
.trim()
|
|
18
|
-
.split("\n")
|
|
19
|
-
.filter(Boolean)
|
|
20
|
-
.map((line) => JSON.parse(line));
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
12
|
+
if (!(error instanceof SyntaxError))
|
|
13
|
+
throw error;
|
|
23
14
|
throw new Error(`session usage parse failed (${sessionFile}): ${errorText(error)}`, { cause: error });
|
|
24
15
|
}
|
|
25
16
|
let total = 0;
|
|
@@ -82,11 +73,11 @@ export async function readAssistantUsageFromSessionFile(sessionFile) {
|
|
|
82
73
|
*/
|
|
83
74
|
export function sessionFileFromPublicSummon(summoned) {
|
|
84
75
|
if (typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== "") {
|
|
85
|
-
return
|
|
76
|
+
return sessionFileOf(summoned.runDirectory);
|
|
86
77
|
}
|
|
87
78
|
const fromArtifacts = summoned.terminal?.artifacts
|
|
88
79
|
?.map((a) => a.path)
|
|
89
|
-
.find((p) => typeof p === "string" && p.endsWith(
|
|
80
|
+
.find((p) => typeof p === "string" && p.endsWith(ROLE_RUN_SESSION_FILENAME));
|
|
90
81
|
if (fromArtifacts !== undefined)
|
|
91
82
|
return fromArtifacts;
|
|
92
83
|
const outcome = summoned.terminal?.roleOutcome;
|
|
@@ -95,7 +86,7 @@ export function sessionFileFromPublicSummon(summoned) {
|
|
|
95
86
|
const facts = outcome.decisiveFacts;
|
|
96
87
|
const pointer = facts?.runPointer;
|
|
97
88
|
if (typeof pointer === "string" && pointer.trim() !== "") {
|
|
98
|
-
return
|
|
89
|
+
return sessionFileOf(pointer);
|
|
99
90
|
}
|
|
100
91
|
return undefined;
|
|
101
92
|
}
|
package/dist/session-identity.js
CHANGED
|
@@ -1,33 +1,63 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
1
|
import { dirname, join } from "node:path";
|
|
3
2
|
import { DEFAULT_ROLE_TURN_HOST, lookupHeadlessHostDescription, lookupHostDescription, } from "./host-descriptions.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { renderCurrentSync, RUN_STATE_FILE } from "./run-dossier.js";
|
|
4
|
+
import { readSitianRecords, reportRunRecord } from "./sitian-facade.js";
|
|
5
|
+
import { errorText, isRecord } from "./unknown-value.js";
|
|
6
|
+
/** Log record kind of one host-session binding (a host's native session id for this run). */
|
|
7
|
+
export const HOST_SESSION_ID_RECORD_KIND = "host-session-id";
|
|
8
|
+
/**
|
|
9
|
+
* Durable session binding: one state row per bind, scoped by host name — a run
|
|
10
|
+
* that changes hosts on resume must never hand one host's native session id to
|
|
11
|
+
* another. The latest record for the host wins. The public call projects
|
|
12
|
+
* `current.json` at the bind so host.sessions appears before the host CLI
|
|
13
|
+
* starts; a refused projection does not undo the binding fact or become a
|
|
14
|
+
* host/session failure (facts stay in state.jsonl; settlement still renders).
|
|
15
|
+
*/
|
|
16
|
+
export function createSessionIdentityAuthority(authority, hostName) {
|
|
17
|
+
const runDirectoryOf = (principal) => dirname(authority.decode(principal).sessionDirectory);
|
|
7
18
|
return {
|
|
8
19
|
resolveSessionFile(principal) {
|
|
9
20
|
return authority.decode(principal).sessionFile;
|
|
10
21
|
},
|
|
11
22
|
async load(principal) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
const runDirectory = runDirectoryOf(principal);
|
|
24
|
+
const { records, diagnostics } = await readSitianRecords(join(runDirectory, RUN_STATE_FILE));
|
|
25
|
+
// Control path fails closed on damaged rows — same class as readPageSync.
|
|
26
|
+
// Rendering may keep reachable facts; dispatch must not reuse a prior binding
|
|
27
|
+
// after syntax damage (#1161 C3).
|
|
28
|
+
if (diagnostics.length > 0) {
|
|
29
|
+
throw new Error(`${RUN_STATE_FILE} has ${diagnostics.length} malformed row(s); refusing stale session binding: ${runDirectory}`);
|
|
30
|
+
}
|
|
31
|
+
let bound;
|
|
32
|
+
for (const record of records) {
|
|
33
|
+
if (record.kind !== HOST_SESSION_ID_RECORD_KIND)
|
|
34
|
+
continue;
|
|
35
|
+
// Non-object / cannot name a host: unattributable damage — refuse
|
|
36
|
+
// (settled C3-payload). Never treat as a skippable "other host" row.
|
|
37
|
+
if (!isRecord(record.payload) || typeof record.payload.host !== "string") {
|
|
15
38
|
throw new Error("durable session binding is invalid");
|
|
16
39
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
// Explicit other host only: that host's bad id must not block this one.
|
|
41
|
+
if (record.payload.host !== hostName)
|
|
42
|
+
continue;
|
|
43
|
+
// This host's null / non-string sessionId: refuse — do not reuse a prior
|
|
44
|
+
// binding for the same host (#1161 C3).
|
|
45
|
+
if (typeof record.payload.sessionId !== "string") {
|
|
46
|
+
throw new Error("durable session binding is invalid");
|
|
47
|
+
}
|
|
48
|
+
bound = record.payload.sessionId;
|
|
23
49
|
}
|
|
50
|
+
return bound;
|
|
24
51
|
},
|
|
25
52
|
async bind(principal, sessionId) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
53
|
+
const runDirectory = runDirectoryOf(principal);
|
|
54
|
+
reportRunRecord(runDirectory, HOST_SESSION_ID_RECORD_KIND, { host: hostName, sessionId }, "session-identity");
|
|
55
|
+
try {
|
|
56
|
+
renderCurrentSync(runDirectory);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
process.stderr.write(`[session-identity] current.json render refused after host-session-id; binding fact kept: ${runDirectory}: ${errorText(error)}\n`);
|
|
60
|
+
}
|
|
31
61
|
},
|
|
32
62
|
};
|
|
33
63
|
}
|
|
@@ -42,7 +72,7 @@ export async function readStoredHostSessionId(host, authority, principal) {
|
|
|
42
72
|
const description = lookupHostDescription(host) ?? lookupHeadlessHostDescription(host);
|
|
43
73
|
if (description === undefined)
|
|
44
74
|
return undefined;
|
|
45
|
-
const sessionId = await createSessionIdentityAuthority(authority,
|
|
75
|
+
const sessionId = await createSessionIdentityAuthority(authority, host).load(principal);
|
|
46
76
|
if (sessionId === undefined || sessionId === "")
|
|
47
77
|
return undefined;
|
|
48
78
|
return sessionId;
|
package/dist/sitian-appender.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { appendFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { parseRunLeaf, sessionDirectoryOf } from "./role-run-placement.js";
|
|
5
|
+
import { RUN_HISTORY_FILE, RUN_HISTORY_KINDS, RUN_LOG_FILE, RUN_STATE_FILE, RUN_STATE_KINDS } from "./run-dossier-files.js";
|
|
6
|
+
import { ticketNumberFromSitianSubject } from "./run-ticket-number.js";
|
|
4
7
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
|
|
5
8
|
import {
|
|
6
9
|
activationBookDirectory,
|
|
@@ -27,27 +30,26 @@ function appendSitianRecordBlock(input, block) {
|
|
|
27
30
|
);
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
|
-
const S4_SUBMISSION_LEDGER_KINDS = /* @__PURE__ */ new Set([
|
|
31
|
-
"candidate",
|
|
32
|
-
"roundContext",
|
|
33
|
-
"outcome",
|
|
34
|
-
"sealed",
|
|
35
|
-
"post-seal-anomaly"
|
|
36
|
-
]);
|
|
37
33
|
function resolveSitianVolumeCategory(kind) {
|
|
38
|
-
if (S4_SUBMISSION_LEDGER_KINDS.has(kind)) {
|
|
39
|
-
return "submission-ledger";
|
|
40
|
-
}
|
|
41
34
|
return kind;
|
|
42
35
|
}
|
|
43
36
|
const SITIAN_RECORDS_LEAF = "records.jsonl";
|
|
37
|
+
function sitianVolumeDirectory(sessionDirectory, category) {
|
|
38
|
+
return join(sessionDirectory, category);
|
|
39
|
+
}
|
|
40
|
+
function sitianRunVolumeDirectory(runDirectory, category) {
|
|
41
|
+
return sitianVolumeDirectory(sessionDirectoryOf(runDirectory), category);
|
|
42
|
+
}
|
|
43
|
+
function sitianVolumeRecordsFile(volumeDirectory) {
|
|
44
|
+
return join(volumeDirectory, SITIAN_RECORDS_LEAF);
|
|
45
|
+
}
|
|
44
46
|
function ticketProvenanceUnderBookPaths(ledgerHome, bookKey, ticketId) {
|
|
45
47
|
const sessionDir = join(activationBookDirectory(ledgerHome, bookKey), ticketId);
|
|
46
|
-
return { sessionDir, recordFile:
|
|
48
|
+
return { sessionDir, recordFile: sitianVolumeRecordsFile(sessionDir) };
|
|
47
49
|
}
|
|
48
50
|
function resolveSitianRecordPathInLedger(input, ledgerHome) {
|
|
49
51
|
const category = resolveSitianVolumeCategory(input.kind);
|
|
50
|
-
const ticketNumber = category === "ticket-provenance" ?
|
|
52
|
+
const ticketNumber = category === "ticket-provenance" ? ticketNumberFromSitianSubject(input.subject) : void 0;
|
|
51
53
|
let sessionDir;
|
|
52
54
|
let recordFile;
|
|
53
55
|
if (ticketNumber !== void 0) {
|
|
@@ -60,13 +62,20 @@ function resolveSitianRecordPathInLedger(input, ledgerHome) {
|
|
|
60
62
|
recordFile = paths.recordFile;
|
|
61
63
|
} else if (category === "ticket-provenance" && input.runDirectory !== void 0) {
|
|
62
64
|
sessionDir = input.runDirectory;
|
|
63
|
-
recordFile = join(sessionDir,
|
|
65
|
+
recordFile = join(sessionDir, RUN_HISTORY_FILE);
|
|
64
66
|
} else {
|
|
65
67
|
if (input.sessionParent === void 0 || input.sessionParent.length === 0 || !physicallyContainedIn(ledgerHome, input.sessionParent)) {
|
|
66
68
|
throw new Error("Sitian record ownership requires a parent session inside the ledger home");
|
|
67
69
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const sessionParentDir = dirname(input.sessionParent);
|
|
71
|
+
const runDirectory = dirname(sessionParentDir);
|
|
72
|
+
if (basename(sessionParentDir) === "session" && parseRunLeaf(basename(runDirectory)) !== void 0) {
|
|
73
|
+
sessionDir = runDirectory;
|
|
74
|
+
recordFile = join(runDirectory, RUN_HISTORY_KINDS.has(input.kind) ? RUN_HISTORY_FILE : RUN_STATE_KINDS.has(input.kind) ? RUN_STATE_FILE : RUN_LOG_FILE);
|
|
75
|
+
} else {
|
|
76
|
+
sessionDir = sitianVolumeDirectory(sessionParentDir, category);
|
|
77
|
+
recordFile = sitianVolumeRecordsFile(sessionDir);
|
|
78
|
+
}
|
|
70
79
|
}
|
|
71
80
|
if (!physicallyContainedIn(ledgerHome, sessionDir)) {
|
|
72
81
|
throw new Error("Sitian record ownership requires a directory inside the ledger home");
|
|
@@ -115,10 +124,12 @@ function appendSitianRecord(input) {
|
|
|
115
124
|
}
|
|
116
125
|
}
|
|
117
126
|
export {
|
|
118
|
-
S4_SUBMISSION_LEDGER_KINDS,
|
|
119
127
|
appendSitianRecord,
|
|
120
128
|
appendSitianRecordBlock,
|
|
121
129
|
resolveSitianRecordPath,
|
|
122
130
|
resolveSitianRecordPathInLedger,
|
|
123
|
-
resolveSitianVolumeCategory
|
|
131
|
+
resolveSitianVolumeCategory,
|
|
132
|
+
sitianRunVolumeDirectory,
|
|
133
|
+
sitianVolumeDirectory,
|
|
134
|
+
sitianVolumeRecordsFile
|
|
124
135
|
};
|
package/dist/sitian-facade.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sessionFileOf } from "./role-run-placement.js";
|
|
1
2
|
import { appendSitianRecord } from "./sitian-appender.js";
|
|
2
3
|
export * from "./sitian-contracts.js";
|
|
3
4
|
export * from "./sitian-appender.js";
|
|
@@ -5,6 +6,16 @@ export * from "./sitian-reader.js";
|
|
|
5
6
|
function sitianReport(input) {
|
|
6
7
|
return appendSitianRecord(input);
|
|
7
8
|
}
|
|
9
|
+
function reportRunRecord(runDirectory, kind, payload, source) {
|
|
10
|
+
return appendSitianRecord({
|
|
11
|
+
level: "event",
|
|
12
|
+
kind,
|
|
13
|
+
sessionParent: sessionFileOf(runDirectory),
|
|
14
|
+
source,
|
|
15
|
+
payload
|
|
16
|
+
});
|
|
17
|
+
}
|
|
8
18
|
export {
|
|
19
|
+
reportRunRecord,
|
|
9
20
|
sitianReport
|
|
10
21
|
};
|
package/dist/sitian-reader.js
CHANGED
|
@@ -1,41 +1,56 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
1
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { isRecord, errorText } from "./unknown-value.js";
|
|
4
|
-
|
|
5
|
-
if (!
|
|
6
|
-
|
|
2
|
+
import { isRecord, errorText, isEnoent } from "./unknown-value.js";
|
|
3
|
+
function decodeSitianRecordLine(line, lineNumber) {
|
|
4
|
+
if (!line.trim()) return void 0;
|
|
5
|
+
try {
|
|
6
|
+
const parsed = JSON.parse(line);
|
|
7
|
+
if (isRecord(parsed)) {
|
|
8
|
+
return { ok: true, record: parsed };
|
|
9
|
+
}
|
|
10
|
+
const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
diagnostic: {
|
|
14
|
+
kind: "malformed",
|
|
15
|
+
line: lineNumber,
|
|
16
|
+
raw: line,
|
|
17
|
+
error: `expected JSON object, got ${typeDesc}`
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
diagnostic: {
|
|
24
|
+
kind: "malformed",
|
|
25
|
+
line: lineNumber,
|
|
26
|
+
raw: line,
|
|
27
|
+
error: errorText(error)
|
|
28
|
+
}
|
|
29
|
+
};
|
|
7
30
|
}
|
|
8
|
-
|
|
31
|
+
}
|
|
32
|
+
function parseSitianRecordText(text) {
|
|
9
33
|
const lines = text.split("\n");
|
|
10
34
|
const records = [];
|
|
11
35
|
const diagnostics = [];
|
|
12
36
|
for (let index = 0; index < lines.length; index += 1) {
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (isRecord(parsed)) {
|
|
18
|
-
records.push(parsed);
|
|
19
|
-
} else {
|
|
20
|
-
const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
21
|
-
diagnostics.push({
|
|
22
|
-
kind: "malformed",
|
|
23
|
-
line: index + 1,
|
|
24
|
-
raw: line,
|
|
25
|
-
error: `expected JSON object, got ${typeDesc}`
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
} catch (error) {
|
|
29
|
-
diagnostics.push({
|
|
30
|
-
kind: "malformed",
|
|
31
|
-
line: index + 1,
|
|
32
|
-
raw: line,
|
|
33
|
-
error: errorText(error)
|
|
34
|
-
});
|
|
35
|
-
}
|
|
37
|
+
const decoded = decodeSitianRecordLine(lines[index], index + 1);
|
|
38
|
+
if (decoded === void 0) continue;
|
|
39
|
+
if (decoded.ok) records.push(decoded.record);
|
|
40
|
+
else diagnostics.push(decoded.diagnostic);
|
|
36
41
|
}
|
|
37
42
|
return { records, diagnostics };
|
|
38
43
|
}
|
|
44
|
+
async function readSitianRecords(recordFile) {
|
|
45
|
+
try {
|
|
46
|
+
return parseSitianRecordText(await readFile(recordFile, "utf8"));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (isEnoent(error)) return { records: [], diagnostics: [] };
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
39
52
|
export {
|
|
53
|
+
decodeSitianRecordLine,
|
|
54
|
+
parseSitianRecordText,
|
|
40
55
|
readSitianRecords
|
|
41
56
|
};
|
package/dist/submission-gate.js
CHANGED
|
@@ -52,7 +52,7 @@ export function createDefaultGateOfficerSummon(options) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
* Book a typed pointer
|
|
55
|
+
* Book a typed pointer in the parent run's current.json officers section.
|
|
56
56
|
* Offline mocks without a real session leave no nested volume (lawful zero).
|
|
57
57
|
*/
|
|
58
58
|
function bookDirectOfficerPointer(context, officer, result, summoned, toolCallId) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sessionFileIn, sessionFileOf } from "./role-run-placement.js";
|
|
2
2
|
import { resolveActivationLedgerHome, tryHomeFromAkRolesPath, } from "./activation-ledger-topology.js";
|
|
3
3
|
import { courtAttemptIdFromHostContext, runDirectoryFromHostContext, } from "./host-contracts.js";
|
|
4
4
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
|
|
@@ -49,7 +49,7 @@ async function submissionRecordFile(cwd, runId, scope) {
|
|
|
49
49
|
const discoveredRun = await findRunDirectoryById(scope.home, runId);
|
|
50
50
|
if (discoveredRun === undefined)
|
|
51
51
|
return undefined;
|
|
52
|
-
sessionParent =
|
|
52
|
+
sessionParent = sessionFileOf(discoveredRun);
|
|
53
53
|
}
|
|
54
54
|
return resolveSitianRecordPathInLedger({
|
|
55
55
|
level: "event",
|
|
@@ -265,13 +265,13 @@ async function restoreState(cwd, runId, scope) {
|
|
|
265
265
|
function sessionParentFromHostContext(context) {
|
|
266
266
|
const runDirectory = runDirectoryFromHostContext(context);
|
|
267
267
|
if (runDirectory !== undefined)
|
|
268
|
-
return
|
|
268
|
+
return sessionFileOf(runDirectory);
|
|
269
269
|
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
270
270
|
if (typeof sessionFile === "string" && sessionFile.length > 0)
|
|
271
271
|
return sessionFile;
|
|
272
272
|
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
273
273
|
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
274
|
-
return
|
|
274
|
+
return sessionFileIn(sessionDir);
|
|
275
275
|
}
|
|
276
276
|
return undefined;
|
|
277
277
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseTicketNumber } from "./run-ticket-number.js";
|
|
1
2
|
import { isRecord } from "./unknown-value.js";
|
|
2
3
|
/**
|
|
3
4
|
* 起居录(ticket-provenance)typed 形状 —— ADR 0075 / ADR 0081 / #1090。
|
|
@@ -9,14 +10,7 @@ import { isRecord } from "./unknown-value.js";
|
|
|
9
10
|
/** Sitian kind for per-ticket court diary volumes. */
|
|
10
11
|
export const TICKET_PROVENANCE_KIND = "ticket-provenance";
|
|
11
12
|
function positiveInteger(value) {
|
|
12
|
-
|
|
13
|
-
return value;
|
|
14
|
-
if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
|
|
15
|
-
const parsed = Number(value);
|
|
16
|
-
if (Number.isSafeInteger(parsed))
|
|
17
|
-
return parsed;
|
|
18
|
-
}
|
|
19
|
-
return undefined;
|
|
13
|
+
return parseTicketNumber(value);
|
|
20
14
|
}
|
|
21
15
|
function projectBound(value) {
|
|
22
16
|
if (!isRecord(value))
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
* 一册=一个追加式 records.jsonl。每轮新投影经司天台 appender 追加为不可变提交;
|
|
4
4
|
* 纯追加、不回读历史去重、不折叠。
|
|
5
5
|
*/
|
|
6
|
-
import { readFile
|
|
7
|
-
import {
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
8
|
import { errnoCode, packageMachineHome, physicallyContainedIn, resolveActivationLedgerHome, } from "./activation-ledger-topology.js";
|
|
9
9
|
import { readLedgerSessionJsonlLines, } from "./ledger-session-read.js";
|
|
10
10
|
import { isSafePositiveTicketNumber } from "./run-ticket-number.js";
|
|
11
|
+
import { runDirectoryOfSessionFile, sessionFileOf } from "./role-run-placement.js";
|
|
12
|
+
import { RUN_HISTORY_FILE } from "./run-dossier-files.js";
|
|
11
13
|
import { adaptSessionDialogue, nativeEventId } from "./session-dialogue.js";
|
|
12
14
|
import { appendSitianRecordBlock, appendSitianRecord, resolveSitianRecordPath, } from "./sitian-facade.js";
|
|
15
|
+
import { decodeSitianRecordLine } from "./sitian-reader.js";
|
|
13
16
|
import { TICKET_PROVENANCE_KIND, } from "./ticket-provenance-contracts.js";
|
|
14
17
|
import { errorText } from "./unknown-value.js";
|
|
15
18
|
/**
|
|
@@ -48,8 +51,7 @@ function isLedgerRoleSessionFile(absolute, home) {
|
|
|
48
51
|
const ledgerHome = resolveActivationLedgerHome(machineHome);
|
|
49
52
|
if (!physicallyContainedIn(ledgerHome, absolute))
|
|
50
53
|
return false;
|
|
51
|
-
return (
|
|
52
|
-
basename(dirname(absolute)) === "session");
|
|
54
|
+
return sessionFileOf(runDirectoryOfSessionFile(absolute)) === absolute;
|
|
53
55
|
}
|
|
54
56
|
/** Real I/O seam gate: only host session stores or sitian role-run session.jsonl. */
|
|
55
57
|
function assertDialogueSessionSourcePath(path, home) {
|
|
@@ -87,18 +89,32 @@ export function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
|
87
89
|
}
|
|
88
90
|
/** Assign a run-owned unbound diary as its original block through Sitian. */
|
|
89
91
|
export async function rehomeUnboundTicketProvenance(runDirectory, ticketNumber, cwd, home) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
+
const ticketInput = ticketProvenanceRecordInput(ticketNumber, cwd, home);
|
|
93
|
+
// Live staging: ticket-provenance rows in the run's history.jsonl. Append-only
|
|
94
|
+
// history keeps the staging rows; the ticket-root diary is the home.
|
|
95
|
+
// Old run/records.jsonl is not a runtime input (#1161); only migration tools read it.
|
|
96
|
+
let history;
|
|
92
97
|
try {
|
|
93
|
-
|
|
98
|
+
history = await readFile(join(runDirectory, RUN_HISTORY_FILE), "utf8");
|
|
94
99
|
}
|
|
95
100
|
catch (error) {
|
|
96
101
|
if (errnoCode(error) === "ENOENT")
|
|
97
102
|
return;
|
|
98
103
|
throw error;
|
|
99
104
|
}
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
const block = history
|
|
106
|
+
.split("\n")
|
|
107
|
+
.flatMap((line, index) => {
|
|
108
|
+
const decoded = decodeSitianRecordLine(line, index + 1);
|
|
109
|
+
if (decoded === undefined || !decoded.ok)
|
|
110
|
+
return [];
|
|
111
|
+
if (decoded.record.kind !== TICKET_PROVENANCE_KIND)
|
|
112
|
+
return [];
|
|
113
|
+
return [`${line}\n`];
|
|
114
|
+
})
|
|
115
|
+
.join("");
|
|
116
|
+
if (block !== "")
|
|
117
|
+
appendSitianRecordBlock(ticketInput, block);
|
|
102
118
|
}
|
|
103
119
|
/**
|
|
104
120
|
* Resolve a bound endpoint against this round's session lines.
|
package/package.json
CHANGED
|
@@ -14,6 +14,8 @@ const entries = [
|
|
|
14
14
|
// navigator-session-contracts is a static import of the published attendance root
|
|
15
15
|
// (#590: published non-bundle graph must stay closed under its own relative edges).
|
|
16
16
|
"navigator-public-session",
|
|
17
|
+
// Static import of navigator-public-session (sitianReportSafe).
|
|
18
|
+
"host-session-record",
|
|
17
19
|
"navigator-session-contracts",
|
|
18
20
|
// Static import of navigator-attendance and navigator-public-session.
|
|
19
21
|
"navigator-work-base",
|
|
@@ -61,7 +61,7 @@ export function createProductionAcpRoleTurnHost(options: ProductionAcpHostOption
|
|
|
61
61
|
|
|
62
62
|
return createComposedAcpRoleTurnHost({
|
|
63
63
|
hostName,
|
|
64
|
-
sessionIdentity: createSessionIdentityAuthority(principalAuthority,
|
|
64
|
+
sessionIdentity: createSessionIdentityAuthority(principalAuthority, hostName),
|
|
65
65
|
modelPassing: description.modelPassing,
|
|
66
66
|
...(description.setModelId === undefined ? {} : { setModelId: description.setModelId }),
|
|
67
67
|
roleRuntimeDependencies: createRoleRuntimeDependencies(packageRoot),
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
1
|
import { spawn } from "node:child_process";
|
|
3
2
|
import { createInterface } from "node:readline";
|
|
4
3
|
|
|
5
4
|
import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
5
|
+
import { sessionDirectoryOf } from "../role-run-placement.ts";
|
|
6
6
|
import {
|
|
7
7
|
createSerializedRoleTurnHost,
|
|
8
8
|
driveExternalRoleTurnRounds,
|
|
9
9
|
disposeExternalRoleTurn,
|
|
10
10
|
externalHostFailure as failure,
|
|
11
11
|
raceAgainstHostAbort,
|
|
12
|
+
recordTurnDelivery,
|
|
12
13
|
} from "../external-host-turn-loop.ts";
|
|
13
14
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
14
15
|
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.ts";
|
|
@@ -372,6 +373,14 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
372
373
|
roundLimitName: "AcpRoundLimit",
|
|
373
374
|
currentSessionId: () => sessionId,
|
|
374
375
|
async runRound({ prompt, abortSignal }) {
|
|
376
|
+
// One history row per actual session/prompt start (催交回合各算一次).
|
|
377
|
+
// MCP advertises tool.parameters; prepared.jsonSchema is the headless
|
|
378
|
+
// draft-07 stamp of that clone — record the delivered parameters face.
|
|
379
|
+
const { $schema: _headlessStamp, ...deliveredSchema } = prepared.jsonSchema;
|
|
380
|
+
await recordTurnDelivery(request.runDirectory, {
|
|
381
|
+
systemPrompt: systemPromptOverride,
|
|
382
|
+
outputSchema: deliveredSchema,
|
|
383
|
+
}, "acp-host");
|
|
375
384
|
let result: Readonly<Record<string, unknown>>;
|
|
376
385
|
// Open the prose gate only for this prompt round; clear any stale chunks first.
|
|
377
386
|
agentProseChunks.length = 0;
|
|
@@ -496,10 +505,8 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
496
505
|
host: config.hostName,
|
|
497
506
|
sessionId,
|
|
498
507
|
cwd: request.cwd,
|
|
499
|
-
sessionDirectory:
|
|
508
|
+
sessionDirectory: sessionDirectoryOf(request.runDirectory),
|
|
500
509
|
sessionParent,
|
|
501
|
-
continuation: request.continuation,
|
|
502
|
-
...(request.model !== undefined ? { model: request.model } : {}),
|
|
503
510
|
...(request.home !== undefined ? { home: request.home } : {}),
|
|
504
511
|
});
|
|
505
512
|
} catch (error) {
|