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
@@ -100,6 +100,24 @@ export function ObservationV3BaseSchema<T extends TSchema>(value: T) {
100
100
  ]);
101
101
  }
102
102
 
103
+ const RuntimeTelemetryCapabilityValueV3BaseSchema = StrictObject({
104
+ support: Type.Union([
105
+ Type.Literal("native"),
106
+ Type.Literal("derived"),
107
+ Type.Literal("conditional"),
108
+ ]),
109
+ source: SafeToken,
110
+ completeness: Type.Union([
111
+ Type.Literal("exact"),
112
+ Type.Literal("lower_bound"),
113
+ Type.Literal("unknown"),
114
+ ]),
115
+ });
116
+
117
+ const RuntimeTelemetryCapabilityObservationV3BaseSchema = ObservationV3BaseSchema(
118
+ RuntimeTelemetryCapabilityValueV3BaseSchema,
119
+ );
120
+
103
121
  export const RuntimeAttestationV3BaseSchema = StrictObject({
104
122
  attestation_id: AttestationId,
105
123
  generation_id: GenerationId,
@@ -110,6 +128,27 @@ export const RuntimeAttestationV3BaseSchema = StrictObject({
110
128
  StrictObject({ id: SafeToken, version: Type.Optional(SafeToken) }),
111
129
  ),
112
130
  model: ObservationV3BaseSchema(StrictObject({ provider: SafeToken, id: SafeToken })),
131
+ /** Runtime tuning identity: the effort dial (and speed tier where the
132
+ * runtime reports one). `observed` with a missing `effort` never occurs —
133
+ * a runtime that proved the model has no dial attests `unsupported` with
134
+ * capability `effort_selection` instead. Values pass through verbatim
135
+ * (`low`…`max`, `standard`/`fast`); display labels are a renderer concern. */
136
+ tuning: ObservationV3BaseSchema(
137
+ StrictObject({
138
+ effort: Type.Optional(SafeToken),
139
+ speed: Type.Optional(SafeToken),
140
+ }),
141
+ ),
142
+ /** Effective telemetry support proven for this runtime session. This is
143
+ * intentionally separate from the static capability profile digest: a
144
+ * missing or late local source changes evidence, not the gated profile. */
145
+ telemetry: StrictObject({
146
+ context_usage: RuntimeTelemetryCapabilityObservationV3BaseSchema,
147
+ wait_spans: RuntimeTelemetryCapabilityObservationV3BaseSchema,
148
+ wait_completeness: RuntimeTelemetryCapabilityObservationV3BaseSchema,
149
+ response_latency: RuntimeTelemetryCapabilityObservationV3BaseSchema,
150
+ inference_timing: RuntimeTelemetryCapabilityObservationV3BaseSchema,
151
+ }),
113
152
  capability_profile: Type.String({ pattern: "^cap_[a-f0-9]{64}$" }),
114
153
  declared_by_event_id: EventId,
115
154
  });
@@ -14,6 +14,21 @@ export type AdapterWaitKindV3 =
14
14
  | "rate_limit"
15
15
  | "unknown";
16
16
 
17
+ export const ADAPTER_WAIT_KINDS_V3: readonly AdapterWaitKindV3[] = [
18
+ "permission",
19
+ "needs_input",
20
+ "decision",
21
+ "approval",
22
+ "scheduled",
23
+ "rate_limit",
24
+ "unknown",
25
+ ] as const;
26
+
27
+ export interface WaitKindCapabilityV3 {
28
+ span_delivery: CapabilitySupportV3;
29
+ turn_completeness: CapabilitySupportV3;
30
+ }
31
+
17
32
  export type BaseAdapterSignalV3 =
18
33
  | "session_start"
19
34
  | "session_end"
@@ -159,6 +174,25 @@ export function adapterTurnWaitCountSupportV3(_adapter: Adapter): CapabilitySupp
159
174
  return "unsupported";
160
175
  }
161
176
 
