harnery 0.34.0 → 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 +91 -162
  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 +2 -1
  37. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  38. package/dist/core/agents/session-name-display.js +10 -3
  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 +114 -38
  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 +35 -17
  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 +104 -152
  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 +11 -3
  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 +126 -41
  195. package/src/core/hooks/remediation-cap.ts +77 -0
  196. package/src/core/hooks/resolve/transcript.ts +49 -17
  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
@@ -24,19 +24,20 @@ import { buildInstructionBundle } from "../../lib/instructions/bundle.js";
24
24
  import { coordBinPath } from "../agents/coord-bin.js";
25
25
  import { classifyWriteClaimFinalization, formatWriteClaimFinalizationDenial, } from "../agents/finalization.js";
26
26
  import { evaluateStopHook, STOP_REMEDIATION_MARKER } from "../agents/rules/stop-hook.js";
27
- import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, sessionNameDisplayInstruction, sessionNameDisplayPending, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
27
+ import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, sessionNameDisplayInstruction, sessionNameDisplayPending, sessionNameDisplayRecoveryInstruction, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
28
28
  import { stampSessionNameSeen } from "../agents/state/heartbeat-writer.js";
29
- import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
29
+ import { ensureLiveCoordinationHeartbeat, readLiveCoordinationRow, } from "../agents/state/live-coordination-view.js";
30
30
  import { writePidmapRow } from "../agents/state/pidmap.js";
31
31
  import { agentsRequireGitFinalization, resolveBinName } from "../config.js";
32
32
  import { checkpointContext, completeContextRecovery, extractContextSample, markContextCompactionCompleted, prepareContextRecovery, readContextState, recordContextSample, } from "../context/index.js";
33
33
  import { tryWriteLiveDisplayV3 } from "../events/v3/live-feed.js";
