harnery 0.37.0 → 0.38.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 (184) hide show
  1. package/dist/commander.d.ts +9 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +7 -2
  4. package/dist/commands/admission.d.ts +21 -0
  5. package/dist/commands/admission.d.ts.map +1 -0
  6. package/dist/commands/admission.js +565 -0
  7. package/dist/commands/agents.d.ts +7 -0
  8. package/dist/commands/agents.d.ts.map +1 -1
  9. package/dist/commands/agents.js +61 -1
  10. package/dist/commands/artifacts.d.ts.map +1 -1
  11. package/dist/commands/artifacts.js +48 -2
  12. package/dist/commands/browse-ai.d.ts +2 -2
  13. package/dist/commands/browse-ai.d.ts.map +1 -1
  14. package/dist/commands/browse-ai.js +6 -4
  15. package/dist/commands/browse.d.ts.map +1 -1
  16. package/dist/commands/browse.js +349 -21
  17. package/dist/commands/fetch.js +1 -0
  18. package/dist/commands/qa-record.d.ts +144 -0
  19. package/dist/commands/qa-record.d.ts.map +1 -0
  20. package/dist/commands/qa-record.js +0 -0
  21. package/dist/commands/qa-run.d.ts +7 -3
  22. package/dist/commands/qa-run.d.ts.map +1 -1
  23. package/dist/commands/qa-run.js +268 -13
  24. package/dist/commands/qa-status.d.ts +71 -0
  25. package/dist/commands/qa-status.d.ts.map +1 -0
  26. package/dist/commands/qa-status.js +490 -0
  27. package/dist/commands/qa-verify.d.ts +40 -0
  28. package/dist/commands/qa-verify.d.ts.map +1 -0
  29. package/dist/commands/qa-verify.js +180 -0
  30. package/dist/commands/review-pack.d.ts +4 -0
  31. package/dist/commands/review-pack.d.ts.map +1 -0
  32. package/dist/commands/review-pack.js +1001 -0
  33. package/dist/core/agents/qa-signal.d.ts +111 -0
  34. package/dist/core/agents/qa-signal.d.ts.map +1 -0
  35. package/dist/core/agents/qa-signal.js +231 -0
  36. package/dist/core/agents/session-name-display.d.ts +20 -5
  37. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  38. package/dist/core/agents/session-name-display.js +67 -7
  39. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  40. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  41. package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
  42. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  43. package/dist/core/agents/state/heartbeat-writer.js +17 -0
  44. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  45. package/dist/core/agents/state/live-coordination-view.js +1 -0
  46. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  47. package/dist/core/artifacts/constants.d.ts +1 -1
  48. package/dist/core/artifacts/constants.js +1 -1
  49. package/dist/core/artifacts/index.d.ts +57 -6
  50. package/dist/core/artifacts/index.d.ts.map +1 -1
  51. package/dist/core/artifacts/index.js +265 -10
  52. package/dist/core/config.d.ts +8 -0
  53. package/dist/core/config.d.ts.map +1 -1
  54. package/dist/core/config.js +16 -0
  55. package/dist/core/diagnostics/bundle.d.ts +16 -0
  56. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  57. package/dist/core/diagnostics/bundle.js +101 -7
  58. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  59. package/dist/core/events/v3/bootstrap.js +10 -0
  60. package/dist/core/events/v3/coordination-view.d.ts +3 -0
  61. package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
  62. package/dist/core/events/v3/coordination-view.js +69 -8
  63. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  64. package/dist/core/events/v3/producers/intake.js +9 -2
  65. package/dist/core/events/v3/producers/recorder.d.ts +23 -0
  66. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  67. package/dist/core/events/v3/producers/recorder.js +264 -18
  68. package/dist/core/hooks/cli.js +120 -27
  69. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  70. package/dist/core/hooks/resolve/transcript.js +10 -3
  71. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  72. package/dist/core/hooks/session-name-presence.js +4 -1
  73. package/dist/core/qa-artifacts.d.ts +20 -0
  74. package/dist/core/qa-artifacts.d.ts.map +1 -0
  75. package/dist/core/qa-artifacts.js +110 -0
  76. package/dist/core/resources/contract.d.ts +6 -0
  77. package/dist/core/resources/contract.d.ts.map +1 -1
  78. package/dist/core/resources/sampler.d.ts +7 -0
  79. package/dist/core/resources/sampler.d.ts.map +1 -1
  80. package/dist/core/resources/sampler.js +106 -5
  81. package/dist/lib/admission.d.ts +71 -0
  82. package/dist/lib/admission.d.ts.map +1 -0
  83. package/dist/lib/admission.js +264 -0
  84. package/dist/lib/agent-browser/client.d.ts +1 -1
  85. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  86. package/dist/lib/agent-browser/client.js +1 -5
  87. package/dist/lib/browser/capture-fidelity.d.ts +39 -0
  88. package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
  89. package/dist/lib/browser/capture-fidelity.js +84 -0
  90. package/dist/lib/browser/client.d.ts +41 -1
  91. package/dist/lib/browser/client.d.ts.map +1 -1
  92. package/dist/lib/browser/client.js +167 -9
  93. package/dist/lib/browser/critique.d.ts +38 -1
  94. package/dist/lib/browser/critique.d.ts.map +1 -1
  95. package/dist/lib/browser/critique.js +34 -6
  96. package/dist/lib/browser/index.d.ts +4 -2
  97. package/dist/lib/browser/index.d.ts.map +1 -1
  98. package/dist/lib/browser/index.js +2 -0
  99. package/dist/lib/browser/page-review-judge.d.ts +64 -0
  100. package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
  101. package/dist/lib/browser/page-review-judge.js +270 -0
  102. package/dist/lib/browser/page-review-pack.d.ts +613 -0
  103. package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
  104. package/dist/lib/browser/page-review-pack.js +1751 -0
  105. package/dist/lib/browser/qa-run-contracts.d.ts +214 -10
  106. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  107. package/dist/lib/browser/qa-run-contracts.js +136 -1
  108. package/dist/lib/browser/qa-run.d.ts +100 -10
  109. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  110. package/dist/lib/browser/qa-run.js +768 -169
  111. package/dist/lib/browser/request-diagnostics.d.ts +13 -0
  112. package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
  113. package/dist/lib/browser/request-diagnostics.js +18 -0
  114. package/dist/lib/browser/tiling.d.ts +19 -0
  115. package/dist/lib/browser/tiling.d.ts.map +1 -1
  116. package/dist/lib/browser/tiling.js +28 -0
  117. package/dist/lib/cookies/client.d.ts +9 -0
  118. package/dist/lib/cookies/client.d.ts.map +1 -1
  119. package/dist/lib/cookies/client.js +197 -44
  120. package/dist/lib/cookies/extra.d.ts +18 -0
  121. package/dist/lib/cookies/extra.d.ts.map +1 -0
  122. package/dist/lib/cookies/extra.js +14 -0
  123. package/dist/lib/cookies/index.d.ts +2 -1
  124. package/dist/lib/cookies/index.d.ts.map +1 -1
  125. package/dist/lib/cookies/index.js +2 -1
  126. package/dist/lib/durable-job.d.ts +124 -0
  127. package/dist/lib/durable-job.d.ts.map +1 -0
  128. package/dist/lib/durable-job.js +296 -0
  129. package/dist/lib/http/client.d.ts +7 -1
  130. package/dist/lib/http/client.d.ts.map +1 -1
  131. package/dist/lib/http/client.js +2 -0
  132. package/dist/lib/instructions/templates.d.ts.map +1 -1
  133. package/dist/lib/instructions/templates.js +5 -2
  134. package/package.json +8 -2
  135. package/src/commander.ts +50 -2
  136. package/src/commands/admission.ts +699 -0
  137. package/src/commands/agents.ts +87 -1
  138. package/src/commands/artifacts.ts +97 -21
  139. package/src/commands/browse-ai.ts +10 -5
  140. package/src/commands/browse.ts +481 -20
  141. package/src/commands/fetch.ts +1 -0
  142. package/src/commands/qa-record.ts +682 -0
  143. package/src/commands/qa-run.ts +335 -16
  144. package/src/commands/qa-status.ts +608 -0
  145. package/src/commands/qa-verify.ts +238 -0
  146. package/src/commands/review-pack.ts +1281 -0
  147. package/src/core/agents/qa-signal.ts +261 -0
  148. package/src/core/agents/session-name-display.ts +78 -7
  149. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  150. package/src/core/agents/state/heartbeat-writer.ts +23 -0
  151. package/src/core/agents/state/live-coordination-view.ts +1 -0
  152. package/src/core/agents/state/live-coordination-writer.ts +5 -0
  153. package/src/core/artifacts/constants.ts +1 -1
  154. package/src/core/artifacts/index.ts +370 -21
  155. package/src/core/config.ts +23 -0
  156. package/src/core/diagnostics/bundle.ts +119 -11
  157. package/src/core/events/v3/bootstrap.ts +10 -0
  158. package/src/core/events/v3/coordination-view.ts +100 -11
  159. package/src/core/events/v3/producers/intake.ts +9 -2
  160. package/src/core/events/v3/producers/recorder.ts +312 -18
  161. package/src/core/hooks/cli.ts +140 -32
  162. package/src/core/hooks/resolve/transcript.ts +10 -3
  163. package/src/core/hooks/session-name-presence.ts +6 -1
  164. package/src/core/qa-artifacts.ts +126 -0
  165. package/src/core/resources/contract.ts +7 -0
  166. package/src/core/resources/sampler.ts +138 -6
  167. package/src/lib/admission.ts +347 -0
  168. package/src/lib/agent-browser/client.ts +2 -10
  169. package/src/lib/browser/capture-fidelity.ts +98 -0
  170. package/src/lib/browser/client.ts +206 -10
  171. package/src/lib/browser/critique.ts +62 -7
  172. package/src/lib/browser/index.ts +36 -0
  173. package/src/lib/browser/page-review-judge.ts +360 -0
  174. package/src/lib/browser/page-review-pack.ts +2384 -0
  175. package/src/lib/browser/qa-run-contracts.ts +366 -3
  176. package/src/lib/browser/qa-run.ts +868 -190
  177. package/src/lib/browser/request-diagnostics.ts +27 -0
  178. package/src/lib/browser/tiling.ts +32 -0
  179. package/src/lib/cookies/client.ts +228 -42
  180. package/src/lib/cookies/extra.ts +28 -0
  181. package/src/lib/cookies/index.ts +2 -0
  182. package/src/lib/durable-job.ts +407 -0
  183. package/src/lib/http/client.ts +13 -1
  184. package/src/lib/instructions/templates.ts +5 -2
