@akagilnc/pi-workflow-roles 0.1.5775 → 0.1.5797

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 (43) hide show
  1. package/dist/acp-host/production-host.js +556 -209
  2. package/dist/external-host-turn-loop.js +3 -0
  3. package/dist/headless-host/production-host.js +529 -182
  4. package/dist/migrate-book-topology.js +63 -23
  5. package/dist/pi/role-turn-host.js +6 -1
  6. package/dist/public-cli/instruction-seat-run.js +10 -7
  7. package/dist/public-cli/invocation.js +95 -39
  8. package/dist/public-cli/main.js +357 -147
  9. package/dist/public-cli/post-admission.js +2 -0
  10. package/dist/public-cli/run-lifecycle.js +10 -4
  11. package/dist/public-cli/turn-request.js +3 -0
  12. package/dist/report-ticket-tool.js +3 -3
  13. package/dist/role-run-placement.js +14 -2
  14. package/dist/role-run-relocation.js +2 -2
  15. package/dist/run-dossier.js +5 -4
  16. package/dist/session-identity.js +3 -2
  17. package/dist/sitian-appender.js +12 -3
  18. package/dist/submission-ledger.js +24 -0
  19. package/dist/ticket-progress.js +306 -0
  20. package/dist/user-dialogue-stdin.js +23 -17
  21. package/package.json +1 -1
  22. package/src/external-host-turn-loop.ts +7 -1
  23. package/src/host-contracts.ts +1 -1
  24. package/src/pi/adapter.ts +57 -45
  25. package/src/pi/role-turn-host.ts +6 -1
  26. package/src/prepared-role-turn.ts +6 -0
  27. package/src/public-cli/instruction-seat-run.ts +10 -7
  28. package/src/public-cli/invocation.ts +116 -38
  29. package/src/public-cli/post-admission.ts +3 -1
  30. package/src/public-cli/run-lifecycle.ts +10 -8
  31. package/src/public-cli/turn-request.ts +3 -0
  32. package/src/report-ticket-tool.ts +7 -3
  33. package/src/role-envelope.ts +5 -0
  34. package/src/role-run-placement.ts +19 -2
  35. package/src/role-run-relocation.ts +2 -2
  36. package/src/role-runtime.ts +14 -3
  37. package/src/run-dossier.ts +5 -3
  38. package/src/run-terminal-artifacts.ts +0 -1
  39. package/src/session-identity.ts +3 -2
  40. package/src/sitian-appender.ts +14 -3
  41. package/src/submission-ledger.ts +27 -0
  42. package/src/ticket-progress.ts +383 -0
  43. package/src/user-dialogue-stdin.ts +21 -14
@@ -958,6 +958,12 @@ import { basename as basename3, dirname as dirname3, join as join2 } from "node:
958
958
  function resolveSitianVolumeCategory(kind) {
959
959
  return kind;
960
960
  }
961
+ function runDirectoryOwningSessionParent(sessionParentDir) {
962
+ if (basename3(sessionParentDir) !== "session") return void 0;
963
+ const runDirectory = dirname3(sessionParentDir);
964
+ if (parseRunLeaf(basename3(runDirectory)) === void 0) return void 0;
965
+ return runDirectory;
966
+ }
961
967
  function sitianVolumeDirectory(sessionDirectory, category) {
962
968
  return join2(sessionDirectory, category);
963
969
  }
@@ -989,8 +995,8 @@ function resolveSitianRecordPathInLedger(input, ledgerHome) {
989
995
  throw new Error("Sitian record ownership requires a parent session inside the ledger home");
990
996
  }
991
997
  const sessionParentDir = dirname3(input.sessionParent);
