@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
|
@@ -25,22 +25,9 @@ export const LEGACY_REVIEW_OUTPUT_ROLES = new Map<string, string>([
|
|
|
25
25
|
["ak_auditor_output", "auditor"],
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Success-face fields that still differ by seat. Presence and omission match
|
|
30
|
-
* the former per-seat publishers. Key order is not part of the contract.
|
|
31
|
-
*/
|
|
32
|
-
export type PackagedArtifactLeaf = {
|
|
33
|
-
readonly key: string;
|
|
34
|
-
readonly from?: string;
|
|
35
|
-
readonly omitUndefined?: true;
|
|
36
|
-
readonly copyArray?: true;
|
|
37
|
-
readonly callerProvenance?: true;
|
|
38
|
-
};
|
|
39
|
-
|
|
28
|
+
/** Success-terminal facts that still differ by seat. */
|
|
40
29
|
export type PackagedArtifactFace = {
|
|
41
30
|
readonly reportPhase?: true;
|
|
42
|
-
readonly evidenceRole?: true;
|
|
43
|
-
readonly leaves: readonly PackagedArtifactLeaf[];
|
|
44
31
|
readonly doctorReportFacts?: true;
|
|
45
32
|
};
|
|
46
33
|
|
|
@@ -126,13 +113,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
126
113
|
settlement: "sealed",
|
|
127
114
|
artifactFace: {
|
|
128
115
|
reportPhase: true,
|
|
129
|
-
evidenceRole: true,
|
|
130
|
-
leaves: [
|
|
131
|
-
{ key: "phase" },
|
|
132
|
-
{ key: "packetPath" },
|
|
133
|
-
{ key: "prerequisitesPath", omitUndefined: true },
|
|
134
|
-
{ key: "prerequisites" },
|
|
135
|
-
],
|
|
136
116
|
},
|
|
137
117
|
boardPlacement: "marshal",
|
|
138
118
|
activationFlags: [
|
|
@@ -164,11 +144,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
164
144
|
settlement: "sealed",
|
|
165
145
|
artifactFace: {
|
|
166
146
|
reportPhase: true,
|
|
167
|
-
evidenceRole: true,
|
|
168
|
-
leaves: [
|
|
169
|
-
{ key: "phase" },
|
|
170
|
-
{ key: "taskPath" },
|
|
171
|
-
],
|
|
172
147
|
},
|
|
173
148
|
activationFlags: [
|
|
174
149
|
{ field: "taskPath", flag: "ak-coder-task", binds: "input" },
|
|
@@ -203,13 +178,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
203
178
|
/** Publish only an accepted ledger outcome; audit escalation stays unsettled here. */
|
|
204
179
|
sealedAcceptedOnly: true,
|
|
205
180
|
artifactFace: {
|
|
206
|
-
evidenceRole: true,
|
|
207
|
-
leaves: [
|
|
208
|
-
{ key: "baseRevision" },
|
|
209
|
-
{ key: "lens" },
|
|
210
|
-
{ key: "authorityRefs", copyArray: true },
|
|
211
|
-
{ key: "callerProvenance", callerProvenance: true },
|
|
212
|
-
],
|
|
213
181
|
},
|
|
214
182
|
/** Frozen base/lens/authority become the initial prompt; instruction follows. */
|
|
215
183
|
transportPrompt: "skill-args",
|
|
@@ -242,12 +210,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
242
210
|
outputTool: COLLECTOR_OUTPUT_TOOL,
|
|
243
211
|
settlement: "sealed",
|
|
244
212
|
artifactFace: {
|
|
245
|
-
evidenceRole: true,
|
|
246
|
-
leaves: [
|
|
247
|
-
{ key: "prNumber", omitUndefined: true },
|
|
248
|
-
{ key: "repository", from: "repository.canonical" },
|
|
249
|
-
{ key: "manifestDigest" },
|
|
250
|
-
],
|
|
251
213
|
},
|
|
252
214
|
boardPlacement: "collector",
|
|
253
215
|
/** Taishi acceptance is a typed groups array, not a status leaf. */
|
|
@@ -277,12 +239,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
277
239
|
auditTool: DOCTOR_AUDIT_TOOL_NAME,
|
|
278
240
|
settlement: "sealed",
|
|
279
241
|
artifactFace: {
|
|
280
|
-
evidenceRole: true,
|
|
281
|
-
leaves: [
|
|
282
|
-
{ key: "issueNumber" },
|
|
283
|
-
{ key: "caseRunsPath" },
|
|
284
|
-
{ key: "caseIdentity" },
|
|
285
|
-
],
|
|
286
242
|
doctorReportFacts: true,
|
|
287
243
|
},
|
|
288
244
|
/** Navigator subject is the doctor case, not the case-path file bytes. */
|
|
@@ -309,11 +265,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
309
265
|
outputTool: MERGER_OUTPUT_TOOL_NAME,
|
|
310
266
|
settlement: "sealed",
|
|
311
267
|
artifactFace: {
|
|
312
|
-
evidenceRole: true,
|
|
313
|
-
leaves: [
|
|
314
|
-
{ key: "mergerInputPath" },
|
|
315
|
-
{ key: "derived" },
|
|
316
|
-
],
|
|
317
268
|
},
|
|
318
269
|
activationFlags: [
|
|
319
270
|
{ field: "inputPath", from: "mergerInputPath", flag: "ak-merger-input", binds: "input" },
|
|
@@ -334,8 +285,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
334
285
|
sourceRunStored: "raw",
|
|
335
286
|
argvResult: "source-run",
|
|
336
287
|
sameParent: "source-locator",
|
|
337
|
-
/** Infrastructure failure at this stage has no accepted gate cycle to project. */
|
|
338
|
-
skipGateOnInfrastructureStage: true,
|
|
339
288
|
/** 符宝郎 is a review officer and inherits the gatekeeper model. */
|
|
340
289
|
reviewOfficer: true,
|
|
341
290
|
provinceConfig: true,
|
|
@@ -443,8 +392,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
443
392
|
sameParent: "gate-pointer",
|
|
444
393
|
/** Resume restores an optional source-run path onto the admitted face. */
|
|
445
394
|
resumeSourcePath: true,
|
|
446
|
-
/** Infrastructure failure at this stage has no accepted gate cycle to project. */
|
|
447
|
-
skipGateOnInfrastructureStage: true,
|
|
448
395
|
/** 台院 is a review officer and inherits the gatekeeper model. */
|
|
449
396
|
reviewOfficer: true,
|
|
450
397
|
provinceConfig: true,
|
|
@@ -476,8 +423,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
476
423
|
presentSettled: "always",
|
|
477
424
|
summonResume: true,
|
|
478
425
|
admission: "instruction",
|
|
479
|
-
/** Infrastructure failure at this stage has no accepted gate cycle to project. */
|
|
480
|
-
skipGateOnInfrastructureStage: true,
|
|
481
426
|
sameParent: "none",
|
|
482
427
|
/** Province model root. Officers name this seat via modelInheritsFrom. */
|
|
483
428
|
provinceConfig: true,
|
|
@@ -725,15 +670,6 @@ export function packagedAuditToolName(role: string): string | undefined {
|
|
|
725
670
|
return record.auditTool;
|
|
726
671
|
}
|
|
727
672
|
|
|
728
|
-
/** Infrastructure-failure stage that must not be re-read as an accepted gate cycle. */
|
|
729
|
-
export function packagedSkipsGateOnInfrastructureStage(stage: unknown): boolean {
|
|
730
|
-
return typeof stage === "string" && PUBLIC_ROLE_RECORDS.some((record) =>
|
|
731
|
-
"skipGateOnInfrastructureStage" in record
|
|
732
|
-
&& record.skipGateOnInfrastructureStage === true
|
|
733
|
-
&& record.role === stage
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
673
|
/** Durable officer entry whose name is the seat role. */
|
|
738
674
|
export function packagedDurableOfficerEntry(officer: unknown): boolean {
|
|
739
675
|
return typeof officer === "string" && PUBLIC_ROLE_RECORDS.some((record) =>
|
package/src/pi/adapter.ts
CHANGED
|
@@ -18,6 +18,10 @@ import type {
|
|
|
18
18
|
import { createOAuthKeepalive, type OAuthKeepaliveOptions } from "../oauth-keepalive.ts";
|
|
19
19
|
import { createRoleRuntimeExtension, type RoleRuntimeDependencies } from "../role-runtime.ts";
|
|
20
20
|
import { renderAgentStartMaterials } from "../agent-start-materials.ts";
|
|
21
|
+
import { runDirectoryFromHostContext } from "../host-contracts.ts";
|
|
22
|
+
import { sitianReportSafe } from "../host-session-record.ts";
|
|
23
|
+
import { isTerminatingToolName } from "../package-contracts/terminating-tools.ts";
|
|
24
|
+
import { sessionFileOf } from "../role-run-placement.ts";
|
|
21
25
|
import { projectCorrectableExecuteRejection } from "../submission-correctable-error.ts";
|
|
22
26
|
import {
|
|
23
27
|
WorkerCommitReminderError,
|
|
@@ -148,6 +152,12 @@ export function createPiRoleHostAdapter(
|
|
|
148
152
|
options: { transcriptFromContext?: (context: ExtensionContext) => string; oauthKeepalive?: OAuthKeepaliveOptions } = {},
|
|
149
153
|
): PiRoleHostAdapter {
|
|
150
154
|
const keepalive = createOAuthKeepalive(options.oauthKeepalive);
|
|
155
|
+
// Pi chains the before_agent_start handlers, each seeing the prompt the one before left; the
|
|
156
|
+
// prompt Pi sends is the one the last registered handler leaves. Only that handler's result is
|
|
157
|
+
// what a start was given, so only it records.
|
|
158
|
+
let beforeAgentStartHandlers = 0;
|
|
159
|
+
// Parameters of registered terminating tools — the schema Pi was handed.
|
|
160
|
+
const registeredToolParameters = new Map<string, unknown>();
|
|
151
161
|
// Decode transport exactly once before Pi's native handler chain. Pi retains
|
|
152
162
|
// its own transform/image propagation and per-handler error isolation.
|
|
153
163
|
pi.on("input", (value) => {
|
|
@@ -160,10 +170,13 @@ export function createPiRoleHostAdapter(
|
|
|
160
170
|
},
|
|
161
171
|
registerFlag: (name, definition) => pi.registerFlag(name, definition),
|
|
162
172
|
getFlag: (name) => pi.getFlag(name),
|
|
163
|
-
registerTool: (tool) =>
|
|
164
|
-
tool,
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
registerTool: (tool) => {
|
|
174
|
+
registeredToolParameters.set(tool.name, tool.parameters);
|
|
175
|
+
return pi.registerTool(toPiToolDefinition(
|
|
176
|
+
tool,
|
|
177
|
+
(context) => projectPiContext(context, options.transcriptFromContext),
|
|
178
|
+
));
|
|
179
|
+
},
|
|
167
180
|
getAllTools: () => pi.getAllTools().map(({ name, sourceInfo }) => ({
|
|
168
181
|
name,
|
|
169
182
|
...(sourceInfo?.path === undefined ? {} : { sourceInfo: { path: sourceInfo.path } }),
|
|
@@ -174,10 +187,40 @@ export function createPiRoleHostAdapter(
|
|
|
174
187
|
const context = (value: ExtensionContext) => projectPiContext(value, options.transcriptFromContext);
|
|
175
188
|
if (registration[0] === "before_agent_start") {
|
|
176
189
|
const [, handler] = registration;
|
|
190
|
+
const position = beforeAgentStartHandlers;
|
|
191
|
+
beforeAgentStartHandlers += 1;
|
|
177
192
|
pi.on("before_agent_start", (value, ctx) => {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
const projected = context(ctx);
|
|
194
|
+
const fold = (settled: unknown) => {
|
|
195
|
+
const folded = foldBeforeAgentStartReturn(settled, value.systemPrompt);
|
|
196
|
+
const runDirectory = runDirectoryFromHostContext(projected);
|
|
197
|
+
if (runDirectory !== undefined && position === beforeAgentStartHandlers - 1) {
|
|
198
|
+
const delivered = (folded as { systemPrompt?: string } | undefined)?.systemPrompt ?? value.systemPrompt;
|
|
199
|
+
// Record the same parameters object Pi was handed — not a
|
|
200
|
+
// headless draft-07 projection of another host's transport.
|
|
201
|
+
let outputSchema: unknown;
|
|
202
|
+
for (const [name, parameters] of registeredToolParameters) {
|
|
203
|
+
if (!isTerminatingToolName(name)) continue;
|
|
204
|
+
outputSchema = parameters;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
// History write failures do not stop a run (#833): declared, and Pi starts.
|
|
208
|
+
sitianReportSafe({
|
|
209
|
+
level: "event",
|
|
210
|
+
kind: "turn-delivery",
|
|
211
|
+
sessionParent: sessionFileOf(runDirectory),
|
|
212
|
+
source: "pi-adapter",
|
|
213
|
+
payload: {
|
|
214
|
+
systemPrompt: delivered,
|
|
215
|
+
...(outputSchema === undefined ? {} : { outputSchema }),
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return folded;
|
|
220
|
+
};
|
|
221
|
+
const result = handler({ prompt: value.prompt, systemPrompt: value.systemPrompt, systemPromptOptions: value.systemPromptOptions }, projected);
|
|
222
|
+
if (result instanceof Promise) return result.then(fold);
|
|
223
|
+
return fold(result);
|
|
181
224
|
});
|
|
182
225
|
} else if (registration[0] === "input") {
|
|
183
226
|
const [, handler] = registration;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
2
|
DurablePrincipal,
|
|
5
3
|
DurablePrincipalAuthority,
|
|
@@ -8,7 +6,7 @@ import type {
|
|
|
8
6
|
} from "../host-contracts.ts";
|
|
9
7
|
import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
|
|
10
8
|
import { resolveActivationLedgerHome } from "../activation-ledger-topology.ts";
|
|
11
|
-
import { roleRunPlacement } from "../role-run-placement.ts";
|
|
9
|
+
import { roleRunPlacement, sessionFileIn } from "../role-run-placement.ts";
|
|
12
10
|
|
|
13
11
|
type PiDurablePrincipal = DurablePrincipal & {
|
|
14
12
|
readonly sessionDirectory: string;
|
|
@@ -69,7 +67,7 @@ export const piDurablePrincipalAuthority: DurablePrincipalAuthority = {
|
|
|
69
67
|
sessionFile:
|
|
70
68
|
typeof record.sessionFile === "string" && record.sessionFile.trim() !== ""
|
|
71
69
|
? record.sessionFile
|
|
72
|
-
:
|
|
70
|
+
: sessionFileIn(record.sessionDirectory),
|
|
73
71
|
};
|
|
74
72
|
},
|
|
75
73
|
};
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { execFile, spawn } from "node:child_process";
|
|
7
7
|
import { constants } from "node:fs";
|
|
8
|
-
import { access, appendFile,
|
|
8
|
+
import { access, appendFile, realpath } from "node:fs/promises";
|
|
9
9
|
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
10
10
|
import { platform } from "node:process";
|
|
11
11
|
import { promisify } from "node:util";
|
|
@@ -25,9 +25,13 @@ import { ExplicitInternalActivationError } from "../host-contracts.ts";
|
|
|
25
25
|
import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.ts";
|
|
26
26
|
import { projectActivationFlags } from "../role-activation-flags.ts";
|
|
27
27
|
import { encodeUserDialogueStdin } from "../user-dialogue-stdin.ts";
|
|
28
|
+
import { readLedgerSessionJsonlLines, readStrictPiSessionJsonl } from "../ledger-session-read.ts";
|
|
29
|
+
import { copyAndRecordHostDossier } from "../host-session-record.ts";
|
|
28
30
|
import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.ts";
|
|
29
31
|
import { describeErrorIdentity } from "../public-cli/run-lifecycle.ts";
|
|
30
32
|
import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.ts";
|
|
33
|
+
import { createSessionIdentityAuthority } from "../session-identity.ts";
|
|
34
|
+
import { sitianReport } from "../sitian-facade.ts";
|
|
31
35
|
|
|
32
36
|
/** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
|
|
33
37
|
const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
|
|
@@ -449,22 +453,96 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
449
453
|
);
|
|
450
454
|
}
|
|
451
455
|
const timeoutMs = request.timeoutMs ?? config.timeoutMs;
|
|
452
|
-
|
|
456
|
+
const result = await spawnRunner(args, {
|
|
453
457
|
cwd: request.cwd,
|
|
454
458
|
env,
|
|
455
459
|
stdin,
|
|
456
460
|
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
457
461
|
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
458
462
|
});
|
|
463
|
+
// Deliver already-written Pi host facts. Pi lands at session/session.jsonl;
|
|
464
|
+
// bind projects host.sessions; copy records the landing. Host adapters do
|
|
465
|
+
// not write current.json — public bind/settlement seams own that render.
|
|
466
|
+
try {
|
|
467
|
+
const { sessionFile, sessionDirectory } = config.principalAuthority.decode(request.principal);
|
|
468
|
+
const sessionId = await readPiSessionHeaderId(sessionFile);
|
|
469
|
+
if (sessionId !== undefined) {
|
|
470
|
+
await createSessionIdentityAuthority(config.principalAuthority, "pi").bind(
|
|
471
|
+
request.principal,
|
|
472
|
+
sessionId,
|
|
473
|
+
);
|
|
474
|
+
copyAndRecordHostDossier({
|
|
475
|
+
host: "pi",
|
|
476
|
+
sessionId,
|
|
477
|
+
cwd: request.cwd,
|
|
478
|
+
sessionDirectory,
|
|
479
|
+
sessionParent: sessionFile,
|
|
480
|
+
...(request.home !== undefined ? { home: request.home } : {}),
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
} catch (error) {
|
|
484
|
+
await retainPackageFault({
|
|
485
|
+
runDirectory: request.runDirectory,
|
|
486
|
+
diagnostic: `pi host dossier record failed beside host terminal: ${describeErrorIdentity(error)}`,
|
|
487
|
+
error,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return result;
|
|
459
491
|
},
|
|
460
492
|
};
|
|
461
493
|
}
|
|
462
494
|
|
|
463
|
-
|
|
495
|
+
/**
|
|
496
|
+
* Session header id from the Pi volume the child (or fixture) already wrote.
|
|
497
|
+
* Reuses the ledger line kernel: a later malformed line must not erase a valid
|
|
498
|
+
* header already present (ADR 0086 originals are not validated whole-file).
|
|
499
|
+
*/
|
|
500
|
+
async function readPiSessionHeaderId(sessionFile: string): Promise<string | undefined> {
|
|
501
|
+
try {
|
|
502
|
+
for (const line of await readLedgerSessionJsonlLines(sessionFile)) {
|
|
503
|
+
const entry = line.row;
|
|
504
|
+
if (entry === undefined || entry.type !== "session") continue;
|
|
505
|
+
if (typeof entry.id === "string" && entry.id.trim() !== "") return entry.id;
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
509
|
+
throw error;
|
|
510
|
+
}
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Last non-session entry id. Session headers are not parents of custom lines. */
|
|
515
|
+
export function piSessionCustomParentId(
|
|
516
|
+
entries: readonly { id?: unknown; type?: unknown }[],
|
|
517
|
+
): string | null {
|
|
518
|
+
let parentId: string | null = null;
|
|
519
|
+
for (const entry of entries) {
|
|
520
|
+
if (typeof entry.id === "string" && entry.type !== "session") parentId = entry.id;
|
|
521
|
+
}
|
|
522
|
+
return parentId;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** One Pi custom JSONL line: type, customType, data, id, parentId, timestamp. */
|
|
526
|
+
export function formatPiSessionCustomEntry(input: {
|
|
527
|
+
readonly customType: string;
|
|
528
|
+
readonly data: unknown;
|
|
529
|
+
readonly parentId: string | null;
|
|
530
|
+
readonly timestamp: string;
|
|
531
|
+
}): string {
|
|
532
|
+
return `${JSON.stringify({
|
|
533
|
+
type: "custom",
|
|
534
|
+
customType: input.customType,
|
|
535
|
+
data: input.data,
|
|
536
|
+
id: randomUUID(),
|
|
537
|
+
parentId: input.parentId,
|
|
538
|
+
timestamp: input.timestamp,
|
|
539
|
+
})}\n`;
|
|
540
|
+
}
|
|
464
541
|
|
|
465
542
|
/**
|
|
466
543
|
* Append one custom JSONL entry to the durable principal's session file.
|
|
467
544
|
* Pi session codec only — AK artifact O_EXCL retention stays in public-cli.
|
|
545
|
+
* Read is raw JSON.parse (SyntaxError propagates). Sitian full-traversal stays elsewhere.
|
|
468
546
|
*/
|
|
469
547
|
export async function appendPiSessionCustomEntry(
|
|
470
548
|
authority: DurablePrincipalAuthority,
|
|
@@ -473,21 +551,14 @@ export async function appendPiSessionCustomEntry(
|
|
|
473
551
|
data: unknown,
|
|
474
552
|
): Promise<void> {
|
|
475
553
|
const { sessionFile } = authority.decode(principal);
|
|
476
|
-
const
|
|
477
|
-
let parentId: string | null = null;
|
|
478
|
-
for (const line of text.trim().split("\n").filter(Boolean)) {
|
|
479
|
-
const entry = JSON.parse(line) as { id?: unknown; type?: unknown };
|
|
480
|
-
if (typeof entry.id === "string" && entry.type !== "session") parentId = entry.id;
|
|
481
|
-
}
|
|
554
|
+
const entries = await readStrictPiSessionJsonl(sessionFile) as { id?: unknown; type?: unknown }[];
|
|
482
555
|
const timestamp = new Date().toISOString();
|
|
483
|
-
const pointerLine =
|
|
484
|
-
type: "custom",
|
|
556
|
+
const pointerLine = formatPiSessionCustomEntry({
|
|
485
557
|
customType,
|
|
486
558
|
data,
|
|
487
|
-
|
|
488
|
-
parentId,
|
|
559
|
+
parentId: piSessionCustomParentId(entries),
|
|
489
560
|
timestamp,
|
|
490
|
-
})
|
|
561
|
+
});
|
|
491
562
|
await appendFile(sessionFile, pointerLine, "utf8");
|
|
492
563
|
sitianReport({
|
|
493
564
|
level: "event",
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
* path continues: same budget, same call-local count semantics. No failure-type
|
|
12
12
|
* classification — every thrown value is treated identically.
|
|
13
13
|
*/
|
|
14
|
-
import { constants as fsConstants } from "node:fs";
|
|
15
|
-
import { randomUUID } from "node:crypto";
|
|
16
|
-
import { open } from "node:fs/promises";
|
|
17
|
-
import { join } from "node:path";
|
|
18
14
|
import type {
|
|
19
15
|
DurablePrincipal,
|
|
20
16
|
DurablePrincipalAuthority,
|
|
@@ -33,14 +29,13 @@ import { processCancelSignalName } from "./process-cancel.ts";
|
|
|
33
29
|
import { isLawfulTypedTerminalOutcome, formatTerminalResult, type TerminalArtifactRef, type TerminalResult, type TerminalRoleName } from "./terminal.ts";
|
|
34
30
|
import {
|
|
35
31
|
attachRecordedSubmissions,
|
|
36
|
-
ensureRealArtifactsDirectory,
|
|
37
32
|
presentFailureTerminal,
|
|
38
33
|
retainPackageFault,
|
|
39
34
|
} from "./settlement.ts";
|
|
40
35
|
|
|
41
|
-
export { ensureRealArtifactsDirectory };
|
|
42
36
|
import type { CliIo } from "./cli-io.ts";
|
|
43
37
|
import { serializeThrownValue } from "../serialize-thrown-value.ts";
|
|
38
|
+
import { reportRunRecord } from "../sitian-facade.ts";
|
|
44
39
|
|
|
45
40
|
import { errorText } from "../unknown-value.ts";
|
|
46
41
|
|
|
@@ -155,41 +150,11 @@ function jsonSafeReplacer(): (key: string, value: unknown) => unknown {
|
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
/**
|
|
158
|
-
* Retain one throwing dispatch attempt's complete exception as
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* attempt can never overwrite an earlier attempt's file.
|
|
153
|
+
* Retain one throwing dispatch attempt's complete exception as a line in the
|
|
154
|
+
* run's log.jsonl, then leave an addressable pointer in the session principal
|
|
155
|
+
* (custom entry). The log only appends (史必追加, #419): a later attempt can
|
|
156
|
+
* never overwrite an earlier attempt's record.
|
|
163
157
|
*/
|
|
164
|
-
/**
|
|
165
|
-
* Hardened create-once JSON write shared by every durable artifact this loop
|
|
166
|
-
* retains directly (dispatch-error dumps, lawful-persist-failure error/
|
|
167
|
-
* evidence records): O_EXCL (fail loud on a colliding name, never overwrite)
|
|
168
|
-
* + O_NOFOLLOW where the platform provides it (a planted symlink is never
|
|
169
|
-
* followed) — mirrors settlement.ts's own hardened artifact writers.
|
|
170
|
-
*/
|
|
171
|
-
export async function writeHardenedArtifactFile(
|
|
172
|
-
artifactsDir: string,
|
|
173
|
-
namePrefix: string,
|
|
174
|
-
payload: Record<string, unknown>,
|
|
175
|
-
): Promise<string> {
|
|
176
|
-
const filePath = join(artifactsDir, `${namePrefix}-${randomUUID()}.json`);
|
|
177
|
-
const body = `${JSON.stringify(payload, jsonSafeReplacer(), 2)}\n`;
|
|
178
|
-
const noFollowFlag =
|
|
179
|
-
typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
|
|
180
|
-
const handle = await open(
|
|
181
|
-
filePath,
|
|
182
|
-
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag,
|
|
183
|
-
0o600,
|
|
184
|
-
);
|
|
185
|
-
try {
|
|
186
|
-
await handle.writeFile(body, "utf8");
|
|
187
|
-
} finally {
|
|
188
|
-
await handle.close();
|
|
189
|
-
}
|
|
190
|
-
return filePath;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
158
|
async function retainDispatchError(
|
|
194
159
|
admitted: { runDirectory: string; principal: DurablePrincipal },
|
|
195
160
|
principalAuthority: DurablePrincipalAuthority,
|
|
@@ -197,14 +162,13 @@ async function retainDispatchError(
|
|
|
197
162
|
attempt: number,
|
|
198
163
|
error: unknown,
|
|
199
164
|
): Promise<{ file: string; pointerError?: unknown }> {
|
|
200
|
-
const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
|
|
201
165
|
// Whole-object dump: everything the thrown value carries, nothing picked.
|
|
202
|
-
const filePath =
|
|
166
|
+
const filePath = reportRunRecord(admitted.runDirectory, "dispatch-exception", JSON.parse(JSON.stringify({
|
|
203
167
|
version: 1,
|
|
204
168
|
attempt,
|
|
205
169
|
recordedAt: new Date().toISOString(),
|
|
206
170
|
error: serializeThrownValue(error),
|
|
207
|
-
});
|
|
171
|
+
}, jsonSafeReplacer())) as Record<string, unknown>, "auto-resume").recordFile;
|
|
208
172
|
// Addressable pointer in the dossier (卷宗): Pi session custom-entry codec
|
|
209
173
|
// (appendPiSessionCustomEntry). Lease still owned here with run-writer.
|
|
210
174
|
let pointerLease: RunWriterLease;
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
type RunDirectoryRelocation,
|
|
57
57
|
} from "./invocation.ts";
|
|
58
58
|
import {
|
|
59
|
+
bindDiaristAssertedTicketNumber,
|
|
59
60
|
presentControlledFailure,
|
|
60
61
|
packageFaultNoteFor,
|
|
61
62
|
prepareSummonsResumeMaterials,
|
|
@@ -83,7 +84,7 @@ import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
|
|
|
83
84
|
import {
|
|
84
85
|
presentStructuralRejection,
|
|
85
86
|
readBoundSessionEntries,
|
|
86
|
-
|
|
87
|
+
attachPostAuditCountersignFact,
|
|
87
88
|
trySettlePublicSeat,
|
|
88
89
|
type SettlementCourtScope,
|
|
89
90
|
} from "./settlement.ts";
|
|
@@ -321,6 +322,8 @@ async function bindAndRelocateDiarist(
|
|
|
321
322
|
authority: DurablePrincipalAuthority,
|
|
322
323
|
lease?: RunWriterLease,
|
|
323
324
|
): Promise<RunDirectoryRelocation | undefined> {
|
|
325
|
+
// The diarist's asserted ticketNumber binds here, in the public call's process.
|
|
326
|
+
await bindDiaristAssertedTicketNumber(admitted);
|
|
324
327
|
const boardTicket = await readBoardTicketNumber(admitted.runDirectory);
|
|
325
328
|
if (boardTicket === undefined) return undefined;
|
|
326
329
|
if (admitted.ticketNumber === undefined) {
|
|
@@ -1096,7 +1099,11 @@ export async function continueParentAfterChild(
|
|
|
1096
1099
|
&& heldUnreadableTerminal(stopped.terminal)
|
|
1097
1100
|
&& stopped.terminal !== undefined
|
|
1098
1101
|
) {
|
|
1099
|
-
const parentTerminal = await trySettlePublicSeat(
|
|
1102
|
+
const parentTerminal = await trySettlePublicSeat(
|
|
1103
|
+
admitted,
|
|
1104
|
+
env.principalAuthority,
|
|
1105
|
+
{ ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) },
|
|
1106
|
+
);
|
|
1100
1107
|
if (
|
|
1101
1108
|
parentTerminal?.roleOutcome.kind === "accepted"
|
|
1102
1109
|
|| parentTerminal?.roleOutcome.kind === "audit_escalation"
|
|
@@ -1115,7 +1122,7 @@ export async function continueParentAfterChild(
|
|
|
1115
1122
|
const terminal = await trySettlePublicSeat(
|
|
1116
1123
|
admitted,
|
|
1117
1124
|
env.principalAuthority,
|
|
1118
|
-
packageFaultScope(admitted, env, io),
|
|
1125
|
+
{ ...await readCurrentCourt(admitted.runDirectory), ...packageFaultScope(admitted, env, io) },
|
|
1119
1126
|
);
|
|
1120
1127
|
if (terminal?.roleOutcome.kind !== "accepted") throw new Error("pending submission is not recorded");
|
|
1121
1128
|
return auditSubmittedRole({ exitCode: 0, admitted, terminal }, env, io, resolved.admitted, latestQueuePayload(resolved.terminal));
|
|
@@ -1399,10 +1406,10 @@ async function auditSubmittedRole(
|
|
|
1399
1406
|
}
|
|
1400
1407
|
}
|
|
1401
1408
|
// The turn already settled this court (payloads, usage, autoResumeCount, history).
|
|
1402
|
-
// Audit only adds
|
|
1403
|
-
//
|
|
1409
|
+
// Audit only adds, for 中书省, the officer fact. A second settle would
|
|
1410
|
+
// republish the same attempt.
|
|
1404
1411
|
if (turn.terminal === undefined) throw new Error(`audited ${admitted.role} submission did not settle`);
|
|
1405
|
-
const terminal = await
|
|
1412
|
+
const terminal = await attachPostAuditCountersignFact(
|
|
1406
1413
|
admitted,
|
|
1407
1414
|
env.principalAuthority,
|
|
1408
1415
|
turn.terminal,
|