@@ -329,7 +329,7 @@ function processHookSignalLocked(control, input, sessionHash, path) {
329
329
  const durability = writeEventV3(input.coordRoot, pendingEvent, input.writerOptions);
330
330
  applyCommittedEvent(state, pendingEvent);
331
331
  state.pending = undefined;
332
- publishProducerState(path, state);
332
+ publishProducerState(input.coordRoot, path, state);
333
333
  if (pendingSource && incomingSource === pendingSource) {
334
334
  recovered = { state: "recorded", event: pendingEvent, durability, recovered: true };
335
335
  }
@@ -373,7 +373,16 @@ function processHookSignalLocked(control, input, sessionHash, path) {
373
373
  state = newProducerState(input, sessionId, epochId, boundaryEventId, genesisId);
374
374
  const cursorPromptBootstrap = isCursorPromptBootstrap(input);
375
375
  const onboarding = buildMidFlightSessionStart(input, state, rootId, cursorPromptBootstrap);
376
- commitEventLocked(input, state, path, onboarding);
376
+ // Onboarding commits eagerly even when the triggering hook defers its
377
+ // drain. The coordination view reads committed history only, so a
378
+ // spooled session.started leaves the session invisible to every
379
+ // authority command (set-task, status --end-turn, heal) until some other
380
+ // writer happens to drain the spool.
381
+ const onboardingInput = withEagerDrain(input);
382
+ commitEventLocked(onboardingInput, state, path, onboarding);
383
+ if (midFlightSignalImpliesOpenTurn(input.signal)) {
384
+ commitMidFlightTurnStart(onboardingInput, state, path, rootId);
385
+ }
377
386
  if (!cursorPromptBootstrap) {
378
387
  writeProducerDiagnosticV3(input.coordRoot, "mid_flight_onboarding", {
379
388
  adapter: input.adapter,
@@ -460,7 +469,7 @@ function processHookSignalLocked(control, input, sessionHash, path) {
460
469
  span_id: state.current_turn_span?.span_id,
461
470
  payload: input.payload,
462
471
  });
463
- publishProducerState(path, state);
472
+ publishProducerState(input.coordRoot, path, state);
464
473
  return { state: "ignored" };
465
474
  }
466
475
  // Every event derived before the native signal has now been committed.
@@ -483,7 +492,7 @@ function processHookSignalLocked(control, input, sessionHash, path) {
483
492
  ? "no_open_turn_span"
484
493
  : "ritual_observation_missing",
485
494
  });
486
- publishProducerState(path, state);
495
+ publishProducerState(input.coordRoot, path, state);
487
496
  return { state: "ignored" };
488
497
  }
489
498
  const observation = {
@@ -494,7 +503,7 @@ function processHookSignalLocked(control, input, sessionHash, path) {
494
503
  status_box_present_strict: input.turn_ritual.status_box_present_strict,
495
504
  };
496
505
  state.cursor_response_ritual = observation;
497
- publishProducerState(path, state);
506
+ publishProducerState(input.coordRoot, path, state);
498
507
  writeProducerDiagnosticV3(input.coordRoot, "cursor_response_ritual_observed", {
499
508
  instance_id: state.instance_id,
500
509
  generation_id: state.generation_id,
@@ -1259,16 +1268,107 @@ function commitRecoveredToolTurnStart(input, state, path, rootId, fingerprintCon
1259
1268
  assertEventV3(event);
1260
1269
  commitEventLocked(input, state, path, event);
1261
1270
  }
1271
+ /**
1272
+ * Signals an adapter can only deliver while a turn is executing. A session
1273
+ * onboarded mid-flight by one of these is provably inside a turn, so the
1274
+ * generation opens with one instead of waiting for the next native prompt.
1275
+ * The native prompt path opens its own turn, session boundaries carry no
1276
+ * turn, and compaction can run between turns.
1277
+ */
1278
+ function midFlightSignalImpliesOpenTurn(signal) {
1279
+ switch (signal) {
1280
+ case "session-start":
1281
+ case "session-end":
1282
+ case "user-prompt-submit":
1283
+ case "pre-compact":
1284
+ case "post-compact":
1285
+ return false;
1286
+ default:
1287
+ return true;
1288
+ }
1289
+ }
1290
+ /** Commit through the append path even when the hook asked to defer its drain. */
1291
+ function withEagerDrain(input) {
1292
+ if (!input.writerOptions?.deferDrain)
1293
+ return input;
1294
+ return { ...input, writerOptions: { ...input.writerOptions, deferDrain: false } };
1295
+ }
1296
+ /**
1297
+ * Open a derived turn for a session onboarded mid-flight (ADR 0078) by a
1298
+ * signal that only an executing turn can deliver. Without it the fresh
1299
+ * generation has no current turn: command telemetry refuses to join
1300
+ * (`turn_not_started`), tool spans nest under the session span, and after an
1301
+ * epoch rotation (ADR 0137) the live session cannot satisfy its end-of-turn
1302
+ * gate until a human submits the next prompt. The payload's native turn id
1303
+ * is kept when the adapter supplies one, so the turn already running keeps
1304
+ * its identity across the boundary and later signals join it directly.
1305
+ */
1306
+ function commitMidFlightTurnStart(input, state, path, rootId, recovery) {
1307
+ const fingerprintContext = fingerprintContextV3(input.coordRoot, rootId, state.generation_id, state.privacy_epoch_id);
1308
+ const clock = signalClock(input);
1309
+ const span = openSpanStateV3({
1310
+ span_id: spanIdV3(),
1311
+ parent_span_id: state.session_span.span_id,
1312
+ boot_id: state.boot_id,
1313
+ clock,
1314
+ });
1315
+ state.current_turn_span = span;
1316
+ const nativeTurnId = input.payload.turn_id;
1317
+ state.current_native_turn_id = nativeTurnId;
1318
+ const event = normalizeHookEventV3("user-prompt-submit", recoveryBoundaryPayload(input.payload), {
1319
+ coordRoot: input.coordRoot,
1320
+ adapter: input.adapter,
1321
+ adapterVersion: input.adapterVersion,
1322
+ harnessVersion: input.harnessVersion,
1323
+ root_id: rootId,
1324
+ run_id: input.run_id,
1325
+ workflow_id: input.workflow_id,
1326
+ workflow_agent_id: input.workflow_agent_id,
1327
+ instance_id: state.instance_id,
1328
+ generation_id: state.generation_id,
1329
+ attestation_id: state.attestation_id,
1330
+ producer_id: input.producer_id,
1331
+ boot_id: state.boot_id,
1332
+ sequence: state.next_sequence,
1333
+ build_id: input.build_id,
1334
+ platform: input.platform,
1335
+ bridge: input.bridge,
1336
+ capability_profile: state.capability_profile,
1337
+ fingerprintContext,
1338
+ turn_native_id: nativeTurnId ?? `mid-flight:${state.generation_id}:${state.turn_ordinal + 1}`,
1339
+ span_id: span.span_id,
1340
+ caused_by: state.last_event_id ? [state.last_event_id] : [],
1341
+ observed_at: clock.observed_at,
1342
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1343
+ clock_id: state.clock_id,
1344
+ });
1345
+ if (event?.event_type !== "turn.started") {
1346
+ throw new Error("mid-flight turn start could not be normalized");
1347
+ }
1348
+ event.provenance = {
1349
+ ...event.provenance,
1350
+ source_event: recovery?.source_event ?? `${input.adapter}.recovery`,
1351
+ attestation: "derived",
1352
+ confidence: recovery?.confidence ?? (nativeTurnId ? "medium" : "low"),
1353
+ attribution: {
1354
+ method: recovery?.attribution_method ?? (nativeTurnId ? "native_payload" : "session_env"),
1355
+ state: "verified",
1356
+ subject_instance_id: state.instance_id,
1357
+ },
1358
+ };
1359
+ assertEventV3(event);
1360
+ commitEventLocked(input, state, path, event);
1361
+ }
1262
1362
  /** The single pending-publish/write/apply/publish cycle every locked event commit uses. */
1263
1363
  function commitEventLocked(input, state, path, event, sourceId) {
1264
1364
  markObservedClockRegressionV3(event, state.last_observed_at);
1265
1365
  assertEventV3(event);
1266
1366
  state.pending = { ...(sourceId ? { source_id: sourceId } : {}), event };
1267
- publishProducerState(path, state);
1367
+ publishProducerState(input.coordRoot, path, state);
1268
1368
  const durability = writeEventV3(input.coordRoot, event, input.writerOptions);
1269
1369
  applyCommittedEvent(state, event);
1270
1370
  state.pending = undefined;
1271
- publishProducerState(path, state);
1371
+ publishProducerState(input.coordRoot, path, state);
1272
1372
  return durability;
1273
1373
  }
1274
1374
  function commitTurnContextObservation(input, state, path, rootId, fingerprintContext, target, measurement, contextProvenance, observedAt) {
@@ -1287,7 +1387,7 @@ function commitContextObservation(input, state, path, rootId, fingerprintContext
1287
1387
  if (measurement.state === "observed" &&
1288
1388
  contextProvenance?.source_witness &&
1289
1389
  state.last_context_source_witness === contextProvenance.source_witness) {
1290
- publishProducerState(path, state);
1390
+ publishProducerState(input.coordRoot, path, state);
1291
1391
  return;
1292
1392
  }
1293
1393
  if (measurement.state === "observed" && contextProvenance?.source_witness) {
@@ -1387,12 +1487,12 @@ function maybeCommitActiveRuntimeContextObservation(input, state, path, rootId,
1387
1487
  return;
1388
1488
  }
1389
1489
  if (input.adapter === "cursor" && state.cursor_mode === "cloud") {
1390
- publishProducerState(path, state);
1490
+ publishProducerState(input.coordRoot, path, state);
1391
1491
  return;
1392
1492
  }
1393
1493
  const transcriptPath = runtimeTranscriptPath(input, state);
1394
1494
  if (!transcriptPath && input.adapter !== "cursor") {
1395
- publishProducerState(path, state);
1495
+ publishProducerState(input.coordRoot, path, state);
1396
1496
  return;
1397
1497
  }
1398
1498
  const runtime = readRuntimeContextTelemetry({
@@ -1409,7 +1509,7 @@ function maybeCommitActiveRuntimeContextObservation(input, state, path, rootId,
1409
1509
  if (runtime.bytes_read > 0)
1410
1510
  recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
1411
1511
  if (runtime.state !== "observed") {
1412
- publishProducerState(path, state);
1512
+ publishProducerState(input.coordRoot, path, state);
1413
1513
  return;
1414
1514
  }
1415
1515
  commitActiveContextObservation(input, state, path, rootId, fingerprintContext, sourceEvent, runtimeContextObservation(runtime), {
@@ -1578,7 +1678,7 @@ function reconcilePendingRuntimeContexts(input, state, path, rootId, fingerprint
1578
1678
  if (state.pending_runtime_contexts?.length === 0) {
1579
1679
  state.pending_runtime_contexts = undefined;
1580
1680
  }
1581
- publishProducerState(path, state);
1681
+ publishProducerState(input.coordRoot, path, state);
1582
1682
  }
1583
1683
  function reconcilePendingRuntimeContextsBeforeApprovedEnd(input, state, path, rootId, fingerprintContext) {
1584
1684
  if (state.adapter !== "codex" || !state.pending_runtime_contexts?.length)
@@ -2234,7 +2334,7 @@ export function recordApprovedSessionEndV3(input) {
2234
2334
  const durability = writeEventV3(input.coordRoot, pending, input.writerOptions);
2235
2335
  applyCommittedEvent(state, pending);
2236
2336
  state.pending = undefined;
2237
- publishProducerState(record.path, state);
2337
+ publishProducerState(input.coordRoot, record.path, state);
2238
2338
  recovered = true;
2239
2339
  if (pending.event_type === "session.ended") {
2240
2340
  return { state: "recorded", event: pending, durability, recovered };
@@ -2317,11 +2417,11 @@ export function recordApprovedSessionEndV3(input) {
2317
2417
  markObservedClockRegressionV3(event, state.last_observed_at);
2318
2418
  assertEventV3(event);
2319
2419
  state.pending = { event };
2320
- publishProducerState(record.path, state);
2420
+ publishProducerState(input.coordRoot, record.path, state);
2321
2421
  const durability = writeEventV3(input.coordRoot, event, input.writerOptions);
2322
2422
  applyCommittedEvent(state, event);
2323
2423
  state.pending = undefined;
2324
- publishProducerState(record.path, state);
2424
+ publishProducerState(input.coordRoot, record.path, state);
2325
2425
  return { state: "recorded", event, durability, recovered };
2326
2426
  }
2327
2427
  finally {
@@ -2383,7 +2483,7 @@ export function salvageOpenSpansV3(input) {
2383
2483
  writeEventV3(input.coordRoot, pendingEvent, input.writerOptions);
2384
2484
  applyCommittedEvent(state, pendingEvent);
2385
2485
  state.pending = undefined;
2386
- publishProducerState(record.path, state);
2486
+ publishProducerState(input.coordRoot, record.path, state);
2387
2487
  }
2388
2488
  const allowed = new Set(input.allowed_span_ids);
2389
2489
  const fingerprintContext = fingerprintContextV3(input.coordRoot, rootId, state.generation_id, state.privacy_epoch_id);
@@ -2445,6 +2545,126 @@ export function listHookProducerStateRecordsV3(coordRoot, options = {}) {
2445
2545
  return records.sort((left, right) => left.state.generation_id.localeCompare(right.state.generation_id) ||
2446
2546
  left.path.localeCompare(right.path));
2447
2547
  }
2548
+ /**
2549
+ * Re-anchor every live archived hook producer into a newly activated epoch.
2550
+ *
2551
+ * The archive rename is the rotation's atomic snapshot: it includes every
2552
+ * state published before the boundary without copying any old boot sequence
2553
+ * or causal link into the successor. Each live session gets a fresh
2554
+ * generation and attestation. Only a turn that was open at the boundary is
2555
+ * reopened, which makes command telemetry joinable before another adapter
2556
+ * hook fires while preserving the between-turn state for idle sessions.
2557
+ */
2558
+ export function reanchorArchivedHookProducersV3(input) {
2559
+ const control = readEventV3ControlState(input.coordRoot);
2560
+ if (control.state !== input.mode) {
2561
+ throw new Error(`event_v3_rotation_reanchor_gate_closed:${control.state}`);
2562
+ }
2563
+ const rootId = control.genesis.event.scope.root_id;
2564
+ const epochId = control.genesis.profile.privacy_key_epoch;
2565
+ const genesisId = control.genesis.event.payload.genesis_id;
2566
+ const boundaryEventId = control.state === "candidate"
2567
+ ? control.genesis.event.event_id
2568
+ : control.activation.event.event_id;
2569
+ const observedAt = input.observed_at ?? new Date().toISOString();
2570
+ let sessions = 0;
2571
+ let turns = 0;
2572
+ for (const archived of listArchivedHookProducerStatesV3(input.archivedEpoch)) {
2573
+ const previous = archived.state;
2574
+ if (previous.terminal || !previous.started_event_id)
2575
+ continue;
2576
+ const path = producerStatePath(input.coordRoot, previous.adapter, archived.sessionHash);
2577
+ const lease = acquireStateLeaseWithRetry(input.coordRoot, path, 40);
2578
+ if (!lease) {
2579
+ throw new Error(`event_v3_rotation_reanchor_busy:${previous.instance_id}`);
2580
+ }
2581
+ try {
2582
+ if (existsSync(path)) {
2583
+ const current = readProducerState(path);
2584
+ if (current.epoch_genesis_id === genesisId)
2585
+ continue;
2586
+ }
2587
+ const sourceEvent = `${previous.adapter}.epoch-rotation-reanchor`;
2588
+ const rotationInput = {
2589
+ coordRoot: input.coordRoot,
2590
+ mode: input.mode,
2591
+ signal: "session-start",
2592
+ payload: {
2593
+ raw: {},
2594
+ ...(previous.current_native_turn_id ? { turn_id: previous.current_native_turn_id } : {}),
2595
+ ...(previous.adapter === "cursor" && previous.cursor_mode
2596
+ ? { cursor_mode: previous.cursor_mode }
2597
+ : {}),
2598
+ },
2599
+ adapter: previous.adapter,
2600
+ instance_id: previous.instance_id,
2601
+ producer_id: "prd_agent-hook",
2602
+ build_id: input.build_id,
2603
+ platform: input.platform,
2604
+ observed_at: observedAt,
2605
+ writerOptions: { expectedGenesisId: genesisId },
2606
+ };
2607
+ const state = newProducerState(rotationInput, previous.session_id, epochId, boundaryEventId, genesisId);
2608
+ const sessionStart = buildMidFlightSessionStart(rotationInput, state, rootId);
2609
+ if (sessionStart.event_type !== "session.started") {
2610
+ throw new Error("rotation re-anchor session start could not be normalized");
2611
+ }
2612
+ sessionStart.provenance = {
2613
+ ...sessionStart.provenance,
2614
+ source_event: sourceEvent,
2615
+ attestation: "derived",
2616
+ confidence: "high",
2617
+ attribution: {
2618
+ method: "session_env",
2619
+ state: "verified",
2620
+ subject_instance_id: state.instance_id,
2621
+ },
2622
+ };
2623
+ sessionStart.payload.resume = {
2624
+ state: "unknown",
2625
+ reason: "epoch_rotation_reanchor",
2626
+ };
2627
+ assertEventV3(sessionStart);
2628
+ commitEventLocked(rotationInput, state, path, sessionStart);
2629
+ sessions += 1;
2630
+ if (previous.current_turn_id && previous.current_turn_span) {
2631
+ commitMidFlightTurnStart(rotationInput, state, path, rootId, {
2632
+ source_event: sourceEvent,
2633
+ confidence: "high",
2634
+ attribution_method: "session_env",
2635
+ });
2636
+ turns += 1;
2637
+ }
2638
+ }
2639
+ finally {
2640
+ lease.release();
2641
+ }
2642
+ }
2643
+ return { sessions, turns };
2644
+ }
2645
+ function listArchivedHookProducerStatesV3(archivedEpoch) {
2646
+ const producerRoot = join(resolve(archivedEpoch), "private-producers");
2647
+ if (!existsSync(producerRoot))
2648
+ return [];
2649
+ const records = [];
2650
+ for (const adapter of ["claude-code", "codex", "cursor"]) {
2651
+ const directory = join(producerRoot, adapter);
2652
+ if (!existsSync(directory))
2653
+ continue;
2654
+ const metadata = lstatSync(directory);
2655
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
2656
+ throw new Error("V3 archived producer state directory is unsafe");
2657
+ }
2658
+ for (const name of readdirSync(directory).filter((entry) => /^hid_[a-f0-9]{64}\.json$/.test(entry))) {
2659
+ records.push({
2660
+ sessionHash: name.slice(0, -".json".length),
2661
+ state: readProducerState(join(directory, name)),
2662
+ });
2663
+ }
2664
+ }
2665
+ return records.sort((left, right) => left.state.generation_id.localeCompare(right.state.generation_id) ||
2666
+ left.sessionHash.localeCompare(right.sessionHash));
2667
+ }
2448
2668
  function hookSignalCapability(signal) {
2449
2669
  switch (signal) {
2450
2670
  case "session-start":
@@ -2895,7 +3115,11 @@ function acquireStateLease(coordRoot, statePath) {
2895
3115
  mkdirSync(directory, { recursive: true, mode: 0o700 });
2896
3116
  chmodSync(producerRoot, 0o700);
2897
3117
  chmodSync(directory, 0o700);
2898
- return acquireNoClobberLease({
3118
+ const acquiredControl = readEventV3ControlState(coordRoot);
3119
+ const acquiredGenesis = acquiredControl.state === "candidate" || acquiredControl.state === "active"
3120
+ ? acquiredControl.genesis.event.payload.genesis_id
3121
+ : undefined;
3122
+ const lease = acquireNoClobberLease({
2899
3123
  path: `${statePath}.lease`,
2900
3124
  scope: "event-v3-hook-producer",
2901
3125
  authoritySha256: createHash("sha256")
@@ -2906,8 +3130,30 @@ function acquireStateLease(coordRoot, statePath) {
2906
3130
  staleAfterMs: 5_000,
2907
3131
  validateStaleOwner: (owner) => owner.host === hostname() && !pidIsAlive(owner.pid),
2908
3132
  });
3133
+ return {
3134
+ ...lease,
3135
+ release() {
3136
+ try {
3137
+ lease.release();
3138
+ }
3139
+ catch (error) {
3140
+ const current = readEventV3ControlState(coordRoot);
3141
+ const epochReplaced = acquiredGenesis !== undefined &&
3142
+ (current.state === "candidate" || current.state === "active") &&
3143
+ current.genesis.event.payload.genesis_id !== acquiredGenesis;
3144
+ if (error.code === "ENOENT" && epochReplaced)
3145
+ return;
3146
+ throw error;
3147
+ }
3148
+ },
3149
+ };
2909
3150
  }
2910
- function publishProducerState(path, state) {
3151
+ function publishProducerState(coordRoot, path, state) {
3152
+ const control = readEventV3ControlState(coordRoot);
3153
+ if ((control.state === "candidate" || control.state === "active") &&
3154
+ state.epoch_genesis_id !== control.genesis.event.payload.genesis_id) {
3155
+ return;
3156
+ }
2911
3157
  const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
2912
3158
  let fd;
2913
3159
  try {
@@ -25,8 +25,8 @@ import { resolveCoordRoot } from "../agents/coord-client.js";
25
25
  import { classifyWriteClaimFinalization, formatWriteClaimFinalizationDenial, } from "../agents/finalization.js";
26
26
  import { recordLiveClaimChangeV3, restoreLiveCoordinationStateAfterEpochV3, } from "../agents/live-authority-v3.js";
27
27
  import { evaluateStopHook, STOP_REMEDIATION_MARKER } from "../agents/rules/stop-hook.js";
28
- import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, sessionNameDisplayInstruction, sessionNameDisplayPending, sessionNameDisplayRecoveryInstruction, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
29
- import { readHeartbeat, stampSessionNameSeen } from "../agents/state/heartbeat-writer.js";
28
+ import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, matchSessionNameDisplay, sessionNameDisplayAcceptedNames, sessionNameDisplayInstruction, sessionNameDisplayPending, sessionNameDisplayRecoveryInstruction, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
29
+ import { readHeartbeat, setAssignedNameCache, stampSessionNameRequested, stampSessionNameSeen, } from "../agents/state/heartbeat-writer.js";
30
30
  import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
31
31
  import { ensureLiveCoordinationHeartbeat } from "../agents/state/live-coordination-writer.js";
32
32
  import { assignName } from "../agents/state/names.js";
@@ -159,6 +159,29 @@ function assignNameInProcess(coordRoot, instanceId, kind, forkedFrom) {
159
159
  return null;
160
160
  }
161
161
  }
162
+ /** Mid-flight sessions can emit tools before SessionStart assigns a pool name.
163
+ * Stamp the disposable cache so Codec does not render the 8-character id. */
164
+ function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter) {
165
+ if (coordEnv("WORKFLOW_CHILD") === "1")
166
+ return;
167
+ const row = readLiveCoordinationRow(coordRoot, instanceId) ?? readHeartbeat(coordRoot, instanceId);
168
+ if (row?.kind === "subagent" || row?.kind === "transient" || row?.workflow_run_id)
169
+ return;
170
+ if (row?.name?.trim())
171
+ return;
172
+ const assigned = assignNameInProcess(coordRoot, instanceId, "session");
173
+ if (!assigned?.name)
174
+ return;
175
+ if (!readHeartbeat(coordRoot, instanceId)) {
176
+ try {
177
+ ensureLiveCoordinationHeartbeat(coordRoot, instanceId, sessionId, adapter);
178
+ }
179
+ catch {
180
+ /* cache materialization is best-effort; history still holds the name */
181
+ }
182
+ }
183
+ setAssignedNameCache(coordRoot, instanceId, assigned.name, "session");
184
+ }
162
185
  /**
163
186
  * Direct (in-process) pidmap write, avoiding the spawn overhead of going via
164
187
  * the agent-coord CLI for every session.started signal. Pid-map rows
@@ -270,11 +293,11 @@ function buildEventData(eventType, ctx) {
270
293
  clean_exit: p?.clean_exit ?? true,
271
294
  };
272
295
  case "turn.started": {
273
- // Cursor can deliver beforeSubmitPrompt before sessionStart. Ensure that
274
- // high-confidence prompt bootstrap also mints durable display identity;
275
- // a later SessionStart remains idempotent.
276
- if (ctx.adapter === "cursor" &&
277
- !readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name) {
296
+ // Mid-flight onboarding (any adapter) can observe a turn before
297
+ // SessionStart. Mint durable display identity here so a later
298
+ // SessionStart remains idempotent and Codec is not stuck on the
299
+ // 8-character native id.
300
+ if (!readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name) {
278
301
  assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session");
279
302
  }
280
303
  const prompt = p?.prompt ?? "";
@@ -559,14 +582,34 @@ async function main() {
559
582
  appendDebug(coordRoot, { ...debugBase, skipped: "no-owner-resolved" });
560
583
  return 0;
561
584
  }
562
- const name = sessionNameDisplayPending(readLiveCoordinationRow(coordRoot, owner.instance_id));
585
+ const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
586
+ const name = sessionNameDisplayPending(row);
563
587
  const text = typeof payload?.raw.text === "string" ? payload.raw.text : "";
564
- if (name && assistantTextStartsWithSessionNameBlock(text, name)) {
565
- stampSessionNameSeen(coordRoot, owner.instance_id, name);
566
- appendDebug(coordRoot, { ...debugBase, effect: "session-name-display-stamped" });
588
+ const sighting = matchSessionNameDisplay(row, text);
589
+ if (sighting) {
590
+ stampSessionNameSeen(coordRoot, owner.instance_id, sighting.pending);
591
+ appendDebug(coordRoot, {
592
+ ...debugBase,
593
+ effect: "session-name-display-stamped",
594
+ session_name_pending: sighting.pending,
595
+ ...(sighting.displayed === sighting.pending
596
+ ? {}
597
+ : { session_name_displayed: sighting.displayed, session_name_drift: true }),
598
+ });
599
+ }
600
+ else if (!name) {
601
+ // Nothing was owed. Distinct from a miss: conflating the two is what made
602
+ // a latched Cursor session undiagnosable from this log.
603
+ appendDebug(coordRoot, { ...debugBase, skipped: "no-pending-session-name" });
567
604
  }
568
605
  else {
569
- appendDebug(coordRoot, { ...debugBase, skipped: "no-session-name-sighting" });
606
+ appendDebug(coordRoot, {
607
+ ...debugBase,
608
+ skipped: "session-name-block-absent",
609
+ session_name_pending: name,
610
+ reply_bytes: text.length,
611
+ reply_leads_with_fence: /^\s*`{3,}/.test(text),
612
+ });
570
613
  }
571
614
  if (ledgerRoute.state === "blocked") {
572
615
  appendDebug(coordRoot, {
@@ -638,6 +681,14 @@ async function main() {
638
681
  hookHealthState.owner_id = owner.instance_id;
639
682
  const sessionId = payload?.session_id ?? payload?.conversation_id ?? owner.instance_id;
640
683
  const priorCoordination = readHeartbeat(coordRoot, owner.instance_id);
684
+ if (norm.event_type !== "session.ended" &&
685
+ coordEnv("WORKFLOW_CHILD") !== "1" &&
686
+ priorCoordination?.kind !== "subagent" &&
687
+ priorCoordination?.kind !== "transient" &&
688
+ !priorCoordination?.workflow_run_id &&
689
+ !priorCoordination?.name?.trim()) {
690
+ assignNameInProcess(coordRoot, owner.instance_id, "session");
691
+ }
641
692
  const data = buildEventData(norm.event_type, {
642
693
  coordRoot,
643
694
  payload,
@@ -723,6 +774,14 @@ async function main() {
723
774
  logError(coordRoot, error, { phase: "epoch-coordination-restore" });
724
775
  }
725
776
  }
777
+ if (norm.event_type !== "session.ended") {
778
+ try {
779
+ ensureSessionDisplayName(coordRoot, owner.instance_id, sessionId, adapter);
780
+ }
781
+ catch (error) {
782
+ logError(coordRoot, error, { phase: "session-display-name" });
783
+ }
784
+ }
726
785
  const v3EventId = v3Result && "event" in v3Result
727
786
  ? v3Result.event.event_id
728
787
  : v3Result && "event_id" in v3Result
@@ -1177,6 +1236,11 @@ async function main() {
1177
1236
  try {
1178
1237
  const name = sessionNameDisplayPending(readLiveCoordinationRow(coordRoot, owner.instance_id));
1179
1238
  if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
1239
+ // Record the title before asking for it. The suggestion can still
1240
+ // change afterwards (an assigned-name rewrite, a lifecycle re-mint, a
1241
+ // rebuilt cache), and the agent must not be stranded for displaying
1242
+ // exactly what it was handed.
1243
+ stampSessionNameRequested(coordRoot, owner.instance_id, name);
1180
1244
  const { emitContext } = await import("./adapter/output.js");
1181
1245
  emitContext(adapter, "PostToolUse", sessionNameDisplayInstruction(name));
1182
1246
  }
@@ -1241,21 +1305,7 @@ async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter,
1241
1305
  const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
1242
1306
  if (isSessionNameRemediationCommand(command, resolveBinName(coordRoot)))
1243
1307
  return true;
1244
- const inspection = adapter === "cursor" && payload?.agent_message !== undefined
1245
- ? {
1246
- state: assistantTextStartsWithSessionNameBlock(payload.agent_message, name)
1247
- ? "present"
1248
- : "unavailable",
1249
- }
1250
- : inspectSessionNameDisplayImmediately(
1251
- // Codex hook payloads omit transcript_path on every event, which
1252
- // left this inspection permanently unavailable and the latch never
1253
- // stamped. Discover the rollout by session id; the scan only runs
1254
- // while a name is pending, so the latch closes after one success.
1255
- payload?.transcript_path ??
1256
- (adapter === "codex"
1257
- ? discoverCodexSessionTranscript(payload?.session_id ?? instanceId)
1258
- : undefined), name, assistantTextStartsWithSessionNameBlock);
1308
+ const inspection = inspectDisplayEvidence(instanceId, adapter, payload, coordination);
1259
1309
  if (inspection.state === "present") {
1260
1310
  stampSessionNameSeen(coordRoot, instanceId, name);
1261
1311
  return true;
@@ -1268,10 +1318,53 @@ async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter,
1268
1318
  // readable transcript can still verify it.
1269
1319
  return true;
1270
1320
  }
1321
+ // Record the refusal. Without this the gate was the one enforcement path
1322
+ // that left no trace, so a session reporting "the gate keeps denying me"
1323
+ // could not be confirmed or refuted from the hook log.
1324
+ appendDebug(coordRoot, {
1325
+ ts: new Date().toISOString(),
1326
+ event_name: "pre-tool-use",
1327
+ adapter,
1328
+ effect: "session-name-display-denied",
1329
+ session_name_pending: name,
1330
+ tool_name: payload?.tool_name,
1331
+ });
1271
1332
  const { emitDeny } = await import("./adapter/output.js");
1272
1333
  emitDeny(adapter, sessionNameDisplayRecoveryInstruction(name, resolveBinName(coordRoot)));
1273
1334
  return false;
1274
1335
  }
1336
+ /**
1337
+ * Evidence for the pending display, over every title whose display counts.
1338
+ *
1339
+ * Cursor supplies the current narration directly; Claude Code and Codex are
1340
+ * resolved from their JSONL transcripts. Absent is the only state that denies,
1341
+ * so a title is refused only when a readable surface positively shows some
1342
+ * other opening text for every accepted title.
1343
+ */
1344
+ function inspectDisplayEvidence(instanceId, adapter, payload, coordination) {
1345
+ if (adapter === "cursor" && payload?.agent_message !== undefined) {
1346
+ return matchSessionNameDisplay(coordination, payload.agent_message)
1347
+ ? { state: "present" }
1348
+ : { state: "unavailable", reason: "transcript_not_ready" };
1349
+ }
1350
+ // Codex hook payloads omit transcript_path on every event, which left this
1351
+ // inspection permanently unavailable and the latch never stamped. Discover
1352
+ // the rollout by session id; the scan only runs while a name is pending, so
1353
+ // the latch closes after one success.
1354
+ const transcriptPath = payload?.transcript_path ??
1355
+ (adapter === "codex"
1356
+ ? discoverCodexSessionTranscript(payload?.session_id ?? instanceId)
1357
+ : undefined);
1358
+ let verdict = { state: "absent" };
1359
+ for (const candidate of sessionNameDisplayAcceptedNames(coordination)) {
1360
+ const inspection = inspectSessionNameDisplayImmediately(transcriptPath, candidate, assistantTextStartsWithSessionNameBlock);
1361
+ if (inspection.state === "present")
1362
+ return inspection;
1363
+ if (inspection.state === "unavailable")
1364
+ verdict = inspection;
1365
+ }
1366
+ return verdict;
1367
+ }
1275
1368
  function generationBoundHeartbeat(heartbeat, generationId) {
1276
1369
  return generationId && heartbeat?.v3_generation_id === generationId ? heartbeat : null;
1277
1370
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,wBAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,OAAO,CAIT;AAED,MAAM,MAAM,4BAA4B,GACpC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,CAAA;CAAE,CAAC;AAEpF;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,4BAA4B,CA0B9B;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;4CAC4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,qBAAqB,GAAG,SAAS,CA8BnC;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAkDpB"}
1
+ {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,wBAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,OAAO,CAIT;AAED,MAAM,MAAM,4BAA4B,GACpC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,CAAA;CAAE,CAAC;AAEpF;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,4BAA4B,CAiC9B;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;4CAC4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,qBAAqB,GAAG,SAAS,CA8BnC;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAkDpB"}
@@ -154,9 +154,16 @@ export function inspectSessionNameDisplayImmediately(transcriptPath, name, start
154
154
  awaitingReply = false;
155
155
  }
156
156
  }
157
- return sawMintResult && awaitingReply
158
- ? { state: "unavailable", reason: "transcript_not_ready" }
159
- : { state: "absent" };
157
+ // Some adapter transcripts persist assistant messages and tool requests but
158
+ // omit tool-result rows entirely. A readable file with no mint result cannot
159
+ // establish the ordered verification boundary, so it is unavailable evidence
160
+ // rather than proof that the agent displayed the title incorrectly. Only a
161
+ // transcript that contains the mint followed by a non-matching assistant
162
+ // reply can safely deny the next tool.
163
+ if (!sawMintResult || awaitingReply) {
164
+ return { state: "unavailable", reason: "transcript_not_ready" };
165
+ }
166
+ return { state: "absent" };
160
167
  }
161
168
  /**
162
169
  * Resolve a transcript path at the adapter boundary. Windows-native Codex can
@@ -1 +1 @@
1
- {"version":3,"file":"session-name-presence.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/session-name-presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,MAAM,WAAW,mBAAmB;IAClC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC3C,mBAAmB,CAcrB"}
1
+ {"version":3,"file":"session-name-presence.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/session-name-presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,MAAM,WAAW,mBAAmB;IAClC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC3C,mBAAmB,CAkBrB"}