992
- const runDirectory = dirname3(sessionParentDir);
993
- if (basename3(sessionParentDir) === "session" && parseRunLeaf(basename3(runDirectory)) !== void 0) {
998
+ const runDirectory = runDirectoryOwningSessionParent(sessionParentDir);
999
+ if (runDirectory !== void 0) {
994
1000
  sessionDir = runDirectory;
995
1001
  recordFile = join2(runDirectory, RUN_HISTORY_KINDS.has(input.kind) ? RUN_HISTORY_FILE : RUN_STATE_KINDS.has(input.kind) ? RUN_STATE_FILE : RUN_LOG_FILE);
996
1002
  } else {
@@ -1183,7 +1189,9 @@ function render(runDirectory, history, state, log, unreadable) {
1183
1189
  original = payload.landingPath;
1184
1190
  }
1185
1191
  }
1186
- if (typeof original === "string") original = join3(runDirectory, "session", basename4(original));
1192
+ if (typeof original === "string") {
1193
+ original = join3(sessionDirectoryOf(runDirectory), basename4(original));
1194
+ }
1187
1195
  whole.host = { sessions, ...original === void 0 ? {} : { original } };
1188
1196
  whole.submission = latest === void 0 ? {} : { latest };
1189
1197
  if (Object.keys(unreadable).length > 0) whole.unreadable = unreadable;
@@ -10176,7 +10184,7 @@ async function rewriteSessionTranscriptBindings(path, rewrites) {
10176
10184
  );
10177
10185
  }
