harnery 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/dist/commander.d.ts.map +1 -1
  2. package/dist/commander.js +2 -0
  3. package/dist/commands/agents.d.ts +15 -4
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +89 -175
  6. package/dist/commands/browse.d.ts.map +1 -1
  7. package/dist/commands/browse.js +26 -2
  8. package/dist/commands/decision.d.ts.map +1 -1
  9. package/dist/commands/decision.js +2 -2
  10. package/dist/commands/events.d.ts.map +1 -1
  11. package/dist/commands/events.js +18 -1
  12. package/dist/commands/presence.d.ts.map +1 -1
  13. package/dist/commands/presence.js +2 -2
  14. package/dist/commands/semantic.d.ts +4 -0
  15. package/dist/commands/semantic.d.ts.map +1 -0
  16. package/dist/commands/semantic.js +147 -0
  17. package/dist/commands/web.d.ts.map +1 -1
  18. package/dist/commands/web.js +17 -0
  19. package/dist/core/agents/canonical-emit.d.ts +3 -0
  20. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  21. package/dist/core/agents/canonical-emit.js +12 -0
  22. package/dist/core/agents/claim-path.d.ts +11 -0
  23. package/dist/core/agents/claim-path.d.ts.map +1 -0
  24. package/dist/core/agents/claim-path.js +22 -0
  25. package/dist/core/agents/coord-client.d.ts +29 -3
  26. package/dist/core/agents/coord-client.d.ts.map +1 -1
  27. package/dist/core/agents/coord-client.js +88 -9
  28. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  29. package/dist/core/agents/live-authority-v3.js +1 -3
  30. package/dist/core/agents/live-lifecycle-v3.js +3 -3
  31. package/dist/core/agents/live-observation-v3.d.ts.map +1 -1
  32. package/dist/core/agents/live-observation-v3.js +2 -2
  33. package/dist/core/agents/render/prompt-context.js +12 -10
  34. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  35. package/dist/core/agents/rules/stop-hook.js +23 -11
  36. package/dist/core/agents/session-name-display.d.ts +1 -0
  37. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  38. package/dist/core/agents/session-name-display.js +8 -1
  39. package/dist/core/agents/state/heartbeat-reader.d.ts +46 -0
  40. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -0
  41. package/dist/core/agents/state/heartbeat-reader.js +25 -0
  42. package/dist/core/agents/state/heartbeat-writer.d.ts +3 -48
  43. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  44. package/dist/core/agents/state/heartbeat-writer.js +16 -34
  45. package/dist/core/agents/state/live-coordination-view.d.ts +1 -1
  46. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  47. package/dist/core/agents/state/live-coordination-view.js +25 -1
  48. package/dist/core/agents/state/names.d.ts +9 -1
  49. package/dist/core/agents/state/names.d.ts.map +1 -1
  50. package/dist/core/agents/state/names.js +190 -182
  51. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  52. package/dist/core/agents/state/stale-sweep.js +23 -5
  53. package/dist/core/artifacts/constants.d.ts +5 -0
  54. package/dist/core/artifacts/constants.d.ts.map +1 -0
  55. package/dist/core/artifacts/constants.js +4 -0
  56. package/dist/core/artifacts/index.d.ts +3 -3
  57. package/dist/core/artifacts/index.d.ts.map +1 -1
  58. package/dist/core/artifacts/index.js +50 -7
  59. package/dist/core/events/v3/base-contract.d.ts +795 -0
  60. package/dist/core/events/v3/base-contract.d.ts.map +1 -1
  61. package/dist/core/events/v3/base-contract.js +33 -0
  62. package/dist/core/events/v3/capabilities.d.ts +11 -0
  63. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  64. package/dist/core/events/v3/capabilities.js +23 -0
  65. package/dist/core/events/v3/generated.d.ts +1 -1
  66. package/dist/core/events/v3/generated.js +1 -1
  67. package/dist/core/events/v3/index.d.ts +4 -3
  68. package/dist/core/events/v3/index.d.ts.map +1 -1
  69. package/dist/core/events/v3/index.js +4 -3
  70. package/dist/core/events/v3/latency.d.ts +31 -1
  71. package/dist/core/events/v3/latency.d.ts.map +1 -1
  72. package/dist/core/events/v3/latency.js +127 -3
  73. package/dist/core/events/v3/live-routing.d.ts +30 -0
  74. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  75. package/dist/core/events/v3/live-routing.js +109 -1
  76. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  77. package/dist/core/events/v3/producers/command-recorder.js +10 -1
  78. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  79. package/dist/core/events/v3/producers/coordination-recorder.js +14 -3
  80. package/dist/core/events/v3/producers/hook-base.d.ts +17 -1
  81. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  82. package/dist/core/events/v3/producers/hook-base.js +82 -1
  83. package/dist/core/events/v3/producers/intake.d.ts +6 -0
  84. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  85. package/dist/core/events/v3/producers/intake.js +11 -0
  86. package/dist/core/events/v3/producers/recorder.d.ts +62 -0
  87. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  88. package/dist/core/events/v3/producers/recorder.js +654 -18
  89. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +51 -0
  90. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -0
  91. package/dist/core/events/v3/runtime-telemetry-capabilities.js +54 -0
  92. package/dist/core/events/v3/turn-telemetry.d.ts +9 -0
  93. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
  94. package/dist/core/hooks/adapter/parse.d.ts +3 -0
  95. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  96. package/dist/core/hooks/adapter/parse.js +14 -0
  97. package/dist/core/hooks/adapter/runtime-telemetry.d.ts +119 -0
  98. package/dist/core/hooks/adapter/runtime-telemetry.d.ts.map +1 -0
  99. package/dist/core/hooks/adapter/runtime-telemetry.js +568 -0
  100. package/dist/core/hooks/cli.js +111 -36
  101. package/dist/core/hooks/remediation-cap.d.ts +34 -0
  102. package/dist/core/hooks/remediation-cap.d.ts.map +1 -0
  103. package/dist/core/hooks/remediation-cap.js +71 -0
  104. package/dist/core/hooks/resolve/transcript.d.ts +25 -9
  105. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  106. package/dist/core/hooks/resolve/transcript.js +22 -10
  107. package/dist/core/semantic/adapters.d.ts +34 -0
  108. package/dist/core/semantic/adapters.d.ts.map +1 -0
  109. package/dist/core/semantic/adapters.js +222 -0
  110. package/dist/core/semantic/contract.d.ts +588 -0
  111. package/dist/core/semantic/contract.d.ts.map +1 -0
  112. package/dist/core/semantic/contract.js +230 -0
  113. package/dist/core/semantic/evidence.d.ts +11 -0
  114. package/dist/core/semantic/evidence.d.ts.map +1 -0
  115. package/dist/core/semantic/evidence.js +262 -0
  116. package/dist/core/semantic/index.d.ts +10 -0
  117. package/dist/core/semantic/index.d.ts.map +1 -0
  118. package/dist/core/semantic/index.js +9 -0
  119. package/dist/core/semantic/once.d.ts +33 -0
  120. package/dist/core/semantic/once.d.ts.map +1 -0
  121. package/dist/core/semantic/once.js +293 -0
  122. package/dist/core/semantic/prompt.d.ts +12 -0
  123. package/dist/core/semantic/prompt.d.ts.map +1 -0
  124. package/dist/core/semantic/prompt.js +70 -0
  125. package/dist/core/semantic/scheduler.d.ts +16 -0
  126. package/dist/core/semantic/scheduler.d.ts.map +1 -0
  127. package/dist/core/semantic/scheduler.js +67 -0
  128. package/dist/core/semantic/service-status.d.ts +31 -0
  129. package/dist/core/semantic/service-status.d.ts.map +1 -0
  130. package/dist/core/semantic/service-status.js +84 -0
  131. package/dist/core/semantic/service.d.ts +51 -0
  132. package/dist/core/semantic/service.d.ts.map +1 -0
  133. package/dist/core/semantic/service.js +470 -0
  134. package/dist/core/semantic/storage.d.ts +71 -0
  135. package/dist/core/semantic/storage.d.ts.map +1 -0
  136. package/dist/core/semantic/storage.js +179 -0
  137. package/dist/core/semantic/validate.d.ts +16 -0
  138. package/dist/core/semantic/validate.d.ts.map +1 -0
  139. package/dist/core/semantic/validate.js +174 -0
  140. package/dist/lib/browser/client.d.ts +11 -0
  141. package/dist/lib/browser/client.d.ts.map +1 -1
  142. package/dist/lib/browser/client.js +13 -0
  143. package/dist/lib/browser/index.d.ts +1 -0
  144. package/dist/lib/browser/index.d.ts.map +1 -1
  145. package/dist/lib/browser/standalone-html.d.ts +25 -0
  146. package/dist/lib/browser/standalone-html.d.ts.map +1 -0
  147. package/dist/lib/browser/standalone-html.js +284 -0
  148. package/dist/lib/instructions/apply.d.ts.map +1 -1
  149. package/dist/lib/instructions/apply.js +1 -0
  150. package/dist/lib/instructions/templates.d.ts +2 -0
  151. package/dist/lib/instructions/templates.d.ts.map +1 -1
  152. package/dist/lib/instructions/templates.js +144 -4
  153. package/package.json +6 -1
  154. package/schemas/event-v3.schema.json +2758 -478
  155. package/src/commander.ts +2 -0
  156. package/src/commands/agents.ts +102 -167
  157. package/src/commands/browse.ts +25 -2
  158. package/src/commands/decision.ts +8 -2
  159. package/src/commands/events.ts +21 -1
  160. package/src/commands/presence.ts +7 -2
  161. package/src/commands/semantic.ts +179 -0
  162. package/src/commands/web.ts +15 -0
  163. package/src/core/agents/canonical-emit.ts +15 -0
  164. package/src/core/agents/claim-path.ts +23 -0
  165. package/src/core/agents/coord-client.ts +92 -8
  166. package/src/core/agents/live-authority-v3.ts +1 -4
  167. package/src/core/agents/live-lifecycle-v3.ts +8 -3
  168. package/src/core/agents/live-observation-v3.ts +7 -2
  169. package/src/core/agents/render/prompt-context.ts +13 -10
  170. package/src/core/agents/rules/stop-hook.ts +24 -11
  171. package/src/core/agents/session-name-display.ts +9 -1
  172. package/src/core/agents/state/heartbeat-reader.ts +69 -0
  173. package/src/core/agents/state/heartbeat-writer.ts +20 -84
  174. package/src/core/agents/state/live-coordination-view.ts +26 -1
  175. package/src/core/agents/state/names.ts +190 -182
  176. package/src/core/agents/state/stale-sweep.ts +25 -5
  177. package/src/core/artifacts/constants.ts +4 -0
  178. package/src/core/artifacts/index.ts +49 -7
  179. package/src/core/events/v3/base-contract.ts +39 -0
  180. package/src/core/events/v3/capabilities.ts +34 -0
  181. package/src/core/events/v3/generated.ts +1 -1
  182. package/src/core/events/v3/index.ts +21 -0
  183. package/src/core/events/v3/latency.ts +213 -2
  184. package/src/core/events/v3/live-routing.ts +120 -1
  185. package/src/core/events/v3/producers/command-recorder.ts +10 -1
  186. package/src/core/events/v3/producers/coordination-recorder.ts +17 -5
  187. package/src/core/events/v3/producers/hook-base.ts +115 -1
  188. package/src/core/events/v3/producers/intake.ts +18 -0
  189. package/src/core/events/v3/producers/recorder.ts +856 -21
  190. package/src/core/events/v3/runtime-telemetry-capabilities.ts +137 -0
  191. package/src/core/events/v3/turn-telemetry.ts +10 -0
  192. package/src/core/hooks/adapter/parse.ts +16 -0
  193. package/src/core/hooks/adapter/runtime-telemetry.ts +805 -0
  194. package/src/core/hooks/cli.ts +123 -39
  195. package/src/core/hooks/remediation-cap.ts +77 -0
  196. package/src/core/hooks/resolve/transcript.ts +37 -11
  197. package/src/core/semantic/adapters.ts +293 -0
  198. package/src/core/semantic/contract.ts +298 -0
  199. package/src/core/semantic/evidence.ts +336 -0
  200. package/src/core/semantic/index.ts +127 -0
  201. package/src/core/semantic/once.ts +410 -0
  202. package/src/core/semantic/prompt.ts +84 -0
  203. package/src/core/semantic/scheduler.ts +107 -0
  204. package/src/core/semantic/service-status.ts +118 -0
  205. package/src/core/semantic/service.ts +580 -0
  206. package/src/core/semantic/storage.ts +286 -0
  207. package/src/core/semantic/validate.ts +212 -0
  208. package/src/lib/browser/client.ts +21 -0
  209. package/src/lib/browser/index.ts +4 -0
  210. package/src/lib/browser/standalone-html.ts +312 -0
  211. package/src/lib/instructions/apply.ts +1 -0
  212. package/src/lib/instructions/templates.ts +149 -4
