@akagilnc/pi-workflow-roles 0.1.3572 → 0.1.3621

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.
Files changed (129) hide show
  1. package/dist/acp-host/production-host.js +20239 -10556
  2. package/dist/analyst-gate-cycles-read.js +387 -0
  3. package/dist/archivist-record-entry.js +22 -1
  4. package/dist/audit-escalation.js +6 -0
  5. package/dist/auditor-soul.js +74 -0
  6. package/dist/collector-config.js +86 -0
  7. package/dist/collector-evidence.js +316 -0
  8. package/dist/collector-github.js +527 -0
  9. package/dist/collector-ledger.js +853 -0
  10. package/dist/collector-tool-schemas.js +33 -0
  11. package/dist/compliance-transport.js +104 -52
  12. package/dist/diarist-mechanical.js +411 -0
  13. package/dist/diarist-ticket-resolution.js +177 -0
  14. package/dist/diarist.js +311 -0
  15. package/dist/doctor-auditor.js +25 -0
  16. package/dist/doctor-contracts.js +2 -0
  17. package/dist/doctor-evidence.js +142 -0
  18. package/dist/gatekeeper-role.js +104 -81
  19. package/dist/host-transition-prior-native.js +78 -0
  20. package/dist/institutional-resolution.js +1 -95
  21. package/dist/judge-auditor.js +26 -0
  22. package/dist/ledger-session-read.js +227 -0
  23. package/dist/merger-git-state.js +78 -0
  24. package/dist/navigator-attendance.js +15 -7
  25. package/dist/navigator-public-session.js +170 -0
  26. package/dist/navigator-session-contracts.js +36 -40
  27. package/dist/notary-source-run.js +122 -0
  28. package/dist/package-contracts/auditor-output.js +66 -0
  29. package/dist/package-contracts/evidence-child-output.js +34 -0
  30. package/dist/package-contracts/judge-output.js +2 -0
  31. package/dist/package-contracts/terminating-tools.js +25 -2
  32. package/dist/package-resources/method-skill.js +254 -0
  33. package/dist/packaged-role-registry.js +36 -0
  34. package/dist/pi/durable-principal.js +61 -0
  35. package/dist/pi/in-process-session.js +25 -6
  36. package/dist/pi/known-failure.js +52 -0
  37. package/dist/pi/role-turn-host.js +430 -0
  38. package/dist/public-cli/auto-resume.js +414 -0
  39. package/dist/public-cli/cli-errors.js +8 -0
  40. package/dist/public-cli/cli-io.js +1 -0
  41. package/dist/public-cli/command-renderer.js +5 -0
  42. package/dist/public-cli/doctor-run.js +84 -0
  43. package/dist/public-cli/inspector-run.js +136 -0
  44. package/dist/public-cli/instruction-seat-run.js +256 -0
  45. package/dist/public-cli/invocation.js +2225 -0
  46. package/dist/public-cli/judge-run.js +118 -0
  47. package/dist/public-cli/load-production-acp-host.js +40 -0
  48. package/dist/public-cli/main.js +1140 -652
  49. package/dist/public-cli/notary-run.js +161 -0
  50. package/dist/public-cli/option-definitions.js +1192 -0
  51. package/dist/public-cli/post-admission.js +667 -0
  52. package/dist/public-cli/public-run-credentials.js +49 -0
  53. package/dist/public-cli/registry.js +4 -1
  54. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  55. package/dist/public-cli/run-lifecycle.js +1344 -0
  56. package/dist/public-cli/seat-ticket-binding.js +113 -0
  57. package/dist/public-cli/settlement.js +3441 -0
  58. package/dist/public-cli/terminal.js +135 -0
  59. package/dist/public-cli/turn-request.js +25 -0
  60. package/dist/public-role-summons.js +301 -0
  61. package/dist/receipt-delivery-policy.js +10 -0
  62. package/dist/reviewer-child-executor.js +80 -8
  63. package/dist/reviewer-execution-ledger.js +2 -1
  64. package/dist/run-terminal-artifacts.js +195 -0
  65. package/dist/run-ticket-number.js +40 -0
  66. package/dist/session-assistant-usage.js +107 -0
  67. package/dist/shape-unreadable-failure.js +34 -0
  68. package/dist/submission-errors.js +3 -0
  69. package/dist/submission-ledger.js +388 -0
  70. package/dist/ticket-provenance-contracts.js +115 -0
  71. package/dist/ticket-provenance.js +340 -0
  72. package/extensions/role-runtime.ts +7 -0
  73. package/package.json +1 -1
  74. package/scripts/build-package.mjs +2 -1
  75. package/souls/doctor-auditor.md +1 -0
  76. package/souls/evidence-child.md +1 -0
  77. package/src/acp-host/production-host.ts +3 -0
  78. package/src/acp-host/role-envelope.ts +2 -2
  79. package/src/acp-host/role-turn-host.ts +9 -1
  80. package/src/analyst-gate-cycles-read.ts +37 -2
  81. package/src/archivist-record-entry.ts +45 -1
  82. package/src/audit-escalation.ts +17 -0
  83. package/src/auditor-role.ts +22 -0
  84. package/src/auditor-soul.ts +42 -0
  85. package/src/compliance-transport.ts +177 -66
  86. package/src/doctor-auditor.ts +10 -14
  87. package/src/doctor-contracts.ts +1 -0
  88. package/src/doctor-role.ts +2 -2
  89. package/src/evidence-child-role.ts +22 -0
  90. package/src/gatekeeper-pass-envelope.ts +109 -0
  91. package/src/gatekeeper-role.ts +145 -104
  92. package/src/host-contracts.ts +9 -1
  93. package/src/institutional-resolution.ts +8 -163
  94. package/src/judge-auditor.ts +10 -15
  95. package/src/judge-role.ts +8 -0
  96. package/src/navigator-attendance.ts +25 -9
  97. package/src/navigator-public-session.ts +217 -0
  98. package/src/navigator-session-contracts.ts +61 -54
  99. package/src/notary-source-run.ts +3 -1
  100. package/src/package-contracts/auditor-output.ts +82 -0
  101. package/src/package-contracts/evidence-child-output.ts +51 -0
  102. package/src/package-contracts/judge-output.ts +1 -0
  103. package/src/package-contracts/reviewer-output.ts +2 -2
  104. package/src/package-contracts/terminating-tools.ts +31 -1
  105. package/src/packaged-role-registry.ts +43 -0
  106. package/src/pi/adapter.ts +2 -1
  107. package/src/pi/in-process-session.ts +38 -12
  108. package/src/pi/role-turn-host.ts +34 -0
  109. package/src/public-cli/cli.ts +14 -6
  110. package/src/public-cli/instruction-seat-run.ts +289 -50
  111. package/src/public-cli/invocation.ts +66 -25
  112. package/src/public-cli/option-definitions.ts +61 -0
  113. package/src/public-cli/post-admission.ts +7 -1
  114. package/src/public-cli/registry.ts +4 -1
  115. package/src/public-cli/run-lifecycle.ts +22 -3
  116. package/src/public-cli/settlement.ts +120 -5
  117. package/src/public-cli/terminal.ts +2 -0
  118. package/src/public-role-summons.ts +431 -0
  119. package/src/receipt-delivery-policy.ts +10 -0
  120. package/src/reviewer-agent.ts +1 -1
  121. package/src/reviewer-child-executor.ts +114 -12
  122. package/src/reviewer-execution-ledger.ts +3 -2
  123. package/src/role-runtime.ts +158 -17
  124. package/src/session-assistant-usage.ts +119 -0
  125. package/src/session-opening-materials.ts +1 -1
  126. package/src/shape-unreadable-failure.ts +49 -0
  127. package/src/submission-errors.ts +3 -0
  128. package/dist/evidence-child-executor.js +0 -829
  129. package/src/evidence-child-executor.ts +0 -1140
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Pi adapter for the host-neutral main-session execution seam (#526 / S1b-2).
3
+ * Owns argv construction, spawn/SIGTERM/close, and session codec helpers.
4
+ * public-cli runners project RoleTurnRequest; this module is the sole argv owner.
5
+ */
6
+ import { execFile, spawn } from "node:child_process";
7
+ import { constants } from "node:fs";
8
+ import { access, appendFile, readFile, realpath } from "node:fs/promises";
9
+ import { delimiter, isAbsolute, join, resolve } from "node:path";
10
+ import { platform } from "node:process";
11
+ import { promisify } from "node:util";
12
+ import { randomUUID } from "node:crypto";
13
+ import { ExplicitInternalActivationError } from "../host-contracts.js";
14
+ import { applyEngineChildEnv } from "../engine-detour.js";
15
+ /** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
16
+ const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
17
+ export function resolveInternalRoleEntrypoint(packageRoot) {
18
+ return join(packageRoot, INTERNAL_ROLE_ENTRYPOINT_RELATIVE);
19
+ }
20
+ /** Non-dispatch args: load Internal once and exit via Pi help (no model turn). */
21
+ export const EXPLICIT_INTERNAL_LOAD_PROBE_ARGS = [
22
+ "--no-skills",
23
+ "--no-prompt-templates",
24
+ "--no-themes",
25
+ "--no-context-files",
26
+ "--no-session",
27
+ "--help",
28
+ ];
29
+ export function buildExplicitInternalActivationArgs(selectedRoleEntry, extraArgs = []) {
30
+ return ["--no-extensions", "-e", selectedRoleEntry, ...extraArgs];
31
+ }
32
+ /**
33
+ * Pi argv for seat model. Host only passes through resolved values — no local
34
+ * thinking whitelist and no package default fill. Bare provider/model omits
35
+ * --thinking so Pi owns its own default (#346/#384). Explicit thinking only.
36
+ */
37
+ function buildSeatModelCliArgs(model) {
38
+ if (model === undefined)
39
+ return [];
40
+ return [
41
+ "--provider",
42
+ model.provider,
43
+ "--model",
44
+ model.model,
45
+ ...(model.thinking === undefined ? [] : ["--thinking", model.thinking]),
46
+ ];
47
+ }
48
+ function buildActivationFlagArgs(activation) {
49
+ switch (activation.role) {
50
+ case "judge":
51
+ return ["--ak-role", "judge"];
52
+ case "coder":
53
+ return [
54
+ "--ak-role",
55
+ "coder",
56
+ "--ak-coder-phase",
57
+ activation.phase,
58
+ "--ak-coder-task",
59
+ activation.taskPath,
60
+ ];
61
+ case "fixer":
62
+ return [
63
+ "--ak-role",
64
+ "fixer",
65
+ "--ak-fixer-phase",
66
+ activation.phase,
67
+ "--ak-fix-packet",
68
+ activation.packetPath,
69
+ ...(activation.prerequisitesPath === undefined
70
+ ? []
71
+ : ["--ak-fixer-prerequisites", activation.prerequisitesPath]),
72
+ ];
73
+ case "reviewer":
74
+ return [
75
+ "--ak-role",
76
+ "reviewer",
77
+ "--ak-review-base",
78
+ activation.baseRevision,
79
+ ...(activation.authorityRefs.length === 0
80
+ ? []
81
+ : ["--ak-review-authority-refs", JSON.stringify([...activation.authorityRefs])]),
82
+ ...(activation.ticketNumber === undefined
83
+ ? []
84
+ : ["--ak-review-ticket-number", String(activation.ticketNumber)]),
85
+ ];
86
+ case "merger":
87
+ return ["--ak-role", "merger", "--ak-merger-input", activation.inputPath];
88
+ case "collector":
89
+ return [
90
+ "--ak-role",
91
+ "collector",
92
+ "--ak-collector-repo",
93
+ activation.repo,
94
+ "--ak-collector-pr",
95
+ activation.pr,
96
+ ...(activation.requestManifestPath === undefined
97
+ ? []
98
+ : ["--ak-collector-request-manifest", activation.requestManifestPath]),
99
+ ];
100
+ case "doctor":
101
+ return ["--ak-role", "doctor", "--ak-doctor-case", activation.casePath];
102
+ case "notary":
103
+ return [
104
+ "--ak-role",
105
+ "notary",
106
+ "--ak-notary-source-run",
107
+ activation.sourceRun,
108
+ ...(activation.ticketNumber === undefined
109
+ ? []
110
+ : ["--ak-notary-ticket-number", String(activation.ticketNumber)]),
111
+ ];
112
+ case "countersign":
113
+ // ticketNumber rides activation/admission/invocation only (#632: no private flag).
114
+ return ["--ak-role", "countersign"];
115
+ case "gleaner-left":
116
+ return [
117
+ "--ak-role",
118
+ "gleaner-left",
119
+ "--ak-gleaner-left-base",
120
+ activation.baseRevision,
121
+ ];
122
+ case "inspector":
123
+ return ["--ak-role", "inspector"];
124
+ case "gatekeeper":
125
+ return ["--ak-role", "gatekeeper"];
126
+ case "navigator":
127
+ return ["--ak-role", "navigator"];
128
+ case "auditor":
129
+ return ["--ak-role", "auditor"];
130
+ case "evidence-child":
131
+ return ["--ak-role", "evidence-child"];
132
+ case "diarist":
133
+ return [
134
+ "--ak-role",
135
+ "diarist",
136
+ ...(activation.sourcesPath === undefined
137
+ ? []
138
+ : ["--ak-diarist-sources", activation.sourcesPath]),
139
+ ];
140
+ default: {
141
+ const _exhaustive = activation;
142
+ return _exhaustive;
143
+ }
144
+ }
145
+ }
146
+ function buildMethodArgs(methods) {
147
+ const skillArgs = [];
148
+ for (const method of methods) {
149
+ if (method.kind === "skill") {
150
+ skillArgs.push("--skill", method.path);
151
+ }
152
+ }
153
+ return skillArgs;
154
+ }
155
+ /**
156
+ * Translate a closed RoleTurnRequest into Pi argv after `--no-extensions -e entry`.
157
+ * Controlled session constants and session coordinates are adapter-internal.
158
+ */
159
+ export function buildPiTurnExtraArgs(request, authority, extraPiArgs = []) {
160
+ const { sessionFile, sessionDirectory } = authority.decode(request.principal);
161
+ const prompt = request.continuation.kind === "initial" || request.continuation.kind === "resume"
162
+ ? request.continuation.prompt
163
+ : (() => {
164
+ const _exhaustive = request.continuation;
165
+ return _exhaustive;
166
+ })();
167
+ return [
168
+ "--no-skills",
169
+ ...buildMethodArgs(request.methods),
170
+ "--no-prompt-templates",
171
+ "--no-themes",
172
+ "--no-context-files",
173
+ "--session",
174
+ sessionFile,
175
+ "--session-dir",
176
+ sessionDirectory,
177
+ ...extraPiArgs,
178
+ ...buildActivationFlagArgs(request.activation),
179
+ "--mode",
180
+ "json",
181
+ ...buildSeatModelCliArgs(request.model),
182
+ prompt,
183
+ ];
184
+ }
185
+ const execFileAsync = promisify(execFile);
186
+ async function resolveSelectedPi(command, cwd, env) {
187
+ const searchPath = env.PATH ?? (platform === "win32" ? (process.env.PATH ?? "") : "/usr/bin:/bin");
188
+ const candidates = isAbsolute(command) || command.includes("/")
189
+ ? [resolve(cwd, command)]
190
+ : searchPath.split(delimiter).map((dir) => resolve(cwd, dir, command));
191
+ for (const candidate of candidates) {
192
+ try {
193
+ await access(candidate, constants.X_OK);
194
+ }
195
+ catch (error) {
196
+ const code = error.code;
197
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES")
198
+ continue;
199
+ throw new ExplicitInternalActivationError(`Pi executable resolution failed: ${String(error.message)}`, { knownCause: "activation", cause: error });
200
+ }
201
+ return await realpath(candidate);
202
+ }
203
+ throw new ExplicitInternalActivationError(`Pi executable not found: ${command}`, {
204
+ knownCause: "activation",
205
+ });
206
+ }
207
+ async function selectedPiIdentity(command, cwd, env) {
208
+ const executable = await resolveSelectedPi(command, cwd, env);
209
+ const { stdout } = await execFileAsync(executable, ["--version"], {
210
+ cwd,
211
+ env,
212
+ encoding: "utf8",
213
+ });
214
+ const version = stdout.trim();
215
+ if (version === "")
216
+ throw new Error(`Pi executable returned an empty version: ${executable}`);
217
+ return { executable, version };
218
+ }
219
+ /**
220
+ * Default child runner: canonically select `pi` on PATH (or PI_BINARY) and launch
221
+ * that exact file. Close settles exactly once for natural return / error / SIGTERM.
222
+ */
223
+ export function createDefaultPiSpawnRunner(options) {
224
+ return async (args, spawnOptions) => {
225
+ const command = spawnOptions.env.PI_BINARY ?? "pi";
226
+ const piIdentity = await selectedPiIdentity(command, spawnOptions.cwd, spawnOptions.env);
227
+ return await new Promise((resolveResult, reject) => {
228
+ // Child stdout is discarded at the stdio seam (CLAUDE.md Role invocation
229
+ // evidence). Do not pipe or accumulate it. stderr stays piped for diagnostics.
230
+ const child = spawn(piIdentity.executable, [...args], {
231
+ cwd: spawnOptions.cwd,
232
+ env: spawnOptions.env,
233
+ stdio: ["ignore", "ignore", "pipe"],
234
+ });
235
+ if (child.stderr === null) {
236
+ throw new Error("Pi child stderr pipe was not created");
237
+ }
238
+ let stderr = "";
239
+ let timedOut = false;
240
+ // No default wall clock. Only an explicit caller budget arms a timer (ADR 0010).
241
+ // SIGKILL is unconditionally forbidden — graceful SIGTERM only.
242
+ let timer;
243
+ let settled = false;
244
+ // `error` fires for pre-spawn failures (ENOENT after identity check is a
245
+ // true activation failure) or kill/dispatch errors. Retain it so `close`
246
+ // remains the SOLE settlement point (spec-B: child close once). Only
247
+ // fall back to rejecting on `error` if `close` never fires (e.g. spawn
248
+ // never succeeded so no `close` event will arrive).
249
+ let hasSpawned = false;
250
+ let executionError;
251
+ const armTimeoutAfterChildReady = () => {
252
+ if (spawnOptions.timeoutMs === undefined)
253
+ return;
254
+ timer = setTimeout(() => {
255
+ timedOut = true;
256
+ child.kill("SIGTERM");
257
+ }, spawnOptions.timeoutMs);
258
+ };
259
+ // Parent cancellation reaches the nested activation: same graceful SIGTERM,
260
+ // same single close settlement. SIGKILL stays forbidden (#675 / ADR 0010).
261
+ const parentSignal = spawnOptions.signal;
262
+ const terminateForParentAbort = () => {
263
+ child.kill("SIGTERM");
264
+ };
265
+ parentSignal?.addEventListener("abort", terminateForParentAbort, { once: true });
266
+ let identityRecorded = Promise.resolve();
267
+ child.once("spawn", () => {
268
+ hasSpawned = true;
269
+ if (parentSignal?.aborted === true)
270
+ terminateForParentAbort();
271
+ armTimeoutAfterChildReady();
272
+ const runDirectory = spawnOptions.env.AK_ROLE_RUN_DIR;
273
+ if (typeof runDirectory === "string" &&
274
+ runDirectory !== "" &&
275
+ options.recordLaunchedPiIdentity !== undefined) {
276
+ identityRecorded = options.recordLaunchedPiIdentity(runDirectory, piIdentity);
277
+ }
278
+ });
279
+ child.stderr.setEncoding("utf8").on("data", (chunk) => {
280
+ stderr += chunk;
281
+ });
282
+ child.on("error", (error) => {
283
+ // A pre-spawn error has no child lifecycle to close. After spawn,
284
+ // retain the execution error and let the mandatory close event own
285
+ // cleanup and the single settlement.
286
+ if (settled || hasSpawned) {
287
+ executionError = error;
288
+ return;
289
+ }
290
+ if (timer !== undefined)
291
+ clearTimeout(timer);
292
+ parentSignal?.removeEventListener("abort", terminateForParentAbort);
293
+ settled = true;
294
+ reject(error);
295
+ });
296
+ child.on("close", (code) => {
297
+ if (timer !== undefined)
298
+ clearTimeout(timer);
299
+ parentSignal?.removeEventListener("abort", terminateForParentAbort);
300
+ void identityRecorded.then(() => {
301
+ if (settled)
302
+ return;
303
+ settled = true;
304
+ if (executionError !== undefined) {
305
+ reject(executionError);
306
+ return;
307
+ }
308
+ resolveResult({
309
+ code,
310
+ stderr,
311
+ timedOut,
312
+ });
313
+ }, (error) => {
314
+ if (settled)
315
+ return;
316
+ settled = true;
317
+ reject(error);
318
+ });
319
+ });
320
+ });
321
+ };
322
+ }
323
+ /** Create the production Pi RoleTurnHost (composition-root assembly). */
324
+ export function createPiRoleTurnHost(config) {
325
+ const spawnRunner = config.spawnRunner ??
326
+ createDefaultPiSpawnRunner({
327
+ ...(config.recordLaunchedPiIdentity === undefined
328
+ ? {}
329
+ : { recordLaunchedPiIdentity: config.recordLaunchedPiIdentity }),
330
+ });
331
+ return {
332
+ async executeTurn(request) {
333
+ // #617 DK-7: Pi argv gets projected native paths once; never record bytes.
334
+ // Pi already owns its own session file, so only sitian prior volume rides in.
335
+ let turnRequest = request;
336
+ const paths = request.hostTransition?.priorNativeKind === "sitian"
337
+ ? request.hostTransition.priorNativePaths
338
+ : undefined;
339
+ if (request.continuation.kind === "resume"
340
+ && paths !== undefined
341
+ && paths.length > 0) {
342
+ turnRequest = {
343
+ ...request,
344
+ continuation: {
345
+ ...request.continuation,
346
+ prompt: `${request.continuation.prompt}\n${paths.join("\n")}`,
347
+ },
348
+ };
349
+ }
350
+ const roleEntry = await realpath(resolveInternalRoleEntrypoint(config.packageRoot));
351
+ const extraArgs = buildPiTurnExtraArgs(turnRequest, config.principalAuthority, config.extraPiArgs ?? []);
352
+ const args = buildExplicitInternalActivationArgs(roleEntry, extraArgs);
353
+ // Shared envelope isolates this call's court identity: omitting courtAttemptId
354
+ // must not inherit a parent process.env.AK_ROLE_COURT_ATTEMPT (#637).
355
+ const env = {
356
+ ...process.env,
357
+ HOME: request.home,
358
+ PI_CODING_AGENT_DIR: request.agentDir,
359
+ AK_ROLE_RUN_DIR: request.runDirectory,
360
+ // Nested public summons resolve package root without import.meta under jiti (#675).
361
+ // Child-process scoped only — not written back onto the parent process.env.
362
+ AK_ROLE_PACKAGE_ROOT: config.packageRoot,
363
+ };
364
+ if (request.courtAttemptId === undefined)
365
+ delete env.AK_ROLE_COURT_ATTEMPT;
366
+ else
367
+ env.AK_ROLE_COURT_ATTEMPT = request.courtAttemptId;
368
+ applyEngineChildEnv(env, request.engine);
369
+ // Nested auditor dossier tool binds the parent run pointer when published.
370
+ if (typeof process.env.AK_ROLE_AUDITOR_SOURCE_RUN === "string"
371
+ && process.env.AK_ROLE_AUDITOR_SOURCE_RUN.trim() !== "") {
372
+ env.AK_ROLE_AUDITOR_SOURCE_RUN = process.env.AK_ROLE_AUDITOR_SOURCE_RUN;
373
+ }
374
+ // Audited-subject input selects soul materials (same for nested and direct).
375
+ if (typeof process.env.AK_ROLE_AUDITOR_SUBJECT === "string"
376
+ && process.env.AK_ROLE_AUDITOR_SUBJECT.trim() !== "") {
377
+ env.AK_ROLE_AUDITOR_SUBJECT = process.env.AK_ROLE_AUDITOR_SUBJECT;
378
+ }
379
+ if (request.correlationId !== undefined && request.correlationId.trim() !== "") {
380
+ env.AK_CORRELATION_ID = request.correlationId;
381
+ }
382
+ if (config.recordLaunchedRolePackageIdentity !== undefined &&
383
+ config.observeLaunchedRolePackageIdentity !== undefined) {
384
+ await config.recordLaunchedRolePackageIdentity(request.runDirectory, await config.observeLaunchedRolePackageIdentity(config.packageRoot, roleEntry));
385
+ }
386
+ const timeoutMs = request.timeoutMs ?? config.timeoutMs;
387
+ return await spawnRunner(args, {
388
+ cwd: request.cwd,
389
+ env,
390
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
391
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
392
+ });
393
+ },
394
+ };
395
+ }
396
+ import { sitianReport } from "../sitian-facade.js";
397
+ /**
398
+ * Append one custom JSONL entry to the durable principal's session file.
399
+ * Pi session codec only — AK artifact O_EXCL retention stays in public-cli.
400
+ */
401
+ export async function appendPiSessionCustomEntry(authority, principal, customType, data) {
402
+ const { sessionFile } = authority.decode(principal);
403
+ const text = await readFile(sessionFile, "utf8");
404
+ let parentId = null;
405
+ for (const line of text.trim().split("\n").filter(Boolean)) {
406
+ const entry = JSON.parse(line);
407
+ if (typeof entry.id === "string" && entry.type !== "session")
408
+ parentId = entry.id;
409
+ }
410
+ const timestamp = new Date().toISOString();
411
+ const pointerLine = `${JSON.stringify({
412
+ type: "custom",
413
+ customType,
414
+ data,
415
+ id: randomUUID(),
416
+ parentId,
417
+ timestamp,
418
+ })}\n`;
419
+ await appendFile(sessionFile, pointerLine, "utf8");
420
+ try {
421
+ sitianReport({
422
+ level: "event",
423
+ kind: "dispatch-error",
424
+ sessionParent: sessionFile,
425
+ payload: { customType, data },
426
+ source: "pi-role-turn-host",
427
+ });
428
+ }
429
+ catch { }
430
+ }