@akagilnc/pi-workflow-roles 0.1.5845 → 0.1.5853

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 (66) hide show
  1. package/README.md +4 -4
  2. package/README.zh-CN.md +4 -4
  3. package/dist/acp-host/production-host.js +348 -1254
  4. package/dist/acp-host/role-turn-host.js +4 -98
  5. package/dist/headless-host/production-host.js +15731 -16582
  6. package/dist/headless-host/role-turn-host.js +6 -31
  7. package/dist/migrate-book-topology.js +31 -16
  8. package/dist/navigator-attendance.js +54 -404
  9. package/dist/navigator-invocation-identity.js +0 -2
  10. package/dist/navigator-lookup-host.js +41 -0
  11. package/dist/navigator-lookup.js +68 -0
  12. package/dist/navigator-route-table.js +42 -0
  13. package/dist/package-contracts/navigator-output.js +3 -11
  14. package/dist/packaged-role-registry.js +3 -27
  15. package/dist/public-cli/instruction-seat-run.js +4 -1
  16. package/dist/public-cli/invocation.js +13 -26
  17. package/dist/public-cli/main.js +286 -114
  18. package/dist/public-cli/option-definitions.js +24 -4
  19. package/dist/public-cli/post-admission.js +0 -9
  20. package/dist/public-cli/role-seat-runtime.js +14 -0
  21. package/dist/public-cli/settlement.js +14 -40
  22. package/dist/public-cli/terminal.js +1 -8
  23. package/dist/public-cli/turn-request.js +0 -3
  24. package/dist/public-role-summons.js +0 -1
  25. package/dist/receipt-delivery-policy.js +0 -23
  26. package/dist/role-runtime-dependencies.js +4 -11
  27. package/dist/role-runtime.js +18 -194
  28. package/dist/submission-ledger.js +19 -15
  29. package/dist/ticket-progress.js +23 -5
  30. package/extensions/role-runtime.ts +0 -16
  31. package/package.json +1 -1
  32. package/scripts/build-package.mjs +3 -7
  33. package/src/acp-host/role-turn-host.ts +8 -97
  34. package/src/headless-host/description.ts +0 -2
  35. package/src/headless-host/role-turn-host.ts +7 -32
  36. package/src/host-contracts.ts +5 -7
  37. package/src/navigator-attendance.ts +86 -549
  38. package/src/navigator-invocation-identity.ts +0 -2
  39. package/src/navigator-lookup-host.ts +45 -0
  40. package/src/navigator-lookup.ts +98 -0
  41. package/src/navigator-route-table.ts +92 -0
  42. package/src/package-contracts/navigator-output.ts +4 -15
  43. package/src/packaged-role-registry.ts +3 -32
  44. package/src/public-cli/instruction-seat-run.ts +3 -0
  45. package/src/public-cli/invocation.ts +18 -30
  46. package/src/public-cli/option-definitions.ts +25 -4
  47. package/src/public-cli/post-admission.ts +0 -11
  48. package/src/public-cli/role-seat-runtime.ts +14 -0
  49. package/src/public-cli/settlement.ts +23 -51
  50. package/src/public-cli/terminal.ts +3 -18
  51. package/src/public-cli/turn-request.ts +0 -5
  52. package/src/public-role-summons.ts +0 -8
  53. package/src/receipt-delivery-policy.ts +0 -21
  54. package/src/role-runtime-dependencies.ts +4 -11
  55. package/src/role-runtime.ts +19 -210
  56. package/src/submission-ledger.ts +23 -16
  57. package/src/ticket-progress.ts +32 -5
  58. package/dist/navigator-public-session.js +0 -205
  59. package/dist/navigator-role.js +0 -6
  60. package/dist/navigator-session-contracts.js +0 -227
  61. package/dist/navigator-work-context.js +0 -72
  62. package/resources/navigator-route-playbook.md +0 -33
  63. package/src/navigator-public-session.ts +0 -305
  64. package/src/navigator-role.ts +0 -15
  65. package/src/navigator-session-contracts.ts +0 -320
  66. package/src/navigator-work-context.ts +0 -97