10178
10186
  async function rewriteNestedMachinePathPages(pagesDirectory, rewrites) {
10179
- const sessionRoot = join6(pagesDirectory, "session");
10187
+ const sessionRoot = sessionDirectoryOf(pagesDirectory);
10180
10188
  async function walk(directory) {
10181
10189
  let entries;
10182
10190
  try {
@@ -10273,13 +10281,30 @@ var init_role_run_relocation = __esm({
10273
10281
  }
10274
10282
  });
10275
10283
 
10276
- // src/sha256.ts
10277
- import { createHash } from "node:crypto";
10278
- function sha256Hex(bytes) {
10279
- return createHash("sha256").update(bytes).digest("hex");
10280
- }
10281
- var init_sha256 = __esm({
10282
- "src/sha256.ts"() {
10284
+ // src/sitian-facade.ts
10285
+ var init_sitian_facade = __esm({
10286
+ "src/sitian-facade.ts"() {
10287
+ "use strict";
10288
+ init_role_run_placement();
10289
+ init_sitian_appender();
10290
+ init_sitian_contracts();
10291
+ init_sitian_appender();
10292
+ init_sitian_reader();
10293
+ }
10294
+ });
10295
+
10296
+ // src/host-session-record.ts
10297
+ var init_host_session_record = __esm({
10298
+ "src/host-session-record.ts"() {
10299
+ "use strict";
10300
+ init_sitian_facade();
10301
+ init_unknown_value();
10302
+ }
10303
+ });
10304
+
10305
+ // src/host-contracts.ts
10306
+ var init_host_contracts = __esm({
10307
+ "src/host-contracts.ts"() {
10283
10308
  "use strict";
10284
10309
  }
10285
10310
  });
@@ -10328,6 +10353,32 @@ var init_terminating_tools = __esm({
10328
10353
  }
10329
10354
  });
10330
10355
 
10356
+ // src/ticket-progress.ts
10357
+ var init_ticket_progress = __esm({
10358
+ "src/ticket-progress.ts"() {
10359
+ "use strict";
10360
+ init_host_session_record();
10361
+ init_host_contracts();
10362
+ init_terminating_tools();
10363
+ init_packaged_role_registry();
10364
+ init_role_run_placement();
10365
+ init_run_dossier();
10366
+ init_sitian_reader();
10367
+ init_unknown_value();
10368
+ }
10369
+ });
10370
+
10371
+ // src/sha256.ts
10372
+ import { createHash } from "node:crypto";
10373
+ function sha256Hex(bytes) {
10374
+ return createHash("sha256").update(bytes).digest("hex");
10375
+ }
10376
+ var init_sha256 = __esm({
10377
+ "src/sha256.ts"() {
10378
+ "use strict";
10379
+ }
10380
+ });
10381
+
10331
10382
  // src/doctor-evidence.ts
10332
10383
  var init_doctor_evidence = __esm({
10333
10384
  "src/doctor-evidence.ts"() {
@@ -10382,18 +10433,6 @@ var init_session_dialogue = __esm({
10382
10433
  }
10383
10434
  });
10384
10435
 
10385
- // src/sitian-facade.ts
10386
- var init_sitian_facade = __esm({
10387
- "src/sitian-facade.ts"() {
10388
- "use strict";
10389
- init_role_run_placement();
10390
- init_sitian_appender();
10391
- init_sitian_contracts();
10392
- init_sitian_appender();
10393
- init_sitian_reader();
10394
- }
10395
- });
10396
-
10397
10436
  // src/ticket-provenance.ts
10398
10437
  var init_ticket_provenance = __esm({
10399
10438
  "src/ticket-provenance.ts"() {
@@ -11205,6 +11244,7 @@ var init_invocation = __esm({
11205
11244
  init_activation_ledger_topology();
11206
11245
  init_activation_ledger_git();
11207
11246
  init_role_run_placement();
11247
+ init_ticket_progress();
11208
11248
  init_packaged_role_registry();
11209
11249
  init_run_ticket_number();
11210
11250
  init_role_run_relocation();
@@ -336,7 +336,9 @@ export function createPiRoleTurnHost(config) {
336
336
  const roleEntry = await realpath(resolveInternalRoleEntrypoint(config.packageRoot));
337
337
  const extraArgs = buildPiTurnExtraArgs(turnRequest, config.principalAuthority, config.extraPiArgs ?? []);
338
338
  const args = buildExplicitInternalActivationArgs(roleEntry, extraArgs);
339
- const stdin = encodeUserDialogueStdin(piUserDialogueBody(turnRequest));
339
+ const transportBody = piUserDialogueBody(turnRequest);
340
+ // #1199: progress instruction is these same body bytes (actual seat input).
341
+ const stdin = encodeUserDialogueStdin(transportBody);
340
342
  // Shared envelope isolates this call's court identity: omitting courtAttemptId
341
343
  // must not inherit a parent process.env.AK_ROLE_COURT_ATTEMPT (#637).
342
344
  const env = {
@@ -362,6 +364,9 @@ export function createPiRoleTurnHost(config) {
362
364
  delete env.AK_ROLE_HOST;
363
365
  else
364
366
  env.AK_ROLE_HOST = request.host.trim();
367
+ // #1199: 传召词 is unknown-length body — never argv/env (execve ARG_MAX / E2BIG).
368
+ // Typed stdin envelope carries dialogue + optional progress 传召词.
369
+ delete env.AK_ROLE_SUMMONS_INSTRUCTION;
365
370
  // #1132: the one effective delivery-request ceiling travels to the child
366
371
  // on its own env so the in-child role runtime and the worker submission
367
372
  // gate count with the same configured number the AK seam resolved. Omit
@@ -395,7 +395,10 @@ export async function resolveReviewSeatDialogueBody(input) {
395
395
  return undefined;
396
396
  return await loadLedgerPeerBody(sourceRunPath, input.projectRoot, input.home);
397
397
  }
398
- async function resolveInitialPrompt(admitted, env) {
398
+ /**
399
+ * Initial host prompt for this turn (#1199: progress instruction = these bytes).
400
+ */
401
+ async function resolveInitialTurnPrompt(admitted, env) {
399
402
  const record = roleRecord(admitted.role);
400
403
  if ("reaskPrompt" in record && record.reaskPrompt === true) {
401
404
  const sourceRunPath = admittedSourceRunPath(admitted);
@@ -513,7 +516,7 @@ async function dispatchAdmitted(admitted, env, io) {
513
516
  if (activeEnv.correlationId !== undefined && activeEnv.correlationId.trim() !== "") {
514
517
  await recordAdmittedCorrelation(admitted, activeEnv.correlationId);
515
518
  }
516
- const initialPromptText = await resolveInitialPrompt(admitted, activeEnv);
519
+ const initialPrompt = await resolveInitialTurnPrompt(admitted, activeEnv);
517
520
  const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
518
521
  if (auto) {
519
522
  // #1208: auto-resume resends this turn's actual content, never a filler phrase.
@@ -523,11 +526,11 @@ async function dispatchAdmitted(admitted, env, io) {
523
526
  io: turnIo,
524
527
  buildInitialRequest: () => buildInstructionSeatTurnRequest(admitted, roleTurnOptions(activeEnv, admitted, {
525
528
  kind: "initial",
526
- prompt: initialPromptText,
529
+ prompt: initialPrompt,
527
530
  })),
528
531
  buildResumeRequest: () => buildInstructionSeatTurnRequest(admitted, roleTurnOptions(activeEnv, admitted, {
529
532
  kind: "resume",
530
- prompt: initialPromptText,
533
+ prompt: initialPrompt,
531
534
  })),
532
535
  adapters,
533
536
  ...(activeEnv.engine === undefined ? {} : { effectiveEngine: activeEnv.engine }),
@@ -539,7 +542,7 @@ async function dispatchAdmitted(admitted, env, io) {
539
542
  io: turnIo,
540
543
  request: buildInstructionSeatTurnRequest(admitted, roleTurnOptions(activeEnv, admitted, {
541
544
  kind: "initial",
542
- prompt: initialPromptText,
545
+ prompt: initialPrompt,
543
546
  })),
544
547
  adapters,
545
548
  ...(activeEnv.engine === undefined ? {} : { effectiveEngine: activeEnv.engine }),
@@ -650,7 +653,7 @@ function resumeSameParentInstructionSeat(input) {
650
653
  });
651
654
  }
652
655
  /**
653
- * Same delivery rule as resolveInitialPrompt for same-parent resume summons.
656
+ * Same delivery rule as resolveInitialTurnPrompt for same-parent resume summons.
654
657
  * Shared resolver owns the input choice — callers only pass env fields.
655
658
  */
656
659
  async function sameParentDialogue(env, fallback, sourceRunPath, projectRoot, role) {
@@ -736,7 +739,7 @@ async function runCountersignBody(parsed, env, io) {
736
739
  // #1208 C1 / #1166 J11: one shared initial-prompt seam (incl. file flags).
737
740
  const turnProjection = roleTurnOptions(env, admitted, {
738
741
  kind: "initial",
739
- prompt: await resolveInitialPrompt(admitted, env),
742
+ prompt: await resolveInitialTurnPrompt(admitted, env),
740
743
  });
741
744
  const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
742
745
  const result = await runPostAdmissionOneShot({
@@ -8,7 +8,8 @@ import { readFile, realpath, rename, writeFile, } from "node:fs/promises";
8
8
  import { dirname, isAbsolute, join, resolve, sep } from "node:path";
9
9
  import { activationBookDirectory, ensureRealDirectoryTree, homeFromRunDirectory, pathContainedIn, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
10
10
  import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
11
- import { ensureRoleRunDirectory, ensureRoleRunPlacement, formatRunLeaf, isUnboundRunDirectory, listBookRunDirectories, roleRunPlacement, } from "../role-run-placement.js";
11
+ import { ensureRoleRunDirectory, ensureRoleRunPlacement, isUnboundRunDirectory, listBookRunDirectories, roleRunPlacement, sessionDirectoryOf, sessionFileIn, } from "../role-run-placement.js";
12
+ import { relocateTicketProgressForLeg } from "../ticket-progress.js";
12
13
  import { packagedAdmittedSubject, packagedArgvResult, packagedBareToken, packagedBaseIsRevision, packagedEmitsAuthorityRefs, packagedPublicInstructionSubject, packagedRoleMetadata, packagedStoresSourceRunRaw, packagedSubjectChoices, } from "../packaged-role-registry.js";
13
14
  import { isSafePositiveTicketNumber, parseTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
14
15
  import { rewriteAdmittedRoleRunPage, } from "../role-run-relocation.js";
@@ -243,12 +244,80 @@ export async function recordAdmittedCorrelation(admitted, correlationId) {
243
244
  mutable.correlationId = correlationId;
244
245
  mutable.correlationIds = correlationIds;
245
246
  }
247
+ /**
248
+ * #1199 R2: registered 起居郎 legs that already belong on this ticket.
249
+ * Unbound source → whole-leg rename + publish; already under ticket → publish
250
+ * leftover staging only. No scan of unregistered siblings.
251
+ */
252
+ async function relocateRegisteredChildDiarists(input) {
253
+ const childRunIds = readPageSync(input.parentRunDirectory, "admitted")?.childDiaristRunIds;
254
+ for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
255
+ if (typeof childRunId !== "string")
256
+ continue;
257
+ const childTarget = roleRunPlacement(input.ledgerHome, {
258
+ bookKey: input.bookKey,
259
+ subject: { ticketNumber: input.ticketNumber },
260
+ runId: childRunId,
261
+ role: "diarist",
262
+ });
263
+ const unboundChildDirectory = roleRunPlacement(input.ledgerHome, {
264
+ bookKey: input.bookKey,
265
+ subject: { unbound: true },
266
+ runId: childRunId,
267
+ role: "diarist",
268
+ }).runDirectory;
269
+ if (existsSync(unboundChildDirectory)) {
270
+ input.authority.seal(childTarget);
271
+ await bindTicketNumberOnRunDirectory(unboundChildDirectory, input.ticketNumber);
272
+ await rehomeUnboundTicketProvenance(unboundChildDirectory, input.ticketNumber, input.projectRoot, homeFromRunDirectory(input.parentRunDirectory));
273
+ ensureRoleRunDirectory(input.ledgerHome, dirname(childTarget.runDirectory));
274
+ // #1199: whole-leg rename first; originals travel with the directory.
275
+ await rename(unboundChildDirectory, childTarget.runDirectory);
276
+ relocateTicketProgressForLeg({
277
+ runDirectory: childTarget.runDirectory,
278
+ });
279
+ // Finished child legs will not settle again; refresh derived host.original
280
+ // after the rename commit. A derived render fault must not undo or block
281
+ // the committed placement (#1161 L1) — note and keep the true cause.
282
+ try {
283
+ renderCurrentSync(childTarget.runDirectory);
284
+ }
285
+ catch (error) {
286
+ process.stderr.write(`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`);
287
+ }
288
+ continue;
289
+ }
290
+ // Prior partial relocate left the child under the ticket with staging still held.
291
+ if (existsSync(childTarget.runDirectory)) {
292
+ relocateTicketProgressForLeg({
293
+ runDirectory: childTarget.runDirectory,
294
+ });
295
+ }
296
+ }
297
+ }
246
298
  /** File a role run under its latest typed ticket identity. */
247
299
  export async function relocateAdmittedRunToTicket(admitted, authority, heldLease) {
248
- if (admitted.ticketNumber === undefined || !isUnboundRunDirectory(admitted.runDirectory))
300
+ if (admitted.ticketNumber === undefined)
249
301
  return undefined;
302
+ const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(admitted.runDirectory));
303
+ // Already under the ticket: publish leftover progress.jsonl (self + registered children).
304
+ if (!isUnboundRunDirectory(admitted.runDirectory)) {
305
+ relocateTicketProgressForLeg({
306
+ runDirectory: admitted.runDirectory,
307
+ });
308
+ if (admitted.role !== "diarist") {
309
+ await relocateRegisteredChildDiarists({
310
+ parentRunDirectory: admitted.runDirectory,
311
+ bookKey: admitted.bookKey,
312
+ ticketNumber: admitted.ticketNumber,
313
+ projectRoot: admitted.projectRoot,
314
+ ledgerHome,
315
+ authority,
316
+ });
317
+ }
318
+ return undefined;
319
+ }
250
320
  const oldRunDirectory = admitted.runDirectory;
251
- const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(oldRunDirectory));
252
321
  const target = roleRunPlacement(ledgerHome, {
253
322
  bookKey: admitted.bookKey,
254
323
  subject: { ticketNumber: admitted.ticketNumber },
@@ -256,54 +325,41 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
256
325
  role: admitted.role,
257
326
  });
258
327
  ensureRoleRunDirectory(ledgerHome, dirname(target.runDirectory));
259
- // Host sealing is pure identity projection, but may reject the coordinates.
260
- // Keep that failure before the filesystem commit point.
261
- const principal = authority.seal(target);
328
+ // Pre-flight seal; final seal after the filesystem commit.
329
+ authority.seal(target);
262
330
  if (admitted.role !== "diarist") {
263
- const childRunIds = readPageSync(oldRunDirectory, "admitted")?.childDiaristRunIds;
264
- for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
265
- if (typeof childRunId !== "string")
266
- continue;
267
- const childDirectory = join(dirname(oldRunDirectory), formatRunLeaf(childRunId, "diarist"));
268
- // A child that already filed under a ticket keeps its own assertion.
269
- if (!existsSync(childDirectory))
270
- continue;
271
- const childTarget = roleRunPlacement(ledgerHome, {
272
- bookKey: admitted.bookKey,
273
- subject: { ticketNumber: admitted.ticketNumber },
274
- runId: childRunId,
275
- role: "diarist",
276
- });
277
- authority.seal(childTarget);
278
- await bindTicketNumberOnRunDirectory(childDirectory, admitted.ticketNumber);
279
- await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
280
- ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
281
- await rename(childDirectory, childTarget.runDirectory);
282
- // Finished child legs will not settle again; refresh derived host.original
283
- // after the rename commit. A derived render fault must not undo or block
284
- // the committed placement (#1161 L1) — note and keep the true cause.
285
- try {
286
- renderCurrentSync(childTarget.runDirectory);
287
- }
288
- catch (error) {
289
- process.stderr.write(`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`);
290
- }
291
- }
331
+ await relocateRegisteredChildDiarists({
332
+ parentRunDirectory: oldRunDirectory,
333
+ bookKey: admitted.bookKey,
334
+ ticketNumber: admitted.ticketNumber,
335
+ projectRoot: admitted.projectRoot,
336
+ ledgerHome,
337
+ authority,
338
+ });
292
339
  }
293
340
  // The first ticket assignment moves only an unbound diarist's own diary.
294
341
  if (admitted.role === "diarist") {
295
342
  await rehomeUnboundTicketProvenance(oldRunDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
296
343
  }
297
344
  // Rename commits the run placement. Diary assignment above is a Sitian append
298
- // outside the atomic directory move.
299
- // Persisted paths are resolved from typed run identity on read.
345
+ // outside the atomic directory move. Session/receipts/progress travel with the leg.
300
346
  await rename(oldRunDirectory, target.runDirectory);
301
347
  // rename moved the open lock inode with the directory. Transfer cleanup and
302
- // admitted path ownership immediately after the commit — before any derived
303
- // render that may refuse (#1161 L1 / BASE order).
348
+ // live path ownership immediately after the commit — before final seal/render
349
+ // (#1161 L1 / BASE order). Live handles first so mid-turn setSessionFile sees
350
+ // the ticket leaf; final seal uses the post-move existence landing.
304
351
  heldLease?.relocate(target.runDirectory);
352
+ const actualSessionDirectory = sessionDirectoryOf(target.runDirectory);
353
+ const principal = authority.seal({
354
+ sessionDirectory: actualSessionDirectory,
355
+ sessionFile: sessionFileIn(actualSessionDirectory),
356
+ });
305
357
  rewriteAdmittedRoleRunPage(admitted, [{ oldRunDirectory, newRunDirectory: target.runDirectory }]);
306
358
  admitted.principal = principal;
359
+ // After whole-leg rename + live-handle handoff: fill rounds and append ticket current.
360
+ relocateTicketProgressForLeg({
361
+ runDirectory: target.runDirectory,
362
+ });
307
363
  // current.json projects host.original from this run directory. Ownership is
308
364
  // already transferred; a refuse still carries the true cause to the caller
309
365
  // (#1161 L1) without leaving the leg under the unbound path.