@akagilnc/pi-workflow-roles 0.1.4239 → 0.1.4259

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.
@@ -6,7 +6,7 @@
6
6
  * Role runners supply only turn request projection and narrow settlement adapters.
7
7
  */
8
8
  import { randomUUID } from "node:crypto";
9
- import { readFile, writeFile } from "node:fs/promises";
9
+ import { writeFile } from "node:fs/promises";
10
10
  import { isAbsolute, join, resolve } from "node:path";
11
11
 
12
12
  import {
@@ -73,6 +73,11 @@ function isStationChildOfficerDialogue(
73
73
  ): boolean {
74
74
  return env.stationChild === true && isOfficerReviewSeat(role);
75
75
  }
76
+ import {
77
+ mintEngineDetourInvocationScope,
78
+ readInvocationSelectedHost,
79
+ withEngineDetourInvocationScope,
80
+ } from "../engine-detour-usage.ts";
76
81
  import { projectHostTransitionPriorNative } from "../host-transition-prior-native.ts";
77
82
  import type { CredentialProviders, SeatModelConfig } from "./config.ts";
78
83
  import {
@@ -208,19 +213,6 @@ async function recordBestEffortPostDispatchDiagnostic<A extends AdmittedRoleInvo
208
213
  }
209
214
  }
210
215
 
211
- /** Previous main-session host recorded on invocation.json, if any. */
212
- async function readInvocationHost(runDirectory: string): Promise<string | undefined> {
213
- try {
214
- const raw = JSON.parse(await readFile(join(runDirectory, "invocation.json"), "utf8")) as {
215
- host?: unknown;
216
- };
217
- return typeof raw.host === "string" && raw.host.trim() !== "" ? raw.host : undefined;
218
- } catch (error) {
219
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
220
- throw error;
221
- }
222
- }
223
-
224
216
  export type PostAdmissionEnv = {
225
217
  home: string;
226
218
  agentDir: string;
@@ -312,6 +304,8 @@ export type ControlledFailureInput = {
312
304
  * already the reported cause.
313
305
  */
314
306
  skipRunStateWrite?: boolean;
307
+ /** Public-invocation scope from the shared Host envelope (#537). */
308
+ invocationScopeId?: string;
315
309
  };
316
310
 
317
311
  /** Result of seat prep after the single pre-lease admitted load. */
@@ -451,9 +445,15 @@ export async function presentControlledFailure<
451
445
  admitted,
452
446
  failure,
453
447
  authority,
454
- resumable
455
- ? { resume: { command: renderResumeCommand(admitted.runId) } }
456
- : {},
448
+ {
449
+ ...(resumable
450
+ ? { resume: { command: renderResumeCommand(admitted.runId) } }
451
+ : {}),
452
+ ...(failureInput.invocationScopeId === undefined ||
453
+ failureInput.invocationScopeId.length === 0
454
+ ? {}
455
+ : { invocationScopeId: failureInput.invocationScopeId }),
456
+ },
457
457
  ),
458
458
  );
459
459
  presentFailureTerminal(terminal, io);