34
- import { recordLiveHookSignalV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
34
+ import { liveHookSignalDefersDrainV3, recordLiveDelegatedChildSessionV3, recordLiveHookSignalV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
35
35
  import { captureSpanClockV3 } from "../events/v3/span-state.js";
36
36
  import { fetchPresence } from "../presence/index.js";
37
37
  import { stableScopeId } from "../workflow/scope-id.js";
38
38
  import { detectAdapter, shouldSkipHookAdapter } from "./adapter/detect.js";
39
39
  import { extractBashCommand, extractToolDescription, normalizeEventName, parsePayload, } from "./adapter/parse.js";
40
+ import { discoverCodexSessionTranscript } from "./adapter/runtime-telemetry.js";
40
41
  import { codexWslFileLinkTelemetry, inspectCodexWslBridge, isWslUncPath, renderCodexWslFileLinkContext, } from "./codex-wsl-bridge.js";
41
42
  import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, recordImageArtifactsV3, resetSoundCounters, runSessionSyncExtension, soundForEvent, } from "./effects/index.js";
42
43
  import { toolInputHash, toolTargetHash } from "./events/input-hash.js";
@@ -45,7 +46,7 @@ import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/
45
46
  import { findCoordRoot } from "./resolve/coord-root.js";
46
47
  import { extractIntentComment, resolveIntent } from "./resolve/intent.js";
47
48
  import { resolveOwner } from "./resolve/owner.js";
48
- import { inspectSessionNameDisplayImmediately, scanAssistantTextIncludes, scanSessionNameDisplayedImmediately, scanStatusBoxPresent, scanTranscriptModel, } from "./resolve/transcript.js";
49
+ import { inspectSessionNameDisplayImmediately, scanAssistantTextIncludes, scanSessionNameDisplayedImmediately, scanStatusBoxPresent, scanTranscriptRuntime, } from "./resolve/transcript.js";
49
50
  import { sessionNamePresence } from "./session-name-presence.js";
50
51
  import { unsafeCrossShellReason } from "./unsafe-cross-shell.js";
51
52
  function parseArgv(argv) {
@@ -221,7 +222,7 @@ function buildEventData(eventType, ctx) {
221
222
  // Claude Code's SessionStart payload omits `model` (Codex + Cursor
222
223
  // supply it). Fall back to the transcript, populated on `resume`, and
223
224
  // backfilled later by `turn.completed` for a fresh startup session.
224
- model: p?.model ?? scanTranscriptModel(p?.transcript_path),
225
+ model: p?.model ?? scanTranscriptRuntime(p?.transcript_path)?.model,
225
226
  pid: adapterPid,
226
227
  source: p?.source,
227
228
  platform: adapterPlatform,
@@ -282,7 +283,7 @@ function buildEventData(eventType, ctx) {
282
283
  // Backfill the model for adapters that omit it at session.started
283
284
  // (Claude Code). The transcript is populated with assistant turns by
284
285
  // Stop-hook time, so this resolves even for fresh `startup` sessions.
285
- model: p?.model ?? scanTranscriptModel(p?.transcript_path),
286
+ model: p?.model ?? scanTranscriptRuntime(p?.transcript_path)?.model,
286
287
  // Phase 2: tool_call_count + text_length aren't cheaply available
287
288
  // from the Stop payload alone (they'd require a transcript scan that
288
289
  // races with the JSONL flush). Emit `-1` / `0` sentinels and let
@@ -296,22 +297,26 @@ function buildEventData(eventType, ctx) {
296
297
  // the verdict now sees because agent-hook emits this turn.completed itself
297
298
  // (the previous path passed those via the no-history fail-open).
298
299
  status_box_present: scanStatusBoxPresent(p?.transcript_path) || lastAssistantMessage.includes("┌─ agent-"),
299
- // Shadow measurement for the rule-2/3 detector: the loose scan above
300
+ // Assistant-text evidence for the rule-2/3 detector: the loose scan above
300
301
  // matches the box prefix ANYWHERE in the transcript tail, including
301
302
  // the tool_result row the status command itself writes, so it can
302
303
  // report a paste that never happened. The strict variant counts
303
304
  // assistant text only (same window, same scanner as the session-name
304
- // detection). Telemetry-only: the Stop verdict still reads
305
- // status_box_present. Once the divergence rate between the two fields
306
- // is known, the gate either flips to strict or the rule gets
307
- // reconsidered; either way this field decides it with data.
305
+ // detection). The Stop verdict enforces this field; the loose field is
306
+ // retained as diagnostic telemetry for the measured detector divergence.
308
307
  status_box_present_strict: scanAssistantTextIncludes(p?.transcript_path, "┌─ agent-") ||
309
308
  lastAssistantMessage.includes("┌─ agent-"),
310
309
  // Whether the session's suggested name is satisfied as of this turn,
311
310
  // and which name that refers to. Reported on every stop while a name
312
311
  // exists (the stop-hook.session_name verdict reads it per turn); the
313
312
  // transcript scan itself stops once the name has been sighted.
314
- ...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) => scanSessionNameDisplayedImmediately(p?.transcript_path, name, assistantTextStartsWithSessionNameBlock)),
313
+ ...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) => scanSessionNameDisplayedImmediately(
314
+ // Codex stops carry no transcript_path; discover the rollout by
315
+ // session id so the naming ritual can verify and stamp there too.
316
+ p?.transcript_path ??
317
+ (ctx.adapter === "codex"
318
+ ? discoverCodexSessionTranscript(p?.session_id ?? ctx.instanceId)
319
+ : undefined), name, assistantTextStartsWithSessionNameBlock)),
315
320
  ...(fileLinkTelemetry ?? {}),
316
321
  stop_hook_active: p?.stop_hook_active,
317
322
  };
@@ -321,7 +326,8 @@ function buildEventData(eventType, ctx) {
321
326
  // Subagents inherit parent's name via the resolve-name session_id path
322
327
  // (agent-coord/state/names.ts → kind=transient). Use the call ID as the
323
328
  // instance_id input; assignName falls through to transient.
324
- const assigned = assignNameViaAgentCoord(ctx.coordRoot, ctx.instanceId, "subagent");
329
+ const childInstanceId = subagentCallId ?? ctx.instanceId;
330
+ const assigned = assignNameViaAgentCoord(ctx.coordRoot, childInstanceId, "subagent");
325
331
  return {
326
332
  agent_type: p?.raw.agent_type ??
327
333
  p?.raw.subagent_type ??
@@ -329,7 +335,7 @@ function buildEventData(eventType, ctx) {
329
335
  prompt_summary: p?.raw.prompt_summary,
330
336
  name: assigned?.name,
331
337
  kind: "subagent",
332
- agent_id: ctx.instanceId,
338
+ agent_id: childInstanceId,
333
339
  subagent_call_id: subagentCallId,
334
340
  parent_session_id: p?.parent_session_id,
335
341
  };
@@ -475,14 +481,21 @@ async function main() {
475
481
  const repoRoot = findCoordRoot(process.cwd());
476
482
  if (repoRoot) {
477
483
  let sid = "";
484
+ let stopContinuation = false;
478
485
  try {
479
486
  const j = JSON.parse(raw);
480
487
  sid = j.session_id ?? j.conversation_id ?? "";
488
+ // A stop with stop_hook_active is a remediation continuation of a
489
+ // turn that already chimed; replaying the sound on every bounce
490
+ // turns a blocked-stop loop into a repeating alarm.
491
+ stopContinuation = eventName === "stop" && j.stop_hook_active === true;
481
492
  }
482
493
  catch {
483
494
  // non-JSON payload: play unkeyed (rate-limit just won't dedup)
484
495
  }
485
- playSound(repoRoot, s.sound, sid, s.maxPlays);
496
+ if (!stopContinuation) {
497
+ playSound(repoRoot, s.sound, sid, s.maxPlays);
498
+ }
486
499
  }
487
500
  }
488
501
  }
@@ -571,6 +584,7 @@ async function main() {
571
584
  logError(coordRoot, err, { phase: "image-capture" });
572
585
  }
573
586
  }
