harnery 0.34.1 → 0.35.0

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 (212) hide show
  1. package/dist/commander.d.ts.map +1 -1
  2. package/dist/commander.js +2 -0
  3. package/dist/commands/agents.d.ts +15 -4
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +89 -175
  6. package/dist/commands/browse.d.ts.map +1 -1
  7. package/dist/commands/browse.js +26 -2
  8. package/dist/commands/decision.d.ts.map +1 -1
  9. package/dist/commands/decision.js +2 -2
  10. package/dist/commands/events.d.ts.map +1 -1
  11. package/dist/commands/events.js +18 -1
  12. package/dist/commands/presence.d.ts.map +1 -1
  13. package/dist/commands/presence.js +2 -2
  14. package/dist/commands/semantic.d.ts +4 -0
  15. package/dist/commands/semantic.d.ts.map +1 -0
  16. package/dist/commands/semantic.js +147 -0
  17. package/dist/commands/web.d.ts.map +1 -1
  18. package/dist/commands/web.js +17 -0
  19. package/dist/core/agents/canonical-emit.d.ts +3 -0
  20. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  21. package/dist/core/agents/canonical-emit.js +12 -0
  22. package/dist/core/agents/claim-path.d.ts +11 -0
  23. package/dist/core/agents/claim-path.d.ts.map +1 -0
  24. package/dist/core/agents/claim-path.js +22 -0
  25. package/dist/core/agents/coord-client.d.ts +29 -3
  26. package/dist/core/agents/coord-client.d.ts.map +1 -1
  27. package/dist/core/agents/coord-client.js +88 -9
  28. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  29. package/dist/core/agents/live-authority-v3.js +1 -3
  30. package/dist/core/agents/live-lifecycle-v3.js +3 -3
  31. package/dist/core/agents/live-observation-v3.d.ts.map +1 -1
  32. package/dist/core/agents/live-observation-v3.js +2 -2
  33. package/dist/core/agents/render/prompt-context.js +12 -10
  34. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  35. package/dist/core/agents/rules/stop-hook.js +23 -11
  36. package/dist/core/agents/session-name-display.d.ts +1 -0
  37. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  38. package/dist/core/agents/session-name-display.js +8 -1
  39. package/dist/core/agents/state/heartbeat-reader.d.ts +46 -0
  40. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -0
  41. package/dist/core/agents/state/heartbeat-reader.js +25 -0
  42. package/dist/core/agents/state/heartbeat-writer.d.ts +3 -48
  43. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  44. package/dist/core/agents/state/heartbeat-writer.js +16 -34
  45. package/dist/core/agents/state/live-coordination-view.d.ts +1 -1
  46. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  47. package/dist/core/agents/state/live-coordination-view.js +25 -1
  48. package/dist/core/agents/state/names.d.ts +9 -1
  49. package/dist/core/agents/state/names.d.ts.map +1 -1
  50. package/dist/core/agents/state/names.js +190 -182
  51. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  52. package/dist/core/agents/state/stale-sweep.js +23 -5
  53. package/dist/core/artifacts/constants.d.ts +5 -0
  54. package/dist/core/artifacts/constants.d.ts.map +1 -0
  55. package/dist/core/artifacts/constants.js +4 -0
  56. package/dist/core/artifacts/index.d.ts +3 -3
  57. package/dist/core/artifacts/index.d.ts.map +1 -1
  58. package/dist/core/artifacts/index.js +50 -7
  59. package/dist/core/events/v3/base-contract.d.ts +795 -0
  60. package/dist/core/events/v3/base-contract.d.ts.map +1 -1
  61. package/dist/core/events/v3/base-contract.js +33 -0
  62. package/dist/core/events/v3/capabilities.d.ts +11 -0
  63. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  64. package/dist/core/events/v3/capabilities.js +23 -0
  65. package/dist/core/events/v3/generated.d.ts +1 -1
  66. package/dist/core/events/v3/generated.js +1 -1
  67. package/dist/core/events/v3/index.d.ts +4 -3
  68. package/dist/core/events/v3/index.d.ts.map +1 -1
  69. package/dist/core/events/v3/index.js +4 -3
  70. package/dist/core/events/v3/latency.d.ts +31 -1
  71. package/dist/core/events/v3/latency.d.ts.map +1 -1
  72. package/dist/core/events/v3/latency.js +127 -3
  73. package/dist/core/events/v3/live-routing.d.ts +30 -0
  74. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  75. package/dist/core/events/v3/live-routing.js +109 -1
  76. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  77. package/dist/core/events/v3/producers/command-recorder.js +10 -1
  78. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  79. package/dist/core/events/v3/producers/coordination-recorder.js +14 -3
  80. package/dist/core/events/v3/producers/hook-base.d.ts +17 -1
  81. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  82. package/dist/core/events/v3/producers/hook-base.js +82 -1
  83. package/dist/core/events/v3/producers/intake.d.ts +6 -0
  84. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  85. package/dist/core/events/v3/producers/intake.js +11 -0
  86. package/dist/core/events/v3/producers/recorder.d.ts +62 -0
  87. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  88. package/dist/core/events/v3/producers/recorder.js +654 -18
  89. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +51 -0
  90. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -0
  91. package/dist/core/events/v3/runtime-telemetry-capabilities.js +54 -0
  92. package/dist/core/events/v3/turn-telemetry.d.ts +9 -0
  93. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
  94. package/dist/core/hooks/adapter/parse.d.ts +3 -0
  95. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  96. package/dist/core/hooks/adapter/parse.js +14 -0
  97. package/dist/core/hooks/adapter/runtime-telemetry.d.ts +119 -0
  98. package/dist/core/hooks/adapter/runtime-telemetry.d.ts.map +1 -0
  99. package/dist/core/hooks/adapter/runtime-telemetry.js +568 -0
  100. package/dist/core/hooks/cli.js +111 -36
  101. package/dist/core/hooks/remediation-cap.d.ts +34 -0
  102. package/dist/core/hooks/remediation-cap.d.ts.map +1 -0
  103. package/dist/core/hooks/remediation-cap.js +71 -0
  104. package/dist/core/hooks/resolve/transcript.d.ts +25 -9
  105. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  106. package/dist/core/hooks/resolve/transcript.js +22 -10
  107. package/dist/core/semantic/adapters.d.ts +34 -0
  108. package/dist/core/semantic/adapters.d.ts.map +1 -0
  109. package/dist/core/semantic/adapters.js +222 -0
  110. package/dist/core/semantic/contract.d.ts +588 -0
  111. package/dist/core/semantic/contract.d.ts.map +1 -0
  112. package/dist/core/semantic/contract.js +230 -0
  113. package/dist/core/semantic/evidence.d.ts +11 -0
  114. package/dist/core/semantic/evidence.d.ts.map +1 -0
  115. package/dist/core/semantic/evidence.js +262 -0
  116. package/dist/core/semantic/index.d.ts +10 -0
  117. package/dist/core/semantic/index.d.ts.map +1 -0
  118. package/dist/core/semantic/index.js +9 -0
  119. package/dist/core/semantic/once.d.ts +33 -0
  120. package/dist/core/semantic/once.d.ts.map +1 -0
  121. package/dist/core/semantic/once.js +293 -0
  122. package/dist/core/semantic/prompt.d.ts +12 -0
  123. package/dist/core/semantic/prompt.d.ts.map +1 -0
  124. package/dist/core/semantic/prompt.js +70 -0
  125. package/dist/core/semantic/scheduler.d.ts +16 -0
  126. package/dist/core/semantic/scheduler.d.ts.map +1 -0
  127. package/dist/core/semantic/scheduler.js +67 -0
  128. package/dist/core/semantic/service-status.d.ts +31 -0
  129. package/dist/core/semantic/service-status.d.ts.map +1 -0
  130. package/dist/core/semantic/service-status.js +84 -0
  131. package/dist/core/semantic/service.d.ts +51 -0
  132. package/dist/core/semantic/service.d.ts.map +1 -0
  133. package/dist/core/semantic/service.js +470 -0
  134. package/dist/core/semantic/storage.d.ts +71 -0
  135. package/dist/core/semantic/storage.d.ts.map +1 -0
  136. package/dist/core/semantic/storage.js +179 -0
  137. package/dist/core/semantic/validate.d.ts +16 -0
  138. package/dist/core/semantic/validate.d.ts.map +1 -0
  139. package/dist/core/semantic/validate.js +174 -0
  140. package/dist/lib/browser/client.d.ts +11 -0
  141. package/dist/lib/browser/client.d.ts.map +1 -1
  142. package/dist/lib/browser/client.js +13 -0
  143. package/dist/lib/browser/index.d.ts +1 -0
  144. package/dist/lib/browser/index.d.ts.map +1 -1
  145. package/dist/lib/browser/standalone-html.d.ts +25 -0
  146. package/dist/lib/browser/standalone-html.d.ts.map +1 -0
  147. package/dist/lib/browser/standalone-html.js +284 -0
  148. package/dist/lib/instructions/apply.d.ts.map +1 -1
  149. package/dist/lib/instructions/apply.js +1 -0
  150. package/dist/lib/instructions/templates.d.ts +2 -0
  151. package/dist/lib/instructions/templates.d.ts.map +1 -1
  152. package/dist/lib/instructions/templates.js +144 -4
  153. package/package.json +6 -1
  154. package/schemas/event-v3.schema.json +2758 -478
  155. package/src/commander.ts +2 -0
  156. package/src/commands/agents.ts +102 -167
  157. package/src/commands/browse.ts +25 -2
  158. package/src/commands/decision.ts +8 -2
  159. package/src/commands/events.ts +21 -1
  160. package/src/commands/presence.ts +7 -2
  161. package/src/commands/semantic.ts +179 -0
  162. package/src/commands/web.ts +15 -0
  163. package/src/core/agents/canonical-emit.ts +15 -0
  164. package/src/core/agents/claim-path.ts +23 -0
  165. package/src/core/agents/coord-client.ts +92 -8
  166. package/src/core/agents/live-authority-v3.ts +1 -4
  167. package/src/core/agents/live-lifecycle-v3.ts +8 -3
  168. package/src/core/agents/live-observation-v3.ts +7 -2
  169. package/src/core/agents/render/prompt-context.ts +13 -10
  170. package/src/core/agents/rules/stop-hook.ts +24 -11
  171. package/src/core/agents/session-name-display.ts +9 -1
  172. package/src/core/agents/state/heartbeat-reader.ts +69 -0
  173. package/src/core/agents/state/heartbeat-writer.ts +20 -84
  174. package/src/core/agents/state/live-coordination-view.ts +26 -1
  175. package/src/core/agents/state/names.ts +190 -182
  176. package/src/core/agents/state/stale-sweep.ts +25 -5
  177. package/src/core/artifacts/constants.ts +4 -0
  178. package/src/core/artifacts/index.ts +49 -7
  179. package/src/core/events/v3/base-contract.ts +39 -0
  180. package/src/core/events/v3/capabilities.ts +34 -0
  181. package/src/core/events/v3/generated.ts +1 -1
  182. package/src/core/events/v3/index.ts +21 -0
  183. package/src/core/events/v3/latency.ts +213 -2
  184. package/src/core/events/v3/live-routing.ts +120 -1
  185. package/src/core/events/v3/producers/command-recorder.ts +10 -1
  186. package/src/core/events/v3/producers/coordination-recorder.ts +17 -5
  187. package/src/core/events/v3/producers/hook-base.ts +115 -1
  188. package/src/core/events/v3/producers/intake.ts +18 -0
  189. package/src/core/events/v3/producers/recorder.ts +856 -21
  190. package/src/core/events/v3/runtime-telemetry-capabilities.ts +137 -0
  191. package/src/core/events/v3/turn-telemetry.ts +10 -0
  192. package/src/core/hooks/adapter/parse.ts +16 -0
  193. package/src/core/hooks/adapter/runtime-telemetry.ts +805 -0
  194. package/src/core/hooks/cli.ts +123 -39
  195. package/src/core/hooks/remediation-cap.ts +77 -0
  196. package/src/core/hooks/resolve/transcript.ts +37 -11
  197. package/src/core/semantic/adapters.ts +293 -0
  198. package/src/core/semantic/contract.ts +298 -0
  199. package/src/core/semantic/evidence.ts +336 -0
  200. package/src/core/semantic/index.ts +127 -0
  201. package/src/core/semantic/once.ts +410 -0
  202. package/src/core/semantic/prompt.ts +84 -0
  203. package/src/core/semantic/scheduler.ts +107 -0
  204. package/src/core/semantic/service-status.ts +118 -0
  205. package/src/core/semantic/service.ts +580 -0
  206. package/src/core/semantic/storage.ts +286 -0
  207. package/src/core/semantic/validate.ts +212 -0
  208. package/src/lib/browser/client.ts +21 -0
  209. package/src/lib/browser/index.ts +4 -0
  210. package/src/lib/browser/standalone-html.ts +312 -0
  211. package/src/lib/instructions/apply.ts +1 -0
  212. package/src/lib/instructions/templates.ts +149 -4
