@akagilnc/pi-workflow-roles 0.1.4874 → 0.1.4885

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.
@@ -587,6 +587,39 @@ var init_archivist_record_topology = __esm({
587
587
  }
588
588
  });
589
589
 
590
+ // src/pi/record-session-host.ts
591
+ import { existsSync } from "node:fs";
592
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
593
+ var piRecordSessionHost;
594
+ var init_record_session_host = __esm({
595
+ "src/pi/record-session-host.ts"() {
596
+ "use strict";
597
+ piRecordSessionHost = {
598
+ openRecordSession({ sessionFile, sessionDir, cwd }) {
599
+ return SessionManager.open(sessionFile, sessionDir, cwd);
600
+ },
601
+ createRecordSession({ cwd, sessionDir, parentSession }) {
602
+ return SessionManager.create(
603
+ cwd,
604
+ sessionDir,
605
+ parentSession === void 0 ? void 0 : { parentSession }
606
+ );
607
+ },
608
+ continueRecentRecordSession({ cwd, sessionDir }) {
609
+ const session = SessionManager.continueRecent(cwd, sessionDir);
610
+ const sessionFile = session.getSessionFile();
611
+ return {
612
+ session,
613
+ resumed: sessionFile !== void 0 && existsSync(sessionFile)
614
+ };
615
+ },
616
+ inMemoryRecordSession(cwd) {
617
+ return SessionManager.inMemory(cwd);
618
+ }
619
+ };
620
+ }
621
+ });
622
+
590
623
  // src/archivist-record-entry.ts
591
624
  var archivist_record_entry_exports = {};