177
+ /**
178
+ * Per-kind wait matrix for native adapter turns. Span delivery and independent
179
+ * completed-turn completeness are deliberately separate: observing a span
180
+ * never proves that an absent span means zero.
181
+ */
182
+ export function adapterWaitCoverageMatrixV3(
183
+ adapter: Adapter,
184
+ ): Record<AdapterWaitKindV3, WaitKindCapabilityV3> {
185
+ return Object.fromEntries(
186
+ ADAPTER_WAIT_KINDS_V3.map((kind) => [
187
+ kind,
188
+ {
189
+ span_delivery: adapterWaitKindSupportV3(adapter, kind),
190
+ turn_completeness: adapterTurnWaitCountSupportV3(adapter),
191
+ },
192
+ ]),
193
+ ) as Record<AdapterWaitKindV3, WaitKindCapabilityV3>;
194
+ }
195
+
162
196
  /**
163
197
  * Local Cursor sessions expose the generic tool channel. Cloud/private-worker
164
198
  * sessions cannot guarantee whole-turn delivery because hooks may start only
@@ -1,6 +1,6 @@
1
1
  /** Generated by scripts/generate-event-v3-contract.ts. Do not edit. */
2
2
  export const EVENT_V3_SCHEMA_DIGEST =
3
- "sha256:682272a35f16177217a272e119c4dea2bf1980a522a183fefd6dceaf1e4509b8" as const;
3
+ "sha256:297ed06021fd046c6f1a2061de4341c7765840bf4f0d9b597ad7c9d78b7a9ba6" as const;
4
4
  export const EVENT_V3_GENERATED_CORE_TYPES = [
5
5
  "ledger.genesis",
6
6
  "ledger.activated",
@@ -34,12 +34,18 @@ export {
34
34
  } from "./canonical.ts";
35
35
  export {
36
36
  ADAPTER_CAPABILITY_PROFILES_V3,
37
+ ADAPTER_WAIT_KINDS_V3,
37
38
  type AdapterCapabilityProfileV3,
38
39
  type AdapterSignalV3,
40
+ type AdapterWaitKindV3,
39
41
  adapterCapabilityProfileDigestV3,
40
42
  adapterCapabilityProfileV3,
41
43
  adapterSignalSupportV3,
44
+ adapterWaitCoverageMatrixV3,
45
+ adapterWaitKindSupportV3,
42
46
  type CapabilitySupportV3,
47
+ type CursorExecutionModeV3,
48
+ type WaitKindCapabilityV3,
43
49
  } from "./capabilities.ts";
44
50
  export {
45
51
  type CapabilityDeliveryV3,
@@ -153,8 +159,12 @@ export {
153
159
  type LatencyProjectionDiagnosticV3,
154
160
  type LatencyProjectionV3,
155
161
  projectLatencyV3,
162
+ type ResponseLatencyV3,
163
+ runtimeTelemetryEvidenceKeyV3,
156
164
  type ToolLatencyV3,
157
165
  type TurnLatencyV3,
166
+ type WaitKindCompletenessV3,
167
+ type WaitKindCoverageV3,
158
168
  } from "./latency.ts";
159
169
  export {
160
170
  EVENT_V3_LIVE_RELATIVE_ROOT,
@@ -245,6 +255,7 @@ export {
245
255
  } from "./projection.ts";
246
256
  export {
247
257
  digestEventV3AuthorityDirectoryV3,
258
+ eventV3ActiveWatchPath,
248
259
  inspectInvalidActiveAuthorityV3,
249
260
  type LedgerCursorV3,
250
261
  type LedgerDiagnosticCodeV3,
@@ -277,6 +288,16 @@ export {
277
288
  validateEventV3RecoveryIntent,
278
289
  validateEventV3RecoveryReceipt,
279
290
  } from "./recovery-record.ts";
291
+ export {
292
+ effectiveRuntimeTelemetryCapabilitiesV3,
293
+ type RuntimeContextCapabilityEvidenceV3,
294
+ type RuntimeTelemetryCapabilitiesV3,
295
+ type RuntimeTelemetryCapabilityEvidenceV3,
296
+ type RuntimeTelemetryCapabilityObservationV3,
297
+ type RuntimeTelemetryCapabilityValueV3,
298
+ type RuntimeTelemetryChannelV3,
299
+ type RuntimeTelemetryCompletenessV3,
300
+ } from "./runtime-telemetry-capabilities.ts";
280
301
  export {
281
302
  type CloseSpanV3Input,
282
303
  captureSpanClockV3,
@@ -1,6 +1,9 @@
1
+ import { canonicalJsonV3, sha256V3 } from "./canonical.ts";
2
+ import { ADAPTER_WAIT_KINDS_V3, type AdapterWaitKindV3 } from "./capabilities.ts";
1
3
  import type { ReadLedgerV3Result } from "./reader.ts";
4
+ import type { RuntimeTelemetryCapabilitiesV3 } from "./runtime-telemetry-capabilities.ts";
2
5
 
3
- export const EVENT_V3_LATENCY_PROJECTION_VERSION = "event-v3-latency-v1" as const;
6
+ export const EVENT_V3_LATENCY_PROJECTION_VERSION = "event-v3-latency-v3" as const;
4
7
 
5
8
  export type LatencyMetricV3 =
6
9
  | { state: "observed"; value_ms: number }
@@ -25,6 +28,30 @@ export interface ContextCoverageV3 {
25
28
  reason: string | null;
26
29
  }
27
30
 
31
+ export interface ResponseLatencyV3 {
32
+ /** Turn start to first tool request, or terminal response for an attested no-tool turn. */
33
+ agent_action_ms: LatencyMetricV3;
34
+ /** Turn start to first tool request; unknown with `no_tool_request` for no-tool turns. */
35
+ first_tool_request_ms: LatencyMetricV3;
36
+ /** Last complete tool terminal to the turn terminal. */
37
+ post_tool_response_ms: LatencyMetricV3;
38
+ /** Turn start to terminal response, only when an attested turn used no tools. */
39
+ no_tool_terminal_response_ms: LatencyMetricV3;
40
+ method: "canonical_event_timestamps_v1";
41
+ }
42
+
43
+ export type WaitKindCompletenessV3 = "complete" | "lower_bound" | "unknown" | "unsupported";
44
+
45
+ export interface WaitKindCoverageV3 {
46
+ kind: AdapterWaitKindV3;
47
+ started_count: number;
48
+ ended_count: number;
49
+ open_count: number;
50
+ duration_ms: LatencyMetricV3;
51
+ completeness: WaitKindCompletenessV3;
52
+ reason: string | null;
53
+ }
54
+
28
55
  export interface TurnLatencyV3 {
29
56
  generation_id: string;
30
57
  turn_id: string;
@@ -39,6 +66,7 @@ export interface TurnLatencyV3 {
39
66
  wait_ms: LatencyMetricV3;
40
67
  occupied_ms: LatencyMetricV3;
41
68
  inference_ms: LatencyMetricV3;
69
+ response_latency: ResponseLatencyV3;
42
70
  harness_ms: LatencyMetricV3;
43
71
  slowest_hook: string | null;
44
72
  slowest_hook_ms: number | null;
@@ -46,6 +74,10 @@ export interface TurnLatencyV3 {
46
74
  over_attributed_ms: number;
47
75
  context_percent: number | null;
48
76
  context_coverage: ContextCoverageV3;
77
+ telemetry_capabilities: RuntimeTelemetryCapabilitiesV3 | null;
78
+ /** Equal keys are required before a report compares telemetry-backed values. */
79
+ telemetry_evidence_key: `sha256:${string}` | null;
80
+ wait_coverage_by_kind: Record<AdapterWaitKindV3, WaitKindCoverageV3>;
49
81
  span_counts: { tool: number; command: number; wait: number };
50
82
  tool_breakdown: ToolLatencyV3[];
51
83
  }
@@ -72,6 +104,7 @@ interface EventShape {
72
104
  event_id: string;
73
105
  event_type: string;
74
106
  scope: { generation_id?: string; turn_id?: string };
107
+ time: { observed_at?: string };
75
108
  payload: Record<string, unknown>;
76
109
  }
77
110
 
@@ -92,6 +125,7 @@ export function projectLatencyV3(read: ReadLedgerV3Result): LatencyProjectionV3
92
125
  const events = read.events.map(({ event }) => event as unknown as EventShape);
93
126
  const diagnostics: LatencyProjectionDiagnosticV3[] = [];
94
127
  const terminalByTurn = new Map<string, EventShape>();
128
+ const telemetryByGeneration = latestTelemetryByGeneration(events);
95
129
 
96
130
  for (const event of events) {
97
131
  if (event.event_type !== "turn.completed") continue;
@@ -104,7 +138,12 @@ export function projectLatencyV3(read: ReadLedgerV3Result): LatencyProjectionV3
104
138
  const turns = [...terminalByTurn.values()].map((terminal) => {
105
139
  const key = turnKey(terminal);
106
140
  const turnEvents = events.filter((event) => turnKey(event) === key);
107
- return projectTurn(terminal, turnEvents, diagnostics);
141
+ return projectTurn(
142
+ terminal,
143
+ turnEvents,
144
+ diagnostics,
145
+ telemetryByGeneration.get(terminal.scope.generation_id ?? "") ?? null,
146
+ );
108
147
  });
109
148
  turns.sort(
110
149
  (left, right) =>
@@ -118,6 +157,7 @@ function projectTurn(
118
157
  terminal: EventShape,
119
158
  events: EventShape[],
120
159
  diagnostics: LatencyProjectionDiagnosticV3[],
160
+ telemetryCapabilities: RuntimeTelemetryCapabilitiesV3 | null,
121
161
  ): TurnLatencyV3 {
122
162
  const span = record(terminal.payload.span);
123
163
  const wall = metricFromObservation(span.duration_ms, "turn_duration_unknown");
@@ -157,6 +197,10 @@ function projectTurn(
157
197
  ) {
158
198
  waits.reasons.push("wait_terminal_count_mismatch");
159
199
  }
200
+ const waitCoverageComplete =
201
+ expectedWaitCount !== undefined &&
202
+ expectedWaitCount === endedWaitIds.size &&
203
+ !waitStartEvents.some(({ payload }) => !endedWaitIds.has(string(record(payload).wait_id)));
160
204
 
161
205
  const tool = metricFromIntervals(tools);
162
206
  const command = metricFromIntervals(commands);
@@ -176,6 +220,7 @@ function projectTurn(
176
220
  "api_time_ms",
177
221
  "inference_timing_unknown",
178
222
  );
223
+ const responseLatency = projectResponseLatency(terminal, events, wall, tools, expectedToolCount);
179
224
  const harness = metricFromNestedObservation(
180
225
  terminal.payload.harness,
181
226
  "hook_time_ms",
@@ -217,6 +262,7 @@ function projectTurn(
217
262
  wait_ms: wait,
218
263
  occupied_ms: occupied,
219
264
  inference_ms: inference,
265
+ response_latency: responseLatency,
220
266
  harness_ms: harness,
221
267
  slowest_hook: slowestHook.name,
222
268
  slowest_hook_ms: slowestHook.duration_ms,
@@ -224,6 +270,18 @@ function projectTurn(
224
270
  over_attributed_ms: overAttributed,
225
271
  context_percent: context.percent,
226
272
  context_coverage: context.coverage,
273
+ telemetry_capabilities: telemetryCapabilities,
274
+ telemetry_evidence_key: telemetryCapabilities
275
+ ? runtimeTelemetryEvidenceKeyV3(telemetryCapabilities)
276
+ : null,
277
+ wait_coverage_by_kind: projectWaitCoverageByKind(
278
+ waitStartEvents,
279
+ waitEvents,
280
+ wallInterval,
281
+ waitCoverageComplete,
282
+ waitCount,
283
+ diagnostics,
284
+ ),
227
285
  span_counts: {
228
286
  tool: toolEvents.length,
229
287
  command: commandEvents.length,
@@ -233,6 +291,159 @@ function projectTurn(
233
291
  };
234
292
  }
235
293
 
294
+ /** Reports may compare a telemetry-backed channel only inside one evidence key. */
295
+ export function runtimeTelemetryEvidenceKeyV3(
296
+ telemetry: RuntimeTelemetryCapabilitiesV3,
297
+ ): `sha256:${string}` {
298
+ return sha256V3(canonicalJsonV3(telemetry));
299
+ }
300
+
301
+ function latestTelemetryByGeneration(
302
+ events: EventShape[],
303
+ ): Map<string, RuntimeTelemetryCapabilitiesV3> {
304
+ const result = new Map<string, RuntimeTelemetryCapabilitiesV3>();
305
+ for (const event of events) {
306
+ if (
307
+ event.event_type !== "session.started" &&
308
+ event.event_type !== "session.attestation_changed"
309
+ ) {
310
+ continue;
311
+ }
312
+ const attestation = record(event.payload.runtime_attestation);
313
+ const telemetry = record(attestation.telemetry);
314
+ if (Object.keys(telemetry).length === 0) continue;
315
+ result.set(event.scope.generation_id ?? "", telemetry as RuntimeTelemetryCapabilitiesV3);
316
+ }
317
+ return result;
318
+ }
319
+
320
+ function projectResponseLatency(
321
+ terminal: EventShape,
322
+ events: EventShape[],
323
+ wall: LatencyMetricV3,
324
+ tools: IntervalSet,
325
+ expectedToolCount: number | undefined,
326
+ ): ResponseLatencyV3 {
327
+ const turnStart = Date.parse(string(record(terminal.payload.span).opened_at));
328
+ const turnEnd =
329
+ Number.isFinite(turnStart) && wall.state === "observed" ? turnStart + wall.value_ms : undefined;
330
+ const requests = events
331
+ .filter(({ event_type }) => event_type === "tool.requested")
332
+ .map(({ time }) => Date.parse(string(time.observed_at)))
333
+ .filter(Number.isFinite)
334
+ .sort((left, right) => left - right);
335
+ const firstRequest = requests[0];
336
+ const firstTool =
337
+ Number.isFinite(turnStart) && firstRequest !== undefined
338
+ ? observedLatency(firstRequest - turnStart)
339
+ : unknownLatency(expectedToolCount === 0 ? "no_tool_request" : "tool_request_unobserved");
340
+ const noToolTerminal =
341
+ expectedToolCount === 0
342
+ ? wall
343
+ : unknownLatency(
344
+ expectedToolCount === undefined ? "tool_channel_unattested" : "turn_contains_tools",
345
+ );
346
+ const agentAction =
347
+ firstRequest !== undefined
348
+ ? firstTool
349
+ : expectedToolCount === 0
350
+ ? noToolTerminal
351
+ : unknownLatency(
352
+ expectedToolCount === undefined ? "tool_channel_unattested" : "tool_request_unobserved",
353
+ );
354
+
355
+ let postTool = unknownLatency(
356
+ expectedToolCount === 0
357
+ ? "no_tool_completion"
358
+ : expectedToolCount === undefined
359
+ ? "tool_channel_unattested"
360
+ : "final_tool_completion_unknown",
361
+ );
362
+ if (
363
+ turnEnd !== undefined &&
364
+ expectedToolCount !== undefined &&
365
+ expectedToolCount > 0 &&
366
+ tools.reasons.length === 0 &&
367
+ tools.intervals.length === expectedToolCount
368
+ ) {
369
+ postTool = observedLatency(turnEnd - Math.max(...tools.intervals.map(({ end }) => end)));
370
+ }
371
+
372
+ return {
373
+ agent_action_ms: agentAction,
374
+ first_tool_request_ms: firstTool,
375
+ post_tool_response_ms: postTool,
376
+ no_tool_terminal_response_ms: noToolTerminal,
377
+ method: "canonical_event_timestamps_v1",
378
+ };
379
+ }
380
+
381
+ function projectWaitCoverageByKind(
382
+ starts: EventShape[],
383
+ ends: EventShape[],
384
+ wall: Interval | undefined,
385
+ complete: boolean,
386
+ waitCount: Record<string, unknown>,
387
+ diagnostics: LatencyProjectionDiagnosticV3[],
388
+ ): Record<AdapterWaitKindV3, WaitKindCoverageV3> {
389
+ const endedIds = new Set(ends.map(({ payload }) => string(record(payload).wait_id)));
390
+ const unsupported = waitCount.state === "unsupported";
391
+ return Object.fromEntries(
392
+ ADAPTER_WAIT_KINDS_V3.map((kind) => {
393
+ const kindStarts = starts.filter(({ payload }) => waitKind(payload) === kind);
394
+ const kindEnds = ends.filter(({ payload }) => waitKind(payload) === kind);
395
+ const openCount = kindStarts.filter(
396
+ ({ payload }) => !endedIds.has(string(record(payload).wait_id)),
397
+ ).length;
398
+ const set = intervalSet(kindEnds, wall, diagnostics);
399
+ if (!complete) set.reasons.push("wait_kind_completeness_unattested");
400
+ const completeness: WaitKindCompletenessV3 = complete
401
+ ? "complete"
402
+ : unsupported
403
+ ? kindStarts.length > 0 || kindEnds.length > 0
404
+ ? "lower_bound"
405
+ : "unsupported"
406
+ : kindStarts.length > 0 || kindEnds.length > 0
407
+ ? "lower_bound"
408
+ : "unknown";
409
+ const reason = complete
410
+ ? null
411
+ : unsupported
412
+ ? "turn_wait_count_unsupported"
413
+ : "turn_wait_count_unattested";
414
+ return [
415
+ kind,
416
+ {
417
+ kind,
418
+ started_count: kindStarts.length,
419
+ ended_count: kindEnds.length,
420
+ open_count: openCount,
421
+ duration_ms: metricFromIntervals(set),
422
+ completeness,
423
+ reason,
424
+ },
425
+ ];
426
+ }),
427
+ ) as Record<AdapterWaitKindV3, WaitKindCoverageV3>;
428
+ }
429
+
430
+ function waitKind(payload: Record<string, unknown>): AdapterWaitKindV3 {
431
+ const value = string(record(payload).kind);
432
+ return ADAPTER_WAIT_KINDS_V3.includes(value as AdapterWaitKindV3)
433
+ ? (value as AdapterWaitKindV3)
434
+ : "unknown";
435
+ }
436
+
437
+ function observedLatency(value: number): LatencyMetricV3 {
438
+ return Number.isFinite(value) && value >= 0
439
+ ? { state: "observed", value_ms: Math.floor(value) }
440
+ : unknownLatency("event_time_order_invalid");
441
+ }
442
+
443
+ function unknownLatency(reason: string): LatencyMetricV3 {
444
+ return { state: "unknown", known_ms: 0, reasons: [reason] };
445
+ }
446
+
236
447
  function groupByTool(events: EventShape[]): Map<string, EventShape[]> {
237
448
  const groups = new Map<string, EventShape[]>();
238
449
  for (const event of events) {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { Adapter } from "../../adapter.ts";
3
3
  import type { ParsedPayload } from "../../hooks/adapter/parse.ts";
4
+ import type { EventV3 } from "./contract.ts";
4
5
  import {
5
6
  type EventV3WriteMode,
6
7
  readEventV3ControlState,
@@ -84,6 +85,20 @@ export function hookSignalV3(eventName: string): HookSignalV3 | undefined {
84
85
  }
85
86
  }
86
87
 
88
+ /**
89
+ * Tool lifecycle hooks may stop after publishing their event to the durable
90
+ * ready WAL. The next non-tool hook drains the causal batch into the active
91
+ * ledger, avoiding one global append-lease cycle for every tool boundary.
92
+ */
93
+ export function liveHookSignalDefersDrainV3(eventName: string, override?: string): boolean {
94
+ if (override === "1") return true;
95
+ if (override === "0") return false;
96
+ const signal = hookSignalV3(eventName);
97
+ return (
98
+ signal === "pre-tool-use" || signal === "post-tool-use" || signal === "post-tool-use-failure"
99
+ );
100
+ }
101
+
87
102
  export function recordLiveHookSignalV3(input: {
88
103
  coordRoot: string;
89
104
  route: Extract<LiveEventLedgerRouteV3, { state: "v3" }>;
@@ -104,11 +119,27 @@ export function recordLiveHookSignalV3(input: {
104
119
  }): RecordHookSignalV3Result | { state: "ignored" } {
105
120
  const signal = hookSignalV3(input.eventName);
106
121
  if (!signal) return { state: "ignored" };
122
+ const nativeChild = input.payload?.subagent_id ?? input.payload?.agent_id;
123
+ const childOwnsSignal =
124
+ signal !== "sub-agent-start" &&
125
+ signal !== "sub-agent-stop" &&
126
+ nativeChild !== undefined &&
127
+ liveInstanceIdV3(input.instanceId) === liveInstanceIdV3(nativeChild);
128
+ const payload = input.payload
129
+ ? childOwnsSignal
130
+ ? {
131
+ ...input.payload,
132
+ session_id: nativeChild,
133
+ conversation_id: undefined,
134
+ parent_session_id: input.payload.session_id ?? input.payload.conversation_id,
135
+ }
136
+ : input.payload
137
+ : { raw: {} };
107
138
  return recordHookSignalV3({
108
139
  coordRoot: input.coordRoot,
109
140
  mode: input.route.mode,
110
141
  signal,
111
- payload: input.payload ?? { raw: {} },
142
+ payload,
112
143
  adapter: input.adapter,
113
144
  instance_id: liveInstanceIdV3(input.instanceId),
114
145
  run_id: input.run_id,
@@ -127,6 +158,94 @@ export function recordLiveHookSignalV3(input: {
127
158
  });
128
159
  }
129
160
 
161
+ /**
162
+ * Open the child generation named by a native SubagentStart event.
163
+ *
164
+ * The parent producer owns the delegation span and mints its child generation
165
+ * id. This second crash-safe session-start gives that exact generation its own
166
+ * producer authority, so later child tool hooks can resolve away from the
167
+ * parent session and the disposable heartbeat projection has a real source.
168
+ */
169
+ export function recordLiveDelegatedChildSessionV3(input: {
170
+ coordRoot: string;
171
+ route: Extract<LiveEventLedgerRouteV3, { state: "v3" }>;
172
+ parentEvent: Extract<EventV3, { event_type: "agent.started" }>;
173
+ payload: ParsedPayload;
174
+ adapter: Adapter;
175
+ bridge?: "codex-wsl";
176
+ monotonic_ns?: string;
177
+ }): RecordHookSignalV3Result | { state: "ignored" } {
178
+ const nativeChild = input.payload.subagent_id ?? input.payload.agent_id;
179
+ if (!nativeChild) return { state: "ignored" };
180
+ const parentGeneration = (input.parentEvent.links as { parent_generation_id?: `gen_${string}` })
181
+ .parent_generation_id;
182
+ if (!parentGeneration) return { state: "ignored" };
183
+ const nativeParent = input.payload.session_id ?? input.payload.conversation_id;
184
+ const childPayload: ParsedPayload = {
185
+ session_id: nativeChild,
186
+ agent_id: nativeChild,
187
+ parent_session_id: nativeParent,
188
+ source: "startup",
189
+ model: input.payload.model,
190
+ cwd: input.payload.cwd,
191
+ raw: {
192
+ session_id: nativeChild,
193
+ agent_id: nativeChild,
194
+ ...(nativeParent ? { parent_session_id: nativeParent } : {}),
195
+ ...(input.payload.raw.agent_type ? { agent_type: input.payload.raw.agent_type } : {}),
196
+ },
197
+ };
198
+ const started = recordHookSignalV3({
199
+ coordRoot: input.coordRoot,
200
+ mode: input.route.mode,
201
+ signal: "session-start",
202
+ payload: childPayload,
203
+ adapter: input.adapter,
204
+ instance_id: liveInstanceIdV3(nativeChild),
205
+ producer_id: LIVE_HOOK_V3_PRODUCER_ID,
206
+ build_id: input.route.build_id,
207
+ platform: livePlatformV3(),
208
+ ...(input.bridge ? { bridge: input.bridge } : {}),
209
+ monotonic_ns: input.monotonic_ns,
210
+ hook_name: "sub-agent-start-child-session",
211
+ delegated_child: {
212
+ generation_id: input.parentEvent.payload.child_generation_id as `gen_${string}`,
213
+ parent_generation_id: parentGeneration,
214
+ delegation_id: input.parentEvent.payload.delegation_id as `del_${string}`,
215
+ caused_by_event_id: input.parentEvent.event_id as `evt_${string}`,
216
+ },
217
+ });
218
+ if (started.state !== "recorded" && started.state !== "already_started") return started;
219
+ const childStartEventId = (
220
+ started.state === "recorded" ? started.event.event_id : started.event_id
221
+ ) as `evt_${string}`;
222
+ recordHookSignalV3({
223
+ coordRoot: input.coordRoot,
224
+ mode: input.route.mode,
225
+ signal: "user-prompt-submit",
226
+ payload: {
227
+ ...childPayload,
228
+ turn_id: `delegated:${nativeChild}`,
229
+ raw: { ...childPayload.raw, turn_id: `delegated:${nativeChild}` },
230
+ },
231
+ adapter: input.adapter,
232
+ instance_id: liveInstanceIdV3(nativeChild),
233
+ producer_id: LIVE_HOOK_V3_PRODUCER_ID,
234
+ build_id: input.route.build_id,
235
+ platform: livePlatformV3(),
236
+ ...(input.bridge ? { bridge: input.bridge } : {}),
237
+ monotonic_ns: input.monotonic_ns,
238
+ hook_name: "sub-agent-start-child-turn",
239
+ delegated_child: {
240
+ generation_id: input.parentEvent.payload.child_generation_id as `gen_${string}`,
241
+ parent_generation_id: parentGeneration,
242
+ delegation_id: input.parentEvent.payload.delegation_id as `del_${string}`,
243
+ caused_by_event_id: childStartEventId,
244
+ },
245
+ });
246
+ return started;
247
+ }
248
+
130
249
  export function livePlatformV3(): "linux" | "windows" | "macos" | "unknown" {
131
250
  if (process.platform === "linux") return "linux";
132
251
  if (process.platform === "win32") return "windows";
@@ -141,6 +141,14 @@ export function recordCommandSignalV3(
141
141
  if (state && !matchesHookState(state, hook, input, epochId)) {
142
142
  throw new Error("V3 command producer state does not match the joined hook generation");
143
143
  }
144
+ if (state && state.attestation_id !== hook.attestation_id) {
145
+ // A mid-generation re-attestation (session.attestation_changed) moves
146
+ // the hook's attestation id without opening a new generation or turn. A
147
+ // command span that straddles the change keeps its span, sequence, and
148
+ // dedupe state; later events ride the live attestation, the same
149
+ // in-place adoption the hook producer performs.
150
+ state.attestation_id = hook.attestation_id;
151
+ }
144
152
  let recovered = false;
145
153
  if (state?.pending) {
146
154
  const pending = state.pending;
@@ -445,7 +453,8 @@ function matchesHookState(
445
453
  state.session_id === hook.session_id &&
446
454
  state.generation_id === hook.generation_id &&
447
455
  state.turn_id === hook.current_turn_id &&
448
- state.attestation_id === hook.attestation_id &&
456
+ // attestation_id is deliberately absent: a re-attestation within the
457
+ // generation is a continuation, adopted by the caller, not a mismatch.
449
458
  state.privacy_epoch_id === epochId
450
459
  );
451
460
  }