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,147 @@
1
+ import { resolve } from "node:path";
2
+ import { createSemanticAdapters, discoverSemanticReaders, inspectSemanticDocument, readSemanticManifest, readSemanticServiceStatus, requestSemanticServiceStop, runSemanticOnce, runSemanticServiceDaemon, semanticPaths, spawnSemanticService, } from "../core/semantic/index.js";
3
+ export function registerSemanticCommand(program, emit, context) {
4
+ const semantic = program
5
+ .command("semantic")
6
+ .description("Build and inspect evidence-cited semantic readings of active V3 generations");
7
+ semantic
8
+ .command("once")
9
+ .description("Run one bounded semantic pass using each generation's source harness")
10
+ .option("--root <path>", "Explicit coordination root")
11
+ .option("--calls-per-hour <count>", "Configured call ceiling below the hard limit", integer)
12
+ .action(async (options) => {
13
+ try {
14
+ emit.data(await runSemanticOnce({
15
+ coordRoot: resolve(options.root ?? coordRoot(context)),
16
+ callsPerHour: options.callsPerHour,
17
+ }));
18
+ }
19
+ catch (error) {
20
+ emitFailure(emit, "semantic_once_failed", error);
21
+ }
22
+ });
23
+ const service = semantic
24
+ .command("service")
25
+ .description("Run semantic reading as a singleton service");
26
+ service
27
+ .command("start")
28
+ .description("Start or resume the detached per-root semantic reader service")
29
+ .option("--root <path>", "Explicit coordination root")
30
+ .option("--calls-per-hour <count>", "Configured call ceiling below the hard limit", integer)
31
+ .action(async (options) => {
32
+ try {
33
+ emit.data(await spawnSemanticService(resolve(options.root ?? coordRoot(context)), {
34
+ callsPerHour: options.callsPerHour,
35
+ }));
36
+ }
37
+ catch (error) {
38
+ emitFailure(emit, "semantic_service_start_failed", error);
39
+ }
40
+ });
41
+ service
42
+ .command("status")
43
+ .description("Show singleton liveness, pass metrics, and pending work")
44
+ .option("--root <path>", "Explicit coordination root")
45
+ .action((options) => {
46
+ try {
47
+ emit.data(readSemanticServiceStatus(resolve(options.root ?? coordRoot(context))));
48
+ }
49
+ catch (error) {
50
+ emitFailure(emit, "semantic_service_status_failed", error);
51
+ }
52
+ });
53
+ service
54
+ .command("stop")
55
+ .description("Request a graceful stop after the current model call")
56
+ .option("--root <path>", "Explicit coordination root")
57
+ .action(async (options) => {
58
+ try {
59
+ const root = resolve(options.root ?? coordRoot(context));
60
+ let status = requestSemanticServiceStop(root);
61
+ const deadline = Date.now() + 5_000;
62
+ while (status.running && Date.now() < deadline) {
63
+ await new Promise((done) => setTimeout(done, 50));
64
+ status = readSemanticServiceStatus(root);
65
+ }
66
+ emit.data(status);
67
+ }
68
+ catch (error) {
69
+ emitFailure(emit, "semantic_service_stop_failed", error);
70
+ }
71
+ });
72
+ service
73
+ .command("daemon", { hidden: true })
74
+ .description("Internal detached semantic service entrypoint")
75
+ .option("--root <path>", "Explicit coordination root")
76
+ .option("--calls-per-hour <count>", "Configured call ceiling below the hard limit", integer)
77
+ .action(async (options) => {
78
+ try {
79
+ emit.data(await runSemanticServiceDaemon({
80
+ coordRoot: resolve(options.root ?? coordRoot(context)),
81
+ callsPerHour: options.callsPerHour,
82
+ }));
83
+ }
84
+ catch (error) {
85
+ emitFailure(emit, "semantic_service_daemon_failed", error);
86
+ }
87
+ });
88
+ semantic
89
+ .command("inspect")
90
+ .description("Inspect one generation or instance semantic document")
91
+ .argument("<instance-or-generation>")
92
+ .option("--root <path>", "Explicit coordination root")
93
+ .action((instanceOrGeneration, options) => {
94
+ try {
95
+ const document = inspectSemanticDocument(resolve(options.root ?? coordRoot(context)), instanceOrGeneration);
96
+ if (!document)
97
+ throw new Error(`no semantic document for ${instanceOrGeneration}`);
98
+ emit.data(document);
99
+ }
100
+ catch (error) {
101
+ emitFailure(emit, "semantic_inspect_failed", error);
102
+ }
103
+ });
104
+ semantic
105
+ .command("doctor")
106
+ .description("Inspect semantic reader availability without making a model request")
107
+ .option("--root <path>", "Explicit coordination root")
108
+ .action((options) => {
109
+ try {
110
+ const root = resolve(options.root ?? coordRoot(context));
111
+ emit.data({
112
+ schema_version: 1,
113
+ root,
114
+ storage: semanticPaths(root).root,
115
+ readers: discoverSemanticReaders(createSemanticAdapters()),
116
+ manifest: readManifestSafe(root),
117
+ });
118
+ }
119
+ catch (error) {
120
+ emitFailure(emit, "semantic_doctor_failed", error);
121
+ }
122
+ });
123
+ }
124
+ function coordRoot(context) {
125
+ return resolve(context?.resolveCoordRoot?.() ??
126
+ context?.repoRoot ??
127
+ process.env.HARNERY_COORD_ROOT ??
128
+ process.cwd());
129
+ }
130
+ function integer(value) {
131
+ const parsed = Number(value);
132
+ if (!Number.isSafeInteger(parsed) || parsed < 1)
133
+ throw new Error("count must be a positive integer");
134
+ return parsed;
135
+ }
136
+ function readManifestSafe(root) {
137
+ try {
138
+ return readSemanticManifest(root);
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ function emitFailure(emit, code, error) {
145
+ emit.error({ code, message: error instanceof Error ? error.message : String(error) });
146
+ throw error;
147
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/commands/web.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAiCnD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKrE;AA8BD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAevE;AAsBD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA0K5E"}
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/commands/web.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAkCnD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKrE;AAyCD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAevE;AAsBD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6K5E"}
@@ -4,6 +4,7 @@ import { createServer } from "node:net";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { DEFAULT_WEB_PORT, resolveWebPort } from "../core/config.js";
7
+ import { ensureSemanticServiceRunning } from "../core/semantic/service.js";
7
8
  import { lazyFetchWebRoot, webRunner } from "./web-fetch.js";
8
9
  /**
9
10
  * `harn web`: Next.js dashboard for harnery's coord state.
@@ -65,6 +66,20 @@ export function nodeOptionsWithHeapCap(mb) {
65
66
  return inherited;
66
67
  return `${inherited} --max-old-space-size=${mb}`.trim();
67
68
  }
69
+ async function startSemanticServiceWithWeb(coordRoot, emit) {
70
+ const result = await ensureSemanticServiceRunning(coordRoot);
71
+ if (result.state === "started")
72
+ emit.log("semantic reader · started automatically", "info");
73
+ if (result.state === "running")
74
+ emit.log("semantic reader · already running", "info");
75
+ if (result.state === "paused")
76
+ emit.log("semantic reader · paused by operator", "info");
77
+ if (result.state === "inactive")
78
+ emit.log("semantic reader · waiting for active V3", "info");
79
+ if (result.state === "unavailable") {
80
+ emit.log(`semantic reader · unavailable (${result.error ?? "startup failed"})`, "warn");
81
+ }
82
+ }
68
83
  /**
69
84
  * Resolve the web app root. A local `web/` (source checkout / submodule) wins.
70
85
  * Otherwise, on an npm install, lazy-fetch it (clone + install) unless the
@@ -168,6 +183,7 @@ export function registerWebCommand(program, emit) {
168
183
  }
169
184
  if (!(await requireAvailablePort(emit, port)))
170
185
  return;
186
+ await startSemanticServiceWithWeb(coordRoot, emit);
171
187
  const heapMb = resolveMaxOldSpaceMb(opts.maxOldSpace);
172
188
  emit.log(`harn web · http://localhost:${port} (${mode})`, "info");
173
189
  emit.log(`files origin · http://harnery-files.localhost:${port}`, "info");
@@ -253,6 +269,7 @@ export function registerWebCommand(program, emit) {
253
269
  }
254
270
  if (!(await requireAvailablePort(emit, port)))
255
271
  return;
272
+ await startSemanticServiceWithWeb(coordRoot, emit);
256
273
  const heapMb = resolveMaxOldSpaceMb(opts.maxOldSpace);
257
274
  emit.log(`harn web · http://localhost:${port} (start)`, "info");
258
275
  emit.log(`reading .harnery/ from: ${coordRoot}`, "info");
@@ -10,6 +10,7 @@
10
10
  * seconds and record the observation before the parent command exits.
11
11
  */
12
12
  import type { LiveCoordinationObservationV3 } from "../events/v3/live-observation.js";
13
+ import type { Heartbeat } from "./state/heartbeat-reader.js";
13
14
  export type EventV3EmitObservation = LiveCoordinationObservationV3 | {
14
15
  event_type: "coord.lifecycle_changed";
15
16
  new_state: "active" | "blocked" | "done";
@@ -35,4 +36,6 @@ export declare function emitEventV3(input: EventV3EmitInput): boolean;
35
36
  * Normalize the heartbeat's `platform` field to the canonical Adapter type.
36
37
  */
37
38
  export declare function normalizeAdapter(platform: string | undefined): "claude-code" | "cursor" | "codex";
39
+ /** Producer joins use the private native session ID, never its V3 fingerprint. */
40
+ export declare function nativeSessionIdentity(row: Pick<Heartbeat, "native_session_id" | "session_id"> | null | undefined, fallback: string): string;
38
41
  //# sourceMappingURL=canonical-emit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAItF,MAAM,MAAM,sBAAsB,GAC9B,6BAA6B,GAC7B;IACE,UAAU,EAAE,yBAAyB,CAAC;IACtC,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEN,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,WAAW,EAAE,sBAAsB,CAAC;CACrC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CA4C5D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAIjG"}
1
+ {"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAGtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,MAAM,sBAAsB,GAC9B,6BAA6B,GAC7B;IACE,UAAU,EAAE,yBAAyB,CAAC;IACtC,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEN,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,WAAW,EAAE,sBAAsB,CAAC;CACrC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CA4C5D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAIjG;AAED,kFAAkF;AAClF,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,mBAAmB,GAAG,YAAY,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3E,QAAQ,EAAE,MAAM,GACf,MAAM,CAQR"}
@@ -80,3 +80,15 @@ export function normalizeAdapter(platform) {
80
80
  return "codex";
81
81
  return "claude-code";
82
82
  }
83
+ /** Producer joins use the private native session ID, never its V3 fingerprint. */
84
+ export function nativeSessionIdentity(row, fallback) {
85
+ if (row?.native_session_id)
86
+ return row.native_session_id;
87
+ // A projection-only row carries the privacy-safe canonical session
88
+ // fingerprint, not the adapter's native session ID needed to join the hook
89
+ // producer. Until a disposable cache exists, the resolved owner is the only
90
+ // native identity available to command surfaces.
91
+ if (row?.session_id && !/^sid_[a-f0-9]{64}$/.test(row.session_id))
92
+ return row.session_id;
93
+ return fallback;
94
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Canonical identity for an authority-bearing write claim.
3
+ *
4
+ * Workspace paths stay portable and repository-relative. Approved paths
5
+ * outside the coordination root become resolved absolute paths so a later
6
+ * release matches whether the caller supplies the stored dot-dot form or the
7
+ * absolute target. This is lexical identity only; finalization policy owns the
8
+ * physical containment and symlink checks before a claim is acquired.
9
+ */
10
+ export declare function canonicalClaimPath(coordRoot: string, value: string): string;
11
+ //# sourceMappingURL=claim-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-path.d.ts","sourceRoot":"","sources":["../../../src/core/agents/claim-path.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO3E"}
@@ -0,0 +1,22 @@
1
+ import { isAbsolute, relative, resolve, sep } from "node:path";
2
+ /**
3
+ * Canonical identity for an authority-bearing write claim.
4
+ *
5
+ * Workspace paths stay portable and repository-relative. Approved paths
6
+ * outside the coordination root become resolved absolute paths so a later
7
+ * release matches whether the caller supplies the stored dot-dot form or the
8
+ * absolute target. This is lexical identity only; finalization policy owns the
9
+ * physical containment and symlink checks before a claim is acquired.
10
+ */
11
+ export function canonicalClaimPath(coordRoot, value) {
12
+ const root = resolve(coordRoot);
13
+ const absolute = resolve(root, value);
14
+ const fromRoot = relative(root, absolute);
15
+ const contained = fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot);
16
+ if (contained)
17
+ return portable(fromRoot || ".");
18
+ return portable(absolute);
19
+ }
20
+ function portable(value) {
21
+ return value.split(sep).join("/");
22
+ }
@@ -104,9 +104,10 @@ export declare function pidStartToken(pid: number): string | null;
104
104
  * Prefer a row whose platform matches `HARNERY_AGENT_COORD_PLATFORM` (default
105
105
  * `claude-code`); otherwise return the first owner seen on the walk.
106
106
  *
107
- * Subagents intentionally do not write pid-map entries; a Bash-tool ppid-walk
108
- * from inside a subagent therefore resolves to the *parent's* pid-map entry.
109
- * v1 ships this behavior; a real subagent-aware bridge is out of scope.
107
+ * Subagents intentionally do not write pid-map entries; a bare Bash-tool
108
+ * ppid-walk from inside an unbridged child therefore resolves to the parent's
109
+ * pid-map entry. Native hook bridges supersede this fallback through the
110
+ * child session environment.
110
111
  */
111
112
  export declare function resolveOwner(): string | null;
112
113
  /**
@@ -120,6 +121,20 @@ export declare function resolveOwnerWithSource(): {
120
121
  owner: string | null;
121
122
  source: "env" | "pidmap" | "pidmap_fallback" | "session_env" | "active_singleton" | "none";
122
123
  };
124
+ /**
125
+ * The nearest pid-map anchor on our own ppid chain whose start token PROVES the
126
+ * row still describes the process holding that pid, or null when the nearest
127
+ * row is unverified, recycled, or absent.
128
+ *
129
+ * Nearest matters: in a nested-agent tree (session A spawns a headless session
130
+ * B whose shell runs this code), both A's and B's adapter processes are
131
+ * ancestors, and only the innermost one owns this process's tool environment.
132
+ * Exported for unit testing with an injectable root.
133
+ */
134
+ export declare function nearestVerifiedSessionAnchor(root: string): {
135
+ owner: string;
136
+ platform: string;
137
+ } | null;
123
138
  /**
124
139
  * Walk our own ppid chain for a pid-map row in ONE given root.
125
140
  *
@@ -149,6 +164,17 @@ export declare function sessionIdentityFromEnv(): string | null;
149
164
  * Exported for unit testing with an injectable root.
150
165
  */
151
166
  export declare function resolveOwnerBySessionEnv(root: string): string | null;
167
+ /**
168
+ * `resolveOwnerBySessionEnv` plus WHICH adapter's producer state the session id
169
+ * joined. The caller uses the adapter to tell a same-adapter conflict (the env
170
+ * var is stamped fresh per tool call and outranks a possibly-lagging pid-map
171
+ * row) from a cross-adapter one (the var is inherited context from an outer
172
+ * session and the verified row wins).
173
+ */
174
+ export declare function resolveOwnerBySessionEnvDetailed(root: string): {
175
+ owner: string;
176
+ adapters: string[];
177
+ } | null;
152
178
  /**
153
179
  * Return the native instance label of the sole live V3 generation in this
154
180
  * coord root, or null if there are zero or more than one.
@@ -1 +1 @@
1
- {"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CA+B7E;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAeD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtD;AAiED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2CxD;AAgCD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,GAAG,MAAM,CAAC;CAC5F,CA2CA;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAClD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,QAAQ,GAAG,iBAAiB,GAAG,MAAM,CAAC;CAC/C,CAyBA;AAqBD,sFAAsF;AACtF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,IAAI,CAEtD;AAuCD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYpE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE"}
1
+ {"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CA+B7E;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAeD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtD;AAiED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2CxD;AAgCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,GAAG,MAAM,CAAC;CAC5F,CAsEA;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsB5C;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAClD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,QAAQ,GAAG,iBAAiB,GAAG,MAAM,CAAC;CAC/C,CAyBA;AAqBD,sFAAsF;AACtF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,IAAI,CAEtD;AAuCD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEpE;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAmB9C;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE"}
@@ -339,9 +339,10 @@ function readPidmapRow(pidmapDir, pid) {
339
339
  * Prefer a row whose platform matches `HARNERY_AGENT_COORD_PLATFORM` (default
340
340
  * `claude-code`); otherwise return the first owner seen on the walk.
341
341
  *
342
- * Subagents intentionally do not write pid-map entries; a Bash-tool ppid-walk
343
- * from inside a subagent therefore resolves to the *parent's* pid-map entry.
344
- * v1 ships this behavior; a real subagent-aware bridge is out of scope.
342
+ * Subagents intentionally do not write pid-map entries; a bare Bash-tool
343
+ * ppid-walk from inside an unbridged child therefore resolves to the parent's
344
+ * pid-map entry. Native hook bridges supersede this fallback through the
345
+ * child session environment.
345
346
  */
346
347
  export function resolveOwner() {
347
348
  return resolveOwnerWithSource().owner;
@@ -368,9 +369,34 @@ export function resolveOwnerWithSource() {
368
369
  // Every supported adapter exports its native session id into subprocesses.
369
370
  // Match it against the live V3 producer state, never a disposable cache.
370
371
  if (shouldPreferSessionEnv()) {
371
- const bySession = resolveOwnerBySessionEnv(root);
372
+ const bySession = resolveOwnerBySessionEnvDetailed(root);
372
373
  if (bySession) {
373
- return { owner: bySession, source: "session_env" };
374
+ // Env vars cross process boundaries that session identity does not: a
375
+ // nested different-adapter child inherits its spawner's exported id (a
376
+ // Codex session's CODEX_THREAD_ID survives into a `claude -p` child's
377
+ // shells untouched), and that stale statement joins the spawner's live
378
+ // producer. The nearest token-verified pid-map anchor names the session
379
+ // whose tool environment this process actually runs in, so on a
380
+ // CROSS-adapter conflict the anchor wins — the anchoring adapter never
381
+ // stamps another adapter's variable, so a foreign-adapter id here is
382
+ // inherited context, while the anchor row is re-healed on every tool
383
+ // call. A SAME-adapter conflict keeps the env: the adapter stamps its
384
+ // own variable fresh per tool call, and the row may lag one heal (e.g.
385
+ // a session id change inside one process). Only per-session-process
386
+ // adapters may anchor (claude-code, codex) — Cursor sessions share one
387
+ // terminal process, so a verified Cursor row can belong to a sibling
388
+ // session. Unverified rows keep losing to the env too: without a start
389
+ // token the row is the guess the env ordering was introduced to beat.
390
+ // Never for bridge-marked children: a connector crosses a process-tree
391
+ // boundary, so an anchor in THIS tree is someone else's by definition.
392
+ const anchor = bridge ? null : nearestVerifiedSessionAnchor(root);
393
+ if (anchor &&
394
+ anchor.owner !== bySession.owner &&
395
+ !bySession.adapters.includes(anchor.platform) &&
396
+ (anchor.platform === "claude-code" || anchor.platform === "codex")) {
397
+ return { owner: anchor.owner, source: "pidmap" };
398
+ }
399
+ return { owner: bySession.owner, source: "session_env" };
374
400
  }
375
401
  }
376
402
  // Connector children cross process-tree boundaries where pid ancestry is
@@ -394,6 +420,42 @@ export function resolveOwnerWithSource() {
394
420
  }
395
421
  return { owner: null, source: "none" };
396
422
  }
423
+ /**
424
+ * The nearest pid-map anchor on our own ppid chain whose start token PROVES the
425
+ * row still describes the process holding that pid, or null when the nearest
426
+ * row is unverified, recycled, or absent.
427
+ *
428
+ * Nearest matters: in a nested-agent tree (session A spawns a headless session
429
+ * B whose shell runs this code), both A's and B's adapter processes are
430
+ * ancestors, and only the innermost one owns this process's tool environment.
431
+ * Exported for unit testing with an injectable root.
432
+ */
433
+ export function nearestVerifiedSessionAnchor(root) {
434
+ const pidmapDir = resolve(root, ".harnery", "pid-map");
435
+ if (!existsSync(pidmapDir))
436
+ return null;
437
+ let pid = process.pid;
438
+ for (let hop = 0; hop < 20; hop++) {
439
+ if (pid === null)
440
+ break;
441
+ const row = readPidmapRow(pidmapDir, pid);
442
+ if (row) {
443
+ const owner = parsePidmapRowOwner(row);
444
+ if (owner && !pidWasRecycled(pid, row)) {
445
+ // A recycled row is about a dead process's tenure of this pid — walk
446
+ // past it like the owner walk does. A live row without a provable
447
+ // token match is the nearest anchor but not a VERIFIED one: stop and
448
+ // report none, so the session env keeps outranking guesses.
449
+ const token = parsePidmapRowStartToken(row);
450
+ if (!token || !pidStartToken(pid))
451
+ return null;
452
+ return { owner, platform: parsePidmapRowPlatform(row) };
453
+ }
454
+ }
455
+ pid = readPpid(pid);
456
+ }
457
+ return null;
458
+ }
397
459
  /**
398
460
  * Walk our own ppid chain for a pid-map row in ONE given root.
399
461
  *
@@ -501,19 +563,36 @@ function shouldPreferSessionEnv() {
501
563
  * Exported for unit testing with an injectable root.
502
564
  */
503
565
  export function resolveOwnerBySessionEnv(root) {
566
+ return resolveOwnerBySessionEnvDetailed(root)?.owner ?? null;
567
+ }
568
+ /**
569
+ * `resolveOwnerBySessionEnv` plus WHICH adapter's producer state the session id
570
+ * joined. The caller uses the adapter to tell a same-adapter conflict (the env
571
+ * var is stamped fresh per tool call and outranks a possibly-lagging pid-map
572
+ * row) from a cross-adapter one (the var is inherited context from an outer
573
+ * session and the verified row wins).
574
+ */
575
+ export function resolveOwnerBySessionEnvDetailed(root) {
504
576
  const sessionIds = sessionIdsFromEnv();
505
577
  if (sessionIds.length === 0)
506
578
  return null;
507
579
  const adapters = adapterCandidatesFromEnv();
508
- const matches = new Set();
580
+ const matches = new Map();
509
581
  for (const sessionId of sessionIds) {
510
582
  for (const adapter of adapters) {
511
583
  const state = readHookProducerStateV3(root, adapter, sessionId);
512
- if (state && !state.terminal)
513
- matches.add(nativeOwnerForV3Instance(root, state.instance_id));
584
+ if (state && !state.terminal) {
585
+ const owner = nativeOwnerForV3Instance(root, state.instance_id);
586
+ const set = matches.get(owner) ?? new Set();
587
+ set.add(adapter);
588
+ matches.set(owner, set);
589
+ }
514
590
  }
515
591
  }
516
- return matches.size === 1 ? [...matches][0] : null;
592
+ if (matches.size !== 1)
593
+ return null;
594
+ const [owner, adapterSet] = [...matches.entries()][0];
595
+ return { owner, adapters: [...adapterSet] };
517
596
  }
518
597
  /**
519
598
  * Return the native instance label of the sole live V3 generation in this
@@ -1 +1 @@
1
- {"version":3,"file":"live-authority-v3.d.ts","sourceRoot":"","sources":["../../../src/core/agents/live-authority-v3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAehE,OAAO,EACL,KAAK,mCAAmC,EAEzC,MAAM,iDAAiD,CAAC;AAoBzD,eAAO,MAAM,gCAAgC,EAAG,iBAA0B,CAAC;AAE3E,qBAAa,gCAAiC,SAAQ,KAAK;aAC7B,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAI3C;AAED,MAAM,MAAM,iCAAiC,GACzC;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,mCAAmC,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,oCAAoC;IACnD,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB,EAAE,OAAO,MAAM,EAAE,CAAC;IACrC,aAAa,EAAE,OAAO,MAAM,EAAE,CAAC;CAChC;AAED,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,KAAK,EAAE;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,oCAAoC,CAkDvC;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C,iCAAiC,CAiCnC;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,mBAAmB,GAAG;IAC3B,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,iCAAiC,CAmCnC;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,mBAAmB,GAAG;IAC3B,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B,GACA,iCAAiC,CAuCnC;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChE,iCAAiC,CAuBnC;AAuDD,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAI/E"}
1
+ {"version":3,"file":"live-authority-v3.d.ts","sourceRoot":"","sources":["../../../src/core/agents/live-authority-v3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAehE,OAAO,EACL,KAAK,mCAAmC,EAEzC,MAAM,iDAAiD,CAAC;AAqBzD,eAAO,MAAM,gCAAgC,EAAG,iBAA0B,CAAC;AAE3E,qBAAa,gCAAiC,SAAQ,KAAK;aAC7B,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAI3C;AAED,MAAM,MAAM,iCAAiC,GACzC;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,mCAAmC,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,oCAAoC;IACnD,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB,EAAE,OAAO,MAAM,EAAE,CAAC;IACrC,aAAa,EAAE,OAAO,MAAM,EAAE,CAAC;CAChC;AAED,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,KAAK,EAAE;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,oCAAoC,CAkDvC;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C,iCAAiC,CAiCnC;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,mBAAmB,GAAG;IAC3B,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,iCAAiC,CAmCnC;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,mBAAmB,GAAG;IAC3B,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B,GACA,iCAAiC,CAuCnC;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChE,iCAAiC,CAuBnC;AAuDD,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAI/E"}
@@ -4,6 +4,7 @@ import { readCoordinationViewV3, requireAuthoritySafeCoordinationViewV3, } from
4
4
  import { LIVE_HOOK_V3_PRODUCER_ID, liveInstanceIdV3, livePlatformV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
5
5
  import { recordCoordinationAuthorityV3, } from "../events/v3/producers/coordination-recorder.js";
6
6
  import { readTerminalHookProducerStateV3, recordHookSignalV3, } from "../events/v3/producers/recorder.js";
7
+ import { canonicalClaimPath } from "./claim-path.js";
7
8
  import { acquireClaim, readHeartbeat, releaseClaim, setIdentityCache, setLifecycleCache, setTask, } from "./state/heartbeat-writer.js";
8
9
  import { ensureLiveCoordinationHeartbeat, liveCoordinationAdapterV3, } from "./state/live-coordination-view.js";
9
10
  import { recordNameAssumption } from "./state/names.js";
@@ -228,6 +229,3 @@ function authorityStateDigest(heartbeat) {
228
229
  display_name: heartbeat.name ?? null,
229
230
  }));
230
231
  }
231
- function canonicalClaimPath(coordRoot, path) {
232
- return path.startsWith(`${coordRoot}/`) ? path.slice(coordRoot.length + 1) : path;
233
- }
@@ -4,7 +4,7 @@ import { normalizeNativeIdV3 } from "../events/v3/canonical.js";
4
4
  import { readEventV3ControlState } from "../events/v3/control.js";
5
5
  import { fingerprintContextV3 } from "../events/v3/fingerprint-keys.js";
6
6
  import { liveInstanceIdV3, livePlatformV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
7
- import { readHookProducerStateV3 } from "../events/v3/producers/recorder.js";
7
+ import { readJoinableHookProducerStateV3 } from "../events/v3/producers/recorder.js";
8
8
  import { writeEventV3 } from "../events/v3/writer.js";
9
9
  import { LiveCoordinationAuthorityV3Error } from "./live-authority-v3.js";
10
10
  import { liveCoordinationAdapterV3 } from "./state/live-coordination-view.js";
@@ -83,8 +83,8 @@ function withAttestedAdapter(input) {
83
83
  return { ...input, adapter };
84
84
  }
85
85
  function requireHookState(input) {
86
- const hook = readHookProducerStateV3(input.coordRoot, input.adapter, input.nativeSessionId);
87
- if (!hook || hook.instance_id !== liveInstanceIdV3(input.owner)) {
86
+ const hook = readJoinableHookProducerStateV3(input.coordRoot, input.adapter, input.nativeSessionId, liveInstanceIdV3(input.owner));
87
+ if (!hook) {
88
88
  throw new LiveCoordinationAuthorityV3Error("hook_generation_not_joinable");
89
89
  }
90
90
  return hook;
@@ -1 +1 @@
1
- {"version":3,"file":"live-observation-v3.d.ts","sourceRoot":"","sources":["../../../src/core/agents/live-observation-v3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAQ7C,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAIrE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAWtF,YAAY,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAEtF,MAAM,WAAW,wCAAwC;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,6BAA6B,CAAC;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yCAAyC;IACxD,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,wCAAwC,GAC9C,yCAAyC,CAkJ3C;AAkBD,MAAM,MAAM,sCAAsC,GAAG,OAAO,CAC1D,WAAW,EACX,6BAA6B,CAAC,YAAY,CAAC,CAC5C,CAAC"}
1
+ {"version":3,"file":"live-observation-v3.d.ts","sourceRoot":"","sources":["../../../src/core/agents/live-observation-v3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAQ7C,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAIrE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAWtF,YAAY,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAEtF,MAAM,WAAW,wCAAwC;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,6BAA6B,CAAC;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yCAAyC;IACxD,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,wCAAwC,GAC9C,yCAAyC,CAuJ3C;AAkBD,MAAM,MAAM,sCAAsC,GAAG,OAAO,CAC1D,WAAW,EACX,6BAA6B,CAAC,YAAY,CAAC,CAC5C,CAAC"}
@@ -5,7 +5,7 @@ import { readEventV3ControlState } from "../events/v3/control.js";
5
5
  import { readCoordinationViewV3 } from "../events/v3/coordination-view.js";
6
6
  import { fingerprintContextV3 } from "../events/v3/fingerprint-keys.js";
7
7
  import { liveInstanceIdV3, livePlatformV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
8
- import { readHookProducerStateV3 } from "../events/v3/producers/recorder.js";
8
+ import { readJoinableHookProducerStateV3 } from "../events/v3/producers/recorder.js";
9
9
  import { writeEventV3 } from "../events/v3/writer.js";
10
10
  import { LiveCoordinationAuthorityV3Error } from "./live-authority-v3.js";
11
11
  import { liveCoordinationAdapterV3 } from "./state/live-coordination-view.js";
@@ -23,7 +23,7 @@ export function recordLiveCoordinationObservationV3(input) {
23
23
  throw new LiveCoordinationAuthorityV3Error("control_state_changed");
24
24
  }
25
25
  const adapter = liveCoordinationAdapterV3(input.coordRoot, input.owner) ?? input.adapter;
26
- const hook = readHookProducerStateV3(input.coordRoot, adapter, input.nativeSessionId);
26
+ const hook = readJoinableHookProducerStateV3(input.coordRoot, adapter, input.nativeSessionId, liveInstanceIdV3(input.owner));
27
27
  if (!hook ||
28
28
  hook.terminal ||
29
29
  !hook.last_event_id ||
@@ -140,7 +140,7 @@ function computeFocusNudgeIfChanged(coordRoot, selfInstanceId, opts) {
140
140
  // Keyed on "a name was ever produced", not task_updated_at: a bare clear
141
141
  // as the first declaration must not end the naming window.
142
142
  needsNudge = true;
143
- nudgeKind = "session-name";
143
+ nudgeKind = pendingSessionName ? "session-name-pending" : "session-name-unminted";
144
144
  message = pendingSessionName
145
145
  ? `This session name is still pending display. Before any prose or another tool call, send this exact block as your next assistant text:\n\n${sessionNameDisplayBlock(pendingSessionName)}`
146
146
  : `This session has no name yet: run \`${bin} agents set-task "<2-5 word session topic>"\` as your first tool call. ` +
@@ -170,18 +170,20 @@ function computeFocusNudgeIfChanged(coordRoot, selfInstanceId, opts) {
170
170
  clearHashFile(hashFile);
171
171
  return "";
172
172
  }
173
- // The session-name reminder is deliberately NOT deduped: it re-emits on
174
- // every prompt until a name is produced, because the "still unnamed" state
175
- // is the failure state — deduping it erases the only reminder after one
176
- // ignored prompt (the miss mode operators reported). Bounded in practice:
177
- // Stop rule 3/3 forces a set-task on the first tool-using turn.
178
- if (nudgeKind === "session-name") {
173
+ // An unminted session still gets a fresh reminder on every prompt until its
174
+ // first focus declaration. Once a name exists, the exact pending-name prompt
175
+ // is delivered once per minted value. Repeating it on every prompt creates
176
+ // an unbounded loop on adapters that cannot verify assistant reply text.
177
+ if (nudgeKind === "session-name-unminted") {
179
178
  clearHashFile(hashFile);
180
179
  return message;
181
180
  }
182
- // Dedup the task-unset/stale reminders on kind + task state so a repeat
183
- // prompt in the same state stays quiet.
184
- const state = `${nudgeKind}|task=${taskValue}|updated=${hb.task_updated_at ?? ""}|threshold=${threshold}`;
181
+ // A pending reminder is keyed only to the minted name: changing the task
182
+ // must not reconstruct or re-request that older title. Task reminders retain
183
+ // their own task-state key.
184
+ const state = nudgeKind === "session-name-pending"
185
+ ? `${nudgeKind}|name=${pendingSessionName ?? ""}`
186
+ : `${nudgeKind}|task=${taskValue}|updated=${hb.task_updated_at ?? ""}|threshold=${threshold}`;
185
187
  const newHash = sha256Hex16(state);
186
188
  const oldHash = safeRead(hashFile);
187
189
  if (oldHash && oldHash === newHash)
@@ -1 +1 @@
1
- {"version":3,"file":"stop-hook.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/rules/stop-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAM3D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AAInE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa,CA6BvF;AAED,oFAAoF;AACpF,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,SAAS,OAAO,EAAE,GACzB,aAAa,CA2Gf"}
1
+ {"version":3,"file":"stop-hook.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/rules/stop-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAM3D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AAInE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa,CA6BvF;AAED,oFAAoF;AACpF,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,SAAS,OAAO,EAAE,GACzB,aAAa,CA6Gf"}