592
625
  __export(archivist_record_entry_exports, {
@@ -600,7 +633,7 @@ __export(archivist_record_entry_exports, {
600
633
  });
601
634
  import {
602
635
  closeSync,
603
- existsSync,
636
+ existsSync as existsSync2,
604
637
  mkdirSync as mkdirSync2,
605
638
  openSync,
606
639
  readdirSync,
@@ -612,7 +645,6 @@ import {
612
645
  } from "node:fs";
613
646
  import { dirname as dirname3, resolve as resolve3, join as join4 } from "node:path";
614
647
  import { StringDecoder } from "node:string_decoder";
615
- import { SessionManager } from "@earendil-works/pi-coding-agent";
616
648
  function readCurrentSession(sessionDir) {
617
649
  const ledger = join4(sessionDir, CURRENT_SESSION_LEDGER);
618
650
  try {
@@ -763,7 +795,7 @@ function mostRecentNavigatorSessionFile(sessionDir, cwd) {
763
795
  }
764
796
  function resolveNavigatorNestContinuation(sessionDir, cwd) {
765
797
  const ledgerPath = currentSessionLedgerPath(sessionDir);
766
- if (existsSync(ledgerPath)) {
798
+ if (existsSync2(ledgerPath)) {
767
799
  const recentFile = readCurrentSession(sessionDir);
768
800
  assertRecentFinalFileUnderSessionDir(sessionDir, recentFile);
769
801
  return recentFile;
@@ -815,7 +847,7 @@ function assertRecentFinalFileUnderSessionDir(sessionDir, recentFile) {
815
847
  );
816
848
  }
817
849
  }
818
- function createRecordSessionOpen(options) {
850
+ function createRecordSessionOpen(options, host = piRecordSessionHost) {
819
851
  const cwd = options.cwd;
820
852
  const parentFile = options.parent?.getSessionFile();
821
853
  let sessionDir;
@@ -835,7 +867,7 @@ function createRecordSessionOpen(options) {
835
867
  mayResumeSameNest = true;
836
868
  } else if (parentFile === void 0 || parentFile.length === 0) {
837
869
  if (options.subject === void 0) {
838
- return { session: SessionManager.inMemory(cwd), resumed: false };
870
+ return { session: host.inMemoryRecordSession(cwd), resumed: false };
839
871
  }
840
872
  throw new Error("Durable record ownership requires a parent session inside the ledger home");
841
873
  } else {
@@ -848,34 +880,32 @@ function createRecordSessionOpen(options) {
848
880
  parentSession = parentFile;
849
881
  mayResumeSameNest = options.kind === WORKER_SUBMISSION_GATE_KIND;
850
882
  }
851
- const nestAlreadyExists = existsSync(sessionDir);
883
+ const nestAlreadyExists = existsSync2(sessionDir);
852
884
  ensureRealDirectoryTree(ledgerHome, sessionDir);
853
885
  if (mayResumeSameNest && nestAlreadyExists) {
854
886
  if (options.kind === NAVIGATOR_RECORD_KIND) {
855
887
  const recentFile = resolveNavigatorNestContinuation(sessionDir, cwd);
856
888
  if (recentFile !== void 0) {
857
889
  return {
858
- session: SessionManager.open(recentFile, sessionDir, cwd),
890
+ session: host.openRecordSession({ sessionFile: recentFile, sessionDir, cwd }),
859
891
  resumed: true
860
892
  };
861
893
  }
862
894
  } else {
863
- const recentFile = readCurrentSession(sessionDir);
864
- assertRecentFinalFileUnderSessionDir(sessionDir, recentFile);
865
- return {
866
- session: SessionManager.open(recentFile, sessionDir, cwd),
867
- resumed: true
868
- };
895
+ const continued = host.continueRecentRecordSession({ cwd, sessionDir });
896
+ if (continued.resumed) {
897
+ return continued;
898
+ }
869
899
  }
870
900
  }
871
- const session = SessionManager.create(
901
+ const session = host.createRecordSession({
872
902
  cwd,
873
903
  sessionDir,
874
- parentSession === void 0 ? void 0 : { parentSession }
875
- );
904
+ ...parentSession === void 0 ? {} : { parentSession }
905
+ });
876
906
  if (session.isPersisted()) {
877
907
  const file = session.getSessionFile();
878
- if (file !== void 0 && !existsSync(file)) {
908
+ if (file !== void 0 && !existsSync2(file)) {
879
909
  const header = session.getHeader();
880
910
  if (header !== null && header.type === "session") {
881
911
  writeFileSync(file, `${JSON.stringify(header)}
@@ -883,7 +913,7 @@ function createRecordSessionOpen(options) {
883
913
  session.setSessionFile(file);
884
914
  }
885
915
  }
886
- if (mayResumeSameNest && file !== void 0) {
916
+ if (options.kind === NAVIGATOR_RECORD_KIND && mayResumeSameNest && file !== void 0) {
887
917
  writeCurrentSession(sessionDir, file);
888
918
  }
889
919
  }
@@ -916,6 +946,7 @@ var init_archivist_record_entry = __esm({
916
946
  "use strict";
917
947
  init_activation_ledger_topology();
918
948
  init_archivist_record_topology();
949
+ init_record_session_host();
919
950
  init_archivist_record_topology();
920
951
  CURRENT_SESSION_LEDGER = "current-session.json";
921
952
  SESSION_HEADER_CHUNK_BYTES = 4096;
@@ -5226,7 +5257,7 @@ var init_session_assistant_usage = __esm({
5226
5257
  });
5227
5258
 
5228
5259
  // src/package-resources/engine-material.ts
5229
- import { existsSync as existsSync2, readdirSync as readdirSync2 } from "node:fs";
5260
+ import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
5230
5261
  import { join as join10 } from "node:path";
5231
5262
  function pickEngineAxis(source) {
5232
5263
  return {
@@ -5269,7 +5300,7 @@ function engineSessionMaterialFromOptions(options) {
5269
5300
  const model = options.engineModel === void 0 ? void 0 : assertLegalEngineModel(options.engineModel);
5270
5301
  const materialPath = resolveEngineMaterialPath(options.packageRoot, name);
5271
5302
  const modelField = model === void 0 ? {} : { model };
5272
- if (existsSync2(materialPath)) {
5303
+ if (existsSync3(materialPath)) {
5273
5304
  return Object.freeze({ name, materialPath, ...modelField });
5274
5305
  }
5275
5306
  return Object.freeze({ name, ...modelField });
@@ -5472,7 +5503,7 @@ var init_sitian_contracts = __esm({
5472
5503
  import { createHash as createHash5, randomUUID } from "node:crypto";
5473
5504
  import {
5474
5505
  appendFileSync,
5475
- existsSync as existsSync3,
5506
+ existsSync as existsSync4,
5476
5507
  readFileSync as readFileSync2,
5477
5508
  unlinkSync,
5478
5509
  writeFileSync as writeFileSync2
@@ -5492,14 +5523,14 @@ function createExclusiveFile(path, contents) {
5492
5523
  writeFileSync2(path, contents, { encoding: "utf8", flag: "wx" });
5493
5524
  }
5494
5525
  function sealTornTail(recordFile) {
5495
- if (!existsSync3(recordFile)) return;
5526
+ if (!existsSync4(recordFile)) return;
5496
5527
  const buffer = readFileSync2(recordFile);
5497
5528
  if (buffer.length > 0 && buffer[buffer.length - 1] !== 10) {
5498
5529
  appendFileSync(recordFile, "\n", "utf8");
5499
5530
  }
5500
5531
  }
5501
5532
  function findIdentityPointer(recordFile, identity, kind, level) {
5502
- if (!existsSync3(recordFile)) return void 0;
5533
+ if (!existsSync4(recordFile)) return void 0;
5503
5534
  const text = readFileSync2(recordFile, "utf8");
5504
5535
  for (const line2 of text.split("\n")) {
5505
5536
  const trimmed = line2.trim();
@@ -5693,13 +5724,13 @@ var init_sitian_appender = __esm({
5693
5724
  });
5694
5725
 
5695
5726
  // src/sitian-reader.ts
5696
- import { existsSync as existsSync4 } from "node:fs";
5727
+ import { existsSync as existsSync5 } from "node:fs";
5697
5728
  import { readFile as readFile4 } from "node:fs/promises";
5698
5729
  function isRecord5(value) {
5699
5730
  return typeof value === "object" && value !== null && !Array.isArray(value);
5700
5731
  }
5701
5732
  async function readSitianRecords(recordFile) {
5702
- if (!existsSync4(recordFile)) {
5733
+ if (!existsSync5(recordFile)) {
5703
5734
  return { records: [], diagnostics: [] };
5704
5735
  }
5705
5736
  const text = await readFile4(recordFile, "utf8");
@@ -9651,7 +9682,7 @@ var init_option_definitions = __esm({
9651
9682
 
9652
9683
  // src/public-cli/invocation.ts
9653
9684
  import { execFileSync as execFileSync3 } from "node:child_process";
9654
- import { existsSync as existsSync5 } from "node:fs";
9685
+ import { existsSync as existsSync6 } from "node:fs";
9655
9686
  import {
9656
9687
  lstat as lstat4,
9657
9688
  mkdtemp as mkdtemp2,
@@ -9913,7 +9944,7 @@ async function bindTicketNumberOnRunDirectory(runDirectory, ticketNumber) {
9913
9944
  `bindTicketNumberOnRunDirectory refuses to replace existing ticket #${existing} with #${ticketNumber}`
9914
9945
  );
9915
9946
  }
9916
- if (existsSync5(invocationPath)) {
9947
+ if (existsSync6(invocationPath)) {
9917
9948
  const invocation = JSON.parse(
9918
9949
  await readFile10(invocationPath, "utf8")
9919
9950
  );
@@ -11342,7 +11373,7 @@ var init_invocation = __esm({
11342
11373
  });
11343
11374
 
11344
11375
  // src/public-cli/load-production-acp-host.ts
11345
- import { existsSync as existsSync6 } from "node:fs";
11376
+ import { existsSync as existsSync7 } from "node:fs";
11346
11377
  import { join as join19 } from "node:path";
11347
11378
  import { pathToFileURL } from "node:url";
11348
11379
  async function loadProductionAcpHostFactory(packageRoot, host) {
@@ -11352,7 +11383,7 @@ async function loadProductionAcpHostFactory(packageRoot, host) {
11352
11383
  }
11353
11384
  const built = join19(packageRoot, "dist/acp-host/production-host.js");
11354
11385
  const source = join19(packageRoot, "src/acp-host/production-host.ts");
11355
- const target = existsSync6(built) ? built : source;
11386
+ const target = existsSync7(built) ? built : source;
11356
11387
  const href = pathToFileURL(target).href;
11357
11388
  const mod = await import(href);
11358
11389
  const create = mod.createProductionAcpRoleTurnHost;
@@ -11366,7 +11397,7 @@ var init_load_production_acp_host = __esm({
11366
11397
  });
11367
11398
 
11368
11399
  // src/public-cli/load-production-headless-host.ts
11369
- import { existsSync as existsSync7 } from "node:fs";
11400
+ import { existsSync as existsSync8 } from "node:fs";
11370
11401
  import { join as join20 } from "node:path";
11371
11402
  import { pathToFileURL as pathToFileURL2 } from "node:url";
11372
11403
  async function loadProductionHeadlessHostFactory(packageRoot, host) {
@@ -11376,7 +11407,7 @@ async function loadProductionHeadlessHostFactory(packageRoot, host) {
11376
11407
  }
11377
11408
  const built = join20(packageRoot, "dist/headless-host/production-host.js");
11378
11409
  const source = join20(packageRoot, "src/headless-host/production-host.ts");
11379
- const target = existsSync7(built) ? built : source;
11410
+ const target = existsSync8(built) ? built : source;
11380
11411
  const href = pathToFileURL2(target).href;
11381
11412
  const mod = await import(href);
11382
11413
  const create = mod.createProductionHeadlessRoleTurnHost;
@@ -13242,14 +13273,14 @@ var init_submission_ledger = __esm({
13242
13273
  });
13243
13274
 
13244
13275
  // src/session-opening-materials.ts
13245
- import { existsSync as existsSync8 } from "node:fs";
13276
+ import { existsSync as existsSync9 } from "node:fs";
13246
13277
  import { readFile as readFile13 } from "node:fs/promises";
13247
13278
  import { dirname as dirname12, join as join25 } from "node:path";
13248
13279
  import { fileURLToPath, pathToFileURL as pathToFileURL3 } from "node:url";
13249
13280
  function resolvePackageRootDir(moduleUrl = import.meta.url) {
13250
13281
  let dir = dirname12(fileURLToPath(moduleUrl));
13251
13282
  for (let i = 0; i < 8; i += 1) {
13252
- if (existsSync8(join25(dir, "package.json")) && existsSync8(join25(dir, "souls"))) {
13283
+ if (existsSync9(join25(dir, "package.json")) && existsSync9(join25(dir, "souls"))) {
13253
13284
  return dir;
13254
13285
  }
13255
13286
  const parent = dirname12(dir);
@@ -13637,7 +13668,7 @@ var init_engine_detour_usage = __esm({
13637
13668
  });
13638
13669
 
13639
13670
  // src/dossier-resolution.ts
13640
- import { existsSync as existsSync9, statSync as statSync3 } from "node:fs";
13671
+ import { existsSync as existsSync10, statSync as statSync3 } from "node:fs";
13641
13672
  import { resolve as resolve10 } from "node:path";
13642
13673
  function isHostContext(value) {
13643
13674
  return "sessionManager" in value;
@@ -13657,7 +13688,7 @@ function resolveAuditDossier(source) {
13657
13688
  }
13658
13689
  const runDirectory = resolve10(raw);
13659
13690
  try {
13660
- if (!existsSync9(runDirectory) || !statSync3(runDirectory).isDirectory()) {
13691
+ if (!existsSync10(runDirectory) || !statSync3(runDirectory).isDirectory()) {
13661
13692
  return { status: "incomplete", observation: { kind: "missing-dossier" } };
13662
13693
  }
13663
13694
  } catch {
@@ -22640,7 +22671,7 @@ __export(public_role_summons_exports, {
22640
22671
  withEphemeralReviewerWorktree: () => withEphemeralReviewerWorktree
22641
22672
  });
22642
22673
  import { execFile as execFile3 } from "node:child_process";
22643
- import { existsSync as existsSync10 } from "node:fs";
22674
+ import { existsSync as existsSync11 } from "node:fs";
22644
22675
  import { mkdir as mkdir4, mkdtemp as mkdtemp4, realpath as realpath7, rm as rm6 } from "node:fs/promises";
22645
22676
  import { tmpdir as tmpdir4 } from "node:os";
22646
22677
  import { join as join40, relative as relative3, sep as sep5 } from "node:path";
@@ -22667,7 +22698,7 @@ function parentDir(path) {
22667
22698
  function walkPackageRoot(start) {
22668
22699
  let dir = start;
22669
22700
  for (let i = 0; i < 12; i += 1) {
22670
- if (existsSync10(join40(dir, "package.json")) && existsSync10(join40(dir, "souls"))) {
22701
+ if (existsSync11(join40(dir, "package.json")) && existsSync11(join40(dir, "souls"))) {
22671
22702
  return dir;
22672
22703
  }
22673
22704
  const parent = parentDir(dir);
@@ -26406,7 +26437,7 @@ init_sitian_facade();
26406
26437
  init_submission_errors();
26407
26438
  init_submission_errors();
26408
26439
  import { execFileSync as execFileSync4 } from "node:child_process";
26409
- import { existsSync as existsSync11, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync5, rmdirSync, rmSync } from "node:fs";
26440
+ import { existsSync as existsSync12, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync5, rmdirSync, rmSync } from "node:fs";
26410
26441
  import { resolve as resolve18 } from "node:path";
26411
26442
  var WORKER_SUBMISSION_GATE_RECORD_KIND = WORKER_SUBMISSION_GATE_KIND;
26412
26443
  var WORKER_COMMIT_BASELINE_ENTRY_TYPE = "commit-baseline";
@@ -26437,7 +26468,7 @@ function statusOf(error) {
26437
26468
  return typeof error === "object" && error !== null && "status" in error ? error.status : void 0;
26438
26469
  }
26439
26470
  function tryGetAll(file, key) {
26440
- if (!existsSync11(file)) return [];
26471
+ if (!existsSync12(file)) return [];
26441
26472
  try {
26442
26473
  const out = gitFile(file, ["--get-all", key]);
26443
26474
  return out.length === 0 ? [] : out.split("\n");
@@ -26447,7 +26478,7 @@ function tryGetAll(file, key) {
26447
26478
  }
26448
26479
  }
26449
26480
  function ownedHook(path) {
26450
- if (!existsSync11(path)) return false;
26481
+ if (!existsSync12(path)) return false;
26451
26482
  return readFileSync5(path, "utf8").includes(HOOK_MARKER);
26452
26483
  }
26453
26484
  function escapeGitConfigValueRegex(value) {
@@ -26474,11 +26505,11 @@ function rmOwnedDir(dir) {
26474
26505
  const hookPath = resolve18(dir, HOOK_FILE);
26475
26506
  if (!ownedHook(hookPath)) return;
26476
26507
  rmSync(hookPath, { force: true });
26477
- if (existsSync11(dir) && readdirSync3(dir).length === 0) rmdirSync(dir);
26508
+ if (existsSync12(dir) && readdirSync3(dir).length === 0) rmdirSync(dir);
26478
26509
  }
26479
26510
  function linkedGitDirs(commonDir) {
26480
26511
  const root = resolve18(commonDir, "worktrees");
26481
- if (!existsSync11(root)) return [];
26512
+ if (!existsSync12(root)) return [];
26482
26513
  return readdirSync3(root).map((name) => resolve18(root, name)).filter((dir) => lstatSync3(dir).isDirectory());
26483
26514
  }
26484
26515
  function uninstallPackageWorkerHooks(cwd) {
@@ -12,7 +12,6 @@ import {
12
12
  } from "node:fs";
13
13
  import { dirname, resolve, join } from "node:path";
14
14
  import { StringDecoder } from "node:string_decoder";
15
- import { SessionManager } from "@earendil-works/pi-coding-agent";
16
15
  import {
17
16
  ActivationLedgerError,
18
17
  ensureRealDirectoryTree,
@@ -26,6 +25,7 @@ import {
26
25
  NAVIGATOR_RECORD_KIND,
27
26
  resolveNavigatorWorkSubjectPlacement
28
27
  } from "./archivist-record-topology.js";
28
+ import { piRecordSessionHost } from "./pi/record-session-host.js";
29
29
  import {
30
30
  NAVIGATOR_RECORD_KIND as NAVIGATOR_RECORD_KIND2,
31
31
  resolveNavigatorWorkSubjectPlacement as resolveNavigatorWorkSubjectPlacement2
@@ -236,7 +236,7 @@ function assertRecentFinalFileUnderSessionDir(sessionDir, recentFile) {
236
236
  );
237
237
  }
238
238
  }
239
- function createRecordSessionOpen(options) {
239
+ function createRecordSessionOpen(options, host = piRecordSessionHost) {
240
240
  const cwd = options.cwd;
241
241
  const parentFile = options.parent?.getSessionFile();
242
242
  let sessionDir;
@@ -256,7 +256,7 @@ function createRecordSessionOpen(options) {
256
256
  mayResumeSameNest = true;
257
257
  } else if (parentFile === void 0 || parentFile.length === 0) {
258
258
  if (options.subject === void 0) {
259
- return { session: SessionManager.inMemory(cwd), resumed: false };
259
+ return { session: host.inMemoryRecordSession(cwd), resumed: false };
260
260
  }
261
261
  throw new Error("Durable record ownership requires a parent session inside the ledger home");
262
262
  } else {
@@ -276,24 +276,22 @@ function createRecordSessionOpen(options) {
276
276
  const recentFile = resolveNavigatorNestContinuation(sessionDir, cwd);
277
277
  if (recentFile !== void 0) {
278
278
  return {
279
- session: SessionManager.open(recentFile, sessionDir, cwd),
279
+ session: host.openRecordSession({ sessionFile: recentFile, sessionDir, cwd }),
280
280
  resumed: true
281
281
  };
282
282
  }
283
283
  } else {
284
- const recentFile = readCurrentSession(sessionDir);
285
- assertRecentFinalFileUnderSessionDir(sessionDir, recentFile);
286
- return {
287
- session: SessionManager.open(recentFile, sessionDir, cwd),
288
- resumed: true
289
- };
284
+ const continued = host.continueRecentRecordSession({ cwd, sessionDir });
285
+ if (continued.resumed) {
286
+ return continued;
287
+ }
290
288
  }
291
289
  }
292
- const session = SessionManager.create(
290
+ const session = host.createRecordSession({
293
291
  cwd,
294
292
  sessionDir,
295
- parentSession === void 0 ? void 0 : { parentSession }
296
- );
293
+ ...parentSession === void 0 ? {} : { parentSession }
294
+ });
297
295
  if (session.isPersisted()) {
298
296
  const file = session.getSessionFile();
299
297
  if (file !== void 0 && !existsSync(file)) {
@@ -304,7 +302,7 @@ function createRecordSessionOpen(options) {
304
302
  session.setSessionFile(file);
305
303
  }
306
304
  }
307
- if (mayResumeSameNest && file !== void 0) {
305
+ if (options.kind === NAVIGATOR_RECORD_KIND && mayResumeSameNest && file !== void 0) {
308
306
  writeCurrentSession(sessionDir, file);
309
307
  }
310
308
  }
@@ -22954,6 +22954,39 @@ var init_archivist_record_topology = __esm({
22954
22954
  }
22955
22955
  });
22956
22956
 
22957
+ // src/pi/record-session-host.ts
22958
+ import { existsSync as existsSync10 } from "node:fs";
22959
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
22960
+ var piRecordSessionHost;
22961
+ var init_record_session_host = __esm({
22962
+ "src/pi/record-session-host.ts"() {
22963
+ "use strict";
22964
+ piRecordSessionHost = {
22965
+ openRecordSession({ sessionFile, sessionDir, cwd }) {
22966
+ return SessionManager.open(sessionFile, sessionDir, cwd);
22967
+ },
22968
+ createRecordSession({ cwd, sessionDir, parentSession }) {
22969
+ return SessionManager.create(
22970
+ cwd,
22971
+ sessionDir,
22972
+ parentSession === void 0 ? void 0 : { parentSession }
22973
+ );
22974
+ },
22975
+ continueRecentRecordSession({ cwd, sessionDir }) {
22976
+ const session = SessionManager.continueRecent(cwd, sessionDir);
22977
+ const sessionFile = session.getSessionFile();
22978
+ return {
22979
+ session,
22980
+ resumed: sessionFile !== void 0 && existsSync10(sessionFile)
22981
+ };
22982
+ },
22983
+ inMemoryRecordSession(cwd) {
22984
+ return SessionManager.inMemory(cwd);
22985
+ }
22986
+ };
22987
+ }
22988
+ });
22989
+
22957
22990
  // src/archivist-record-entry.ts
22958
22991
  var archivist_record_entry_exports = {};
22959
22992
  __export(archivist_record_entry_exports, {
@@ -22967,7 +23000,7 @@ __export(archivist_record_entry_exports, {
22967
23000
  });
22968
23001
  import {
22969
23002
  closeSync,
22970
- existsSync as existsSync10,
23003
+ existsSync as existsSync11,
22971
23004
  mkdirSync as mkdirSync2,
22972
23005
  openSync,
22973
23006
  readdirSync as readdirSync2,
@@ -22979,7 +23012,6 @@ import {
22979
23012
  } from "node:fs";
22980
23013
  import { dirname as dirname20, resolve as resolve15, join as join40 } from "node:path";
22981
23014
  import { StringDecoder } from "node:string_decoder";
22982
- import { SessionManager } from "@earendil-works/pi-coding-agent";
22983
23015
  function readCurrentSession(sessionDir) {
22984
23016
  const ledger = join40(sessionDir, CURRENT_SESSION_LEDGER);
22985
23017
  try {
@@ -23130,7 +23162,7 @@ function mostRecentNavigatorSessionFile(sessionDir, cwd) {
23130
23162
  }
23131
23163
  function resolveNavigatorNestContinuation(sessionDir, cwd) {
23132
23164
  const ledgerPath = currentSessionLedgerPath(sessionDir);
23133
- if (existsSync10(ledgerPath)) {
23165
+ if (existsSync11(ledgerPath)) {
23134
23166
  const recentFile = readCurrentSession(sessionDir);
23135
23167
  assertRecentFinalFileUnderSessionDir(sessionDir, recentFile);
23136
23168
  return recentFile;
@@ -23182,7 +23214,7 @@ function assertRecentFinalFileUnderSessionDir(sessionDir, recentFile) {
23182
23214
  );
23183
23215
  }
23184
23216
  }
23185
- function createRecordSessionOpen(options) {
23217
+ function createRecordSessionOpen(options, host = piRecordSessionHost) {
23186
23218
  const cwd = options.cwd;
23187
23219
  const parentFile = options.parent?.getSessionFile();
23188
23220
  let sessionDir;
@@ -23202,7 +23234,7 @@ function createRecordSessionOpen(options) {
23202
23234
  mayResumeSameNest = true;
23203
23235
  } else if (parentFile === void 0 || parentFile.length === 0) {
23204
23236
  if (options.subject === void 0) {
23205
- return { session: SessionManager.inMemory(cwd), resumed: false };
23237
+ return { session: host.inMemoryRecordSession(cwd), resumed: false };
23206
23238
  }
23207
23239
  throw new Error("Durable record ownership requires a parent session inside the ledger home");
23208
23240
  } else {
@@ -23215,34 +23247,32 @@ function createRecordSessionOpen(options) {
23215
23247
  parentSession = parentFile;
23216
23248
  mayResumeSameNest = options.kind === WORKER_SUBMISSION_GATE_KIND;
23217
23249
  }
23218
- const nestAlreadyExists = existsSync10(sessionDir);
23250
+ const nestAlreadyExists = existsSync11(sessionDir);
23219
23251
  ensureRealDirectoryTree(ledgerHome, sessionDir);
23220
23252
  if (mayResumeSameNest && nestAlreadyExists) {
23221
23253
  if (options.kind === NAVIGATOR_RECORD_KIND) {
23222
23254
  const recentFile = resolveNavigatorNestContinuation(sessionDir, cwd);
23223
23255
  if (recentFile !== void 0) {
23224
23256
  return {
23225
- session: SessionManager.open(recentFile, sessionDir, cwd),
23257
+ session: host.openRecordSession({ sessionFile: recentFile, sessionDir, cwd }),
23226
23258
  resumed: true
23227
23259
  };
23228
23260
  }
23229
23261
  } else {
23230
- const recentFile = readCurrentSession(sessionDir);
23231
- assertRecentFinalFileUnderSessionDir(sessionDir, recentFile);
23232
- return {
23233
- session: SessionManager.open(recentFile, sessionDir, cwd),
23234
- resumed: true
23235
- };
23262
+ const continued = host.continueRecentRecordSession({ cwd, sessionDir });
23263
+ if (continued.resumed) {
23264
+ return continued;
23265
+ }
23236
23266
  }
23237
23267
  }
23238
- const session = SessionManager.create(
23268
+ const session = host.createRecordSession({
23239
23269
  cwd,
23240
23270
  sessionDir,
23241
- parentSession === void 0 ? void 0 : { parentSession }
23242
- );
23271
+ ...parentSession === void 0 ? {} : { parentSession }
23272
+ });
23243
23273
  if (session.isPersisted()) {
23244
23274
  const file = session.getSessionFile();
23245
- if (file !== void 0 && !existsSync10(file)) {
23275
+ if (file !== void 0 && !existsSync11(file)) {
23246
23276
  const header = session.getHeader();
23247
23277
  if (header !== null && header.type === "session") {
23248
23278
  writeFileSync2(file, `${JSON.stringify(header)}
@@ -23250,7 +23280,7 @@ function createRecordSessionOpen(options) {
23250
23280
  session.setSessionFile(file);
23251
23281
  }
23252
23282
  }
23253
- if (mayResumeSameNest && file !== void 0) {
23283
+ if (options.kind === NAVIGATOR_RECORD_KIND && mayResumeSameNest && file !== void 0) {
23254
23284
  writeCurrentSession(sessionDir, file);
23255
23285
  }
23256
23286
  }
@@ -23283,6 +23313,7 @@ var init_archivist_record_entry = __esm({
23283
23313
  "use strict";
23284
23314
  init_activation_ledger_topology();
23285
23315
  init_archivist_record_topology();
23316
+ init_record_session_host();
23286
23317
  init_archivist_record_topology();
23287
23318
  CURRENT_SESSION_LEDGER = "current-session.json";
23288
23319
  SESSION_HEADER_CHUNK_BYTES = 4096;
@@ -26363,7 +26394,7 @@ init_sitian_facade();
26363
26394
  init_submission_errors();
26364
26395
  init_submission_errors();
26365
26396
  import { execFileSync as execFileSync4 } from "node:child_process";
26366
- import { existsSync as existsSync11, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync5, rmdirSync, rmSync } from "node:fs";
26397
+ import { existsSync as existsSync12, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync5, rmdirSync, rmSync } from "node:fs";
26367
26398
  import { resolve as resolve20 } from "node:path";
26368
26399
  var WORKER_SUBMISSION_GATE_RECORD_KIND = WORKER_SUBMISSION_GATE_KIND;
26369
26400
  var WORKER_COMMIT_BASELINE_ENTRY_TYPE = "commit-baseline";
@@ -26394,7 +26425,7 @@ function statusOf(error) {
26394
26425
  return typeof error === "object" && error !== null && "status" in error ? error.status : void 0;
26395
26426
  }
26396
26427
  function tryGetAll(file, key) {
26397
- if (!existsSync11(file)) return [];
26428
+ if (!existsSync12(file)) return [];
26398
26429
  try {
26399
26430
  const out = gitFile(file, ["--get-all", key]);
26400
26431
  return out.length === 0 ? [] : out.split("\n");
@@ -26404,7 +26435,7 @@ function tryGetAll(file, key) {
26404
26435
  }
26405
26436
  }
26406
26437
  function ownedHook(path) {
26407
- if (!existsSync11(path)) return false;
26438
+ if (!existsSync12(path)) return false;
26408
26439
  return readFileSync5(path, "utf8").includes(HOOK_MARKER);
26409
26440
  }
26410
26441
  function escapeGitConfigValueRegex(value) {
@@ -26431,11 +26462,11 @@ function rmOwnedDir(dir) {
26431
26462
  const hookPath = resolve20(dir, HOOK_FILE);
26432
26463
  if (!ownedHook(hookPath)) return;
26433
26464
  rmSync(hookPath, { force: true });
26434
- if (existsSync11(dir) && readdirSync3(dir).length === 0) rmdirSync(dir);
26465
+ if (existsSync12(dir) && readdirSync3(dir).length === 0) rmdirSync(dir);
26435
26466
  }
26436
26467
  function linkedGitDirs(commonDir) {
26437
26468
  const root = resolve20(commonDir, "worktrees");
26438
- if (!existsSync11(root)) return [];
26469
+ if (!existsSync12(root)) return [];
26439
26470
  return readdirSync3(root).map((name) => resolve20(root, name)).filter((dir) => lstatSync3(dir).isDirectory());
26440
26471
  }
26441
26472
  function uninstallPackageWorkerHooks(cwd) {
@@ -29705,7 +29736,7 @@ function headlessMcpConfigDocument(mcpServers) {
29705
29736
  // src/headless-host/role-turn-host.ts
29706
29737
  import { spawn as spawn4, spawnSync } from "node:child_process";
29707
29738
  import { randomUUID as randomUUID10 } from "node:crypto";
29708
- import { existsSync as existsSync12 } from "node:fs";
29739
+ import { existsSync as existsSync13 } from "node:fs";
29709
29740
  import { writeFile as writeFile12 } from "node:fs/promises";
29710
29741
  import { dirname as dirname26, isAbsolute as isAbsolute12, join as join48, resolve as resolve21 } from "node:path";
29711
29742
 
@@ -29927,7 +29958,7 @@ function formatCodexFailurePayload(payload) {
29927
29958
  function cwdIsGitWorkTree(cwd) {
29928
29959
  let dir = cwd;
29929
29960
  for (; ; ) {
29930
- if (existsSync12(join48(dir, ".git"))) return true;
29961
+ if (existsSync13(join48(dir, ".git"))) return true;
29931
29962
  const parent = dirname26(dir);
29932
29963
  if (parent === dir) return false;
29933
29964
  dir = parent;
@@ -14644,14 +14644,6 @@ function rewriteAdmittedRoleRunPage(page, rewrites) {
14644
14644
  );
14645
14645
  }
14646
14646
  }
14647
- async function rewriteJsonObjectFile(path7, fields, rewrites) {
14648
- if (!existsSync(path7)) return;
14649
- const page = JSON.parse(await readFile2(path7, "utf8"));
14650
- if (!isPlainObject(page)) return;
14651
- rewriteRunDirectoryPathFieldsAgainstRewrites(page, fields, rewrites);
14652
- await writeFile(path7, `${JSON.stringify(page, null, 2)}
14653
- `, "utf8");
14654
- }
14655
14647
  async function rewriteOfficerPointerFile(path7, rewrites) {
14656
14648
  if (!existsSync(path7)) return;
14657
14649
  const page = JSON.parse(await readFile2(path7, "utf8"));
@@ -14793,9 +14785,7 @@ async function rewriteNestedMachinePathPages(pagesDirectory, rewrites) {
14793
14785
  continue;
14794
14786
  }
14795
14787
  if (!entry.isFile()) continue;
14796
- if (entry.name === "current-session.json") {
14797
- await rewriteJsonObjectFile(path7, CURRENT_SESSION_FIELDS, rewrites);
14798
- } else if (entry.name.endsWith(".pointer.json")) {
14788
+ if (entry.name.endsWith(".pointer.json")) {
14799
14789
  await rewriteOfficerPointerFile(path7, rewrites);
14800
14790
  } else if (entry.name === "records.jsonl") {
14801
14791
  await rewriteSitianRecordsJsonl(path7, rewrites);
@@ -14854,7 +14844,7 @@ async function rewriteRoleRunDurablePages(input) {
14854
14844
  }
14855
14845
  await rewriteNestedMachinePathPages(pagesDirectory, rewrites);
14856
14846
  }
14857
- var ADMITTED_PAGE_FIELDS, INVOCATION_PAGE_FIELDS, RUN_STATE_PAGE_FIELDS, SOURCE_RUN_LOCATOR_FIELDS, SUMMONS_PATH_FIELDS, CURRENT_SESSION_FIELDS, OFFICER_POINTER_FIELDS, SITIAN_RECORD_FIELDS, SESSION_HEADER_FIELDS, BINDING_PARENT_FIELDS;
14847
+ var ADMITTED_PAGE_FIELDS, INVOCATION_PAGE_FIELDS, RUN_STATE_PAGE_FIELDS, SOURCE_RUN_LOCATOR_FIELDS, SUMMONS_PATH_FIELDS, OFFICER_POINTER_FIELDS, SITIAN_RECORD_FIELDS, SESSION_HEADER_FIELDS, BINDING_PARENT_FIELDS;
14858
14848
  var init_role_run_relocation = __esm({
14859
14849
  "src/role-run-relocation.ts"() {
14860
14850
  "use strict";
@@ -14884,7 +14874,6 @@ var init_role_run_relocation = __esm({
14884
14874
  ];
14885
14875
  SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"];
14886
14876
  SUMMONS_PATH_FIELDS = ["sourceRunPath"];
14887
- CURRENT_SESSION_FIELDS = ["sessionFile"];
14888
14877
  OFFICER_POINTER_FIELDS = ["sessionFile", "runDirectory"];
14889
14878
  SITIAN_RECORD_FIELDS = ["sessionParent"];
14890
14879
  SESSION_HEADER_FIELDS = ["parentSession"];
@@ -255467,6 +255456,13 @@ async function migrateBookTopology(input) {
255467
255456
  import { appendFile, cp, lstat as lstat2, mkdir as mkdir2, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
255468
255457
  import { dirname as dirname7, join as join12, relative as relative4, resolve as resolve6, sep as sep7 } from "node:path";
255469
255458
 
255459
+ // src/archivist-record-entry.ts
255460
+ init_activation_ledger_topology();
255461
+
255462
+ // src/archivist-record-topology.ts
255463
+ init_activation_ledger_git();
255464
+ init_activation_ledger_topology();
255465
+
255470
255466
  // node_modules/.pnpm/@earendil-works+pi-coding-agent@0.84.1_ws@8.21.1_zod@4.4.3/node_modules/@earendil-works/pi-coding-agent/dist/config.js
255471
255467
  import { accessSync, constants, existsSync as existsSync2, readFileSync, realpathSync as realpathSync2 } from "fs";
255472
255468
  import { homedir as homedir3 } from "os";
@@ -277726,13 +277722,6 @@ var DEFAULT_PROJECT_TRUST_BY_LABEL = new Map(Object.entries(DEFAULT_PROJECT_TRUS
277726
277722
  // node_modules/.pnpm/@earendil-works+pi-coding-agent@0.84.1_ws@8.21.1_zod@4.4.3/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
277727
277723
  var CONFIG_COMMAND_USAGE = `${APP_NAME} config [-l] [--approve|--no-approve]`;
277728
277724
 
277729
- // src/archivist-record-entry.ts
277730
- init_activation_ledger_topology();
277731
-
277732
- // src/archivist-record-topology.ts
277733
- init_activation_ledger_git();
277734
- init_activation_ledger_topology();
277735
-
277736
277725
  // src/archivist-record-entry.ts
277737
277726
  var MAX_SESSION_HEADER_SCAN_BYTES2 = 1024 * 1024;
277738
277727
  var WORKER_SUBMISSION_GATE_KIND = "worker-submission-gate";
@@ -278337,7 +278326,6 @@ async function migrateWorkerSubmissionGate(context2) {
278337
278326
  if (entries.length === 0) continue;
278338
278327
  const bookRoots = bookHistoricalRoots(booksDirectory, backupBooksDirectory, bookKey);
278339
278328
  const volumes = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => entry.name).sort();
278340
- const volumeDestinations = /* @__PURE__ */ new Map();
278341
278329
  for (const name of volumes) {
278342
278330
  const sourcePath = join12(sourceDir, name);
278343
278331
  const source = posixRelative(backupBooksDirectory, sourcePath);
@@ -278360,8 +278348,6 @@ async function migrateWorkerSubmissionGate(context2) {
278360
278348
  );
278361
278349
  await mkdir2(dirname7(dest), { recursive: true });
278362
278350
  await cp(sourcePath, dest, { preserveTimestamps: true });
278363
- volumeDestinations.set(resolve6(sourcePath), dest);
278364
- volumeDestinations.set(resolve6(join12(booksDirectory, bookKey, kind, name)), dest);
278365
278351
  outcomes.push({
278366
278352
  disposition: destRun?.disposition === "placed" ? "placed" : "unbound",
278367
278353
  source
@@ -278373,17 +278359,6 @@ async function migrateWorkerSubmissionGate(context2) {
278373
278359
  });
278374
278360
  }
278375
278361
  }
278376
- await migrateCurrentSessionPointer({
278377
- sourceDir,
278378
- volumeDestinations,
278379
- unboundPointerFile: join12(
278380
- booksDirectory,
278381
- bookKey,
278382
- "unbound",
278383
- kind,
278384
- CURRENT_SESSION_LEDGER
278385
- )
278386
- });
278387
278362
  }
278388
278363
  const report = reconcileMigrationPartition(kind, "entries", outcomes);
278389
278364
  return {
@@ -0,0 +1,28 @@
1
+ import { existsSync } from "node:fs";
2
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
3
+ const piRecordSessionHost = {
4
+ openRecordSession({ sessionFile, sessionDir, cwd }) {
5
+ return SessionManager.open(sessionFile, sessionDir, cwd);
6
+ },
7
+ createRecordSession({ cwd, sessionDir, parentSession }) {
8
+ return SessionManager.create(
9
+ cwd,
10
+ sessionDir,
11
+ parentSession === void 0 ? void 0 : { parentSession }
12
+ );
13
+ },
14
+ continueRecentRecordSession({ cwd, sessionDir }) {
15
+ const session = SessionManager.continueRecent(cwd, sessionDir);
16
+ const sessionFile = session.getSessionFile();
17
+ return {
18
+ session,
19
+ resumed: sessionFile !== void 0 && existsSync(sessionFile)
20
+ };
21
+ },
22
+ inMemoryRecordSession(cwd) {
23
+ return SessionManager.inMemory(cwd);
24
+ }
25
+ };
26
+ export {
27
+ piRecordSessionHost
28
+ };
@@ -35,7 +35,6 @@ const RUN_STATE_PAGE_FIELDS = [
35
35
  ];
36
36
  const SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"];
37
37
  const SUMMONS_PATH_FIELDS = ["sourceRunPath"];
38
- const CURRENT_SESSION_FIELDS = ["sessionFile"];
39
38
  const OFFICER_POINTER_FIELDS = ["sessionFile", "runDirectory"];
40
39
  const SITIAN_RECORD_FIELDS = ["sessionParent"];
41
40
  const SESSION_HEADER_FIELDS = ["parentSession"];
@@ -147,15 +146,6 @@ export function rewriteAdmittedRoleRunPage(page, rewrites) {
147
146
  rewriteRunDirectoryPathFieldsAgainstRewrites(page.principal, ["sessionDirectory", "sessionFile"], rewrites);
148
147
  }
149
148
  }
150
- async function rewriteJsonObjectFile(path, fields, rewrites) {
151
- if (!existsSync(path))
152
- return;
153
- const page = JSON.parse(await readFile(path, "utf8"));
154
- if (!isPlainObject(page))
155
- return;
156
- rewriteRunDirectoryPathFieldsAgainstRewrites(page, fields, rewrites);
157
- await writeFile(path, `${JSON.stringify(page, null, 2)}\n`, "utf8");
158
- }
159
149
  async function rewriteOfficerPointerFile(path, rewrites) {
160
150
  if (!existsSync(path))
161
151
  return;
@@ -280,7 +270,7 @@ async function rewriteSessionTranscriptBindings(path, rewrites) {
280
270
  }
281
271
  /**
282
272
  * Package-owned nested seams under session/ only:
283
- * current-session.json, direct-officer *.pointer.json, sitian records.jsonl,
273
+ * direct-officer *.pointer.json, sitian records.jsonl,
284
274
  * session transcript typed parent bindings. Never walks attachments/ or artifacts/.
285
275
  */
286
276
  async function rewriteNestedMachinePathPages(pagesDirectory, rewrites) {
@@ -303,10 +293,7 @@ async function rewriteNestedMachinePathPages(pagesDirectory, rewrites) {
303
293
  }
304
294
  if (!entry.isFile())
305
295
  continue;
306
- if (entry.name === "current-session.json") {
307
- await rewriteJsonObjectFile(path, CURRENT_SESSION_FIELDS, rewrites);
308
- }
309
- else if (entry.name.endsWith(".pointer.json")) {
296
+ if (entry.name.endsWith(".pointer.json")) {
310
297
  await rewriteOfficerPointerFile(path, rewrites);
311
298
  }
312
299
  else if (entry.name === "records.jsonl") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.4874",
3
+ "version": "0.1.4885",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -20,6 +20,7 @@ const entries = [
20
20
  "activation-ledger-git",
21
21
  "activation-ledger-topology",
22
22
  "archivist-record-entry",
23
+ "pi/record-session-host",
23
24
  // Pure subject nest topology — static import of archivist-record-entry;
24
25
  // cold discovery without SessionManager (#636). Keep listed while that
25
26
  // relative edge remains (#857 removal left the import graph open).
@@ -17,8 +17,6 @@ import {
17
17
  } from "node:fs";
18
18
  import { dirname, resolve, join } from "node:path";
19
19
  import { StringDecoder } from "node:string_decoder";
20
- import { SessionManager } from "@earendil-works/pi-coding-agent";
21
-
22
20
  import {
23
21
  ActivationLedgerError,
24
22
  ensureRealDirectoryTree,
@@ -32,6 +30,8 @@ import {
32
30
  NAVIGATOR_RECORD_KIND,
33
31
  resolveNavigatorWorkSubjectPlacement,
34
32
  } from "./archivist-record-topology.ts";
33
+ import type { HostRecordSession, RecordSessionHost } from "./host-contracts.ts";
34
+ import { piRecordSessionHost } from "./pi/record-session-host.ts";
35
35
 
36
36
  export {
37
37
  NAVIGATOR_RECORD_KIND,
@@ -355,27 +355,29 @@ function assertRecentFinalFileUnderSessionDir(
355
355
 
356
356
  /** Open result including the sole continuation fact. */
357
357
  export type RecordSessionOpen = {
358
- readonly session: SessionManager;
359
- /** True when an existing navigator work-subject or worker-submission-gate volume was reopened. */
358
+ readonly session: HostRecordSession;
359
+ /** True when an existing navigator work-subject or worker-submission-gate volume was continued. */
360
360
  readonly resumed: boolean;
361
361
  };
362
362
 
363
363
  /**
364
364
  * Sole package entry that constructs a durable Pi session record (ADR 0065).
365
365
  * No destination/path parameters — location is computed from ledger topology only.
366
- * SessionDir placement is owned by ensureRealDirectoryTree; resumed recent final-file
367
- * identity is checked once before SessionManager.open (directory walk cannot see a
368
- * trailing .jsonl symlink). New principals mint under the already-validated sessionDir
369
- * via destination-free SessionManager.create — no derived postcondition.
370
- * Resume via the AK-owned current-session ledger is limited to navigator work-subject
371
- * nests and the authorized worker-submission-gate durable path (ADR 0066 / #852).
366
+ * SessionDir placement is owned by ensureRealDirectoryTree. Navigator sidecar targets
367
+ * are checked once before host open (the directory walk cannot see a trailing .jsonl
368
+ * symlink). New principals mint under the already-validated sessionDir.
369
+ * Navigator work-subject nests use their AK ledger; worker-submission-gate asks the
370
+ * host to continue its recent native session without selecting a stored file path.
372
371
  * Other ordinary no-subject children (auditor-roles, …) always mint fresh.
373
372
  * New persisted principals materialize their deferred session header before return so
374
373
  * custom-entry-only writers do not need a parallel delayed-header helper.
375
374
  *
376
375
  * `resumed` is the sole open-or-continue fact — callers must not re-probe nest existence.
377
376
  */
378
- export function createRecordSessionOpen(options: CreateRecordSessionOptions): RecordSessionOpen {
377
+ export function createRecordSessionOpen(
378
+ options: CreateRecordSessionOptions,
379
+ host: RecordSessionHost = piRecordSessionHost,
380
+ ): RecordSessionOpen {
379
381
  const cwd = options.cwd;
380
382
  const parentFile = options.parent?.getSessionFile();
381
383
 
@@ -402,7 +404,7 @@ export function createRecordSessionOpen(options: CreateRecordSessionOptions): Re
402
404
  } else if (parentFile === undefined || parentFile.length === 0) {
403
405
  if (options.subject === undefined) {
404
406
  // No durable principal requested: preserve prior in-memory child behavior.
405
- return { session: SessionManager.inMemory(cwd), resumed: false };
407
+ return { session: host.inMemoryRecordSession(cwd), resumed: false };
406
408
  }
407
409
  throw new Error("Durable record ownership requires a parent session inside the ledger home");
408
410
  } else {
@@ -427,26 +429,26 @@ export function createRecordSessionOpen(options: CreateRecordSessionOptions): Re
427
429
  const recentFile = resolveNavigatorNestContinuation(sessionDir, cwd);
428
430
  if (recentFile !== undefined) {
429
431
  return {
430
- session: SessionManager.open(recentFile, sessionDir, cwd),
432
+ session: host.openRecordSession({ sessionFile: recentFile, sessionDir, cwd }),
431
433
  resumed: true,
432
434
  };
433
435
  }
434
436
  // No sidecar and no cwd-matching session — mint fresh in the existing nest.
435
437
  } else {
436
- const recentFile = readCurrentSession(sessionDir);
437
- assertRecentFinalFileUnderSessionDir(sessionDir, recentFile);
438
- return {
439
- session: SessionManager.open(recentFile, sessionDir, cwd),
440
- resumed: true,
441
- };
438
+ const continued = host.continueRecentRecordSession({ cwd, sessionDir });
439
+ if (continued.resumed) {
440
+ return continued;
441
+ }
442
+ // Pi's native continuation falls back to an unparented fresh session. The
443
+ // archivist owns the durable parent, so mint through the ordinary fresh path.
442
444
  }
443
445
  }
444
446
 
445
- const session = SessionManager.create(
447
+ const session = host.createRecordSession({
446
448
  cwd,
447
449
  sessionDir,
448
- parentSession === undefined ? undefined : { parentSession },
449
- );
450
+ ...(parentSession === undefined ? {} : { parentSession }),
451
+ });
450
452
  // Pi defers session-file create until the first assistant message. Custom-entry-only
451
453
  // records never get that turn, so the sole record entry materializes the in-memory
452
454
  // header onto the UUIDv7 path before returning. Existing path → early return.
@@ -460,7 +462,7 @@ export function createRecordSessionOpen(options: CreateRecordSessionOptions): Re
460
462
  session.setSessionFile(file);
461
463
  }
462
464
  }
463
- if (mayResumeSameNest && file !== undefined) {
465
+ if (options.kind === NAVIGATOR_RECORD_KIND && mayResumeSameNest && file !== undefined) {
464
466
  writeCurrentSession(sessionDir, file);
465
467
  }
466
468
  }
@@ -468,7 +470,7 @@ export function createRecordSessionOpen(options: CreateRecordSessionOptions): Re
468
470
  }
469
471
 
470
472
  /** Session-only facade — most callers only need the manager. */
471
- export function createRecordSession(options: CreateRecordSessionOptions): SessionManager {
473
+ export function createRecordSession(options: CreateRecordSessionOptions): HostRecordSession {
472
474
  return createRecordSessionOpen(options).session;
473
475
  }
474
476
 
@@ -391,7 +391,6 @@ async function migrateWorkerSubmissionGate(
391
391
  .filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
392
392
  .map((entry) => entry.name)
393
393
  .sort();
394
- const volumeDestinations = new Map<string, string>();
395
394
 
396
395
  for (const name of volumes) {
397
396
  const sourcePath = join(sourceDir, name);
@@ -417,8 +416,6 @@ async function migrateWorkerSubmissionGate(
417
416
  );
418
417
  await mkdir(dirname(dest), { recursive: true });
419
418
  await cp(sourcePath, dest, { preserveTimestamps: true });
420
- volumeDestinations.set(resolve(sourcePath), dest);
421
- volumeDestinations.set(resolve(join(booksDirectory, bookKey, kind, name)), dest);
422
419
  // One outcome per volume keeps the entries closure; bad rows attach below.
423
420
  outcomes.push({
424
421
  disposition: destRun?.disposition === "placed" ? "placed" : "unbound",
@@ -431,18 +428,6 @@ async function migrateWorkerSubmissionGate(
431
428
  });
432
429
  }
433
430
  }
434
-
435
- await migrateCurrentSessionPointer({
436
- sourceDir,
437
- volumeDestinations,
438
- unboundPointerFile: join(
439
- booksDirectory,
440
- bookKey,
441
- "unbound",
442
- kind,
443
- CURRENT_SESSION_LEDGER,
444
- ),
445
- });
446
431
  }
447
432
 
448
433
  const report = reconcileMigrationPartition(kind, "entries", outcomes);
@@ -532,3 +532,40 @@ export interface InstitutionalSessionHost {
532
532
  ): Promise<HostInstitutionalSessionHandle>;
533
533
  }
534
534
 
535
+ /** Host-owned durable record session used by AK ledger placement. */
536
+ export interface HostRecordSession {
537
+ getSessionFile(): string | undefined;
538
+ getSessionDir(): string;
539
+ getEntries(): readonly {
540
+ readonly type: string;
541
+ readonly customType?: string;
542
+ readonly data?: unknown;
543
+ }[];
544
+ getHeader(): { readonly type: string } | null;
545
+ isPersisted(): boolean;
546
+ setSessionFile(path: string): void;
547
+ appendCustomEntry(customType: string, data?: unknown): unknown;
548
+ }
549
+
550
+ /** Host session lifecycle seam; native selection stays inside its adapter. */
551
+ export interface RecordSessionHost {
552
+ openRecordSession(options: {
553
+ readonly sessionFile: string;
554
+ readonly sessionDir: string;
555
+ readonly cwd: string;
556
+ }): HostRecordSession;
557
+ createRecordSession(options: {
558
+ readonly cwd: string;
559
+ readonly sessionDir: string;
560
+ readonly parentSession?: string;
561
+ }): HostRecordSession;
562
+ continueRecentRecordSession(options: {
563
+ readonly cwd: string;
564
+ readonly sessionDir: string;
565
+ }): {
566
+ readonly session: HostRecordSession;
567
+ /** Whether the host continued an existing session rather than falling back to a new one. */
568
+ readonly resumed: boolean;
569
+ };
570
+ inMemoryRecordSession(cwd: string): HostRecordSession;
571
+ }
@@ -62,6 +62,7 @@ function streamIdleTimeoutFromUnknown(value: unknown): StreamIdleTimeoutError |
62
62
  : undefined;
63
63
  }
64
64
 
65
+
65
66
  // ── Stream / remote error projection helpers ───────────────────────────────
66
67
 
67
68
  function emptyUsage(): Usage {
@@ -599,12 +600,12 @@ export async function openPiInProcessSession(
599
600
  kind: options.sessionIdentity.kind,
600
601
  ...(options.sessionIdentity.subject === undefined ? {} : { subject: options.sessionIdentity.subject }),
601
602
  ...(options.sessionIdentity.parent === undefined ? {} : { parent: options.sessionIdentity.parent }),
602
- });
603
+ }) as SessionManager;
603
604
  } else {
604
605
  sessionManager = createRecordSession({
605
606
  cwd: options.cwd,
606
607
  kind: "institutional",
607
- });
608
+ }) as SessionManager;
608
609
  }
609
610
 
610
611
  // 6. Tools mapping
@@ -858,4 +859,3 @@ export async function openPiInProcessSession(
858
859
  throw openError;
859
860
  }
860
861
  }
861
-
@@ -0,0 +1,30 @@
1
+ import { existsSync } from "node:fs";
2
+
3
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
4
+
5
+ import type { RecordSessionHost } from "../host-contracts.ts";
6
+
7
+ /** Pi-native implementation of the shared record-session lifecycle seam. */
8
+ export const piRecordSessionHost: RecordSessionHost = {
9
+ openRecordSession({ sessionFile, sessionDir, cwd }) {
10
+ return SessionManager.open(sessionFile, sessionDir, cwd);
11
+ },
12
+ createRecordSession({ cwd, sessionDir, parentSession }) {
13
+ return SessionManager.create(
14
+ cwd,
15
+ sessionDir,
16
+ parentSession === undefined ? undefined : { parentSession },
17
+ );
18
+ },
19
+ continueRecentRecordSession({ cwd, sessionDir }) {
20
+ const session = SessionManager.continueRecent(cwd, sessionDir);
21
+ const sessionFile = session.getSessionFile();
22
+ return {
23
+ session,
24
+ resumed: sessionFile !== undefined && existsSync(sessionFile),
25
+ };
26
+ },
27
+ inMemoryRecordSession(cwd) {
28
+ return SessionManager.inMemory(cwd);
29
+ },
30
+ };
@@ -40,7 +40,6 @@ const RUN_STATE_PAGE_FIELDS = [
40
40
 
41
41
  const SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"] as const;
42
42
  const SUMMONS_PATH_FIELDS = ["sourceRunPath"] as const;
43
- const CURRENT_SESSION_FIELDS = ["sessionFile"] as const;
44
43
  const OFFICER_POINTER_FIELDS = ["sessionFile", "runDirectory"] as const;
45
44
  const SITIAN_RECORD_FIELDS = ["sessionParent"] as const;
46
45
  const SESSION_HEADER_FIELDS = ["parentSession"] as const;
@@ -219,18 +218,6 @@ export function rewriteAdmittedRoleRunPage(
219
218
  }
220
219
  }
221
220
 
222
- async function rewriteJsonObjectFile(
223
- path: string,
224
- fields: readonly string[],
225
- rewrites: readonly RunDirectoryPathRewrite[],
226
- ): Promise<void> {
227
- if (!existsSync(path)) return;
228
- const page = JSON.parse(await readFile(path, "utf8")) as unknown;
229
- if (!isPlainObject(page)) return;
230
- rewriteRunDirectoryPathFieldsAgainstRewrites(page, fields, rewrites);
231
- await writeFile(path, `${JSON.stringify(page, null, 2)}\n`, "utf8");
232
- }
233
-
234
221
  async function rewriteOfficerPointerFile(
235
222
  path: string,
236
223
  rewrites: readonly RunDirectoryPathRewrite[],
@@ -377,7 +364,7 @@ async function rewriteSessionTranscriptBindings(
377
364
 
378
365
  /**
379
366
  * Package-owned nested seams under session/ only:
380
- * current-session.json, direct-officer *.pointer.json, sitian records.jsonl,
367
+ * direct-officer *.pointer.json, sitian records.jsonl,
381
368
  * session transcript typed parent bindings. Never walks attachments/ or artifacts/.
382
369
  */
383
370
  async function rewriteNestedMachinePathPages(
@@ -400,9 +387,7 @@ async function rewriteNestedMachinePathPages(
400
387
  continue;
401
388
  }
402
389
  if (!entry.isFile()) continue;
403
- if (entry.name === "current-session.json") {
404
- await rewriteJsonObjectFile(path, CURRENT_SESSION_FIELDS, rewrites);
405
- } else if (entry.name.endsWith(".pointer.json")) {
390
+ if (entry.name.endsWith(".pointer.json")) {
406
391
  await rewriteOfficerPointerFile(path, rewrites);
407
392
  } else if (entry.name === "records.jsonl") {
408
393
  await rewriteSitianRecordsJsonl(path, rewrites);
@@ -2,13 +2,13 @@
2
2
  import { execFileSync } from "node:child_process";
3
3
  import { existsSync, lstatSync, readdirSync, readFileSync, rmdirSync, rmSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
- import type { SessionManager } from "@earendil-works/pi-coding-agent";
6
5
 
7
6
  import {
8
7
  createRecordSession,
9
8
  type RecordSessionParent,
10
9
  WORKER_SUBMISSION_GATE_KIND,
11
10
  } from "./archivist-record-entry.ts";
11
+ import type { HostRecordSession } from "./host-contracts.ts";
12
12
  import { sitianReport } from "./sitian-facade.ts";
13
13
  import {
14
14
  WorkerCommitReminderError,
@@ -162,7 +162,7 @@ function unfinishedReasonPresent(details?: unknown): boolean {
162
162
  return typeof reason === "string" && reason.trim().length > 0;
163
163
  }
164
164
 
165
- function readGateState(session: SessionManager): {
165
+ function readGateState(session: HostRecordSession): {
166
166
  baseline: string | null | undefined;
167
167
  reminded: boolean;
168
168
  prefixReminded: boolean;
@@ -239,7 +239,7 @@ export function createWorkerSubmissionGate(
239
239
  let reminded = false;
240
240
  let prefixReminded = false;
241
241
  let unfinishedReasonBounces = 0;
242
- let record: SessionManager | undefined;
242
+ let record: HostRecordSession | undefined;
243
243
  /** Parent session file retained so every gate sitian write path-derives the same ledger home. */
244
244
  let sessionParent: string | undefined;
245
245
  const explicitHome =