harnery 0.9.0 → 0.10.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 (201) hide show
  1. package/README.md +1 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +6 -0
  4. package/dist/commands/agents.d.ts +24 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +244 -13
  7. package/dist/commands/backup.d.ts +5 -4
  8. package/dist/commands/backup.d.ts.map +1 -1
  9. package/dist/commands/backup.js +15 -14
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +27 -1
  12. package/dist/commands/claude-desktop.d.ts +19 -0
  13. package/dist/commands/claude-desktop.d.ts.map +1 -0
  14. package/dist/commands/claude-desktop.js +168 -0
  15. package/dist/commands/context.d.ts.map +1 -1
  16. package/dist/commands/context.js +149 -1
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +2 -3
  19. package/dist/commands/eml.d.ts +32 -0
  20. package/dist/commands/eml.d.ts.map +1 -1
  21. package/dist/commands/eml.js +16 -3
  22. package/dist/commands/harness.d.ts +11 -0
  23. package/dist/commands/harness.d.ts.map +1 -0
  24. package/dist/commands/harness.js +118 -0
  25. package/dist/commands/presence.d.ts +9 -4
  26. package/dist/commands/presence.d.ts.map +1 -1
  27. package/dist/commands/presence.js +88 -5
  28. package/dist/commands/relay.d.ts +9 -0
  29. package/dist/commands/relay.d.ts.map +1 -0
  30. package/dist/commands/relay.js +143 -0
  31. package/dist/commands/sync.d.ts.map +1 -1
  32. package/dist/commands/sync.js +5 -0
  33. package/dist/commands/workflow.d.ts.map +1 -1
  34. package/dist/commands/workflow.js +8 -15
  35. package/dist/core/agents/canonical-emit.d.ts +15 -0
  36. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  37. package/dist/core/agents/canonical-emit.js +41 -3
  38. package/dist/core/agents/cli.js +4 -2
  39. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  40. package/dist/core/agents/render/prompt-context.js +44 -3
  41. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  42. package/dist/core/agents/render/session-context.js +26 -1
  43. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  44. package/dist/core/agents/rules/commit-conflict.js +4 -37
  45. package/dist/core/agents/session-events.d.ts.map +1 -1
  46. package/dist/core/agents/session-events.js +16 -43
  47. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  48. package/dist/core/agents/state/heartbeat-projector.js +11 -2
  49. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  50. package/dist/core/agents/state/heartbeat-writer.js +3 -3
  51. package/dist/core/agents/state/names.d.ts +38 -3
  52. package/dist/core/agents/state/names.d.ts.map +1 -1
  53. package/dist/core/agents/state/names.js +46 -7
  54. package/dist/core/agents/state/pidmap.d.ts +5 -1
  55. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  56. package/dist/core/agents/state/pidmap.js +58 -2
  57. package/dist/core/agents/state/stale-sweep.d.ts +3 -2
  58. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  59. package/dist/core/agents/state/stale-sweep.js +6 -6
  60. package/dist/core/config.d.ts +68 -11
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +191 -25
  63. package/dist/core/context/index.d.ts +144 -0
  64. package/dist/core/context/index.d.ts.map +1 -0
  65. package/dist/core/context/index.js +380 -0
  66. package/dist/core/harnesses/bench.d.ts +29 -0
  67. package/dist/core/harnesses/bench.d.ts.map +1 -0
  68. package/dist/core/harnesses/bench.js +151 -0
  69. package/dist/core/harnesses/index.d.ts +9 -0
  70. package/dist/core/harnesses/index.d.ts.map +1 -0
  71. package/dist/core/harnesses/index.js +4 -0
  72. package/dist/core/harnesses/profiles.d.ts +62 -0
  73. package/dist/core/harnesses/profiles.d.ts.map +1 -0
  74. package/dist/core/harnesses/profiles.js +115 -0
  75. package/dist/core/harnesses/registry.d.ts +15 -0
  76. package/dist/core/harnesses/registry.d.ts.map +1 -0
  77. package/dist/core/harnesses/registry.js +113 -0
  78. package/dist/core/harnesses/types.d.ts +64 -0
  79. package/dist/core/harnesses/types.d.ts.map +1 -0
  80. package/dist/core/harnesses/types.js +20 -0
  81. package/dist/core/hooks/cli.js +280 -63
  82. package/dist/core/hooks/events/schema.d.ts +38 -1
  83. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  84. package/dist/core/hooks/harness/events.d.ts.map +1 -1
  85. package/dist/core/hooks/harness/events.js +3 -0
  86. package/dist/core/hooks/harness/parse.d.ts +1 -1
  87. package/dist/core/hooks/harness/parse.d.ts.map +1 -1
  88. package/dist/core/hooks/harness/parse.js +4 -0
  89. package/dist/core/presence/blob.d.ts +40 -0
  90. package/dist/core/presence/blob.d.ts.map +1 -0
  91. package/dist/core/presence/blob.js +91 -0
  92. package/dist/core/presence/git.d.ts +68 -0
  93. package/dist/core/presence/git.d.ts.map +1 -0
  94. package/dist/core/presence/git.js +173 -0
  95. package/dist/core/presence/index.d.ts +74 -0
  96. package/dist/core/presence/index.d.ts.map +1 -0
  97. package/dist/core/presence/index.js +224 -0
  98. package/dist/core/presence/relay-client.d.ts +39 -0
  99. package/dist/core/presence/relay-client.d.ts.map +1 -0
  100. package/dist/core/presence/relay-client.js +295 -0
  101. package/dist/core/presence/relay-protocol.d.ts +94 -0
  102. package/dist/core/presence/relay-protocol.d.ts.map +1 -0
  103. package/dist/core/presence/relay-protocol.js +169 -0
  104. package/dist/core/workflow/billing.d.ts +1 -1
  105. package/dist/core/workflow/billing.d.ts.map +1 -1
  106. package/dist/core/workflow/billing.js +7 -1
  107. package/dist/core/workflow/engine.d.ts.map +1 -1
  108. package/dist/core/workflow/engine.js +11 -2
  109. package/dist/core/workflow/harnesses.d.ts +3 -3
  110. package/dist/core/workflow/harnesses.d.ts.map +1 -1
  111. package/dist/core/workflow/harnesses.js +8 -17
  112. package/dist/core/workflow/spawn-claude.d.ts +4 -1
  113. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  114. package/dist/core/workflow/spawn-claude.js +39 -18
  115. package/dist/core/workflow/spawn-codex.d.ts +4 -1
  116. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  117. package/dist/core/workflow/spawn-codex.js +44 -22
  118. package/dist/core/workflow/spawn-cursor.d.ts +4 -1
  119. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  120. package/dist/core/workflow/spawn-cursor.js +35 -16
  121. package/dist/core/workflow/types.d.ts +9 -2
  122. package/dist/core/workflow/types.d.ts.map +1 -1
  123. package/dist/lib/browser/client.d.ts +16 -0
  124. package/dist/lib/browser/client.d.ts.map +1 -1
  125. package/dist/lib/browser/client.js +22 -0
  126. package/dist/lib/browser/index.d.ts +1 -0
  127. package/dist/lib/browser/index.d.ts.map +1 -1
  128. package/dist/lib/browser/index.js +1 -0
  129. package/dist/lib/browser/launch-args.d.ts +21 -0
  130. package/dist/lib/browser/launch-args.d.ts.map +1 -0
  131. package/dist/lib/browser/launch-args.js +32 -0
  132. package/dist/lib/claude-desktop.d.ts +105 -0
  133. package/dist/lib/claude-desktop.d.ts.map +1 -0
  134. package/dist/lib/claude-desktop.js +217 -0
  135. package/dist/lib/docs-lint.d.ts.map +1 -1
  136. package/dist/lib/docs-lint.js +5 -2
  137. package/dist/lib/identities/assume.d.ts +51 -0
  138. package/dist/lib/identities/assume.d.ts.map +1 -0
  139. package/dist/lib/identities/assume.js +274 -0
  140. package/dist/lib/identities/index.d.ts +7 -7
  141. package/dist/lib/identities/index.d.ts.map +1 -1
  142. package/dist/lib/identities/index.js +24 -24
  143. package/dist/lib/instructions/templates.d.ts.map +1 -1
  144. package/dist/lib/instructions/templates.js +15 -2
  145. package/package.json +12 -1
  146. package/schemas/config.schema.json +88 -19
  147. package/src/commander.ts +6 -0
  148. package/src/commands/agents.ts +280 -14
  149. package/src/commands/backup.ts +17 -16
  150. package/src/commands/browse.ts +32 -0
  151. package/src/commands/claude-desktop.ts +215 -0
  152. package/src/commands/context.ts +174 -1
  153. package/src/commands/doctor.ts +2 -3
  154. package/src/commands/eml.ts +22 -4
  155. package/src/commands/harness.ts +147 -0
  156. package/src/commands/presence.ts +111 -5
  157. package/src/commands/relay.ts +160 -0
  158. package/src/commands/sync.ts +4 -0
  159. package/src/commands/workflow.ts +9 -18
  160. package/src/core/agents/canonical-emit.ts +41 -3
  161. package/src/core/agents/cli.ts +4 -2
  162. package/src/core/agents/render/prompt-context.ts +48 -3
  163. package/src/core/agents/render/session-context.ts +27 -1
  164. package/src/core/agents/rules/commit-conflict.ts +4 -33
  165. package/src/core/agents/session-events.ts +15 -39
  166. package/src/core/agents/state/heartbeat-projector.ts +10 -1
  167. package/src/core/agents/state/heartbeat-writer.ts +3 -2
  168. package/src/core/agents/state/names.ts +68 -9
  169. package/src/core/agents/state/pidmap.ts +59 -2
  170. package/src/core/agents/state/stale-sweep.ts +6 -11
  171. package/src/core/config.ts +248 -24
  172. package/src/core/context/index.ts +575 -0
  173. package/src/core/harnesses/bench.ts +214 -0
  174. package/src/core/harnesses/index.ts +31 -0
  175. package/src/core/harnesses/profiles.ts +141 -0
  176. package/src/core/harnesses/registry.ts +140 -0
  177. package/src/core/harnesses/types.ts +90 -0
  178. package/src/core/hooks/cli.ts +331 -62
  179. package/src/core/hooks/events/schema.ts +68 -0
  180. package/src/core/hooks/harness/events.ts +3 -0
  181. package/src/core/hooks/harness/parse.ts +7 -1
  182. package/src/core/presence/blob.ts +125 -0
  183. package/src/core/presence/git.ts +191 -0
  184. package/src/core/presence/index.ts +274 -0
  185. package/src/core/presence/relay-client.ts +322 -0
  186. package/src/core/presence/relay-protocol.ts +246 -0
  187. package/src/core/workflow/billing.ts +8 -2
  188. package/src/core/workflow/engine.ts +11 -2
  189. package/src/core/workflow/harnesses.ts +14 -19
  190. package/src/core/workflow/spawn-claude.ts +39 -20
  191. package/src/core/workflow/spawn-codex.ts +47 -28
  192. package/src/core/workflow/spawn-cursor.ts +37 -18
  193. package/src/core/workflow/types.ts +9 -2
  194. package/src/lib/browser/client.ts +37 -0
  195. package/src/lib/browser/index.ts +1 -0
  196. package/src/lib/browser/launch-args.ts +33 -0
  197. package/src/lib/claude-desktop.ts +293 -0
  198. package/src/lib/docs-lint.ts +5 -2
  199. package/src/lib/identities/assume.ts +363 -0
  200. package/src/lib/identities/index.ts +28 -24
  201. package/src/lib/instructions/templates.ts +16 -2