@@ -613,12 +613,12 @@ export async function dispatchPostAdmissionTurn<
613
613
  return {
614
614
  ...(await presentControlledFailure(
615
615
  admitted,
616
- {
616
+ withEngineDetourInvocationScope({
617
617
  timedOut: false,
618
618
  code: null,
619
619
  stderr: "",
620
620
  thrown: error,
621
- },
621
+ }, request.invocationScopeId),
622
622
  adapters,
623
623
  env.principalAuthority,
624
624
  io,
@@ -639,7 +639,7 @@ export async function dispatchPostAdmissionTurn<
639
639
  return {
640
640
  ...(await presentControlledFailure(
641
641
  admitted,
642
- missingCredential,
642
+ withEngineDetourInvocationScope(missingCredential, request.invocationScopeId),
643
643
  adapters,
644
644
  env.principalAuthority,
645
645
  io,
@@ -659,7 +659,7 @@ export async function dispatchPostAdmissionTurn<
659
659
  : env.principalAuthority.decode(admitted.principal);
660
660
  let hostTransition: RoleTurnRequest["hostTransition"];
661
661
  try {
662
- previousHost = await readInvocationHost(admitted.runDirectory);
662
+ previousHost = readInvocationSelectedHost(admitted.runDirectory);
663
663
  hostTransition =
664
664
  previousHost !== undefined && liveHost !== undefined && principalCoordinates !== undefined
665
665
  ? await projectHostTransitionPriorNative({
@@ -673,12 +673,12 @@ export async function dispatchPostAdmissionTurn<
673
673
  return {
674
674
  ...(await presentControlledFailure(
675
675
  admitted,
676
- {
676
+ withEngineDetourInvocationScope({
677
677
  timedOut: false,
678
678
  code: null,
679
679
  stderr: "",
680
680
  thrown: error,
681
- },
681
+ }, request.invocationScopeId),
682
682
  adapters,
683
683
  env.principalAuthority,
684
684
  io,
@@ -696,7 +696,7 @@ export async function dispatchPostAdmissionTurn<
696
696
  // executeTurn — not merely past beforeDispatch (#840 r9 判词 class 2). Any
697
697
  // pre-turn retry (beforeDispatch, dossier projection, continuation
698
698
  // assembly) must still see the prior invocation host on its next attempt;
699
- // committing env.host earlier would make readInvocationHost read back the
699
+ // committing env.host earlier would make readInvocationSelectedHost read back the
700
700
  // new host on the retry and silently drop hostTransition.
701
701
  // Failures settle the run (presentControlledFailure); they must not leave it
702
702
  // permanently running. Call-local auto-resume retries this hook until it
@@ -708,12 +708,12 @@ export async function dispatchPostAdmissionTurn<
708
708
  } catch (error) {
709
709
  const settled = (await presentControlledFailure(
710
710
  admitted,
711
- {
711
+ withEngineDetourInvocationScope({
712
712
  timedOut: false,
713
713
  code: null,
714
714
  stderr: "",
715
715
  thrown: error,
716
- },
716
+ }, request.invocationScopeId),
717
717
  adapters,
718
718
  env.principalAuthority,
719
719
  io,
@@ -740,6 +740,11 @@ export async function dispatchPostAdmissionTurn<
740
740
  if (hostTransition !== undefined) {
741
741
  turnRequest = { ...turnRequest, hostTransition };
742
742
  }
743
+ // Selected host axis rides the shared Host envelope for in-turn tools
744
+ // (detour usage ledger) — never a pre-spawn invocation.json reread.
745
+ if (typeof liveHost === "string" && liveHost.trim() !== "") {
746
+ turnRequest = { ...turnRequest, host: liveHost.trim() };
747
+ }
743
748
  if (isStationChildOfficerDialogue(admitted.role, env)) {
744
749
  // 0081 non-body face: freeze pointer section under run/attachments/.
745
750
  // Peer dialogue continuation.prompt stays parent payload only — the seat
@@ -780,18 +785,22 @@ export async function dispatchPostAdmissionTurn<
780
785
  env.engineModel,
781
786
  );
782
787
 
788
+ // #537 invocation scope is bound once at the public-entry boundary
789
+ // (runPostAdmissionResumable / ManualResume / station-child), not here:
790
+ // auto-resume re-enters this dispatch and must reuse the same scope.
791
+
783
792
  let result: RoleTurnResult;
784
793
  try {
785
794
  result = await env.roleTurnHost.executeTurn(turnRequest);
786
795
  } catch (error) {
787
796
  const settled = await settleAfterTurnStarted(
788
- admitted,
789
- {
797
+ admitted,
798
+ withEngineDetourInvocationScope({
790
799
  timedOut: false,
791
800
  code: null,
792
801
  stderr: "",
793
802
  thrown: error,
794
- },
803
+ }, request.invocationScopeId),
795
804
  adapters,
796
805
  env.principalAuthority,
797
806
  io,
@@ -851,9 +860,18 @@ export async function dispatchPostAdmissionTurn<
851
860
  }
852
861
 
853
862
  const courtScope =
854
- request.courtAttemptId === undefined || request.courtAttemptId.length === 0
863
+ (request.courtAttemptId === undefined || request.courtAttemptId.length === 0) &&
864
+ (request.invocationScopeId === undefined || request.invocationScopeId.length === 0)
855
865
  ? undefined
856
- : { courtAttemptId: request.courtAttemptId };
866
+ : {
867
+ ...(request.courtAttemptId === undefined || request.courtAttemptId.length === 0
868
+ ? {}
869
+ : { courtAttemptId: request.courtAttemptId }),
870
+ ...(request.invocationScopeId === undefined ||
871
+ request.invocationScopeId.length === 0
872
+ ? {}
873
+ : { invocationScopeId: request.invocationScopeId }),
874
+ };
857
875
 
858
876
  // Single complete boundary (#840 r9 判词 class 1): resolving the
859
877
  // host/runner failure facts, trySettle, its shouldPresent gate, and the
@@ -989,13 +1007,13 @@ export async function dispatchPostAdmissionTurn<
989
1007
  // Settle (or its shouldPresent gate, or the failure-fact resolution
990
1008
  // above) throw is a real failure fact — never swallow into undefined.
991
1009
  const settledFailure = await settleAfterTurnStarted(
992
- admitted,
993
- {
1010
+ admitted,
1011
+ withEngineDetourInvocationScope({
994
1012
  timedOut: false,
995
1013
  code: result.code,
996
1014
  stderr: result.stderr,
997
1015
  thrown: error,
998
- },
1016
+ }, request.invocationScopeId),
999
1017
  adapters,
1000
1018
  env.principalAuthority,
1001
1019
  io,
@@ -1019,14 +1037,14 @@ export async function dispatchPostAdmissionTurn<
1019
1037
  // write presentControlledFailure would otherwise retry — don't
1020
1038
  // call a known-failing operation twice.
1021
1039
  const failed = await settleAfterTurnStarted(
1022
- admitted,
1023
- {
1040
+ admitted,
1041
+ withEngineDetourInvocationScope({
1024
1042
  timedOut: false,
1025
1043
  code: null,
1026
1044
  stderr: "",
1027
1045
  thrown: error,
1028
1046
  skipRunStateWrite: true,
1029
- },
1047
+ }, request.invocationScopeId),
1030
1048
  adapters,
1031
1049
  env.principalAuthority,
1032
1050
  io,
@@ -1048,8 +1066,8 @@ export async function dispatchPostAdmissionTurn<
1048
1066
  ? undefined
1049
1067
  : { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
1050
1068
  const failed = await settleAfterTurnStarted(
1051
- admitted,
1052
- {
1069
+ admitted,
1070
+ withEngineDetourInvocationScope({
1053
1071
  timedOut: result.timedOut,
1054
1072
  code: result.code,
1055
1073
  stderr: result.stderr,
@@ -1067,7 +1085,7 @@ export async function dispatchPostAdmissionTurn<
1067
1085
  },
1068
1086
  }
1069
1087
  : { knownDetails: stderrLogWriteDetails }),
1070
- },
1088
+ }, request.invocationScopeId),
1071
1089
  adapters,
1072
1090
  env.principalAuthority,
1073
1091
  io,
@@ -1081,13 +1099,13 @@ export async function dispatchPostAdmissionTurn<
1081
1099
  // lawful absence of a receipt. Honest and loud, not silently no_receipt.
1082
1100
  if (stderrLogWriteFailure !== undefined) {
1083
1101
  const failed = await settleAfterTurnStarted(
1084
- admitted,
1085
- {
1102
+ admitted,
1103
+ withEngineDetourInvocationScope({
1086
1104
  timedOut: false,
1087
1105
  code: result.code,
1088
1106
  stderr: result.stderr,
1089
1107
  thrown: stderrLogWriteFailure,
1090
- },
1108
+ }, request.invocationScopeId),
1091
1109
  adapters,
1092
1110
  env.principalAuthority,
1093
1111
  io,
@@ -1098,7 +1116,7 @@ export async function dispatchPostAdmissionTurn<
1098
1116
 
1099
1117
  const noReceipt = await attachRecordedSubmissions(
1100
1118
  admitted,
1101
- await settleHostEndedNoReceipt(admitted, env.principalAuthority) as T,
1119
+ await settleHostEndedNoReceipt(admitted, env.principalAuthority, courtScope) as T,
1102
1120
  courtScope,
1103
1121
  );
1104
1122
  if (persistRunState) {
@@ -1112,13 +1130,13 @@ export async function dispatchPostAdmissionTurn<
1112
1130
  // skipRunStateWrite: don't retry the write that just threw.
1113
1131
  const failed = await settleAfterTurnStarted(
1114
1132
  admitted,
1115
- {
1133
+ withEngineDetourInvocationScope({
1116
1134
  timedOut: false,
1117
1135
  code: null,
1118
1136
  stderr: "",
1119
1137
  thrown: error,
1120
1138
  skipRunStateWrite: true,
1121
- },
1139
+ }, request.invocationScopeId),
1122
1140
  adapters,
1123
1141
  env.principalAuthority,
1124
1142
  io,
@@ -1463,6 +1481,12 @@ export async function runPostAdmissionSeatResume<
1463
1481
  let firstTurn: RoleTurnRequest | undefined;
1464
1482
  const stationAdapters = withOnceSuccessfulBeforeDispatch(adapters);
1465
1483
  type StationChildAttempt = { readonly resumeTurn: boolean };
1484
+ // One public call → one detour scope across in-place auto-resume dispatches.
1485
+ const invocationScopeId = mintEngineDetourInvocationScope({
1486
+ ...(input.effectiveEngine === undefined
1487
+ ? {}
1488
+ : { effectiveEngine: input.effectiveEngine }),
1489
+ });
1466
1490
  return await runWithAutoResumeLoop({
1467
1491
  admitted: loaded.admitted,
1468
1492
  principalAuthority: input.env.principalAuthority,
@@ -1500,7 +1524,10 @@ export async function runPostAdmissionSeatResume<
1500
1524
  },
1501
1525
  };
1502
1526
  }
1503
- const turnRequest = await buildRequestAfterLease();
1527
+ const turnRequest = withEngineDetourInvocationScope(
1528
+ await buildRequestAfterLease(),
1529
+ invocationScopeId,
1530
+ );
1504
1531
  firstTurn = turnRequest;
1505
1532
  return turnRequest;
1506
1533
  },
@@ -1621,6 +1648,15 @@ export async function runPostAdmissionResumable<
1621
1648
  const { admitted, env, io, buildInitialRequest, buildResumeRequest, effectiveEngine } = input;
1622
1649
  const adapters = withOnceSuccessfulBeforeDispatch(input.adapters);
1623
1650
 
1651
+ // One public call → one detour scope across in-place auto-resume dispatches.
1652
+ const invocationScopeId = mintEngineDetourInvocationScope({
1653
+ ...(effectiveEngine === undefined ? {} : { effectiveEngine }),
1654
+ });
1655
+ const buildScopedInitial = (): RoleTurnRequest =>
1656
+ withEngineDetourInvocationScope(buildInitialRequest(), invocationScopeId);
1657
+ const buildScopedResume = (): RoleTurnRequest =>
1658
+ withEngineDetourInvocationScope(buildResumeRequest(), invocationScopeId);
1659
+
1624
1660
  return runWithAutoResumeLoop({
1625
1661
  admitted,
1626
1662
  principalAuthority: env.principalAuthority,
@@ -1628,8 +1664,8 @@ export async function runPostAdmissionResumable<
1628
1664
  io,
1629
1665
  sessionAppender: env.sessionAppender,
1630
1666
  autoResumeLimit: env.autoResumeLimit,
1631
- buildInitialPayload: buildInitialRequest,
1632
- buildResumePayload: buildResumeRequest,
1667
+ buildInitialPayload: buildScopedInitial,
1668
+ buildResumePayload: buildScopedResume,
1633
1669
  dispatch: async (request, lease, _isFirst, attemptIo) => {
1634
1670
  const result = await dispatchPostAdmissionTurn({
1635
1671
  admitted,
@@ -1714,6 +1750,11 @@ export async function runPostAdmissionManualResume<
1714
1750
  throw error;
1715
1751
  }
1716
1752
 
1753
+ // Explicit public resume is a new counting unit — mint once for this call.
1754
+ const invocationScopeId = mintEngineDetourInvocationScope({
1755
+ ...(effectiveEngine === undefined ? {} : { effectiveEngine }),
1756
+ });
1757
+
1717
1758
  const result = await dispatchAfterWriterLease({
1718
1759
  lease,
1719
1760
  build: async () => {
@@ -1725,6 +1766,7 @@ export async function runPostAdmissionManualResume<
1725
1766
  }
1726
1767
  request = await buildRequestAfterLease();
1727
1768
  }
1769
+ request = withEngineDetourInvocationScope(request, invocationScopeId);
1728
1770
  return request;
1729
1771
  },
1730
1772
  dispatch: (turnRequest) =>