@@ -1,4 +1,3 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { readPageSync, RUN_STATE_FILE } from "./run-dossier.ts";
3
2
  import { admittedSourceRunPath } from "./public-cli/invocation.ts";
4
3
  import { readFileSync, writeSync } from "node:fs";
@@ -16,7 +15,7 @@ import {
16
15
  import { Value } from "typebox/value";
17
16
  import { sitianReport } from "./sitian-facade.ts";
18
17
  import { sitianReportSafe } from "./host-session-record.ts";
19
- import { createSubmissionLedgerHost, sealAcceptedSubmission } from "./submission-ledger.ts";
18
+ import { createSubmissionLedgerHost } from "./submission-ledger.ts";
20
19
  import { registerFiledSubmissionTool, type FiledSubmissionBeforeAccept } from "./filed-submission.ts";
21
20
  import {
22
21
  registerReportTicketTool,
@@ -35,7 +34,6 @@ import {
35
34
  durableSessionPointer,
36
35
  resolveBookKeyFromGit,
37
36
  } from "./activation-ledger.ts";
38
- import { readPackageMaterial } from "./session-opening-materials.ts";
39
37
  import { writeStderrJsonlRecord } from "./stderr-jsonl.ts";
40
38
  import {
41
39
  ENGINE_DETOUR_TOOL_NAME,
@@ -107,10 +105,6 @@ import {
107
105
  GATEKEEPER_TOOL_SPEC,
108
106
  type GatekeeperRuntimeDependencies,
109
107
  } from "./gatekeeper-role.ts";
110
- import {
111
- NAVIGATOR_TOOL_SPEC,
112
- type NavigatorRuntimeDependencies,
113
- } from "./navigator-role.ts";
114
108
  import {
115
109
  AUDITOR_TOOL_SPEC,
116
110
  type AuditorRuntimeDependencies,
@@ -120,7 +114,7 @@ import {
120
114
  auditedRunIdentityFromDirectory,
121
115
  auditedRunIdentityMaterial,
122
116
  } from "./audited-run-identity.ts";
123
- import { formatNavigatorReport, NAVIGATOR_EVENT_TYPE, NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, navigatorSubjectKey, navigatorUnavailableError, subjectPath, type NavigatorAttendance, type NavigatorAttendanceOptions, type NavigatorEvent, type NavigatorPhase, type NavigatorReport, type NavigatorSettlement, type NavigatorSubjectProvenance, type NavigatorWorkContext } from "./navigator-attendance.ts";
117
+ import { formatNavigatorReport, NAVIGATOR_EVENT_TYPE, subjectPath, type NavigatorAttendance, type NavigatorAttendanceOptions, type NavigatorEvent, type NavigatorPhase, type NavigatorReport, type NavigatorSettlement } from "./navigator-attendance.ts";
124
118
 
125
119
  import { AK_ROLE_AUDITOR_SOURCE_RUN_ENV } from "./auditor-soul.ts";
126
120
  import {
@@ -134,8 +128,8 @@ import {
134
128
  import { NAVIGATOR_POST_ROLE_GRACE_MS, raceNavigatorGrace } from "./public-cli/settlement.ts";
135
129
  import {
136
130
  PACKAGED_ROLE_REGISTRY,
131
+ isNavigatorSeat,
137
132
  packagedRoleActivationFlags,
138
- packagedRoleInputFlag,
139
133
  packagedRoleMetadata,
140
134
  packagedRoleOutputTool,
141
135
  packagedRolePhaseFlag,
@@ -473,8 +467,7 @@ export type RoleRuntimeDependencies = {
473
467
  loadNotarySourceRun?(path: string): Promise<import("./notary-contracts.ts").NotarySourceRunLocator>;
474
468
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
475
469
  loadMergerInput?(path: string): Promise<unknown>;
476
- createNavigatorAttendance?(options: { context: HostContext; role: string; phase: NavigatorPhase; subjectKey: string; contextError?: unknown; invocationId: string; deliveryRequestLimit?: number; onEvent: (event: import("./navigator-attendance.ts").NavigatorEvent, report: import("./navigator-attendance.ts").NavigatorReport) => void | Promise<void> }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
477
- loadNavigatorWorkContext?(options: { context: HostContext; role: string; phase: NavigatorPhase; getFlag?: (name: string) => unknown }): Promise<NavigatorWorkContext>;
470
+ createNavigatorAttendance?(options: { context: HostContext; role: string; phase: NavigatorPhase; subjectKey: string; invocationId: string; onEvent: NavigatorAttendanceOptions["onEvent"] }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
478
471
  activationClock?(): string;
479
472
  activationTraceWriter?: (record: ActivationTraceRecord) => void | Promise<void>;
480
473
  };
@@ -683,56 +676,6 @@ export function createGatekeeperRoleRuntime(
683
676
  );
684
677
  }
685
678
 
686
- /** #639: direct Navigator public seat on the shared filed-officer envelope. */
687
- export function createNavigatorRoleRuntime(
688
- roleHost: RoleHost,
689
- dependencies: NavigatorRuntimeDependencies,
690
- ) {
691
- const base = createFiledOfficerRuntime(
692
- roleHost,
693
- {
694
- role: "navigator",
695
- tool: NAVIGATOR_TOOL_SPEC,
696
- soulTag: "navigator",
697
- },
698
- dependencies,
699
- );
700
- let playbookBound = false;
701
- return {
702
- async activate() {
703
- await base.activate();
704
- if (playbookBound) return;
705
- playbookBound = true;
706
- // Standing system prompt, not a per-turn user message. Native read failure
707
- // is the explanation (ADR 0061); no code-written sentence.
708
- const loadRoutePlaybook = dependencies.loadRoutePlaybook
709
- ?? (() => readPackageMaterial("resources/navigator-route-playbook.md"));
710
- let playbookRead: Promise<string> | undefined;
711
- roleHost.on("before_agent_start", async (event, ctx) => {
712
- const basePrompt = typeof event.systemPrompt === "string" ? event.systemPrompt : "";
713
- const parts: string[] = [];
714
- try {
715
- playbookRead ??= loadRoutePlaybook();
716
- const content = await playbookRead;
717
- dependencies.recordRoutePlaybookReadFailure?.(undefined);
718
- if (content.trim() !== "") parts.push(content);
719
- } catch (error) {
720
- const message = errorText(error);
721
- if (message.trim() !== "") {
722
- dependencies.recordRoutePlaybookReadFailure?.(message);
723
- parts.push(message);
724
- }
725
- }
726
- if (parts.length === 0) return;
727
- const text = parts.join("\n\n");
728
- return {
729
- systemPrompt: basePrompt.trim() === "" ? text : `${basePrompt}\n\n${text}`,
730
- };
731
- });
732
- },
733
- };
734
- }
735
-
736
679
  /** #675: public 审刑院 seat on the shared filed-officer envelope. */
737
680
  export function createAuditorRoleRuntime(
738
681
  roleHost: RoleHost,
@@ -978,28 +921,6 @@ export function createCountersignRoleRuntime(
978
921
  );
979
922
  }
980
923
 
981
- /**
982
- * #959: last assistant text parts as navigator prose exit.
983
- * Tool-call-only messages yield undefined — those already ride the tool path.
984
- */
985
- function lastAssistantProse(
986
- messages: readonly { role: string; content?: readonly { type: string; text?: string }[] }[],
987
- ): string | undefined {
988
- for (let index = messages.length - 1; index >= 0; index -= 1) {
989
- const message = messages[index];
990
- if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
991
- const texts: string[] = [];
992
- for (const part of message.content) {
993
- if (part.type === "text" && typeof part.text === "string" && part.text.length > 0) {
994
- texts.push(part.text);
995
- }
996
- }
997
- if (texts.length === 0) continue;
998
- return texts.join("");
999
- }
1000
- return undefined;
1001
- }
1002
-
1003
924
  export function createRoleRuntimeExtension(
1004
925
  dependencies: RoleRuntimeDependencies,
1005
926
  /**
@@ -1069,7 +990,7 @@ export function createRoleRuntimeExtension(
1069
990
  let navigatorActivation = 0;
1070
991
  let navigatorDeliveryClosed = false;
1071
992
  let pendingNavigatorSettlement: Promise<void> | undefined;
1072
- let navigatorWorkContext: NavigatorWorkContext | undefined;
993
+ let navigatorSubjectKey: string | undefined;
1073
994
  let navigatorSessionParent: string | undefined;
1074
995
  let navigatorCwd: string | undefined;
1075
996
  /** toolCallId → fact+evidence; one-shot projected onto durable tool_result (#475). */
@@ -1121,7 +1042,7 @@ export function createRoleRuntimeExtension(
1121
1042
  const settleNavigatorProjection = async (settlement: NavigatorSettlement | undefined): Promise<NavigatorEvent | undefined> => {
1122
1043
  const attendance = navigatorAttendance;
1123
1044
  if (settlement === undefined || attendance === undefined) return;
1124
- const workContext = navigatorWorkContext;
1045
+ const subjectKey = navigatorSubjectKey;
1125
1046
  const pending = (async () => {
1126
1047
  // ADR 0052 / #959 / #1160: every settlement that waits on parallel prepare
1127
1048
  // (accepted, human_decision, role_infrastructure_failure) shares one grace
@@ -1139,7 +1060,6 @@ export function createRoleRuntimeExtension(
1139
1060
  disposition: "unavailable",
1140
1061
  unavailableReason: "Navigator exceeded post-role delivery grace",
1141
1062
  unavailableSource: "unknown",
1142
- unavailableCause: "unknown",
1143
1063
  };
1144
1064
  const event: NavigatorEvent = {
1145
1065
  version: 1,
@@ -1147,10 +1067,9 @@ export function createRoleRuntimeExtension(
1147
1067
  invocationId: "post-role-grace-timeout",
1148
1068
  role: settlement.role,
1149
1069
  phase: settlement.phase,
1150
- subjectKey: workContext?.subjectKey ?? "",
1070
+ subjectKey: subjectKey ?? "",
1151
1071
  unavailableReason: "Navigator exceeded post-role delivery grace",
1152
1072
  unavailableSource: "unknown",
1153
- unavailableCause: "unknown",
1154
1073
  };
1155
1074
  pendingNavigatorPresentation = { event, report };
1156
1075
  }
@@ -1209,30 +1128,6 @@ export function createRoleRuntimeExtension(
1209
1128
  ctx.sessionManager.appendCustomEntry?.(NOTARY_SESSION_BOUND_ENTRY, bound);
1210
1129
  }
1211
1130
  }
1212
- if (navigatorAttendance !== undefined && navigatorWorkContext !== undefined && navigatorWorkContext.contextError === undefined) {
1213
- // Flagged roles already have a concrete packet/task/case/review input.
1214
- // A bare Judge (and other bare packaged entrypoint) gets its concrete
1215
- // user task at this seam; do not copy the assembled system prompt.
1216
- // Replacement is keyed by typed subject provenance, never prose prefixes.
1217
- // Soft session_start placeholders (no materials yet) recover here; hard
1218
- // contextError from true loader failures stays poisoned and honest.
1219
- if (navigatorWorkContext.subjectProvenance === "placeholder") {
1220
- // Navigator's own input bootstrap (#836): human prompt bytes key the
1221
- // identity when nothing else has supplied a concrete subject yet.
1222
- // Auto prepare stays identity-only — no subject/authority material copy.
1223
- const subject = prompt.trim();
1224
- if (subject !== "") {
1225
- const root = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
1226
- const subjectProvenance = "user_prompt" satisfies NavigatorSubjectProvenance;
1227
- navigatorWorkContext = {
1228
- subjectKey: navigatorSubjectKey(root, subject, subjectProvenance),
1229
- subjectProvenance,
1230
- };
1231
- await navigatorAttendance.setWorkContext(navigatorWorkContext);
1232
- }
1233
- }
1234
- }
1235
- navigatorAttendance?.prepare();
1236
1131
  // Reviewer parent prompt assembly.
1237
1132
  if (activeReviewerParent !== undefined && selectedRole === role) {
1238
1133
  return {
@@ -1344,55 +1239,6 @@ export function createRoleRuntimeExtension(
1344
1239
  }
1345
1240
  return;
1346
1241
  }
1347
- const role = selectedRole ?? roleHost.getFlag(ROLE_FLAG.name);
1348
- // #959: navigator prose exit — final assistant text is the receipt.
1349
- // No typed-tool 催交; no JSON required. Tool path still wins when already accepted.
1350
- if (
1351
- typeof role === "string"
1352
- && packagedRoleOutputTool(role) === NAVIGATOR_TOOL_SPEC.name
1353
- && receiptDelivery.nextAction() !== "accepted"
1354
- ) {
1355
- const prose = lastAssistantProse(event.messages);
1356
- if (prose !== undefined && prose.trim() !== "") {
1357
- const accepted = { prose };
1358
- const toolCallId = `navigator-prose-exit:${randomUUID()}`;
1359
- await sealAcceptedSubmission({
1360
- context: ctx,
1361
- role: "navigator",
1362
- accepted,
1363
- toolCallId,
1364
- });
1365
- await projectClosedSubmission(
1366
- { role: "navigator", kind: "accepted", accepted },
1367
- ctx,
1368
- toolCallId,
1369
- );
1370
- return;
1371
- }
1372
- // Attended with neither tool nor prose → honest no_receipt (not typed 催交).
1373
- // Close the budget without sending prompts; the fact stays the real count.
1374
- if (!noReceiptRecorded) {
1375
- const runPointer = runDirectoryFromHostContext(ctx);
1376
- if (runPointer !== undefined) {
1377
- noReceiptRecorded = true;
1378
- receiptDelivery.closeBudget();
1379
- const facts = receiptDelivery.facts({
1380
- runPointer,
1381
- attemptPointer: receiptAttemptPointer(runPointer, ctx.invocationScopeId),
1382
- });
1383
- envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
1384
- sitianReportSafe({
1385
- level: "event",
1386
- kind: "no-receipt-lifecycle",
1387
- cwd: ctx.cwd,
1388
- sessionParent: ctx.sessionManager.getSessionFile(),
1389
- payload: facts,
1390
- source: "role-runtime",
1391
- });
1392
- }
1393
- }
1394
- return;
1395
- }
1396
1242
  if (receiptDelivery.nextAction() === "request-delivery") {
1397
1243
  receiptDelivery.recordDeliveryRequest();
1398
1244
  // Keep the package-owned continuation off the public input lifecycle:
@@ -1556,14 +1402,6 @@ export function createRoleRuntimeExtension(
1556
1402
  const gatekeeper = createGatekeeperRoleRuntime(roleHost, {
1557
1403
  loadSoul: () => requireRoleSoul("gatekeeper"),
1558
1404
  });
1559
- const navigator = createNavigatorRoleRuntime(roleHost, {
1560
- loadSoul: () => requireRoleSoul("navigator"),
1561
- recordRoutePlaybookReadFailure: (message) => {
1562
- envelopeHost.appendEntry(NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY, {
1563
- message: message ?? "",
1564
- });
1565
- },
1566
- });
1567
1405
  const auditor = createAuditorRoleRuntime(roleHost, {
1568
1406
  loadSoul: () => requireRoleSoul("auditor"),
1569
1407
  });
@@ -1674,7 +1512,7 @@ export function createRoleRuntimeExtension(
1674
1512
  navigatorDeliveryClosed = false;
1675
1513
  pendingNavigatorSettlement = undefined;
1676
1514
  pendingInfrastructureFailures.clear();
1677
- navigatorWorkContext = undefined;
1515
+ navigatorSubjectKey = undefined;
1678
1516
  // #351: OAuth keepalive is orthogonal to --ak-role; start before role early-return
1679
1517
  // so role-less sessions (and reload after shutdown stop) still keep tokens alive.
1680
1518
  envelopeHost.startKeepalive(ctx);
@@ -1707,7 +1545,9 @@ export function createRoleRuntimeExtension(
1707
1545
  "gleaner-left": () => gleanerLeft.activate(),
1708
1546
  inspector: () => inspector.activate(),
1709
1547
  gatekeeper: () => gatekeeper.activate(),
1710
- navigator: () => navigator.activate(),
1548
+ navigator: async () => {
1549
+ throw new Error("navigator is a table lookup (#1192); no host runtime serves it");
1550
+ },
1711
1551
  auditor: () => auditor.activate(),
1712
1552
  diarist: () => diarist.activate(),
1713
1553
  secretariat: () => secretariat.activate(),
@@ -1731,39 +1571,16 @@ export function createRoleRuntimeExtension(
1731
1571
 
1732
1572
  // Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
1733
1573
  // Top-level public entry legs still attend automatically.
1734
- // Navigator seat never re-attaches itself — prepare turns already ARE the navigator
1735
- // public activation (prevents summonPublicRole navigator ↔ attendance recursion).
1574
+ // The navigator seat never attends itself (summonPublicRole navigator ↔ attendance recursion).
1736
1575
  const isStationChild = roleHost.getFlag(STATION_CHILD_FLAG.name) === true;
1737
1576
  if (
1738
1577
  dependencies.createNavigatorAttendance !== undefined
1739
- && entry.outputTool !== NAVIGATOR_TOOL_SPEC.name
1578
+ && !isNavigatorSeat(entry.role)
1740
1579
  && !isStationChild
1741
1580
  ) {
1742
1581
  navigatorSessionParent = ctx.sessionManager.getSessionFile();
1743
1582
  navigatorCwd = ctx.cwd;
1744
- let work: NavigatorWorkContext;
1745
- let contextError: unknown;
1746
- if (dependencies.loadNavigatorWorkContext === undefined) {
1747
- const fallbackSubjectKey = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
1748
- contextError = new Error("Navigator work context loader is not configured");
1749
- work = { subjectKey: fallbackSubjectKey, subjectProvenance: "placeholder" };
1750
- } else {
1751
- try {
1752
- work = await dependencies.loadNavigatorWorkContext({
1753
- context: ctx,
1754
- role: entry.role,
1755
- phase: navigatorPhase(roleHost, entry.role),
1756
- getFlag: (name) => roleHost.getFlag(name),
1757
- });
1758
- contextError = work.contextError;
1759
- } catch (error) {
1760
- // Contract: README.md#Navigator-attendance — a failed context load continues with a typed placeholder work context; the original cause is retained in contextError for the typed unavailable report.
1761
- contextError = navigatorUnavailableError("context", error);
1762
- const fallbackSubjectKey = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
1763
- work = { subjectKey: fallbackSubjectKey, subjectProvenance: "placeholder" };
1764
- }
1765
- }
1766
- navigatorWorkContext = { ...work, ...(contextError === undefined ? {} : { contextError }) };
1583
+ navigatorSubjectKey = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
1767
1584
  // Shared envelope owns exact invocation principal from admitted session lifecycle.
1768
1585
  // session_start is process activation: resume unfinished principal only when marker
1769
1586
  // role/phase/subjectKey still match; mint for contradictory marker, malformed nearest,
@@ -1773,7 +1590,7 @@ export function createRoleRuntimeExtension(
1773
1590
  const lifecyclePrincipal = resolveLifecycleInvocationPrincipal(sessionEntries, {
1774
1591
  role: entry.role,
1775
1592
  phase: invocationPhase,
1776
- subjectKey: work.subjectKey,
1593
+ subjectKey: navigatorSubjectKey,
1777
1594
  });
1778
1595
  const invocationId = lifecyclePrincipal.invocationId;
1779
1596
  if (!lifecyclePrincipal.resume) {
@@ -1781,7 +1598,7 @@ export function createRoleRuntimeExtension(
1781
1598
  invocationId,
1782
1599
  role: entry.role,
1783
1600
  phase: invocationPhase,
1784
- subjectKey: work.subjectKey,
1601
+ subjectKey: navigatorSubjectKey,
1785
1602
  };
1786
1603
  envelopeHost.appendEntry(NAVIGATOR_INVOCATION_ENTRY, data);
1787
1604
  sitianReportSafe({
@@ -1798,24 +1615,16 @@ export function createRoleRuntimeExtension(
1798
1615
  context: ctx,
1799
1616
  role: entry.role,
1800
1617
  phase: invocationPhase,
1801
- subjectKey: work.subjectKey,
1618
+ subjectKey: navigatorSubjectKey,
1802
1619
  invocationId,
1803
- deliveryRequestLimit: deliveryLimit,
1804
- ...(contextError === undefined ? {} : { contextError }),
1805
1620
  onEvent: (navigatorEvent, report) => {
1806
1621
  if (activation === navigatorActivation && !navigatorDeliveryClosed) {
1807
1622
  pendingNavigatorPresentation = { event: navigatorEvent, report };
1808
1623
  }
1809
1624
  },
1810
1625
  });
1811
- // Concrete work context starts parallel prepare (model runs now; #1160).
1812
- // Placeholder subjects wait for before_agent_start (user prompt may replace the subject key).
1813
- if (
1814
- navigatorWorkContext.contextError === undefined &&
1815
- navigatorWorkContext.subjectProvenance !== "placeholder"
1816
- ) {
1817
- navigatorAttendance.prepare();
1818
- }
1626
+ // The lookup starts with the parent role (#1160 / #1192).
1627
+ navigatorAttendance.prepare();
1819
1628
  }
1820
1629
 
1821
1630
  await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { clock, writeTrace });
@@ -22,6 +22,7 @@ import { reportTicketFromHostContext } from "./report-ticket-tool.ts";
22
22
  import { readDeclaredTicketNumber } from "./run-ticket-number.ts";
23
23
  import {
24
24
  captureWorktreeHead,
25
+ landTicketProgress,
25
26
  landTicketProgressForSealedSubmission,
26
27
  } from "./ticket-progress.ts";
27
28
 
@@ -465,23 +466,27 @@ function homeFromHostContext(context: HostContext, home?: string): string | unde
465
466
  }
466
467
 
467
468
  /**
468
- * #959: seal one accepted submission without a model tool call.
469
- * Same ledger row shape as the terminating-tool wrap.
470
- * Used when a prose-exit seat (navigator) harvests the final assistant text.
469
+ * #1192: seal one accepted submission without a model tool call or host session
470
+ * (the navigator lookup run). Same sealed ledger event and the same original
471
+ * receipt / progress landing as the terminating-tool wrap; the progress line
472
+ * names no session because the run has none.
471
473
  */
472
- export async function sealAcceptedSubmission(options: {
473
- readonly context: HostContext;
474
+ export function sealAcceptedSubmission(options: {
475
+ readonly runDirectory: string;
476
+ readonly cwd: string;
474
477
  readonly role: TerminalRoleName;
475
478
  readonly accepted: unknown;
476
479
  readonly toolCallId: string;
477
480
  readonly home?: string;
478
- }): Promise<void> {
479
- const runId = runIdentity(options.context);
480
- const attemptId = attemptIdentity(options.context, runId);
481
- const sessionParent = sessionParentFromHostContext(options.context);
482
- const home = homeFromHostContext(options.context, options.home);
483
- // #1199: same seal-time HEAD + progress landing as the terminating-tool wrap.
484
- const sealHead = captureWorktreeHead(options.context.cwd);
481
+ readonly courtAttemptId?: string;
482
+ readonly instruction?: string;
483
+ }): void {
484
+ const runId = runIdFromRunDirectory(options.runDirectory);
485
+ if (runId === undefined) throw new Error("提交账需要已受理的 run 身份");
486
+ const attemptId = options.courtAttemptId ?? `${runId}:initial`;
487
+ const sealHead = captureWorktreeHead(options.cwd);
488
+ const sessionParent = sessionFileOf(options.runDirectory);
489
+ const home = options.home ?? tryHomeFromAkRolesPath(sessionParent);
485
490
  sitianReport({
486
491
  level: "event",
487
492
  kind: "sealed",
@@ -494,15 +499,17 @@ export async function sealAcceptedSubmission(options: {
494
499
  accepted: options.accepted,
495
500
  },
496
501
  source: "role-runtime",
497
- cwd: options.context.cwd,
502
+ cwd: options.cwd,
498
503
  ...(home !== undefined ? { home } : {}),
499
- ...(sessionParent === undefined ? {} : { sessionParent }),
504
+ sessionParent,
500
505
  });
501
- landTicketProgressForSealedSubmission({
502
- context: options.context,
506
+ landTicketProgress({
507
+ runDirectory: options.runDirectory,
503
508
  role: options.role,
504
509
  accepted: options.accepted,
505
510
  sealHead,
511
+ sessionFile: undefined,
512
+ instruction: options.instruction ?? "",
506
513
  });
507
514
  }
508
515
 
@@ -254,6 +254,34 @@ export function landTicketProgressForSealedSubmission(input: {
254
254
  }): TicketProgressLine | undefined {
255
255
  const runDirectory = runDirectoryFromHostContext(input.context);
256
256
  if (runDirectory === undefined) return undefined;
257
+ return landTicketProgress({
258
+ runDirectory,
259
+ role: input.role,
260
+ accepted: input.accepted,
261
+ sealHead: input.sealHead,
262
+ ...(input.lens === undefined ? {} : { lens: input.lens }),
263
+ sessionFile: progressSessionAbsolute(runDirectory, input.context),
264
+ instruction: typeof input.context.summonsInstruction === "string"
265
+ ? input.context.summonsInstruction
266
+ : "",
267
+ });
268
+ }
269
+
270
+ /**
271
+ * Original receipt plus progress line for one sealed submission.
272
+ * `sessionFile` is absent for a run that never had a host session (the
273
+ * navigator lookup, #1192): the line then names no session.
274
+ */
275
+ export function landTicketProgress(input: {
276
+ readonly runDirectory: string;
277
+ readonly role: string;
278
+ readonly accepted: unknown;
279
+ readonly sealHead: WorktreeHeadCapture;
280
+ readonly lens?: string;
281
+ readonly sessionFile: string | undefined;
282
+ readonly instruction: string;
283
+ }): TicketProgressLine | undefined {
284
+ const { runDirectory } = input;
257
285
 
258
286
  const subjectDirectory = subjectDirectoryOfRun(runDirectory);
259
287
  if (subjectDirectory === undefined) return undefined;
@@ -289,12 +317,11 @@ export function landTicketProgressForSealedSubmission(input: {
289
317
  // Unbound: round stays empty until whole-leg bind publishes into ticket current.
290
318
  const round = unbound ? "" : nextRoundFromLines(existing, input.role, lens);
291
319
 
292
- const sessionFile = progressSessionAbsolute(runDirectory, input.context);
293
- const sessionRelative = relative(subjectDirectory, sessionFile).split(sep).join("/");
320
+ const sessionRelative = input.sessionFile === undefined
321
+ ? ""
322
+ : relative(subjectDirectory, input.sessionFile).split(sep).join("/");
294
323
  const { status, summary } = statusAndSummaryFromPayload(input.role, input.accepted);
295
- const instruction = typeof input.context.summonsInstruction === "string"
296
- ? input.context.summonsInstruction
297
- : "";
324
+ const instruction = input.instruction;
298
325
  const line: TicketProgressLine = {
299
326
  at: new Date().toISOString(),
300
327
  seat: input.role,