@@ -3,17 +3,19 @@ import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, open
3
3
  import { hostname } from "node:os";
4
4
  import { basename, join, resolve } from "node:path";
5
5
  import { extractBashCommand } from "../../../hooks/adapter/parse.js";
6
+ import { discoverCodexSessionTranscript, readRuntimeContextTelemetry, readRuntimeTuning, } from "../../../hooks/adapter/runtime-telemetry.js";
6
7
  import { fsyncParentDirectory } from "../../../workflow/durable-record.js";
7
8
  import { acquireNoClobberLease } from "../../../workflow/workspaces/leases.js";
8
9
  import { buildEventV3 } from "../builder.js";
9
- import { fingerprintV3, normalizeNativeIdV3 } from "../canonical.js";
10
+ import { canonicalJsonV3, fingerprintV3, normalizeNativeIdV3 } from "../canonical.js";
10
11
  import { adapterCapabilityProfileDigestV3, adapterSignalSupportV3, cursorToolChannelSupportV3, } from "../capabilities.js";
11
12
  import { capabilityDriftPayloadsV3 } from "../capability-drift.js";
12
13
  import { markObservedClockRegressionV3 } from "../clock-order.js";
13
14
  import { readEventV3ControlState } from "../control.js";
14
15
  import { fingerprintContextV3 } from "../fingerprint-keys.js";
15
- import { attestationIdV3, clockIdV3, delegationIdV3, generationIdV3, spanIdV3 } from "../ids.js";
16
+ import { attestationIdV3, clockIdV3, delegationIdV3, eventIdV3, generationIdV3, spanIdV3, } from "../ids.js";
16
17
  import { readLedgerV3 } from "../reader.js";
18
+ import { effectiveRuntimeTelemetryCapabilitiesV3, } from "../runtime-telemetry-capabilities.js";
17
19
  import { closeSpanStateV3, openSpanStateV3, recoverSpanUpperBoundV3, } from "../span-state.js";
18
20
  import { extractTurnTelemetryV3, } from "../turn-telemetry.js";