@@ -26,6 +26,7 @@ import { createHash } from "node:crypto";
26
26
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
27
27
  import { join } from "node:path";
28
28
  import { coordEnv } from "../../../lib/env.js";
29
+ import { readRemoteMachines } from "../../presence/index.js";
29
30
  import { formatPendingCouncils } from "./session-context.js";
30
31
  /**
31
32
  * Build the combined UserPromptSubmit additionalContext string. Returns "" when
@@ -132,7 +133,10 @@ function computePeerTableIfChanged(coordRoot, selfInstanceId, selfSessionIdFallb
132
133
  continue;
133
134
  peers.push(hb);
134
135
  }
135
- if (peers.length === 0)
136
+ // Cross-machine presence (ADR 0016): sessions on other machines, from the
137
+ // locally-fetched presence refs. Advisory (no claim blocking in v1).
138
+ const remote = readRemoteMachinesSafe(coordRoot);
139
+ if (peers.length === 0 && remote.length === 0)
136
140
  return "";
137
141
  // Build hash basis: sorted-by-instance_id projection of semantically-relevant fields.
138
142
  const basis = peers
@@ -146,19 +150,56 @@ function computePeerTableIfChanged(coordRoot, selfInstanceId, selfSessionIdFallb
146
150
  platform: p.platform ?? null,
147
151
  }))
148
152
  .sort((a, b) => (a.instance_id ?? "").localeCompare(b.instance_id ?? ""));
149
- const newHash = sha256Hex16(JSON.stringify(basis));
153
+ // Remote basis: machine + per-agent identity/task/files (published_at is
154
+ // excluded — a keepalive re-publish alone shouldn't re-emit the table).
155
+ const remoteBasis = remote.map((m) => ({
156
+ machine: m.machine,
157
+ agents: m.agents.map((a) => ({
158
+ instance_id: a.instance_id,
159
+ name: a.name ?? null,
160
+ task: a.task ?? null,
161
+ files_touched: Array.from(a.files_touched ?? []).sort(),
162
+ })),
163
+ }));
164
+ const newHash = sha256Hex16(JSON.stringify({ local: basis, remote: remoteBasis }));
150
165
  const hashFile = join(coordRoot, ".harnery", `.last-peer-hash.${selfInstanceId}`);
151
166
  const oldHash = safeRead(hashFile);
152
167
  if (oldHash && oldHash === newHash)
153
168
  return "";
154
169
  // Render peer table via the same formatter used at SessionStart.
155
- const table = formatPeerTable(peers, mySessionId);
170
+ const local = formatPeerTable(peers, mySessionId);
171
+ const remoteTable = formatRemoteTable(remote);
172
+ const table = [local, remoteTable].filter(Boolean).join("\n\n");
156
173
  if (!table)
157
174
  return "";
158
175
  // Persist hash atomically (temp + rename, same convention as other coord writes).
159
176
  writeHashFile(hashFile, newHash);
160
177
  return table;
161
178
  }
179
+ /** readRemoteMachines with a local failure guard (rendering must never throw). */
180
+ function readRemoteMachinesSafe(coordRoot) {
181
+ try {
182
+ return readRemoteMachines(coordRoot);
183
+ }
184
+ catch {
185
+ return [];
186
+ }
187
+ }
188
+ /** Render sessions on other machines (presence transport) as a subtable. */
189
+ function formatRemoteTable(remote) {
190
+ if (remote.length === 0)
191
+ return "";
192
+ const nowSec = Math.floor(Date.now() / 1000);
193
+ const lines = [];
194
+ for (const m of remote) {
195
+ for (const a of m.agents.slice(0, 10)) {
196
+ lines.push(formatRow({ ...a, display_files: Array.from(a.files_touched ?? []).sort() }, nowSec).replace(/^ {2}- (agent-[^ ]+)/, ` - $1 @${m.machine}`));
197
+ }
198
+ }
199
+ if (lines.length === 0)
200
+ return "";
201
+ return `Sessions on other machines (advisory, via presence refs):\n${lines.join("\n")}`;
202
+ }
162
203
  function computeCouncilPendingIfChanged(coordRoot, selfInstanceId, agentName) {
163
204
  const councilsDir = join(coordRoot, ".harnery", "councils");
164
205
  if (!existsSync(councilsDir))
@@ -1 +1 @@
1
- {"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAyC,QAAQ,EAAE,MAAM,SAAS,CAAC;AAoB1E,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+BAC2B;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA+D7D;AAwHD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBvD;AAwDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiDlF;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,iBAAW,CAAC"}
1
+ {"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAyC,QAAQ,EAAE,MAAM,SAAS,CAAC;AAqB1E,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+BAC2B;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAiE7D;AA+ID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBvD;AAwDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiDlF;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,iBAAW,CAAC"}
@@ -11,6 +11,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { resolveBinName, resolveHooksSetupHint } from "../../config.js";
13
13
  import { harneryVersion, loadHarnessWiring } from "../../hooks/harness/wiring.js";
14
+ import { readRemoteMachines } from "../../presence/index.js";
14
15
  /**
15
16
  * Build the combined SessionStart systemMessage. Returns the additionalContext
16
17
  * string (or "" if there's nothing to say).
@@ -20,7 +21,9 @@ export function renderSessionContext(opts) {
20
21
  const messages = [];
21
22
  // 1. Self-name line + peer table (folded if peers present)
22
23
  const peers = readActivePeers(coordRoot, instanceId);
23
- const peerTable = formatPeerTable(peers, sessionId);
24
+ const localTable = formatPeerTable(peers, sessionId);
25
+ // Cross-machine presence (ADR 0016): sessions on other machines (advisory).
26
+ const peerTable = [localTable, formatRemoteMachines(coordRoot)].filter(Boolean).join("\n\n");
24
27
  if (agentName) {
25
28
  const suffix = platformLabel ? ` (${platformLabel})` : "";
26
29
  const selfLine = `You are agent-${agentName}${suffix}.`;
@@ -72,6 +75,28 @@ export function renderSessionContext(opts) {
72
75
  }
73
76
  return messages.join("\n\n");
74
77
  }
78
+ /** One-line-per-agent view of sessions on other machines (presence refs). */
79
+ function formatRemoteMachines(coordRoot) {
80
+ try {
81
+ const remote = readRemoteMachines(coordRoot);
82
+ if (remote.length === 0)
83
+ return "";
84
+ const lines = [];
85
+ for (const m of remote) {
86
+ for (const a of m.agents.slice(0, 10)) {
87
+ const task = a.task ? ` "${a.task.slice(0, 60)}"` : "";
88
+ const files = a.files_touched?.length
89
+ ? `holds: ${a.files_touched.slice(0, 3).join(", ")}${a.files_touched.length > 3 ? `, +${a.files_touched.length - 3} more` : ""}`
90
+ : "nothing held";
91
+ lines.push(` - agent-${a.name ?? a.instance_id.slice(0, 8)} @${m.machine}${task} (${files})`);
92
+ }
93
+ }
94
+ return `Sessions on other machines (advisory, via presence refs):\n${lines.join("\n")}`;
95
+ }
96
+ catch {
97
+ return "";
98
+ }
99
+ }
75
100
  /** Read all peer heartbeats from .harnery/active/, excluding self. */
76
101
  function readActivePeers(coordRoot, selfInstanceId) {
77
102
  const out = [];
@@ -1 +1 @@
1
- {"version":3,"file":"commit-conflict.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/rules/commit-conflict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAiBH,UAAU,QAAQ;IAChB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;wEACoE;IACpE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,qFAAqF;IACrF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,GAAG,mBAAmB,CAsGhG"}
1
+ {"version":3,"file":"commit-conflict.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/rules/commit-conflict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAgBH,UAAU,QAAQ;IAChB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;wEACoE;IACpE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,qFAAqF;IACrF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,GAAG,mBAAmB,CAsGhG"}
@@ -22,8 +22,8 @@
22
22
  import { spawnSync } from "node:child_process";
23
23
  import { existsSync, readdirSync, readFileSync } from "node:fs";
24
24
  import { join } from "node:path";
25
- import { coordEnv } from "../../../lib/env.js";
26
- const DEFAULT_FRESHNESS_SECS = 600;
25
+ import { coordFreshnessSeconds } from "../../config.js";
26
+ import { instanceHasLivePid } from "../state/pidmap.js";
27
27
  export function evaluateCommit(coordRoot, req) {
28
28
  if (req.staged_paths.length === 0) {
29
29
  return {
@@ -36,7 +36,7 @@ export function evaluateCommit(coordRoot, req) {
36
36
  };
37
37
  }
38
38
  const peers = readActivePeers(coordRoot);
39
- const cutoffMs = Date.now() - freshnessSecs() * 1000;
39
+ const cutoffMs = Date.now() - coordFreshnessSeconds(coordRoot) * 1000;
40
40
  const stagedSet = new Set(req.staged_paths);
41
41
  const gitlinkSet = new Set(req.staged_gitlinks ?? []);
42
42
  const conflicts = [];
@@ -166,33 +166,7 @@ function isHolderSelfAttributed(coordRoot, holderId, stagedSet, peers) {
166
166
  return true;
167
167
  }
168
168
  function holderHasLiveForeignPid(coordRoot, holderId) {
169
- const dir = join(coordRoot, ".harnery", "pid-map");
170
- if (!existsSync(dir))
171
- return false;
172
- for (const f of readdirSync(dir)) {
173
- let row = "";
174
- try {
175
- row = readFileSync(join(dir, f), "utf8").trim();
176
- }
177
- catch {
178
- continue;
179
- }
180
- const owner = row.split("\t")[0]?.trim() ?? "";
181
- if (owner !== holderId)
182
- continue;
183
- // Process still alive?
184
- const pid = Number.parseInt(f, 10);
185
- if (!Number.isFinite(pid))
186
- continue;
187
- try {
188
- process.kill(pid, 0); // signal 0 = liveness probe
189
- return true; // live foreign pid found
190
- }
191
- catch {
192
- // ESRCH (no such process): pid-map entry is stale, skip
193
- }
194
- }
195
- return false;
169
+ return instanceHasLivePid(coordRoot, holderId);
196
170
  }
197
171
  function isPathCleanInHead(coordRoot, relPath) {
198
172
  // Path is tracked + diff-clean against HEAD = "already committed, holder
@@ -230,13 +204,6 @@ function readActivePeers(coordRoot) {
230
204
  }
231
205
  return out;
232
206
  }
233
- function freshnessSecs() {
234
- const raw = coordEnv("AGENT_FRESHNESS");
235
- if (!raw)
236
- return DEFAULT_FRESHNESS_SECS;
237
- const n = Number.parseInt(raw, 10);
238
- return Number.isFinite(n) && n > 0 ? n : DEFAULT_FRESHNESS_SECS;
239
- }
240
207
  function shortName(peer) {
241
208
  if (peer.name && peer.name.length > 0)
242
209
  return `agent-${peer.name}`;
@@ -1 +1 @@
1
- {"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../../src/core/agents/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;iFAEiF;AACjF,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,QAAQ,GACR,aAAa,GACb,aAAa,GACb,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,cAAc,GACd,aAAa,GACb,WAAW,CAAC;AAEhB;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAwB1C;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8BjE;AAsGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,IAAI,CAEN;AAED,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CAGxD"}
1
+ {"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../../src/core/agents/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;iFAEiF;AACjF,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,QAAQ,GACR,aAAa,GACb,aAAa,GACb,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,cAAc,GACd,aAAa,GACb,WAAW,CAAC;AAEhB;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAa1C;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjE;AAsGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,IAAI,CAEN;AAED,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CAGxD"}
@@ -8,10 +8,10 @@
8
8
  * middleware callers need no edits.
9
9
  */
10
10
  import { randomBytes } from "node:crypto";
11
- import { existsSync, readFileSync, statSync } from "node:fs";
11
+ import { existsSync, readFileSync } from "node:fs";
12
12
  import { dirname, resolve } from "node:path";
13
13
  // Kept dependency-light: vendored verbatim into a downstream consumer, so no coordEnv import.
14
- import { normalizeHarness } from "./canonical-emit.js";
14
+ import { normalizeHarness, resolveEmitRoot } from "./canonical-emit.js";
15
15
  import { emit } from "./events/emit.js";
16
16
  /**
17
17
  * Resolved path of the ndjson sidecar file. Lives inside `.harnery/` so a
@@ -22,27 +22,15 @@ export function sessionEventsPath() {
22
22
  const explicit = process.env.HARNERY_OUTPUT_SESSION_EVENTS;
23
23
  if (explicit)
24
24
  return explicit;
25
- // Walk up looking for an EXISTING .harnery/ directory; only match dirs
26
- // (submodule .git is a file/gitlink, and CLAUDE.md / AGENTS.md can
27
- // proliferate per-subdirectory in many monorepos, so neither is a safe
28
- // anchor). Don't reintroduce CLAUDE.md / .git as anchors, which creates
25
+ // Superproject-aware root resolution (git first, cwd walk fallback) via
26
+ // resolveEmitRoot. A plain cwd walk here mis-anchored to a NESTED
27
+ // `.harnery/` when the shell sat inside a submodule carrying its own
28
+ // (e.g. an embedded harnery checkout), silently splitting the event
29
+ // stream. Don't reintroduce CLAUDE.md / .git as anchors, which creates
29
30
  // stray .harnery/ dirs under submodule subtrees.
30
- let dir = process.cwd();
31
- for (let i = 0; i < 10; i++) {
32
- const candidate = resolve(dir, ".harnery");
33
- try {
34
- if (existsSync(candidate) && statSync(candidate).isDirectory()) {
35
- return resolve(candidate, "session-events.ndjson");
36
- }
37
- }
38
- catch {
39
- /* keep walking */
40
- }
41
- const parent = dirname(dir);
42
- if (parent === dir)
43
- break;
44
- dir = parent;
45
- }
31
+ const root = resolveEmitRoot();
32
+ if (root)
33
+ return resolve(root, ".harnery", "session-events.ndjson");
46
34
  return resolve(process.env.HOME || "/tmp", ".harnery", "session-events.ndjson");
47
35
  }
48
36
  /** Random 8-char hex id for grouping output lines under a single command. */
@@ -60,28 +48,13 @@ export function newCmdId() {
60
48
  export function readLastIntent(instanceId) {
61
49
  if (!instanceId)
62
50
  return null;
63
- // Walk up from cwd looking for .harnery/, same resolution as
64
- // sessionEventsPath() (only match existing directories).
65
- let dir = process.cwd();
66
- let agentsDir = null;
67
- for (let i = 0; i < 10; i++) {
68
- const candidate = resolve(dir, ".harnery");
69
- try {
70
- if (existsSync(candidate) && statSync(candidate).isDirectory()) {
71
- agentsDir = candidate;
72
- break;
73
- }
74
- }
75
- catch {
76
- /* keep walking */
77
- }
78
- const parent = dirname(dir);
79
- if (parent === dir)
80
- break;
81
- dir = parent;
82
- }
83
- if (!agentsDir)
51
+ // Same superproject-aware root resolution as sessionEventsPath(): the
52
+ // intent stamp is written by the PreToolUse hook into the SUPERPROJECT's
53
+ // .harnery/, so a nested-`.harnery/` cwd must not redirect the read.
54
+ const root = resolveEmitRoot();
55
+ if (!root)
84
56
  return null;
57
+ const agentsDir = resolve(root, ".harnery");
85
58
  const intentPath = resolve(agentsDir, `.last-intent.${instanceId}`);
86
59
  if (!existsSync(intentPath))
87
60
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat-projector.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-projector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,OAAO,EAAE;QACP,cAAc,EAAE,CAAC,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAoD9D;AAgXD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E"}
1
+ {"version":3,"file":"heartbeat-projector.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-projector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,OAAO,EAAE;QACP,cAAc,EAAE,CAAC,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAoD9D;AAyXD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E"}
@@ -92,8 +92,8 @@ function seed(ev, coordRoot) {
92
92
  if (resolved) {
93
93
  hb.name = resolved.name;
94
94
  hb.kind = resolved.kind;
95
- if (resolved.kind === "subagent")
96
- hb.agent_id = ev.instance_id;
95
+ hb.agent_id =
96
+ resolved.agent_id ?? (resolved.kind === "subagent" ? ev.instance_id : undefined);
97
97
  }
98
98
  }
99
99
  catch {
@@ -239,6 +239,15 @@ function apply(hb, ev, coordRoot) {
239
239
  case "state.status_checked":
240
240
  hb.last_status_at = ev.ts;
241
241
  break;
242
+ case "identity.assumed": {
243
+ const name = pickStr(d, "name");
244
+ const agentId = pickStr(d, "agent_id");
245
+ if (name)
246
+ hb.name = name;
247
+ if (agentId)
248
+ hb.agent_id = agentId;
249
+ break;
250
+ }
242
251
  case "state.presence_change": {
243
252
  const to = pickStr(d, "to");
244
253
  if (to === "mobile" || to === "office")
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6DH,MAAM,WAAW,SAAS;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAiBD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQrF;AAeD,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAS7F;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKxF;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CAMlB;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAYlB;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CAsChG;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAS5E;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAuBnF;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,SAAS,GAAG,IAAI,CAqDlB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,SAAS,GAAG,IAAI,CAOlB"}
1
+ {"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6DH,MAAM,WAAW,SAAS;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAiBD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQrF;AAeD,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAS7F;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKxF;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CAMlB;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAYlB;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CAsChG;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAS5E;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAuBnF;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,SAAS,GAAG,IAAI,CAsDlB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,SAAS,GAAG,IAAI,CAOlB"}
@@ -245,9 +245,9 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, harness)
245
245
  if (resolved) {
246
246
  name = resolved.name;
247
247
  kind = resolved.kind;
248
- // subagent kind: agent_id == instance_id. Else (session/transient): no id stamp.
249
- if (resolved.kind === "subagent")
250
- agentId = instanceId;
248
+ // An explicitly assumed session carries its durable persona UUID in
249
+ // name-history. Native subagents continue to use instance_id.
250
+ agentId = resolved.agent_id ?? (resolved.kind === "subagent" ? instanceId : "");
251
251
  }
252
252
  }
253
253
  catch {
@@ -8,14 +8,30 @@
8
8
  * Durable persistence: `.harnery/.name-history` (JSONL, one row per assignment)
9
9
  * + `.harnery/.name-counter` (current counter, atomic temp+rename).
10
10
  *
11
- * Recreation rule (mirrors v1):
12
- * 1. Own instance_id in name-history → (original name, original kind)
13
- * 2. session_id in name-history (owner != session) → (parent's name, "transient")
11
+ * Recreation rule:
12
+ * 1. Own instance_id in name-history → latest (name, kind, agent_id)
13
+ * 2. session_id in name-history (owner != session) → latest parent's
14
+ * (name, agent_id) with kind="transient"
15
+ *
16
+ * Multiple rows for one instance are intentional. `identity assume` appends a
17
+ * new binding instead of rewriting history; readers therefore resolve from the
18
+ * end of the file. Older one-row histories retain their original behavior.
14
19
  * 3. Else: new assignment, consume a counter slot.
15
20
  */
16
21
  /** All 260 names. */
17
22
  export declare const COORD_NAMES: readonly ["Anna", "Bob", "Carlos", "Dalia", "Erika", "Fred", "Gemma", "Hank", "Imelda", "Jerry", "Kirk", "Lee", "Milton", "Nate", "Oscar", "Patty", "Quentin", "Rafael", "Sara", "Tony", "Ulrik", "Valerie", "Whitney", "Xander", "Yara", "Zara", "Adelaide", "Bertha", "Carmen", "Dorian", "Edna", "Francine", "Greta", "Helene", "Ines", "Joyce", "Karen", "Lorenzo", "Mindy", "Nadine", "Odette", "Paulette", "Quinn", "Rebekah", "Sean", "Tammy", "Ursula", "Vinson", "Wallace", "Xanthe", "Yusuf", "Zoe", "Alex", "Beatrice", "Cyrus", "Delia", "Ernesto", "Florence", "Gordon", "Hester", "Isaias", "Jenna", "Klaus", "Larry", "Maxine", "Nicholas", "Otto", "Peter", "Quill", "Renee", "Stella", "Theresa", "Umberto", "Virgil", "Willow", "Xavier", "Yolanda", "Zane", "Anita", "Bruno", "Cora", "Damon", "Edith", "Felix", "Gloria", "Holly", "Iris", "Jasper", "Kaia", "Linda", "Marco", "Nora", "Olive", "Petra", "Querida", "Roman", "Sebastian", "Tobias", "Una", "Vera", "Walter", "Xena", "Yusra", "Zelda", "Aaron", "Bonnie", "Caleb", "Daphne", "Elias", "Fiona", "Galileo", "Hugo", "Ian", "Jude", "Knox", "Leo", "Maya", "Nash", "Owen", "Paxton", "Quetzal", "Rosa", "Sage", "Talia", "Uri", "Vincent", "Wesley", "Ximena", "Yvette", "Zephyr", "Astrid", "Beau", "Celeste", "Dexter", "Ezra", "Fitz", "Genevieve", "Hadley", "Imogen", "Joaquin", "Kira", "Lila", "Mason", "Nila", "Olga", "Pearl", "Quincy", "Royce", "Sterling", "Trent", "Ulysses", "Violet", "Wynne", "Xerxes", "Yael", "Zinnia", "Amelia", "Bianca", "Cody", "Dahlia", "Esme", "Fern", "Greer", "Harriet", "Ivan", "Juno", "Kendrick", "Luther", "Margot", "Nigel", "Orion", "Phillip", "Quenby", "Reagan", "Sienna", "Tessa", "Uma", "Vesper", "Watson", "Xiomara", "Yuri", "Zoltan", "Andre", "Boris", "Cassidy", "Davis", "Evander", "Forrest", "Gibson", "Henry", "Irene", "Justus", "Kasper", "Lainey", "Miles", "Naomi", "Ophelia", "Phoebe", "Quark", "Rylie", "Saul", "Truman", "Ulrich", "Voss", "Winifred", "Xuxa", "Yvonne", "Zia", "Aria", "Brenda", "Crispin", "Dolores", "Estelle", "Felicity", "Goldie", "Hazel", "Ira", "Jovi", "Kestrel", "Lyric", "Mavis", "Nico", "Otis", "Percy", "Querubin", "Rhett", "Scout", "Tatum", "Unity", "Vivian", "Wyatt", "Xan", "Yancy", "Zev", "Atticus", "Barnaby", "Calliope", "Drake", "Eustace", "Foster", "Granger", "Hollis", "Indira", "Jericho", "Kaleb", "Logan", "Magnus", "Noor", "Oakley", "Posy", "Quito", "Rosalind", "Sloane", "Theron", "Upton", "Vance", "Wendell", "Xola", "Yann", "Zora"];
18
23
  export type NameKind = "session" | "subagent" | "transient";
24
+ export interface NameHistoryRow {
25
+ instance_id: string;
26
+ name: string;
27
+ kind: NameKind;
28
+ ts: string;
29
+ /** Durable persona UUID. Present after `agents identity assume`. */
30
+ agent_id?: string;
31
+ /** Audit marker distinguishing an explicit role adoption from pool assignment. */
32
+ source?: "pool" | "identity.assume";
33
+ previous_name?: string;
34
+ }
19
35
  /**
20
36
  * Resolve (name, kind) for an existing owner without consuming a counter
21
37
  * slot. Returns null if no history match found.
@@ -26,7 +42,26 @@ export type NameKind = "session" | "subagent" | "transient";
26
42
  export declare function resolveName(coordRoot: string, instanceId: string, sessionId?: string): {
27
43
  name: string;
28
44
  kind: NameKind;
45
+ agent_id?: string;
29
46
  } | null;
47
+ export interface NameAssumptionResult {
48
+ changed: boolean;
49
+ previous: {
50
+ name: string;
51
+ kind: NameKind;
52
+ agent_id?: string;
53
+ } | null;
54
+ current: {
55
+ name: string;
56
+ kind: NameKind;
57
+ agent_id: string;
58
+ };
59
+ }
60
+ /**
61
+ * Append an explicit instance → durable-persona binding. Latest-row-wins makes
62
+ * this auditable and retry-safe without mutating prior assignment history.
63
+ */
64
+ export declare function recordNameAssumption(coordRoot: string, instanceId: string, name: string, agentId: string, kind?: NameKind): NameAssumptionResult;
30
65
  /**
31
66
  * Assign a name to <instanceId> with the given <kind>. Counter-consuming when
32
67
  * the owner is new. Idempotent: returns existing name on resume.
@@ -1 +1 @@
1
- {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,qBAAqB;AACrB,eAAO,MAAM,WAAW,y5EA+Qd,CAAC;AAMX,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AA6C5D;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,IAAI,CAkBzC;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAqBxF"}
1
+ {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,qBAAqB;AACrB,eAAO,MAAM,WAAW,y5EA+Qd,CAAC;AAMX,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAsCD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4B5D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,QAAoB,GACzB,oBAAoB,CAoBtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAsBxF"}
@@ -8,9 +8,14 @@
8
8
  * Durable persistence: `.harnery/.name-history` (JSONL, one row per assignment)
9
9
  * + `.harnery/.name-counter` (current counter, atomic temp+rename).
10
10
  *
11
- * Recreation rule (mirrors v1):
12
- * 1. Own instance_id in name-history → (original name, original kind)
13
- * 2. session_id in name-history (owner != session) → (parent's name, "transient")
11
+ * Recreation rule:
12
+ * 1. Own instance_id in name-history → latest (name, kind, agent_id)
13
+ * 2. session_id in name-history (owner != session) → latest parent's
14
+ * (name, agent_id) with kind="transient"
15
+ *
16
+ * Multiple rows for one instance are intentional. `identity assume` appends a
17
+ * new binding instead of rewriting history; readers therefore resolve from the
18
+ * end of the file. Older one-row histories retain their original behavior.
14
19
  * 3. Else: new assignment, consume a counter slot.
15
20
  */
16
21
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
@@ -333,20 +338,53 @@ function appendHistory(coordRoot, row) {
333
338
  */
334
339
  export function resolveName(coordRoot, instanceId, sessionId) {
335
340
  const history = readHistory(coordRoot);
336
- for (const row of history) {
341
+ for (let i = history.length - 1; i >= 0; i--) {
342
+ const row = history[i];
337
343
  if (row.instance_id === instanceId) {
338
- return { name: row.name, kind: row.kind };
344
+ return {
345
+ name: row.name,
346
+ kind: row.kind,
347
+ ...(row.agent_id ? { agent_id: row.agent_id } : {}),
348
+ };
339
349
  }
340
350
  }
341
351
  if (sessionId && sessionId !== instanceId) {
342
- for (const row of history) {
352
+ for (let i = history.length - 1; i >= 0; i--) {
353
+ const row = history[i];
343
354
  if (row.instance_id === sessionId) {
344
- return { name: row.name, kind: "transient" };
355
+ return {
356
+ name: row.name,
357
+ kind: "transient",
358
+ ...(row.agent_id ? { agent_id: row.agent_id } : {}),
359
+ };
345
360
  }
346
361
  }
347
362
  }
348
363
  return null;
349
364
  }
365
+ /**
366
+ * Append an explicit instance → durable-persona binding. Latest-row-wins makes
367
+ * this auditable and retry-safe without mutating prior assignment history.
368
+ */
369
+ export function recordNameAssumption(coordRoot, instanceId, name, agentId, kind = "session") {
370
+ const previous = resolveName(coordRoot, instanceId);
371
+ const current = { name, kind, agent_id: agentId };
372
+ if (previous?.name === current.name &&
373
+ previous.kind === current.kind &&
374
+ previous.agent_id === current.agent_id) {
375
+ return { changed: false, previous, current };
376
+ }
377
+ appendHistory(coordRoot, {
378
+ instance_id: instanceId,
379
+ name,
380
+ kind,
381
+ agent_id: agentId,
382
+ source: "identity.assume",
383
+ previous_name: previous?.name,
384
+ ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
385
+ });
386
+ return { changed: true, previous, current };
387
+ }
350
388
  /**
351
389
  * Assign a name to <instanceId> with the given <kind>. Counter-consuming when
352
390
  * the owner is new. Idempotent: returns existing name on resume.
@@ -370,6 +408,7 @@ export function assignName(coordRoot, instanceId, kind) {
370
408
  instance_id: instanceId,
371
409
  name,
372
410
  kind,
411
+ source: "pool",
373
412
  ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
374
413
  });
375
414
  return name;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pid-map writer.
2
+ * Pid-map writer + liveness helpers.
3
3
  *
4
4
  * Per-harness pid-map at `.harnery/pid-map/<pid>` containing
5
5
  * `<instance_id>\t<platform>`. `harn agents whoami` walks ppid up 20 hops looking
@@ -8,4 +8,8 @@
8
8
  * Atomic temp+rename. Idempotent: re-writing the same row is a no-op.
9
9
  */
10
10
  export declare function writePidmapRow(coordRoot: string, pid: number, instanceId: string, platform: string): void;
11
+ /** True when any pid-map row for `instanceId` still belongs to a live process. */
12
+ export declare function instanceHasLivePid(coordRoot: string, instanceId: string): boolean;
13
+ /** Drop every pid-map row owned by `instanceId`. Returns how many files were removed. */
14
+ export declare function removePidmapRowsForInstance(coordRoot: string, instanceId: string): number;
11
15
  //# sourceMappingURL=pidmap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pidmap.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/pidmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,IAAI,CAYN"}
1
+ {"version":3,"file":"pidmap.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/pidmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,IAAI,CAYN;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAsBjF;AAED,yFAAyF;AACzF,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqBzF"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pid-map writer.
2
+ * Pid-map writer + liveness helpers.
3
3
  *
4
4
  * Per-harness pid-map at `.harnery/pid-map/<pid>` containing
5
5
  * `<instance_id>\t<platform>`. `harn agents whoami` walks ppid up 20 hops looking
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Atomic temp+rename. Idempotent: re-writing the same row is a no-op.
9
9
  */
10
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
11
11
  import { dirname, join } from "node:path";
12
12
  function atomicWrite(path, content) {
13
13
  mkdirSync(dirname(path), { recursive: true });
@@ -30,3 +30,59 @@ export function writePidmapRow(coordRoot, pid, instanceId, platform) {
30
30
  }
31
31
  atomicWrite(path, row);
32
32
  }
33
+ /** True when any pid-map row for `instanceId` still belongs to a live process. */
34
+ export function instanceHasLivePid(coordRoot, instanceId) {
35
+ const dir = join(coordRoot, ".harnery", "pid-map");
36
+ if (!existsSync(dir))
37
+ return false;
38
+ for (const f of readdirSync(dir)) {
39
+ let row = "";
40
+ try {
41
+ row = readFileSync(join(dir, f), "utf8").trim();
42
+ }
43
+ catch {
44
+ continue;
45
+ }
46
+ const owner = row.split("\t")[0]?.trim() ?? "";
47
+ if (owner !== instanceId)
48
+ continue;
49
+ const pid = Number.parseInt(f, 10);
50
+ if (!Number.isFinite(pid))
51
+ continue;
52
+ try {
53
+ process.kill(pid, 0); // signal 0 = liveness probe
54
+ return true;
55
+ }
56
+ catch {
57
+ // ESRCH (no such process): pid-map entry is stale, keep scanning
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+ /** Drop every pid-map row owned by `instanceId`. Returns how many files were removed. */
63
+ export function removePidmapRowsForInstance(coordRoot, instanceId) {
64
+ const dir = join(coordRoot, ".harnery", "pid-map");
65
+ if (!existsSync(dir))
66
+ return 0;
67
+ let removed = 0;
68
+ for (const f of readdirSync(dir)) {
69
+ let row = "";
70
+ try {
71
+ row = readFileSync(join(dir, f), "utf8").trim();
72
+ }
73
+ catch {
74
+ continue;
75
+ }
76
+ const owner = row.split("\t")[0]?.trim() ?? "";
77
+ if (owner !== instanceId)
78
+ continue;
79
+ try {
80
+ unlinkSync(join(dir, f));
81
+ removed += 1;
82
+ }
83
+ catch {
84
+ /* best-effort cleanup */
85
+ }
86
+ }
87
+ return removed;
88
+ }
@@ -5,8 +5,9 @@
5
5
  * Fires at session.start to clean up crashed-peer detritus before the new
6
6
  * session's UX layer reads peer state.
7
7
  *
8
- * Freshness threshold defaults to 600s; configurable via
9
- * HARNERY_AGENT_COORD_FRESHNESS env var.
8
+ * Freshness threshold defaults to 600s; configurable via the
9
+ * HARNERY_AGENT_COORD_FRESHNESS env var or `.harnery/config.jsonc`
10
+ * `coord.freshness_seconds` (see `coordFreshnessSeconds`).
10
11
  */
11
12
  export declare function staleSweep(coordRoot: string): {
12
13
  heartbeatsRemoved: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"stale-sweep.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/stale-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiDH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG;IAC7C,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CA+HA"}
1
+ {"version":3,"file":"stale-sweep.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/stale-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA+CH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG;IAC7C,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CA2HA"}