@@ -0,0 +1,69 @@
1
+ /** Read-only access to the generation-bound heartbeat cache. */
2
+
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { dirname, resolve, sep } from "node:path";
5
+ import type { AgentActivity, TaskState } from "./session-state.ts";
6
+
7
+ export interface Heartbeat {
8
+ schema_version?: number;
9
+ instance_id: string;
10
+ name?: string;
11
+ kind?: string;
12
+ agent_id?: string;
13
+ session_id: string;
14
+ /** Native adapter session ID retained only in a generation-bound local projection. */
15
+ native_session_id?: string;
16
+ subagent_call_id?: string;
17
+ model?: string;
18
+ platform?: string;
19
+ started_at?: string;
20
+ last_heartbeat: string;
21
+ files_touched: string[];
22
+ task?: string;
23
+ task_updated_at?: string | null;
24
+ activity?: AgentActivity;
25
+ activity_updated_at?: string;
26
+ activity_source?: string;
27
+ task_state?: TaskState;
28
+ task_state_updated_at?: string;
29
+ task_state_reason?: string;
30
+ /** Session name built on the first non-empty set-task (never rebuilt). */
31
+ suggested_session_name?: string;
32
+ /** Stamped once the suggested name is seen in assistant reply text. */
33
+ session_name_seen_at?: string;
34
+ /** The specific suggested name proven present by the sighting stamp. */
35
+ session_name_seen_for?: string;
36
+ last_status_at?: string;
37
+ current_turn_id?: string;
38
+ parent_instance_id?: string;
39
+ workflow_run_id?: string;
40
+ workflow_agent_id?: string;
41
+ /** Disposable-cache bindings that prove a row belongs to the current V3 generation. */
42
+ v3_instance_id?: `inst_${string}`;
43
+ v3_generation_id?: `gen_${string}`;
44
+ v3_projection_event_id?: string;
45
+ v3_task_state?: "set" | "cleared";
46
+ [extra: string]: unknown;
47
+ }
48
+
49
+ export function heartbeatPath(coordRoot: string, instanceId: string): string {
50
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(instanceId)) {
51
+ throw new Error("instance_id must be 1-128 ASCII letters, digits, hyphens, or underscores");
52
+ }
53
+ const root = resolve(coordRoot, ".harnery", "active");
54
+ const candidate = resolve(root, `${instanceId}.json`);
55
+ if (!candidate.startsWith(`${root}${sep}`) || dirname(candidate) !== root) {
56
+ throw new Error("coordination filename must resolve directly beneath its root");
57
+ }
58
+ return candidate;
59
+ }
60
+
61
+ export function readHeartbeat(coordRoot: string, instanceId: string): Heartbeat | null {
62
+ const path = heartbeatPath(coordRoot, instanceId);
63
+ if (!existsSync(path)) return null;
64
+ try {
65
+ return JSON.parse(readFileSync(path, "utf8")) as Heartbeat;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
@@ -23,65 +23,12 @@ import {
23
23
  unlinkSync,
24
24
  writeFileSync,
25
25
  } from "node:fs";
26
- import { basename, dirname, join } from "node:path";
27
- import { assertSafeInstanceId, resolveContainedFile } from "../coord-client.ts";
28
- import type { AgentActivity, TaskState } from "./session-state.ts";
26
+ import { dirname, join } from "node:path";
27
+ import { canonicalClaimPath } from "../claim-path.ts";
28
+ import { type Heartbeat, heartbeatPath, readHeartbeat } from "./heartbeat-reader.ts";
29
+ import type { TaskState } from "./session-state.ts";
29
30
 
30
- export interface Heartbeat {
31
- schema_version?: number;
32
- instance_id: string;
33
- name?: string;
34
- kind?: string;
35
- agent_id?: string;
36
- session_id: string;
37
- /** Native adapter session ID retained only in a generation-bound local projection. */
38
- native_session_id?: string;
39
- subagent_call_id?: string;
40
- model?: string;
41
- platform?: string;
42
- started_at?: string;
43
- last_heartbeat: string;
44
- files_touched: string[];
45
- task?: string;
46
- task_updated_at?: string | null;
47
- activity?: AgentActivity;
48
- activity_updated_at?: string;
49
- activity_source?: string;
50
- task_state?: TaskState;
51
- task_state_updated_at?: string;
52
- task_state_reason?: string;
53
- /** Session name built on the first non-empty set-task (never rebuilt). Its
54
- * presence is the "this session has been named" signal the prompt-context
55
- * nudge and the Stop-hook naming rule key on. */
56
- suggested_session_name?: string;
57
- /** Stamped by turn.completed once the suggested name is seen in assistant reply
58
- * text, ending the per-turn transcript scan. */
59
- session_name_seen_at?: string;
60
- /** WHICH name that sighting was for. The scan is skipped only while this
61
- * matches the current suggested name, so a re-minted name is detectable
62
- * again rather than being suppressed by the earlier sighting. */
63
- session_name_seen_for?: string;
64
- last_status_at?: string;
65
- current_turn_id?: string;
66
- parent_instance_id?: string;
67
- workflow_run_id?: string;
68
- workflow_agent_id?: string;
69
- /** Disposable-cache bindings that prove a row belongs to the current V3 generation. */
70
- v3_instance_id?: `inst_${string}`;
71
- v3_generation_id?: `gen_${string}`;
72
- v3_projection_event_id?: string;
73
- v3_task_state?: "set" | "cleared";
74
- [extra: string]: unknown;
75
- }
76
-
77
- function heartbeatPath(coordRoot: string, instanceId: string): string {
78
- assertSafeInstanceId(instanceId);
79
- const safeInstanceId = basename(instanceId);
80
- if (safeInstanceId !== instanceId) {
81
- throw new Error("instance_id must be a direct-child coordination basename");
82
- }
83
- return resolveContainedFile(join(coordRoot, ".harnery", "active"), `${safeInstanceId}.json`);
84
- }
31
+ export { type Heartbeat, readHeartbeat } from "./heartbeat-reader.ts";
85
32
 
86
33
  function nowIsoSeconds(): string {
87
34
  return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -94,16 +41,6 @@ function atomicWrite(path: string, content: string): void {
94
41
  renameSync(tmp, path);
95
42
  }
96
43
 
97
- export function readHeartbeat(coordRoot: string, instanceId: string): Heartbeat | null {
98
- const path = heartbeatPath(coordRoot, instanceId);
99
- if (!existsSync(path)) return null;
100
- try {
101
- return JSON.parse(readFileSync(path, "utf8")) as Heartbeat;
102
- } catch {
103
- return null;
104
- }
105
- }
106
-
107
44
  /** Persist operator-facing lifecycle prose in the generation-bound cache only. */
108
45
  export function setLifecycleCache(
109
46
  coordRoot: string,
@@ -221,16 +158,12 @@ export function releaseClaim(
221
158
  instanceId: string,
222
159
  path: string,
223
160
  ): Heartbeat | null {
224
- // files_touched can hold either absolute-under-coordRoot or canonical
225
- // monorepo-relative entries; normalize both sides so release matches
226
- // regardless of the form the caller passes (the old exact-string filter
227
- // silently no-op'd on a form mismatch).
228
- const norm = (p: string): string =>
229
- p.startsWith(`${coordRoot}/`) ? p.slice(coordRoot.length + 1) : p;
230
- const target = norm(path);
161
+ const target = canonicalClaimPath(coordRoot, path);
231
162
  return mutate(coordRoot, instanceId, (hb) => ({
232
163
  ...hb,
233
- files_touched: (hb.files_touched ?? []).filter((p) => norm(p) !== target),
164
+ files_touched: (hb.files_touched ?? []).filter(
165
+ (candidate) => canonicalClaimPath(coordRoot, candidate) !== target,
166
+ ),
234
167
  }));
235
168
  }
236
169
 
@@ -240,12 +173,15 @@ export function acquireClaim(
240
173
  instanceId: string,
241
174
  path: string,
242
175
  ): Heartbeat | null {
243
- const norm = (p: string): string =>
244
- p.startsWith(`${coordRoot}/`) ? p.slice(coordRoot.length + 1) : p;
245
- const target = norm(path);
176
+ const target = canonicalClaimPath(coordRoot, path);
246
177
  return mutate(coordRoot, instanceId, (hb) => ({
247
178
  ...hb,
248
- files_touched: [...new Set([...(hb.files_touched ?? []).map(norm), target])].sort(),
179
+ files_touched: [
180
+ ...new Set([
181
+ ...(hb.files_touched ?? []).map((candidate) => canonicalClaimPath(coordRoot, candidate)),
182
+ target,
183
+ ]),
184
+ ].sort(),
249
185
  }));
250
186
  }
251
187
 
@@ -282,9 +218,7 @@ export function findGroupClaims(
282
218
  if (!groupId || !path) return hits;
283
219
  const activeDir = join(coordRoot, ".harnery", "active");
284
220
  if (!existsSync(activeDir)) return hits;
285
- const norm = (p: string): string =>
286
- p.startsWith(`${coordRoot}/`) ? p.slice(coordRoot.length + 1) : p;
287
- const target = norm(path);
221
+ const target = canonicalClaimPath(coordRoot, path);
288
222
  for (const f of readdirSync(activeDir)) {
289
223
  if (!f.endsWith(".json")) continue;
290
224
  const hbPath = join(activeDir, f);
@@ -298,7 +232,9 @@ export function findGroupClaims(
298
232
  (body.session_id as string | undefined) ?? (body.instance_id as string | undefined);
299
233
  if (peerSession !== groupId) continue;
300
234
  const files = (body.files_touched as string[] | undefined) ?? [];
301
- if (!files.some((candidate) => norm(candidate) === target)) continue;
235
+ if (!files.some((candidate) => canonicalClaimPath(coordRoot, candidate) === target)) {
236
+ continue;
237
+ }
302
238
  hits.push({
303
239
  instance_id: (body.instance_id as string | undefined) ?? f.replace(/\.json$/, ""),
304
240
  session_id: body.session_id as string | undefined,
@@ -19,7 +19,7 @@ import {
19
19
  nativeInstanceIdV3,
20
20
  resolveLiveEventLedgerRouteV3,
21
21
  } from "../../events/v3/live-routing.ts";
22
- import { type Heartbeat, readHeartbeat } from "./heartbeat-writer.ts";
22
+ import { type Heartbeat, readHeartbeat } from "./heartbeat-reader.ts";
23
23
  import { resolveName } from "./names.ts";
24
24
 
25
25
  /**
@@ -141,8 +141,33 @@ export function ensureLiveCoordinationHeartbeat(
141
141
 
142
142
  const resolved = resolveName(coordRoot, nativeInstanceId, nativeSessionId);
143
143
  const projected = projectHeartbeatV3(coordRoot, generation, undefined, nativeInstanceId);
144
+ // A generation reopen (resume, mid-flight onboarding) discards the stale
145
+ // cache, but the adapter tab it names is unchanged: same native session,
146
+ // same agent identity. Rebuilding without the naming evidence re-mints a
147
+ // fresh suggested name on the next set-task, so a multi-day session
148
+ // accumulates titles it is asked to display over and over. Carry the
149
+ // naming evidence across generations; everything else re-projects from V3.
150
+ // Same native session id means the same adapter tab; drop the carry only
151
+ // when the durable identity observably changed (a reassumed name would make
152
+ // the old "Agent <name> - …" block wrong for the new owner).
153
+ const identityUnchanged = !resolved?.name || !current?.name || resolved.name === current.name;
154
+ const carriedNaming =
155
+ current && current.session_id === nativeSessionId && identityUnchanged
156
+ ? {
157
+ ...(current.suggested_session_name
158
+ ? { suggested_session_name: current.suggested_session_name }
159
+ : {}),
160
+ ...(current.session_name_seen_for
161
+ ? { session_name_seen_for: current.session_name_seen_for }
162
+ : {}),
163
+ ...(current.session_name_seen_at
164
+ ? { session_name_seen_at: current.session_name_seen_at }
165
+ : {}),
166
+ }
167
+ : {};
144
168
  const materialized: V3HeartbeatMaterialization = {
145
169
  ...projected,
170
+ ...carriedNaming,
146
171
  schema_version: 2,
147
172
  instance_id: nativeInstanceId,
148
173
  session_id: nativeSessionId,