19
21
  import { assertEventV3, validateEventV3 } from "../validate.js";
@@ -27,6 +29,10 @@ const CLOSED_SPAN_TURN_RETENTION = 2;
27
29
  const CLOSED_SPAN_MEMORY_CAP = 512;
28
30
  const CLOSED_TURN_MEMORY_CAP = 16;
29
31
  const SPAN_SOFT_WATERMARK = 128;
32
+ const PENDING_RUNTIME_CONTEXT_CAP = 4;
33
+ const RUNTIME_CONTEXT_RETRY_LIMIT = 2;
34
+ /** Finalization is off the interactive hook path; briefly cover Codex's delayed transcript flush. */
35
+ const APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS = [0, 250, 250];
30
36
  /**
31
37
  * Adapters whose turn-boundary recovery and mid-flight onboarding are enabled.
32
38
  * Kept in code, outside the digested capability profiles, so tuning recovery
@@ -88,7 +94,14 @@ function hookSignalGate(control, adapter, signal) {
88
94
  return undefined;
89
95
  }
90
96
  function sessionHashForSignal(input, context) {
91
- const nativeSession = input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id;
97
+ const nativeChild = input.payload.subagent_id ?? input.payload.agent_id;
98
+ const childOwnsSignal = input.signal !== "sub-agent-start" &&
99
+ input.signal !== "sub-agent-stop" &&
100
+ nativeChild !== undefined &&
101
+ input.instance_id === asLiveInstanceId(nativeChild);
102
+ const nativeSession = childOwnsSignal
103
+ ? nativeChild
104
+ : (input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id);
92
105
  if (nativeSession !== undefined) {
93
106
  return normalizeNativeIdV3(context, `${input.adapter}.session`, nativeSession);
94
107
  }
@@ -130,6 +143,7 @@ function intakeRecord(input) {
130
143
  : {}),
131
144
  ...(input.stop_remediation !== undefined ? { stop_remediation: input.stop_remediation } : {}),
132
145
  ...(input.turn_ritual ? { turn_ritual: input.turn_ritual } : {}),
146
+ ...(input.delegated_child ? { delegated_child: input.delegated_child } : {}),
133
147
  };
134
148
  }
135
149
  function inputForIntakeRecord(coordRoot, record) {
@@ -152,6 +166,7 @@ function inputForIntakeRecord(coordRoot, record) {
152
166
  ...(record.hook_duration_ms !== undefined ? { hook_duration_ms: record.hook_duration_ms } : {}),
153
167
  ...(record.stop_remediation !== undefined ? { stop_remediation: record.stop_remediation } : {}),
154
168
  ...(record.turn_ritual ? { turn_ritual: record.turn_ritual } : {}),
169
+ ...(record.delegated_child ? { delegated_child: record.delegated_child } : {}),
155
170
  };
156
171
  }
157
172
  /**
@@ -386,6 +401,9 @@ function processHookSignalLocked(control, input, sessionHash, path) {
386
401
  (cursorPromptWithinOpenTurn ||
387
402
  (nativeTid !== undefined && state.current_turn_id === nativeTid));
388
403
  recordTurnHarnessTiming(state, input, !duplicateOpenTurnStart);
404
+ reconcilePendingRuntimeContexts(input, state, path, rootId, fingerprintContext, {
405
+ finalAttempt: input.signal === "session-end",
406
+ });
389
407
  closeResolvedWaits(input, state, path, rootId, fingerprintContext, nativeTid);
390
408
  if (input.signal === "permission-request") {
391
409
  const waitId = waitIdForInput(input, fingerprintContext);
@@ -435,6 +453,7 @@ function processHookSignalLocked(control, input, sessionHash, path) {
435
453
  boot_id: state.boot_id,
436
454
  clock: eventClock,
437
455
  });
456
+ state.current_native_turn_id = input.payload.turn_id;
438
457
  }
439
458
  let sourceId = sourceIdForSignal(input, rootFingerprintContext);
440
459
  const semanticKey = cursorShellSemanticKey(input, rootFingerprintContext);
@@ -628,7 +647,7 @@ function processHookSignalLocked(control, input, sessionHash, path) {
628
647
  })
629
648
  : undefined;
630
649
  const turnTelemetry = input.signal === "stop" || input.signal === "stop-failure"
631
- ? extractTurnTelemetryV3(input.adapter, input.payload.raw, eventClock.observed_at)
650
+ ? turnTelemetryForTerminal(input, state, eventClock.observed_at)
632
651
  : undefined;
633
652
  const cursorToolChannelSupport = input.adapter === "cursor"
634
653
  ? cursorToolChannelSupportV3(state.cursor_mode ?? "unknown")
@@ -642,6 +661,9 @@ function processHookSignalLocked(control, input, sessionHash, path) {
642
661
  }
643
662
  const toolCallCountScopeMismatch = (input.signal === "stop" || input.signal === "stop-failure") &&
644
663
  (!state.current_turn_id || state.tool_call_count_turn_id !== state.current_turn_id);
664
+ // Before the signal's own event: the tool call or turn that carries the
665
+ // new effort already ran under it, so it must ride the new attestation.
666
+ maybeAttestTuningChange(input, state, path, rootId);
645
667
  const event = normalizeHookEventV3(input.signal, eventInput.payload, {
646
668
  coordRoot: input.coordRoot,
647
669
  adapter: input.adapter,
@@ -665,6 +687,7 @@ function processHookSignalLocked(control, input, sessionHash, path) {
665
687
  platform: input.platform,
666
688
  bridge: input.bridge,
667
689
  capability_profile: state.capability_profile,
690
+ cursor_mode: state.cursor_mode,
668
691
  fingerprintContext,
669
692
  turn_id: state.current_turn_id,
670
693
  span_id: openingSpan?.span_id ?? closingSpan?.span_id,
@@ -702,6 +725,44 @@ function processHookSignalLocked(control, input, sessionHash, path) {
702
725
  });
703
726
  if (!event)
704
727
  return { state: "ignored" };
728
+ if (input.signal === "session-start" &&
729
+ input.delegated_child &&
730
+ event.event_type === "session.started") {
731
+ event.links = {
732
+ caused_by: [input.delegated_child.caused_by_event_id],
733
+ parent_generation_id: input.delegated_child.parent_generation_id,
734
+ delegation_id: input.delegated_child.delegation_id,
735
+ };
736
+ event.provenance = {
737
+ ...event.provenance,
738
+ source_event: `${input.adapter}.subagent-start.child-generation`,
739
+ attestation: "derived",
740
+ confidence: "high",
741
+ attribution: {
742
+ method: "native_payload",
743
+ state: "verified",
744
+ subject_instance_id: state.instance_id,
745
+ },
746
+ };
747
+ assertEventV3(event);
748
+ }
749
+ if (input.signal === "user-prompt-submit" &&
750
+ input.delegated_child &&
751
+ event.event_type === "turn.started") {
752
+ event.links = { caused_by: [input.delegated_child.caused_by_event_id] };
753
+ event.provenance = {
754
+ ...event.provenance,
755
+ source_event: `${input.adapter}.subagent-start.child-turn`,
756
+ attestation: "derived",
757
+ confidence: "high",
758
+ attribution: {
759
+ method: "native_payload",
760
+ state: "verified",
761
+ subject_instance_id: state.instance_id,
762
+ },
763
+ };
764
+ assertEventV3(event);
765
+ }
705
766
  if (input.signal === "session-start" &&
706
767
  input.session_start_derivation === "approved_lifecycle_reopen" &&
707
768
  event.event_type === "session.started") {
@@ -742,7 +803,9 @@ function processHookSignalLocked(control, input, sessionHash, path) {
742
803
  commitProgressObservation(input, state, path, rootId, event, progressKind);
743
804
  }
744
805
  if (turnTelemetry) {
745
- commitTurnContextObservation(input, state, path, rootId, event, turnTelemetry.context);
806
+ const contextTarget = turnContextTarget(event);
807
+ queuePendingRuntimeContext(input, state, contextTarget, turnTelemetry.context);
808
+ commitTurnContextObservation(input, state, path, rootId, fingerprintContext, contextTarget, turnTelemetry.context, turnTelemetry.context_provenance);
746
809
  }
747
810
  if (event.event_type === "session.ended") {
748
811
  commitCapabilityDriftEvents(input, state, path, rootId);
@@ -1101,8 +1164,8 @@ function commitEventLocked(input, state, path, event, sourceId) {
1101
1164
  publishProducerState(path, state);
1102
1165
  return durability;
1103
1166
  }
1104
- function commitTurnContextObservation(input, state, path, rootId, turnTerminal, measurement) {
1105
- if (turnTerminal.event_type !== "turn.completed")
1167
+ function commitTurnContextObservation(input, state, path, rootId, fingerprintContext, target, measurement, contextProvenance, observedAt) {
1168
+ if (!target)
1106
1169
  return;
1107
1170
  const event = buildEventV3("context.observed", {
1108
1171
  producer: {
@@ -1119,34 +1182,523 @@ function commitTurnContextObservation(input, state, path, rootId, turnTerminal,
1119
1182
  instance_id: state.instance_id,
1120
1183
  session_id: state.session_id,
1121
1184
  generation_id: state.generation_id,
1122
- turn_id: turnTerminal.scope.turn_id,
1123
- ...(input.run_id ? { run_id: input.run_id } : {}),
1124
- ...(input.workflow_id ? { workflow_id: input.workflow_id } : {}),
1125
- ...(input.workflow_agent_id ? { workflow_agent_id: input.workflow_agent_id } : {}),
1185
+ turn_id: target.turn_id,
1186
+ ...(target.run_id ? { run_id: target.run_id } : {}),
1187
+ ...(target.workflow_id ? { workflow_id: target.workflow_id } : {}),
1188
+ ...(target.workflow_agent_id ? { workflow_agent_id: target.workflow_agent_id } : {}),
1126
1189
  },
1127
1190
  attestation_id: state.attestation_id,
1128
- links: { caused_by: [turnTerminal.event_id] },
1191
+ links: { caused_by: [target.terminal_event_id] },
1129
1192
  provenance: {
1130
- source_event: `${input.adapter}.turn-context`,
1193
+ source_event: safeRole([
1194
+ contextProvenance?.source_event ?? `${input.adapter}.turn-context`,
1195
+ contextProvenance?.runtime_version,
1196
+ ]
1197
+ .filter(Boolean)
1198
+ .join(".")),
1199
+ attestation: contextProvenance?.attestation ?? "native",
1200
+ confidence: contextProvenance?.confidence ?? "exact",
1201
+ ...(contextProvenance?.source_witness
1202
+ ? {
1203
+ source_record_id: normalizeNativeIdV3(fingerprintContext, `${input.adapter}.runtime-context`, contextProvenance.source_witness),
1204
+ }
1205
+ : {}),
1206
+ attribution: {
1207
+ method: "native_payload",
1208
+ state: "verified",
1209
+ subject_instance_id: state.instance_id,
1210
+ },
1211
+ },
1212
+ // The source sample may precede native task completion. The event itself
1213
+ // is committed after turn.completed, while measurement.measured_at keeps
1214
+ // the sample clock without creating a causal wall-clock regression.
1215
+ observed_at: observedAt ?? target.terminal_observed_at,
1216
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1217
+ clock_id: state.clock_id,
1218
+ payload: {
1219
+ measurement,
1220
+ },
1221
+ });
1222
+ commitEventLocked(input, state, path, event);
1223
+ maybeAttestContextCapabilityChange(input, state, path, rootId, measurement, contextProvenance);
1224
+ }
1225
+ function maybeAttestContextCapabilityChange(input, state, path, rootId, measurement, provenance) {
1226
+ const context = measurement.state === "observed"
1227
+ ? {
1228
+ state: "observed",
1229
+ source: provenance?.source_event ?? `${input.adapter}.turn_context`,
1230
+ attestation: provenance?.attestation ?? measurement.attestation,
1231
+ confidence: provenance?.confidence ?? measurement.confidence,
1232
+ }
1233
+ : measurement.state === "expected_but_missing"
1234
+ ? { state: "partial", reason: measurement.reason }
1235
+ : { state: "unsupported" };
1236
+ const telemetry = effectiveRuntimeTelemetryCapabilitiesV3({
1237
+ adapter: input.adapter,
1238
+ ...(state.cursor_mode ? { cursor_mode: state.cursor_mode } : {}),
1239
+ context,
1240
+ canonical_turn_boundaries: true,
1241
+ });
1242
+ if (!state.last_attested_telemetry)
1243
+ return;
1244
+ if (canonicalJsonV3(telemetry) === canonicalJsonV3(state.last_attested_telemetry))
1245
+ return;
1246
+ if (!state.last_attested_model_observation || !state.last_attested_tuning_observation)
1247
+ return;
1248
+ commitRuntimeAttestationChange(input, state, path, rootId, {
1249
+ model: state.last_attested_model_observation,
1250
+ tuning: state.last_attested_tuning_observation,
1251
+ telemetry,
1252
+ reason: "runtime_telemetry_changed",
1253
+ source_event: provenance?.source_event ?? `${input.adapter}.turn_context`,
1254
+ attestation: provenance?.attestation ?? "derived",
1255
+ confidence: provenance?.confidence ?? (measurement.state === "observed" ? "exact" : "high"),
1256
+ });
1257
+ }
1258
+ function turnContextTarget(turnTerminal) {
1259
+ if (turnTerminal.event_type !== "turn.completed")
1260
+ return undefined;
1261
+ const scope = turnTerminal.scope;
1262
+ return {
1263
+ terminal_event_id: turnTerminal.event_id,
1264
+ terminal_observed_at: turnTerminal.time.observed_at,
1265
+ turn_id: scope.turn_id,
1266
+ ...(scope.run_id ? { run_id: scope.run_id } : {}),
1267
+ ...(scope.workflow_id ? { workflow_id: scope.workflow_id } : {}),
1268
+ ...(scope.workflow_agent_id ? { workflow_agent_id: scope.workflow_agent_id } : {}),
1269
+ };
1270
+ }
1271
+ function queuePendingRuntimeContext(input, state, target, measurement) {
1272
+ if (input.adapter !== "codex" ||
1273
+ !target ||
1274
+ measurement.state !== "expected_but_missing" ||
1275
+ !retryableRuntimeContextReason(measurement.reason)) {
1276
+ return;
1277
+ }
1278
+ const nativeSessionId = input.payload.session_id ?? input.payload.conversation_id;
1279
+ const nativeTurnId = input.payload.turn_id;
1280
+ if (!nativeSessionId || !nativeTurnId)
1281
+ return;
1282
+ const pending = {
1283
+ ...target,
1284
+ native_session_id: nativeSessionId,
1285
+ native_turn_id: nativeTurnId,
1286
+ ...((input.payload.transcript_path ?? state.runtime_transcript_path)
1287
+ ? { transcript_path: input.payload.transcript_path ?? state.runtime_transcript_path }
1288
+ : {}),
1289
+ ...(input.adapterVersion ? { runtime_version: input.adapterVersion } : {}),
1290
+ attempts: 0,
1291
+ };
1292
+ state.pending_runtime_contexts = [
1293
+ ...(state.pending_runtime_contexts ?? []).filter((candidate) => candidate.turn_id !== target.turn_id),
1294
+ pending,
1295
+ ].slice(-PENDING_RUNTIME_CONTEXT_CAP);
1296
+ }
1297
+ function reconcilePendingRuntimeContexts(input, state, path, rootId, fingerprintContext, options = {}) {
1298
+ if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length)
1299
+ return;
1300
+ for (const pending of [...state.pending_runtime_contexts]) {
1301
+ const runtime = readRuntimeContextTelemetry({
1302
+ adapter: "codex",
1303
+ session_id: pending.native_session_id,
1304
+ turn_id: pending.native_turn_id,
1305
+ transcript_path: pending.transcript_path,
1306
+ mode: "turn",
1307
+ }, input.runtimeTelemetryOptions);
1308
+ if (runtime.bytes_read > 0)
1309
+ recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
1310
+ const queue = state.pending_runtime_contexts;
1311
+ if (!queue)
1312
+ break;
1313
+ const index = queue.findIndex((candidate) => candidate.turn_id === pending.turn_id);
1314
+ if (index < 0)
1315
+ continue;
1316
+ if (runtime.state === "observed") {
1317
+ queue.splice(index, 1);
1318
+ if (queue.length === 0) {
1319
+ state.pending_runtime_contexts = undefined;
1320
+ }
1321
+ commitTurnContextObservation(input, state, path, rootId, fingerprintContext, pending, runtimeContextObservation(runtime), {
1322
+ source_event: runtime.source_event,
1323
+ source_witness: runtime.source_witness,
1324
+ ...(pending.runtime_version ? { runtime_version: pending.runtime_version } : {}),
1325
+ attestation: "derived",
1326
+ confidence: "exact",
1327
+ }, signalClock(input).observed_at);
1328
+ continue;
1329
+ }
1330
+ const attempts = pending.attempts + 1;
1331
+ if (options.finalAttempt ||
1332
+ runtime.state === "unsupported" ||
1333
+ !retryableRuntimeContextReason(runtime.reason) ||
1334
+ (!options.deferRetryLimit && attempts >= RUNTIME_CONTEXT_RETRY_LIMIT)) {
1335
+ queue.splice(index, 1);
1336
+ }
1337
+ else {
1338
+ queue[index] = { ...pending, attempts };
1339
+ }
1340
+ }
1341
+ if (state.pending_runtime_contexts?.length === 0) {
1342
+ state.pending_runtime_contexts = undefined;
1343
+ }
1344
+ publishProducerState(path, state);
1345
+ }
1346
+ function reconcilePendingRuntimeContextsBeforeApprovedEnd(input, state, path, rootId, fingerprintContext) {
1347
+ if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length)
1348
+ return;
1349
+ for (const [index, delayMs] of APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS.entries()) {
1350
+ if (delayMs > 0)
1351
+ sleepSync(delayMs);
1352
+ reconcilePendingRuntimeContexts({
1353
+ coordRoot: input.coordRoot,
1354
+ mode: input.mode,
1355
+ signal: "session-end",
1356
+ payload: { raw: {} },
1357
+ adapter: state.adapter,
1358
+ instance_id: state.instance_id,
1359
+ producer_id: "prd_agent-finalizer",
1360
+ build_id: input.build_id,
1361
+ platform: input.platform,
1362
+ observed_at: new Date().toISOString(),
1363
+ writerOptions: input.writerOptions,
1364
+ }, state, path, rootId, fingerprintContext, {
1365
+ deferRetryLimit: true,
1366
+ finalAttempt: index === APPROVED_END_RUNTIME_CONTEXT_RETRY_DELAYS_MS.length - 1,
1367
+ });
1368
+ if (!state.pending_runtime_contexts?.length)
1369
+ return;
1370
+ }
1371
+ }
1372
+ function retryableRuntimeContextReason(reason) {
1373
+ return (reason === "codex_transcript_turn_not_found" ||
1374
+ reason === "codex_transcript_turn_not_terminal" ||
1375
+ reason === "codex_transcript_token_count_missing");
1376
+ }
1377
+ function turnTelemetryForTerminal(input, state, observedAt) {
1378
+ const native = extractTurnTelemetryV3(input.adapter, input.payload.raw, observedAt);
1379
+ if (native.context.state === "observed" || input.adapter === "cursor")
1380
+ return native;
1381
+ const runtime = readRuntimeContextTelemetry({
1382
+ adapter: input.adapter,
1383
+ session_id: input.payload.session_id ?? input.payload.conversation_id,
1384
+ turn_id: input.payload.turn_id ?? state.current_native_turn_id,
1385
+ transcript_path: runtimeTranscriptPath(input, state),
1386
+ mode: "turn",
1387
+ }, input.runtimeTelemetryOptions);
1388
+ if (runtime.bytes_read > 0)
1389
+ recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
1390
+ if (runtime.state === "unsupported")
1391
+ return native;
1392
+ return {
1393
+ ...native,
1394
+ context: runtimeContextObservation(runtime),
1395
+ context_provenance: {
1396
+ source_event: runtime.state === "observed" ? runtime.source_event : `${input.adapter}.runtime_context`,
1397
+ ...(runtime.state === "observed" ? { source_witness: runtime.source_witness } : {}),
1398
+ ...(input.adapterVersion ? { runtime_version: input.adapterVersion } : {}),
1399
+ attestation: "derived",
1400
+ confidence: runtime.state === "observed" ? "exact" : "high",
1401
+ },
1402
+ };
1403
+ }
1404
+ /**
1405
+ * Resolve Codex's rollout once, retain it in owner-only producer state, and
1406
+ * verify the cached path against the native session before every reuse. Public
1407
+ * events receive only numeric telemetry and an opaque source witness.
1408
+ */
1409
+ function runtimeTranscriptPath(input, state) {
1410
+ if (input.adapter !== "codex")
1411
+ return input.payload.transcript_path;
1412
+ const nativeSessionId = input.payload.session_id ?? input.payload.conversation_id;
1413
+ if (!nativeSessionId)
1414
+ return input.payload.transcript_path;
1415
+ const supplied = input.payload.transcript_path;
1416
+ if (supplied) {
1417
+ const verified = discoverCodexSessionTranscript(nativeSessionId, supplied, input.runtimeTelemetryOptions);
1418
+ if (verified)
1419
+ state.runtime_transcript_path = verified;
1420
+ // Preserve a supplied mismatch so the typed reader reports it honestly.
1421
+ return verified ?? supplied;
1422
+ }
1423
+ if (state.runtime_transcript_path) {
1424
+ const verified = discoverCodexSessionTranscript(nativeSessionId, state.runtime_transcript_path, input.runtimeTelemetryOptions);
1425
+ if (verified)
1426
+ return verified;
1427
+ state.runtime_transcript_path = undefined;
1428
+ }
1429
+ const discovered = discoverCodexSessionTranscript(nativeSessionId, undefined, input.runtimeTelemetryOptions);
1430
+ if (discovered)
1431
+ state.runtime_transcript_path = discovered;
1432
+ return discovered;
1433
+ }
1434
+ function runtimeContextObservation(runtime) {
1435
+ if (runtime.state === "observed") {
1436
+ return {
1437
+ state: "observed",
1438
+ value: {
1439
+ used_tokens: runtime.used_tokens,
1440
+ limit_tokens: runtime.limit_tokens,
1441
+ remaining_tokens: Math.max(0, runtime.limit_tokens - runtime.used_tokens),
1442
+ measured_at: runtime.measured_at,
1443
+ method: runtime.method,
1444
+ },
1445
+ attestation: "derived",
1446
+ confidence: "exact",
1447
+ };
1448
+ }
1449
+ return {
1450
+ state: "expected_but_missing",
1451
+ capability: "context_usage",
1452
+ reason: runtime.reason,
1453
+ };
1454
+ }
1455
+ /** Seed the change-detection baseline from a committed session.started. */
1456
+ function seedAttestedTuning(state, event) {
1457
+ const attestation = event.payload
1458
+ .runtime_attestation;
1459
+ if (!attestation)
1460
+ return;
1461
+ const tuning = attestation.tuning;
1462
+ state.last_attested_tuning =
1463
+ tuning?.state === "observed"
1464
+ ? {
1465
+ ...(tuning.value?.effort ? { effort: tuning.value.effort } : {}),
1466
+ ...(tuning.value?.speed ? { speed: tuning.value.speed } : {}),
1467
+ }
1468
+ : {};
1469
+ const model = attestation.model;
1470
+ if (model.state === "observed") {
1471
+ state.last_attested_model = { provider: model.value.provider, id: model.value.id };
1472
+ }
1473
+ state.last_attested_model_observation = model;
1474
+ state.last_attested_tuning_observation = tuning;
1475
+ state.last_attested_telemetry = attestation.telemetry;
1476
+ }
1477
+ /**
1478
+ * Emit `session.attestation_changed` when the observed tuning moves.
1479
+ *
1480
+ * Detection channels are deliberately cheap: CC stamps `effort.level` on
1481
+ * every tool hook and Stop (zero I/O), and Codex tuning is read from the
1482
+ * rollout only at turn terminals, where the context enrichment already pays
1483
+ * for a bounded tail read. A pre-upgrade state file (no baseline) seeds
1484
+ * silently so existing sessions do not fire a spurious change on their next
1485
+ * tool event; a fresh session's baseline comes from its session.started.
1486
+ */
1487
+ function maybeAttestTuningChange(input, state, path, rootId) {
1488
+ if (input.signal !== "pre-tool-use" &&
1489
+ input.signal !== "post-tool-use" &&
1490
+ input.signal !== "stop") {
1491
+ return;
1492
+ }
1493
+ if (state.terminal)
1494
+ return;
1495
+ let candidate;
1496
+ let attestationSource = "native";
1497
+ let sourceEvent = `${input.adapter}.effort-payload`;
1498
+ if (input.adapter === "claude-code" || input.adapter === "cursor") {
1499
+ if (!input.payload.effort)
1500
+ return;
1501
+ candidate = { effort: input.payload.effort };
1502
+ }
1503
+ else if (input.adapter === "codex" &&
1504
+ (input.signal === "stop" ||
1505
+ // Early probe: without it, a Codex card shows no effort until the first
1506
+ // turn terminal. One rollout read per turn while effort is unknown.
1507
+ (input.signal === "post-tool-use" &&
1508
+ !state.last_attested_tuning?.effort &&
1509
+ state.current_turn_id !== undefined &&
1510
+ state.tuning_probe_turn_id !== state.current_turn_id))) {
1511
+ if (input.signal === "post-tool-use")
1512
+ state.tuning_probe_turn_id = state.current_turn_id;
1513
+ const runtime = readRuntimeTuning({
1514
+ adapter: "codex",
1515
+ session_id: input.payload.session_id ?? input.payload.conversation_id,
1516
+ transcript_path: runtimeTranscriptPath(input, state),
1517
+ }, input.runtimeTelemetryOptions);
1518
+ if (runtime.bytes_read > 0)
1519
+ recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
1520
+ if (runtime.state !== "observed")
1521
+ return;
1522
+ candidate = {
1523
+ ...(runtime.effort ? { effort: runtime.effort } : {}),
1524
+ ...(runtime.speed ? { speed: runtime.speed } : {}),
1525
+ ...(runtime.model ? { model: runtime.model } : {}),
1526
+ };
1527
+ attestationSource = "derived";
1528
+ sourceEvent = runtime.source_event;
1529
+ }
1530
+ else {
1531
+ return;
1532
+ }
1533
+ const baseline = state.last_attested_tuning;
1534
+ const next = { effort: candidate.effort, speed: candidate.speed ?? baseline?.speed };
1535
+ if (baseline === undefined || state.last_attested_telemetry === undefined) {
1536
+ // Pre-upgrade state: adopt without emitting; there is no attested prior.
1537
+ state.last_attested_tuning = { ...(next.effort ? { effort: next.effort } : {}) };
1538
+ if (next.speed)
1539
+ state.last_attested_tuning.speed = next.speed;
1540
+ return;
1541
+ }
1542
+ if (baseline.effort === next.effort && (candidate.speed ?? baseline.speed) === baseline.speed) {
1543
+ return;
1544
+ }
1545
+ // CC change detection rides the payload; the paired transcript row also
1546
+ // carries speed and the (possibly swapped) model, so refresh from it.
1547
+ if (input.adapter === "claude-code") {
1548
+ const runtime = readRuntimeTuning({
1549
+ adapter: "claude-code",
1550
+ transcript_path: input.payload.transcript_path,
1551
+ });
1552
+ if (runtime.bytes_read > 0)
1553
+ recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
1554
+ if (runtime.state === "observed" && runtime.effort === candidate.effort) {
1555
+ if (runtime.speed)
1556
+ candidate.speed = runtime.speed;
1557
+ if (runtime.model)
1558
+ candidate.model = runtime.model;
1559
+ }
1560
+ }
1561
+ const model = candidate.model
1562
+ ? {
1563
+ state: "observed",
1564
+ value: { provider: modelProviderFor(input.adapter), id: safeRole(candidate.model) },
1565
+ attestation: "derived",
1566
+ confidence: "high",
1567
+ }
1568
+ : state.last_attested_model
1569
+ ? {
1570
+ state: "observed",
1571
+ value: state.last_attested_model,
1572
+ attestation: "derived",
1573
+ confidence: "high",
1574
+ }
1575
+ : { state: "expected_but_missing", capability: "model_identity", reason: "not_reported" };
1576
+ const tuning = candidate.effort || candidate.speed
1577
+ ? {
1578
+ state: "observed",
1579
+ value: {
1580
+ ...(candidate.effort ? { effort: candidate.effort } : {}),
1581
+ ...(candidate.speed ? { speed: candidate.speed } : {}),
1582
+ },
1583
+ attestation: attestationSource,
1584
+ confidence: "exact",
1585
+ }
1586
+ : { state: "unsupported", capability: "effort_selection" };
1587
+ commitRuntimeAttestationChange(input, state, path, rootId, {
1588
+ model,
1589
+ tuning,
1590
+ telemetry: state.last_attested_telemetry,
1591
+ reason: "runtime_tuning_changed",
1592
+ source_event: sourceEvent,
1593
+ attestation: attestationSource,
1594
+ confidence: attestationSource === "native" ? "exact" : "high",
1595
+ });
1596
+ }
1597
+ function commitRuntimeAttestationChange(input, state, path, rootId, change) {
1598
+ const priorAttestationId = state.attestation_id;
1599
+ const nextAttestationId = attestationIdV3();
1600
+ const eventId = eventIdV3();
1601
+ const runtimeAttestation = {
1602
+ attestation_id: nextAttestationId,
1603
+ generation_id: state.generation_id,
1604
+ adapter: {
1605
+ state: "observed",
1606
+ value: {
1607
+ id: input.adapter,
1608
+ ...(input.adapterVersion ? { version: input.adapterVersion } : {}),
1609
+ },
1610
+ attestation: "native",
1611
+ confidence: "exact",
1612
+ },
1613
+ harness: {
1614
+ state: "observed",
1615
+ value: {
1616
+ id: input.adapter,
1617
+ ...(input.harnessVersion ? { version: input.harnessVersion } : {}),
1618
+ },
1131
1619
  attestation: "native",
1132
1620
  confidence: "exact",
1621
+ },
1622
+ model: change.model,
1623
+ tuning: change.tuning,
1624
+ telemetry: change.telemetry,
1625
+ capability_profile: state.capability_profile,
1626
+ declared_by_event_id: eventId,
1627
+ };
1628
+ const event = buildEventV3("session.attestation_changed", {
1629
+ event_id: eventId,
1630
+ producer: {
1631
+ producer_id: input.producer_id,
1632
+ boot_id: state.boot_id,
1633
+ sequence: state.next_sequence,
1634
+ component: "agent-hook",
1635
+ build_id: input.build_id,
1636
+ platform: input.platform,
1637
+ ...(input.bridge ? { bridge: input.bridge } : {}),
1638
+ },
1639
+ scope: {
1640
+ root_id: rootId,
1641
+ instance_id: state.instance_id,
1642
+ session_id: state.session_id,
1643
+ generation_id: state.generation_id,
1644
+ ...(input.run_id ? { run_id: input.run_id } : {}),
1645
+ ...(input.workflow_id ? { workflow_id: input.workflow_id } : {}),
1646
+ ...(input.workflow_agent_id ? { workflow_agent_id: input.workflow_agent_id } : {}),
1647
+ },
1648
+ attestation_id: nextAttestationId,
1649
+ links: { caused_by: state.last_event_id ? [state.last_event_id] : [] },
1650
+ provenance: {
1651
+ source_event: safeRole(change.source_event),
1652
+ attestation: change.attestation,
1653
+ confidence: change.confidence,
1133
1654
  attribution: {
1134
1655
  method: "native_payload",
1135
1656
  state: "verified",
1136
1657
  subject_instance_id: state.instance_id,
1137
1658
  },
1138
1659
  },
1139
- observed_at: measurement.state === "observed"
1140
- ? measurement.value.measured_at
1141
- : turnTerminal.time.observed_at,
1660
+ observed_at: input.observed_at ?? new Date().toISOString(),
1142
1661
  monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1143
1662
  clock_id: state.clock_id,
1144
1663
  payload: {
1145
- measurement,
1664
+ prior_attestation_id: priorAttestationId,
1665
+ runtime_attestation: runtimeAttestation,
1666
+ reason: change.reason,
1146
1667
  },
1147
1668
  });
1669
+ // Mutate BEFORE the commit: commitEventLocked publishes the state file, so
1670
+ // post-commit mutations would be dropped by the next signal's fresh read.
1671
+ // A crash between publishes replays the pending event against a state that
1672
+ // already carries the new attestation id, which is the id the event declares.
1673
+ state.attestation_id = nextAttestationId;
1674
+ state.last_attested_model_observation = change.model;
1675
+ state.last_attested_tuning_observation = change.tuning;
1676
+ state.last_attested_telemetry = change.telemetry;
1677
+ state.last_attested_model =
1678
+ change.model.state === "observed" ? { ...change.model.value } : undefined;
1679
+ state.last_attested_tuning = change.tuning.state === "observed" ? { ...change.tuning.value } : {};
1148
1680
  commitEventLocked(input, state, path, event);
1149
1681
  }