587
+ const deferV3Drain = liveHookSignalDefersDrainV3(eventName, coordEnv("EXPERIMENT_DEFER_V3_DRAIN"));
574
588
  const v3Result = recordLiveHookSignalV3({
575
589
  coordRoot,
576
590
  route: ledgerRoute,
@@ -593,13 +607,39 @@ async function main() {
593
607
  monotonic_ns: hookClock.monotonic_ns,
594
608
  ...(stopRemediation ? { stop_remediation: true } : {}),
595
609
  ...(turnRitual ? { turn_ritual: turnRitual } : {}),
596
- ...(coordEnv("EXPERIMENT_DEFER_V3_DRAIN") === "1" ? { defer_drain: true } : {}),
610
+ ...(deferV3Drain ? { defer_drain: true } : {}),
597
611
  });
598
612
  const v3EventId = v3Result && "event" in v3Result
599
613
  ? v3Result.event.event_id
600
614
  : v3Result && "event_id" in v3Result
601
615
  ? v3Result.event_id
602
616
  : undefined;
617
+ if (norm.event_type === "agent.started" &&
618
+ payload &&
619
+ v3Result?.state === "recorded" &&
620
+ v3Result.event.event_type === "agent.started") {
621
+ try {
622
+ const childResult = recordLiveDelegatedChildSessionV3({
623
+ coordRoot,
624
+ route: ledgerRoute,
625
+ parentEvent: v3Result.event,
626
+ payload,
627
+ adapter,
628
+ ...(adapter === "codex" && isWslUncPath(payload.cwd)
629
+ ? { bridge: "codex-wsl" }
630
+ : {}),
631
+ monotonic_ns: hookClock.monotonic_ns,
632
+ });
633
+ const nativeChild = payload.subagent_id ?? payload.agent_id;
634
+ if (nativeChild &&
635
+ (childResult.state === "recorded" || childResult.state === "already_started")) {
636
+ ensureLiveCoordinationHeartbeat(coordRoot, nativeChild, sessionId, adapter, payload.model);
637
+ }
638
+ }
639
+ catch (err) {
640
+ logError(coordRoot, err, { phase: "subagent-child-session-start" });
641
+ }
642
+ }
603
643
  if (v3Result?.state === "recorded" && capturedImages.length > 0) {
604
644
  try {
605
645
  recordImageArtifactsV3(coordRoot, v3Result.event, capturedImages);
@@ -744,11 +784,12 @@ async function main() {
744
784
  runSessionSyncExtension(coordRoot, true);
745
785
  }
746
786
  }
747
- // Phase 8: SubagentStart: sync-project to create the subagent heartbeat,
748
- // log the lifecycle event, and emit a context message announcing the
749
- // subagent (claude-code + cursor; codex doesn't fan out subagents today).
787
+ // Phase 8: SubagentStart: the child generation was opened above from the
788
+ // native delegation identity. Refresh its disposable projection, log the
789
+ // lifecycle event, and emit a context message announcing the subagent.
750
790
  if (norm.event_type === "agent.started") {
751
791
  try {
792
+ const childInstanceId = data.agent_id ?? owner.instance_id;
752
793
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
753
794
  if (existsSync(agentCoordBin)) {
754
795
  spawnSync(agentCoordBin, ["project"], {
@@ -758,12 +799,12 @@ async function main() {
758
799
  });
759
800
  spawnSync(agentCoordBin, [
760
801
  "log",
761
- `SUBAGENT_START agent_type=${data.agent_type ?? "unknown"} agent_id=${owner.instance_id.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
802
+ `SUBAGENT_START agent_type=${data.agent_type ?? "unknown"} agent_id=${childInstanceId.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
762
803
  "--instance",
763
- owner.instance_id,
804
+ childInstanceId,
764
805
  ], { encoding: "utf8", timeout: 2000, env: childEnv(coordRoot) });
765
806
  }
766
- emitSubagentStartContext(coordRoot, owner.instance_id, sessionId, data, adapter);
807
+ emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
767
808
  }
768
809
  catch (err) {
769
810
  logError(coordRoot, err, { phase: "subagent-start-project" });
@@ -772,16 +813,19 @@ async function main() {
772
813
  // Phase 8: SubagentStop: delete subagent heartbeat + log.
773
814
  if (norm.event_type === "agent.completed") {
774
815
  try {
775
- cleanupSessionEnd(coordRoot, owner.instance_id, data.reason ?? "unknown");
816
+ const childInstanceId = payload?.subagent_id ?? payload?.agent_id ?? owner.instance_id;
817
+ cleanupSessionEnd(coordRoot, childInstanceId, data.reason ?? "unknown");
776
818
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
777
819
  if (existsSync(agentCoordBin)) {
778
820
  spawnSync(agentCoordBin, [
779
821
  "log",
780
- `SUBAGENT_STOP agent_id=${owner.instance_id.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
822
+ `SUBAGENT_STOP agent_id=${childInstanceId.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
781
823
  "--instance",
782
- owner.instance_id,
824
+ childInstanceId,
783
825
  ], { encoding: "utf8", timeout: 2000, env: childEnv(coordRoot) });
784
826
  }
827
+ const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.js");
828
+ reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
785
829
  }
786
830
  catch (err) {
787
831
  logError(coordRoot, err, { phase: "subagent-stop-cleanup" });
@@ -853,12 +897,32 @@ async function main() {
853
897
  workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
854
898
  });
855
899
  if (!verdict.allow) {
856
- // Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
857
- // as a turn block; Cursor ignores exit codes and re-prompts only via a
858
- // `followup_message` it auto-submits. Codex returned observe-only above.
859
- // emitStopBlock writes the right shape and returns the exit code to use.
860
- const { emitStopBlock } = await import("./adapter/output.js");
861
- return emitStopBlock(adapter, verdict, coordRoot);
900
+ // Backstop for sessions whose evidence can never land (e.g. a headless
901
+ // child whose status command resolves to a different owner): after
902
+ // `cap` consecutive blocked stops in one cycle, allow the stop instead
903
+ // of bouncing the model forever. Cycle boundaries come from the
904
+ // adapter's continuation flag, so adapters without one are unaffected.
905
+ const { remediationCapExceeded } = await import("./remediation-cap.js");
906
+ if (remediationCapExceeded(sessionId, payload?.stop_hook_active === true)) {
907
+ appendDebug(coordRoot, {
908
+ ...debugBase,
909
+ skipped: "stop-remediation-cap-exhausted",
910
+ blocked_rule: verdict.rule,
911
+ session_id: sessionId,
912
+ });
913
+ }
914
+ else {
915
+ // Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
916
+ // as a turn block; Cursor ignores exit codes and re-prompts only via a
917
+ // `followup_message` it auto-submits. Codex returned observe-only above.
918
+ // emitStopBlock writes the right shape and returns the exit code to use.
919
+ const { emitStopBlock } = await import("./adapter/output.js");
920
+ return emitStopBlock(adapter, verdict, coordRoot);
921
+ }
922
+ }
923
+ else {
924
+ const { clearRemediationCount } = await import("./remediation-cap.js");
925
+ clearRemediationCount(sessionId);
862
926
  }
863
927
  // An explicit end requested from inside this turn cannot be authoritative
864
928
  // until the adapter has committed turn.completed above. Reconcile only
@@ -944,8 +1008,9 @@ async function main() {
944
1008
  return 0;
945
1009
  }
946
1010
  if (norm.event_type === "tool.completed" && eventName === "post-tool-use") {
947
- // Inject only at the successful tool boundary that actually minted a name:
948
- // the first non-empty set-task or the transition to lifecycle done. The
1011
+ // Inject only at the successful tool boundary that actually minted or
1012
+ // retried a name: the first non-empty set-task, a pending-name set-task
1013
+ // retry, or the transition to lifecycle done. The
949
1014
  // coordination row intentionally remains pending until transcript evidence
950
1015
  // catches up; reading that latch alone here would re-inject after every
951
1016
  // later tool and make the agent print the same block repeatedly.
@@ -979,24 +1044,35 @@ async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter,
979
1044
  const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
980
1045
  if (isSessionNameRemediationCommand(command, resolveBinName(coordRoot)))
981
1046
  return true;
982
- const inspection = payload?.agent_message !== undefined
1047
+ const inspection = adapter === "cursor" && payload?.agent_message !== undefined
983
1048
  ? {
984
1049
  state: assistantTextStartsWithSessionNameBlock(payload.agent_message, name)
985
1050
  ? "present"
986
1051
  : "absent",
987
1052
  }
988
- : inspectSessionNameDisplayImmediately(payload?.transcript_path, name, assistantTextStartsWithSessionNameBlock);
1053
+ : inspectSessionNameDisplayImmediately(
1054
+ // Codex hook payloads omit transcript_path on every event, which
1055
+ // left this inspection permanently unavailable and the latch never
1056
+ // stamped. Discover the rollout by session id; the scan only runs
1057
+ // while a name is pending, so the latch closes after one success.
1058
+ payload?.transcript_path ??
1059
+ (adapter === "codex"
1060
+ ? discoverCodexSessionTranscript(payload?.session_id ?? instanceId)
1061
+ : undefined), name, assistantTextStartsWithSessionNameBlock);
989
1062
  if (inspection.state === "present") {
990
1063
  stampSessionNameSeen(coordRoot, instanceId, name);
991
1064
  return true;
992
1065
  }
993
1066
  if (inspection.state === "unavailable") {
994
- const { emitContext } = await import("./adapter/output.js");
995
- emitContext(adapter, "PreToolUse", "Harnery could not verify the pending session-name display because the adapter transcript is unavailable or not flushed yet. This tool is allowed so the session cannot deadlock; display the requested block before later work. Verification remains pending.");
1067
+ // PostToolUse already delivered the exact block for this mint. An
1068
+ // unavailable transcript cannot become evidence, and repeating a generic
1069
+ // reminder on every tool creates an unbounded Codex loop. Keep the latch
1070
+ // honestly pending and fail open without another instruction. A later
1071
+ // readable transcript can still verify it.
996
1072
  return true;
997
1073
  }
998
1074
  const { emitDeny } = await import("./adapter/output.js");
999
- emitDeny(adapter, sessionNameDisplayInstruction(name));
1075
+ emitDeny(adapter, sessionNameDisplayRecoveryInstruction(name, resolveBinName(coordRoot)));
1000
1076
  return false;
1001
1077
  }
1002
1078
  async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapter) {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Per-session cap on consecutive blocked Stop remediations.
3
+ *
4
+ * The Stop verdict re-blocks until the ledger shows the end-of-turn evidence,
5
+ * which assumes the agent CAN produce it. A session whose evidence can never
6
+ * land (e.g. a headless child whose status command resolves to a different
7
+ * owner) bounces forever: every retry replays the stop cycle until the budget
8
+ * or the operator kills it. This counter is the backstop: once a single stop
9
+ * cycle has been blocked `cap` times in a row, the verdict is allowed through
10
+ * so the session can terminate.
11
+ *
12
+ * A "stop cycle" is delimited by the adapter's continuation flag (Claude
13
+ * Code's `stop_hook_active`): a stop without the flag starts a fresh cycle and
14
+ * resets the count. Adapters that never set the flag (Cursor) reset on every
15
+ * stop, so the cap never engages there and behavior is unchanged.
16
+ *
17
+ * State is a tmpdir counter file keyed by session id — deliberately outside
18
+ * the ledger, because remediation stops do not land fresh `turn.completed`
19
+ * events (the turn span is already closed), so ledger evidence cannot count
20
+ * them.
21
+ */
22
+ export declare const DEFAULT_STOP_REMEDIATION_CAP = 5;
23
+ /**
24
+ * Record one blocked stop and report whether the cap is exhausted.
25
+ *
26
+ * Call only when the Stop verdict blocked. `continuation` is the adapter's
27
+ * "this stop follows a stop-hook block" flag; false starts a fresh cycle.
28
+ * Returns true when this cycle has already been blocked `cap` times, meaning
29
+ * the caller should allow the stop instead of blocking again.
30
+ */
31
+ export declare function remediationCapExceeded(sessionId: string, continuation: boolean, cap?: number): boolean;
32
+ /** Clear the cycle counter after an allowed stop. */
33
+ export declare function clearRemediationCount(sessionId: string): void;
34
+ //# sourceMappingURL=remediation-cap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remediation-cap.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/remediation-cap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAO9C;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,EACrB,GAAG,SAA+B,GACjC,OAAO,CAsBT;AAED,qDAAqD;AACrD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAM7D"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Per-session cap on consecutive blocked Stop remediations.
3
+ *
4
+ * The Stop verdict re-blocks until the ledger shows the end-of-turn evidence,
5
+ * which assumes the agent CAN produce it. A session whose evidence can never
6
+ * land (e.g. a headless child whose status command resolves to a different
7
+ * owner) bounces forever: every retry replays the stop cycle until the budget
8
+ * or the operator kills it. This counter is the backstop: once a single stop
9
+ * cycle has been blocked `cap` times in a row, the verdict is allowed through
10
+ * so the session can terminate.
11
+ *
12
+ * A "stop cycle" is delimited by the adapter's continuation flag (Claude
13
+ * Code's `stop_hook_active`): a stop without the flag starts a fresh cycle and
14
+ * resets the count. Adapters that never set the flag (Cursor) reset on every
15
+ * stop, so the cap never engages there and behavior is unchanged.
16
+ *
17
+ * State is a tmpdir counter file keyed by session id — deliberately outside
18
+ * the ledger, because remediation stops do not land fresh `turn.completed`
19
+ * events (the turn span is already closed), so ledger evidence cannot count
20
+ * them.
21
+ */
22
+ import { readFileSync, rmSync, writeFileSync } from "node:fs";
23
+ import os from "node:os";
24
+ import { join } from "node:path";
25
+ export const DEFAULT_STOP_REMEDIATION_CAP = 5;
26
+ function counterPath(sessionId) {
27
+ const key = (sessionId || "default").replace(/[^A-Za-z0-9_-]/g, "_");
28
+ return join(os.tmpdir(), `harnery-stop-remediation-${key}.count`);
29
+ }
30
+ /**
31
+ * Record one blocked stop and report whether the cap is exhausted.
32
+ *
33
+ * Call only when the Stop verdict blocked. `continuation` is the adapter's
34
+ * "this stop follows a stop-hook block" flag; false starts a fresh cycle.
35
+ * Returns true when this cycle has already been blocked `cap` times, meaning
36
+ * the caller should allow the stop instead of blocking again.
37
+ */
38
+ export function remediationCapExceeded(sessionId, continuation, cap = DEFAULT_STOP_REMEDIATION_CAP) {
39
+ const path = counterPath(sessionId);
40
+ let count = 1;
41
+ if (continuation) {
42
+ let prior = 0;
43
+ try {
44
+ prior = Number.parseInt(readFileSync(path, "utf8"), 10);
45
+ }
46
+ catch {
47
+ // No counter yet (cycle started before the counter existed): treat the
48
+ // continuation itself as evidence of one earlier blocked stop, so an
49
+ // already-running loop still converges on the cap.
50
+ prior = 1;
51
+ }
52
+ count = Number.isFinite(prior) && prior >= 0 ? prior + 1 : 1;
53
+ }
54
+ try {
55
+ writeFileSync(path, String(count), "utf8");
56
+ }
57
+ catch {
58
+ // Counter IO must never turn into a block or a crash; fail open (no cap).
59
+ return false;
60
+ }
61
+ return count > cap;
62
+ }
63
+ /** Clear the cycle counter after an allowed stop. */
64
+ export function clearRemediationCount(sessionId) {
65
+ try {
66
+ rmSync(counterPath(sessionId), { force: true });
67
+ }
68
+ catch {
69
+ // best-effort
70
+ }
71
+ }
@@ -55,19 +55,35 @@ export declare function inspectSessionNameDisplayImmediately(transcriptPath: str
55
55
  * file is mounted at `/mnt/c/...`.
56
56
  */
57
57
  export declare function transcriptPathCandidates(transcriptPath: string | undefined): string[];
58
+ /** Runtime identity read from one transcript row: the model plus the tuning
59
+ * values that row reported alongside it. */
60
+ export interface TranscriptRuntimeScan {
61
+ model: string;
62
+ /** CC stamps the effective effort level on each assistant row; absent when
63
+ * the model has no effort dial (verified: haiku rows omit it entirely). */
64
+ effort?: string;
65
+ /** CC reports the response speed tier inside `message.usage`. */
66
+ speed?: string;
67
+ }
58
68
  /**
59
- * Resolve the agent's model from a CC-style JSONL transcript by reading the
60
- * most-recent assistant message's `message.model`. Claude Code's SessionStart
61
- * payload omits `model` (Codex + Cursor supply it directly), so this is the
62
- * fallback that lets `session.started` / `turn.completed` populate the cache's
63
- * model field once the transcript has at least one assistant turn.
69
+ * Resolve the agent's runtime identity from a CC-style JSONL transcript by
70
+ * reading the most-recent assistant row. Claude Code's SessionStart payload
71
+ * omits `model` and `effort` (Codex + Cursor supply model directly), so this
72
+ * is the fallback that lets `session.started` / `turn.completed` populate
73
+ * model and tuning once the transcript has at least one assistant turn.
74
+ *
75
+ * Model, effort, and speed are read from the SAME row, never independently:
76
+ * effort varies with model inside one transcript (a mid-session model swap
77
+ * changes both), so pairing the newest model with a separately scanned newest
78
+ * effort can mislabel the session.
64
79
  *
65
80
  * Tail-reads the same 256KB window as the status-box scan and walks lines from
66
- * the end, returning the first real model id found. Synthetic placeholders
67
- * (`<synthetic>`) and empty values are skipped. Returns undefined when the
68
- * transcript is missing/empty (e.g. a fresh session's first SessionStart).
81
+ * the end, returning the first row with a real model id. Synthetic
82
+ * placeholders (`<synthetic>`) and empty values are skipped. Returns undefined
83
+ * when the transcript is missing/empty (e.g. a fresh session's first
84
+ * SessionStart).
69
85
  */
70
- export declare function scanTranscriptModel(transcriptPath: string | undefined): string | undefined;
86
+ export declare function scanTranscriptRuntime(transcriptPath: string | undefined): TranscriptRuntimeScan | undefined;
71
87
  /**
72
88
  * Detect the parent session of a forked CC conversation from its transcript.
73
89
  *
@@ -1 +1 @@
1
- {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;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,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,CAsB9B;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAuB1F;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;;;;;;;;;;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,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,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { basename, dirname, join } from "node:path";
3
+ import { toolResponseMintedSessionName } from "../../agents/session-name-display.js";
3
4
  /**
4
5
  * Scan a CC-style JSONL transcript for the `┌─ agent-` status-box prefix in
5
6
  * the most-recent assistant turn. Used by `turn.completed` events to populate
@@ -123,21 +124,26 @@ export function inspectSessionNameDisplayImmediately(transcriptPath, name, start
123
124
  if (!text)
124
125
  return { state: "unavailable", reason: "transcript_not_ready" };
125
126
  let sawMintResult = false;
127
+ let awaitingReply = false;
126
128
  for (const line of text.split("\n")) {
127
- if (!sawMintResult) {
128
- if (line.includes(name) && line.includes("suggested_session_name"))
129
- sawMintResult = true;
130
- continue;
131
- }
132
129
  const row = parseTranscriptRow(line);
133
130
  if (!row)
134
131
  continue;
132
+ if (toolResponseMintedSessionName(row, name)) {
133
+ sawMintResult = true;
134
+ awaitingReply = true;
135
+ continue;
136
+ }
137
+ if (!awaitingReply)
138
+ continue;
135
139
  const assistantText = assistantTextFromRow(row);
136
140
  if (assistantText !== null) {
137
- return { state: startsWithBlock(assistantText, name) ? "present" : "absent" };
141
+ if (startsWithBlock(assistantText, name))
142
+ return { state: "present" };
143
+ awaitingReply = false;
138
144
  }
139
145
  }
140
- return sawMintResult
146
+ return sawMintResult && awaitingReply
141
147
  ? { state: "unavailable", reason: "transcript_not_ready" }
142
148
  : { state: "absent" };
143
149
  }
@@ -210,18 +216,24 @@ function textFromContent(content) {
210
216
  return parts.join("\n");
211
217
  }
212
218
  /**
213
- * Resolve the agent's model from a CC-style JSONL transcript by reading the
214
- * most-recent assistant message's `message.model`. Claude Code's SessionStart
215
- * payload omits `model` (Codex + Cursor supply it directly), so this is the
216
- * fallback that lets `session.started` / `turn.completed` populate the cache's
217
- * model field once the transcript has at least one assistant turn.
219
+ * Resolve the agent's runtime identity from a CC-style JSONL transcript by
220
+ * reading the most-recent assistant row. Claude Code's SessionStart payload
221
+ * omits `model` and `effort` (Codex + Cursor supply model directly), so this
222
+ * is the fallback that lets `session.started` / `turn.completed` populate
223
+ * model and tuning once the transcript has at least one assistant turn.
224
+ *
225
+ * Model, effort, and speed are read from the SAME row, never independently:
226
+ * effort varies with model inside one transcript (a mid-session model swap
227
+ * changes both), so pairing the newest model with a separately scanned newest
228
+ * effort can mislabel the session.
218
229
  *
219
230
  * Tail-reads the same 256KB window as the status-box scan and walks lines from
220
- * the end, returning the first real model id found. Synthetic placeholders
221
- * (`<synthetic>`) and empty values are skipped. Returns undefined when the
222
- * transcript is missing/empty (e.g. a fresh session's first SessionStart).
231
+ * the end, returning the first row with a real model id. Synthetic
232
+ * placeholders (`<synthetic>`) and empty values are skipped. Returns undefined
233
+ * when the transcript is missing/empty (e.g. a fresh session's first
234
+ * SessionStart).
223
235
  */
224
- export function scanTranscriptModel(transcriptPath) {
236
+ export function scanTranscriptRuntime(transcriptPath) {
225
237
  const readablePath = resolveTranscriptPath(transcriptPath);
226
238
  if (!readablePath)
227
239
  return undefined;
@@ -237,7 +249,13 @@ export function scanTranscriptModel(transcriptPath) {
237
249
  const obj = JSON.parse(line);
238
250
  const model = obj.message?.model ?? obj.model;
239
251
  if (typeof model === "string" && model.length > 0 && !model.startsWith("<")) {
240
- return model;
252
+ const effort = obj.effort;
253
+ const speed = obj.message?.usage?.speed;
254
+ return {
255
+ model,
256
+ ...(typeof effort === "string" && effort.length > 0 ? { effort } : {}),
257
+ ...(typeof speed === "string" && speed.length > 0 ? { speed } : {}),
258
+ };
241
259
  }
242
260
  }
243
261
  catch {
@@ -0,0 +1,34 @@
1
+ import type { TSchema } from "@sinclair/typebox";
2
+ import { type SemanticConfiguredModel, type SemanticHarness } from "./contract.js";
3
+ import type { SemanticReaderResolution } from "./storage.js";
4
+ export interface SemanticReaderRoute {
5
+ harness: SemanticHarness;
6
+ binary: string;
7
+ configured_model: SemanticConfiguredModel;
8
+ invocation_model_id: string;
9
+ }
10
+ export interface SemanticAdapterSuccess {
11
+ ok: true;
12
+ text: string;
13
+ resolved_model_id: string;
14
+ model_attestation: "verified" | "requested-only";
15
+ duration_ms: number;
16
+ output_bytes: number;
17
+ }
18
+ export interface SemanticAdapterFailure {
19
+ ok: false;
20
+ reason_code: "harness_unavailable" | "authentication_unavailable" | "model_unavailable" | "model_mismatch";
21
+ resolved_model_id?: string;
22
+ model_attestation?: "verified" | "requested-only";
23
+ duration_ms: number;
24
+ }
25
+ export type SemanticAdapterResult = SemanticAdapterSuccess | SemanticAdapterFailure;
26
+ export interface SemanticAdapter {
27
+ readonly route: SemanticReaderRoute;
28
+ discover(now?: () => Date): SemanticReaderResolution;
29
+ invoke(prompt: string, timeoutMs?: number, responseSchema?: TSchema): Promise<SemanticAdapterResult>;
30
+ }
31
+ export declare const SEMANTIC_READER_ROUTES: Record<SemanticHarness, SemanticReaderRoute>;
32
+ export declare function createSemanticAdapters(): Record<SemanticHarness, SemanticAdapter>;
33
+ export declare function discoverSemanticReaders(adapters?: Record<SemanticHarness, SemanticAdapter>): Record<SemanticHarness, SemanticReaderResolution>;
34
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/core/semantic/adapters.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EAErB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAM7D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,eAAe,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,UAAU,GAAG,gBAAgB,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,KAAK,CAAC;IACV,WAAW,EACP,qBAAqB,GACrB,4BAA4B,GAC5B,mBAAmB,GACnB,gBAAgB,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,UAAU,GAAG,gBAAgB,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAEpF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,GAAG,wBAAwB,CAAC;IACrD,MAAM,CACJ,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,OAAO,GACvB,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACnC;AAED,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,eAAe,EAAE,mBAAmB,CAmB/E,CAAC;AAEF,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,CAMjF;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,GAAE,MAAM,CAAC,eAAe,EAAE,eAAe,CAA4B,GAC5E,MAAM,CAAC,eAAe,EAAE,wBAAwB,CAAC,CAMnD"}