1682
+ function modelProviderFor(adapter) {
1683
+ if (adapter === "claude-code")
1684
+ return "anthropic";
1685
+ if (adapter === "codex")
1686
+ return "openai";
1687
+ return "cursor";
1688
+ }
1689
+ function recordRuntimeTelemetryTiming(state, durationMs) {
1690
+ const duration = Math.max(0, Math.ceil(durationMs));
1691
+ if (!Number.isSafeInteger(duration))
1692
+ return;
1693
+ state.turn_harness = {
1694
+ hook_time_ms: state.turn_harness.hook_time_ms + duration,
1695
+ hook_count: state.turn_harness.hook_count + 1,
1696
+ slowest_hook: duration >= state.turn_harness.slowest_hook_ms
1697
+ ? "runtime-context"
1698
+ : state.turn_harness.slowest_hook,
1699
+ slowest_hook_ms: Math.max(duration, state.turn_harness.slowest_hook_ms),
1700
+ };
1701
+ }
1150
1702
  function commitCapabilityDriftEvents(input, state, path, rootId) {
1151
1703
  const read = readLedgerV3(input.coordRoot);
1152
1704
  if (!read.complete)
@@ -1390,6 +1942,7 @@ export function recordApprovedSessionEndV3(input) {
1390
1942
  }
1391
1943
  const rootId = control.genesis.event.scope.root_id;
1392
1944
  const context = fingerprintContextV3(input.coordRoot, rootId, state.generation_id, state.privacy_epoch_id);
1945
+ reconcilePendingRuntimeContextsBeforeApprovedEnd(input, state, record.path, rootId, context);
1393
1946
  const expected = [
1394
1947
  "session_started",
1395
1948
  "turn_closed",
@@ -1645,6 +2198,21 @@ export function readHookProducerStateByInstanceV3(coordRoot, instanceId) {
1645
2198
  }
1646
2199
  return matches.length === 1 ? matches[0] : undefined;
1647
2200
  }
2201
+ /**
2202
+ * Join a coordination actor to its hook producer.
2203
+ *
2204
+ * Native subagent commands can retain the parent's session id even after the
2205
+ * child has its own canonical generation. Prefer the native session join, but
2206
+ * when that points at a different instance accept only one exact live
2207
+ * instance match for the same adapter.
2208
+ */
2209
+ export function readJoinableHookProducerStateV3(coordRoot, adapter, nativeSessionId, instanceId) {
2210
+ const direct = readHookProducerStateV3(coordRoot, adapter, nativeSessionId);
2211
+ if (direct?.instance_id === instanceId && !direct.terminal)
2212
+ return direct;
2213
+ const byInstance = readHookProducerStateByInstanceV3(coordRoot, instanceId);
2214
+ return byInstance?.adapter === adapter && !byInstance.terminal ? byInstance : undefined;
2215
+ }
1648
2216
  function newProducerState(input, sessionId, epochId, boundaryEventId) {
1649
2217
  const bootId = `boot_${randomUUID()}`;
1650
2218
  return {
@@ -1653,7 +2221,7 @@ function newProducerState(input, sessionId, epochId, boundaryEventId) {
1653
2221
  adapter: input.adapter,
1654
2222
  instance_id: input.instance_id,
1655
2223
  session_id: sessionId,
1656
- generation_id: generationIdV3(),
2224
+ generation_id: input.delegated_child?.generation_id ?? generationIdV3(),
1657
2225
  attestation_id: attestationIdV3(),
1658
2226
  capability_profile: adapterCapabilityProfileDigestV3(input.adapter),
1659
2227
  cursor_mode: input.adapter === "cursor" ? (input.payload.cursor_mode ?? "unknown") : undefined,
@@ -1694,6 +2262,7 @@ function applyCommittedEvent(state, event) {
1694
2262
  if (event.event_type === "session.started") {
1695
2263
  state.started_event_id = event.event_id;
1696
2264
  state.session_span.open_event_id = event.event_id;
2265
+ seedAttestedTuning(state, event);
1697
2266
  }
1698
2267
  if (event.event_type === "turn.started") {
1699
2268
  const nextTurnId = event.scope.turn_id;
@@ -1773,6 +2342,7 @@ function applyCommittedEvent(state, event) {
1773
2342
  }
1774
2343
  }
1775
2344
  state.current_turn_id = undefined;
2345
+ state.current_native_turn_id = undefined;
1776
2346
  state.current_turn_span = undefined;
1777
2347
  state.tool_call_count = 0;
1778
2348
  state.tool_call_count_turn_id = undefined;
@@ -1918,6 +2488,14 @@ function safeRole(value) {
1918
2488
  .slice(0, 128);
1919
2489
  return /^[a-zA-Z0-9]/.test(normalized) ? normalized : "agent";
1920
2490
  }
2491
+ function asLiveInstanceId(nativeInstanceId) {
2492
+ if (/^inst_[a-zA-Z0-9._-]{1,128}$/.test(nativeInstanceId)) {
2493
+ return nativeInstanceId;
2494
+ }
2495
+ if (/^[a-zA-Z0-9._-]{1,128}$/.test(nativeInstanceId))
2496
+ return `inst_${nativeInstanceId}`;
2497
+ return `inst_${createHash("sha256").update(nativeInstanceId.normalize("NFC")).digest("hex")}`;
2498
+ }
1921
2499
  function safeTokenOrUndefined(value) {
1922
2500
  if (typeof value !== "string")
1923
2501
  return undefined;
@@ -1996,6 +2574,10 @@ function readProducerState(path) {
1996
2574
  state.waits ??= [];
1997
2575
  state.closed_turn_ids ??= [];
1998
2576
  state.turn_harness ??= emptyTurnHarnessTiming();
2577
+ if (Array.isArray(state.pending_runtime_contexts) &&
2578
+ state.pending_runtime_contexts.length === 0) {
2579
+ state.pending_runtime_contexts = undefined;
2580
+ }
1999
2581
  if (state.adapter === "cursor")
2000
2582
  state.cursor_mode ??= "unknown";
2001
2583
  const allowedKeys = new Set([
@@ -2007,6 +2589,7 @@ function readProducerState(path) {
2007
2589
  "closed_spans",
2008
2590
  "closed_turn_ids",
2009
2591
  "current_turn_id",
2592
+ "current_native_turn_id",
2010
2593
  "current_turn_span",
2011
2594
  "cursor_mode",
2012
2595
  "delegations",
@@ -2014,11 +2597,19 @@ function readProducerState(path) {
2014
2597
  "format_version",
2015
2598
  "generation_id",
2016
2599
  "instance_id",
2600
+ "last_attested_model",
2601
+ "last_attested_model_observation",
2602
+ "last_attested_telemetry",
2603
+ "last_attested_tuning",
2604
+ "last_attested_tuning_observation",
2605
+ "tuning_probe_turn_id",
2017
2606
  "last_event_id",
2018
2607
  "last_monotonic_ns",
2019
2608
  "last_observed_at",
2020
2609
  "next_sequence",
2021
2610
  "pending",
2611
+ "pending_runtime_contexts",
2612
+ "runtime_transcript_path",
2022
2613
  "privacy_epoch_id",
2023
2614
  "session_id",
2024
2615
  "session_span",
@@ -2097,11 +2688,25 @@ function readProducerState(path) {
2097
2688
  !Number.isSafeInteger(state.turn_ordinal) ||
2098
2689
  state.turn_ordinal < 0 ||
2099
2690
  (state.current_turn_id !== undefined && !/^tid_[a-f0-9]{64}$/.test(state.current_turn_id)) ||
2691
+ (state.current_native_turn_id !== undefined &&
2692
+ (typeof state.current_native_turn_id !== "string" ||
2693
+ state.current_native_turn_id.length === 0 ||
2694
+ state.current_native_turn_id.length > 512)) ||
2100
2695
  (state.last_event_id !== undefined && !/^evt_[0-9a-f-]{36}$/.test(state.last_event_id)) ||
2101
2696
  (state.last_monotonic_ns !== undefined && !/^\d+$/.test(state.last_monotonic_ns)) ||
2102
2697
  (state.last_observed_at !== undefined &&
2103
2698
  !Number.isFinite(Date.parse(state.last_observed_at))) ||
2104
2699
  (state.started_event_id !== undefined && !/^evt_[0-9a-f-]{36}$/.test(state.started_event_id)) ||
2700
+ (state.pending_runtime_contexts !== undefined &&
2701
+ (!Array.isArray(state.pending_runtime_contexts) ||
2702
+ state.pending_runtime_contexts.length === 0 ||
2703
+ state.pending_runtime_contexts.length > PENDING_RUNTIME_CONTEXT_CAP ||
2704
+ state.pending_runtime_contexts.some((pending) => !validPendingRuntimeContext(pending)))) ||
2705
+ (state.runtime_transcript_path !== undefined &&
2706
+ (typeof state.runtime_transcript_path !== "string" ||
2707
+ state.runtime_transcript_path.length === 0 ||
2708
+ state.runtime_transcript_path.length > 4096 ||
2709
+ state.runtime_transcript_path.includes("\0"))) ||
2105
2710
  (state.pending?.source_id !== undefined &&
2106
2711
  !/^hid_[a-f0-9]{64}$/.test(state.pending.source_id)) ||
2107
2712
  (state.pending && !validateEventV3(state.pending.event).ok)) {
@@ -2163,6 +2768,37 @@ function validTurnHarnessTiming(value) {
2163
2768
  (value.slowest_hook === undefined ||
2164
2769
  /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(value.slowest_hook)));
2165
2770
  }
2771
+ function validPendingRuntimeContext(value) {
2772
+ if (!value || typeof value !== "object" || Array.isArray(value))
2773
+ return false;
2774
+ const pending = value;
2775
+ return (typeof pending.terminal_event_id === "string" &&
2776
+ /^evt_[0-9a-f-]{36}$/.test(pending.terminal_event_id) &&
2777
+ typeof pending.terminal_observed_at === "string" &&
2778
+ Number.isFinite(Date.parse(pending.terminal_observed_at)) &&
2779
+ typeof pending.turn_id === "string" &&
2780
+ /^tid_[a-f0-9]{64}$/.test(pending.turn_id) &&
2781
+ (pending.run_id === undefined || /^run_[0-9a-f-]{36}$/.test(pending.run_id)) &&
2782
+ (pending.workflow_id === undefined || /^wf_[0-9a-f-]{36}$/.test(pending.workflow_id)) &&
2783
+ (pending.workflow_agent_id === undefined ||
2784
+ /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(pending.workflow_agent_id)) &&
2785
+ typeof pending.native_session_id === "string" &&
2786
+ pending.native_session_id.length > 0 &&
2787
+ pending.native_session_id.length <= 512 &&
2788
+ typeof pending.native_turn_id === "string" &&
2789
+ pending.native_turn_id.length > 0 &&
2790
+ pending.native_turn_id.length <= 512 &&
2791
+ (pending.transcript_path === undefined ||
2792
+ (typeof pending.transcript_path === "string" &&
2793
+ pending.transcript_path.length > 0 &&
2794
+ pending.transcript_path.length <= 4096)) &&
2795
+ (pending.runtime_version === undefined ||
2796
+ (typeof pending.runtime_version === "string" &&
2797
+ /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(pending.runtime_version))) &&
2798
+ Number.isSafeInteger(pending.attempts) &&
2799
+ pending.attempts >= 0 &&
2800
+ pending.attempts < RUNTIME_CONTEXT_RETRY_LIMIT);
2801
+ }
2166
2802
  function pidIsAlive(pid) {
2167
2803
  if (!Number.isSafeInteger(pid) || pid < 1)
2168
2804
  return false;