brainclaw 0.29.2 → 1.5.4

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 (197) hide show
  1. package/LICENSE +21 -74
  2. package/README.md +199 -176
  3. package/dist/brainclaw-vscode.vsix +0 -0
  4. package/dist/cli.js +710 -25
  5. package/dist/commands/accept.js +3 -0
  6. package/dist/commands/add-step.js +11 -26
  7. package/dist/commands/agent-board.js +70 -3
  8. package/dist/commands/audit.js +19 -0
  9. package/dist/commands/check-policy.js +54 -0
  10. package/dist/commands/check-security-mcp.js +145 -0
  11. package/dist/commands/check-security.js +106 -0
  12. package/dist/commands/claim-resource.js +1 -0
  13. package/dist/commands/codev.js +672 -0
  14. package/dist/commands/compact.js +74 -0
  15. package/dist/commands/complete-step.js +16 -26
  16. package/dist/commands/constraint.js +8 -20
  17. package/dist/commands/decision.js +9 -20
  18. package/dist/commands/delete-plan.js +10 -12
  19. package/dist/commands/delete-step.js +16 -0
  20. package/dist/commands/dispatch.js +163 -0
  21. package/dist/commands/doctor.js +1122 -49
  22. package/dist/commands/enable-agent.js +1 -0
  23. package/dist/commands/export.js +280 -22
  24. package/dist/commands/handoff.js +33 -0
  25. package/dist/commands/harvest.js +189 -0
  26. package/dist/commands/hooks.js +82 -25
  27. package/dist/commands/inbox.js +169 -0
  28. package/dist/commands/init.js +38 -31
  29. package/dist/commands/install-hooks.js +71 -44
  30. package/dist/commands/link.js +89 -0
  31. package/dist/commands/list-claims.js +48 -3
  32. package/dist/commands/list-plans.js +129 -25
  33. package/dist/commands/loops-handlers.js +409 -0
  34. package/dist/commands/mcp-read-handlers.js +1628 -0
  35. package/dist/commands/mcp-schemas.generated.js +269 -0
  36. package/dist/commands/mcp.js +4224 -1501
  37. package/dist/commands/plan-resource.js +64 -0
  38. package/dist/commands/plan.js +12 -26
  39. package/dist/commands/prune.js +37 -2
  40. package/dist/commands/reflect.js +20 -7
  41. package/dist/commands/release-claim.js +11 -6
  42. package/dist/commands/release-notes.js +170 -0
  43. package/dist/commands/repair.js +210 -0
  44. package/dist/commands/run-profile.js +57 -0
  45. package/dist/commands/sequence.js +113 -0
  46. package/dist/commands/session-end.js +423 -14
  47. package/dist/commands/session-start.js +214 -41
  48. package/dist/commands/setup-security.js +103 -0
  49. package/dist/commands/setup.js +42 -4
  50. package/dist/commands/stale.js +109 -0
  51. package/dist/commands/switch.js +100 -2
  52. package/dist/commands/trap.js +14 -31
  53. package/dist/commands/update-handoff.js +63 -4
  54. package/dist/commands/update-plan.js +21 -28
  55. package/dist/commands/update-step.js +37 -0
  56. package/dist/commands/upgrade.js +313 -6
  57. package/dist/commands/usage.js +102 -0
  58. package/dist/commands/version.js +20 -0
  59. package/dist/commands/who.js +33 -5
  60. package/dist/commands/worktree.js +105 -0
  61. package/dist/core/actions.js +315 -0
  62. package/dist/core/agent-capability.js +610 -17
  63. package/dist/core/agent-context.js +7 -1
  64. package/dist/core/agent-files.js +1169 -85
  65. package/dist/core/agent-integrations.js +160 -5
  66. package/dist/core/agent-inventory.js +2 -0
  67. package/dist/core/agent-profiles.js +93 -0
  68. package/dist/core/agent-registry.js +162 -30
  69. package/dist/core/agentrun-reconciler.js +345 -0
  70. package/dist/core/agentruns.js +424 -0
  71. package/dist/core/ai-agent-detection.js +31 -10
  72. package/dist/core/archival.js +77 -0
  73. package/dist/core/assignment-sweeper.js +82 -0
  74. package/dist/core/assignments.js +367 -0
  75. package/dist/core/audit.js +30 -0
  76. package/dist/core/brainclaw-version.js +94 -2
  77. package/dist/core/candidates.js +93 -2
  78. package/dist/core/claims.js +419 -0
  79. package/dist/core/codev-metrics.js +77 -0
  80. package/dist/core/codev-personas.js +31 -0
  81. package/dist/core/codev-plan-gen.js +35 -0
  82. package/dist/core/codev-prompts.js +74 -0
  83. package/dist/core/codev-responses.js +62 -0
  84. package/dist/core/codev-rounds.js +218 -0
  85. package/dist/core/config.js +4 -0
  86. package/dist/core/context.js +381 -34
  87. package/dist/core/coordination.js +201 -6
  88. package/dist/core/cross-project.js +230 -16
  89. package/dist/core/default-profiles/doctor.yaml +11 -0
  90. package/dist/core/default-profiles/janitor.yaml +11 -0
  91. package/dist/core/default-profiles/onboarder.yaml +11 -0
  92. package/dist/core/default-profiles/reviewer.yaml +13 -0
  93. package/dist/core/dispatcher.js +1189 -0
  94. package/dist/core/duplicates.js +2 -2
  95. package/dist/core/entity-operations.js +450 -0
  96. package/dist/core/entity-registry.js +344 -0
  97. package/dist/core/events.js +106 -2
  98. package/dist/core/execution-adapters.js +154 -0
  99. package/dist/core/execution-context.js +63 -0
  100. package/dist/core/execution-profile.js +270 -0
  101. package/dist/core/execution.js +255 -0
  102. package/dist/core/facade-schema.js +81 -0
  103. package/dist/core/federation-cloud.js +99 -0
  104. package/dist/core/federation-message.js +52 -0
  105. package/dist/core/federation-transport.js +65 -0
  106. package/dist/core/gc-semantic.js +482 -0
  107. package/dist/core/governance.js +247 -0
  108. package/dist/core/guards.js +19 -0
  109. package/dist/core/ideation.js +72 -0
  110. package/dist/core/identity.js +110 -25
  111. package/dist/core/ids.js +6 -0
  112. package/dist/core/input-validation.js +2 -2
  113. package/dist/core/instruction-templates.js +344 -136
  114. package/dist/core/io.js +90 -11
  115. package/dist/core/lock.js +6 -2
  116. package/dist/core/loops/brief-assembly.js +213 -0
  117. package/dist/core/loops/facade-schema.js +148 -0
  118. package/dist/core/loops/index.js +7 -0
  119. package/dist/core/loops/iteration-engine.js +139 -0
  120. package/dist/core/loops/lock.js +385 -0
  121. package/dist/core/loops/store.js +201 -0
  122. package/dist/core/loops/types.js +403 -0
  123. package/dist/core/loops/verbs.js +534 -0
  124. package/dist/core/markdown.js +15 -3
  125. package/dist/core/memory-compactor.js +432 -0
  126. package/dist/core/memory-git.js +152 -8
  127. package/dist/core/messaging.js +278 -0
  128. package/dist/core/migration.js +32 -1
  129. package/dist/core/mutation-pipeline.js +4 -2
  130. package/dist/core/operations/memory-mutation.js +129 -0
  131. package/dist/core/operations/memory-write.js +78 -0
  132. package/dist/core/operations/plan.js +190 -0
  133. package/dist/core/policy.js +169 -0
  134. package/dist/core/reputation.js +9 -3
  135. package/dist/core/schema.js +491 -6
  136. package/dist/core/search.js +21 -2
  137. package/dist/core/security-cache.js +71 -0
  138. package/dist/core/security-guard.js +152 -0
  139. package/dist/core/security-scoring.js +86 -0
  140. package/dist/core/sequence.js +130 -0
  141. package/dist/core/socket-client.js +113 -0
  142. package/dist/core/staleness.js +246 -0
  143. package/dist/core/state.js +98 -22
  144. package/dist/core/store-resolution.js +43 -11
  145. package/dist/core/toml-writer.js +76 -0
  146. package/dist/core/upgrades/backup.js +232 -0
  147. package/dist/core/upgrades/health-check.js +169 -0
  148. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  149. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  150. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  151. package/dist/core/upgrades/schema-version.js +97 -0
  152. package/dist/core/worktree.js +606 -0
  153. package/dist/facts.js +114 -0
  154. package/dist/facts.json +111 -0
  155. package/docs/architecture/project-refs.md +5 -1
  156. package/docs/cli.md +690 -43
  157. package/docs/concepts/ideation-loop.md +317 -0
  158. package/docs/concepts/loop-engine.md +456 -0
  159. package/docs/concepts/mcp-governance.md +268 -0
  160. package/docs/concepts/memory-staleness.md +122 -0
  161. package/docs/concepts/multi-agent-workflows.md +166 -0
  162. package/docs/concepts/plans-and-claims.md +31 -6
  163. package/docs/concepts/project-md-convention.md +35 -0
  164. package/docs/concepts/troubleshooting.md +220 -0
  165. package/docs/concepts/upgrade-cli.md +202 -0
  166. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  167. package/docs/context-format-changelog.md +2 -2
  168. package/docs/context-format.md +2 -2
  169. package/docs/index.md +68 -0
  170. package/docs/integrations/agents.md +15 -16
  171. package/docs/integrations/cline.md +88 -0
  172. package/docs/integrations/codex.md +75 -23
  173. package/docs/integrations/continue.md +60 -0
  174. package/docs/integrations/copilot.md +67 -9
  175. package/docs/integrations/kilocode.md +72 -0
  176. package/docs/integrations/mcp.md +304 -21
  177. package/docs/integrations/mistral-vibe.md +122 -0
  178. package/docs/integrations/opencode.md +84 -0
  179. package/docs/integrations/overview.md +23 -8
  180. package/docs/integrations/roo.md +74 -0
  181. package/docs/integrations/windsurf.md +83 -0
  182. package/docs/mcp-schema-changelog.md +191 -1
  183. package/docs/playbooks/integration/index.md +121 -0
  184. package/docs/playbooks/productivity/index.md +102 -0
  185. package/docs/playbooks/team/index.md +122 -0
  186. package/docs/product/agent-first-model.md +184 -0
  187. package/docs/product/entity-model-audit.md +462 -0
  188. package/docs/product/positioning.md +10 -10
  189. package/docs/quickstart-existing-project.md +135 -0
  190. package/docs/quickstart.md +124 -37
  191. package/docs/release-maintenance.md +79 -0
  192. package/docs/review.md +2 -0
  193. package/docs/server-operations.md +118 -0
  194. package/package.json +21 -13
  195. package/dist/commands/claude-desktop-extension.js +0 -18
  196. package/dist/commands/diff.js +0 -99
  197. package/dist/core/claude-desktop-extension.js +0 -224
@@ -0,0 +1,345 @@
1
+ /**
2
+ * AgentRun reconciliation — silent-completion recovery + post-spawn health-check.
3
+ *
4
+ * Closes two gaps observed empirically in May 2026 dispatches:
5
+ *
6
+ * 1. **Silent completion** (pln#496 step stp_344f99b3). Dispatched workers
7
+ * sometimes finish their work and exit without ever calling
8
+ * `bclaw_assignment_update(status: 'completed')`. The agent_run stays in
9
+ * `running` forever, blocking review loops that wait on `run_completed`
10
+ * to converge. Concrete witnesses: codex review of pln#494 (37 min silent
11
+ * run_running, no completion event) and codex review of pln#480 (2h26
12
+ * silent). Also: claude-code worker on pln#480 implementation — committed
13
+ * fine but never released the claim.
14
+ *
15
+ * 2. **Post-spawn health check** (pln#496 step stp_e2b4429c). The dispatcher
16
+ * facade returns `delivered_and_started` as soon as the spawn fires, even
17
+ * when the process dies milliseconds later or hangs without producing any
18
+ * life-sign. Callers have no way to distinguish "spawned, working" from
19
+ * "spawned, already dead". A 60s grace window followed by an evidence
20
+ * check tells callers when the spawn is unverified.
21
+ *
22
+ * Approach: lazy reconciliation, no daemon. A single `reconcileAgentRun()`
23
+ * function inspects evidence (process liveness, claim release, post-start
24
+ * commits on the worktree branch) and either transitions the run to a
25
+ * terminal state with `inferred=true` provenance or emits a synthetic
26
+ * `delivered_but_unverified` runtime event without changing run status.
27
+ *
28
+ * Callers integrate this at read paths so stale runs converge on access
29
+ * (bclaw_assignment_events, bclaw_loop intent=get) and the supervisor can
30
+ * also trigger it explicitly via `brainclaw doctor --dispatch` (separate
31
+ * step stp_8c072d75 — wired in later).
32
+ *
33
+ * @module
34
+ */
35
+ import { spawnSync } from 'node:child_process';
36
+ import { loadAgentRun, transitionAgentRun, listAgentRuns } from './agentruns.js';
37
+ import { loadClaim } from './claims.js';
38
+ import { loadAssignment } from './assignments.js';
39
+ import { createRuntimeEvent } from './events.js';
40
+ import { nowISO } from './ids.js';
41
+ // ── Constants ──────────────────────────────────────────────────────────────
42
+ /**
43
+ * Minimum age before a run is eligible for reconciliation. Below this, the
44
+ * worker has not been given a fair chance to emit its first life-sign.
45
+ * Default 60 000 ms = 60 s — matches the pln#496 spec.
46
+ */
47
+ export const DEFAULT_HEALTH_CHECK_GRACE_MS = 60_000;
48
+ /**
49
+ * Age past which a run with no evidence of life and a dead process is
50
+ * declared `failed` with `silent_termination_no_evidence`. Default 30 min.
51
+ */
52
+ export const DEFAULT_STALE_AFTER_MS = 30 * 60_000;
53
+ const TERMINAL_STATUSES = new Set([
54
+ 'completed', 'failed', 'cancelled', 'timed_out', 'interrupted',
55
+ ]);
56
+ // ── Process liveness ───────────────────────────────────────────────────────
57
+ /**
58
+ * Cross-platform "is this PID alive" check. `process.kill(pid, 0)` does NOT
59
+ * actually send a signal — it queries existence. ESRCH = dead, EPERM = alive
60
+ * but owned by another user (still counts as alive for our purposes).
61
+ *
62
+ * Returns undefined when no PID is tracked on the run, so the caller can
63
+ * distinguish "definitely dead" from "we have no way to know".
64
+ */
65
+ export function isProcessAlive(pid) {
66
+ if (pid === undefined || pid === 0)
67
+ return undefined;
68
+ try {
69
+ process.kill(pid, 0);
70
+ return true;
71
+ }
72
+ catch (err) {
73
+ if (typeof err === 'object' && err && 'code' in err) {
74
+ const code = err.code;
75
+ if (code === 'ESRCH')
76
+ return false;
77
+ if (code === 'EPERM')
78
+ return true;
79
+ }
80
+ return false;
81
+ }
82
+ }
83
+ // ── Evidence collection ────────────────────────────────────────────────────
84
+ function safeGit(args, cwd) {
85
+ try {
86
+ const result = spawnSync('git', args, { cwd, encoding: 'utf-8', windowsHide: true });
87
+ if (result.status !== 0)
88
+ return { ok: false, stdout: '' };
89
+ return { ok: true, stdout: (result.stdout ?? '').toString() };
90
+ }
91
+ catch {
92
+ return { ok: false, stdout: '' };
93
+ }
94
+ }
95
+ /**
96
+ * True when the worktree branch has at least one commit whose committer
97
+ * timestamp is at or after the run's start. A worker that committed its
98
+ * work — even without releasing the claim or emitting run_completed — leaves
99
+ * this evidence behind.
100
+ *
101
+ * Failure cases (returns false defensively, never throws):
102
+ * - run.worktree_path missing or no longer on disk
103
+ * - git not on PATH
104
+ * - branch detached / corrupt
105
+ */
106
+ function hasPostStartCommitEvidence(run) {
107
+ if (!run.worktree_path)
108
+ return false;
109
+ if (!run.started_at && !run.created_at)
110
+ return false;
111
+ const startISO = run.started_at ?? run.created_at;
112
+ // git log on HEAD since the start timestamp, format author + commit times.
113
+ // We intentionally check committer time (%ct) because rebases preserve
114
+ // author time but reset committer time to the rebase moment, and we want
115
+ // to count the actual operation that landed on the branch.
116
+ const result = safeGit(['log', `--since=${startISO}`, '-n', '1', '--format=%H'], run.worktree_path);
117
+ if (!result.ok)
118
+ return false;
119
+ return result.stdout.trim().length > 0;
120
+ }
121
+ /**
122
+ * Best-effort evidence collection. Each individual signal is wrapped in
123
+ * try/catch so partial failures (missing claim, dead worktree path) do not
124
+ * propagate as errors — they just contribute zero evidence.
125
+ *
126
+ * `cwd` MUST be threaded so loadClaim / loadAssignment look in the right
127
+ * brainclaw store. Tests that supplied cwd to reconcileAgentRun but not
128
+ * here saw all evidence as zero because the loaders defaulted to
129
+ * process.cwd().
130
+ */
131
+ export function collectEvidence(run, cwd, options) {
132
+ const now = options?.nowMs ?? Date.now();
133
+ const startISO = run.started_at ?? run.created_at;
134
+ const age_ms = now - new Date(startISO).getTime();
135
+ let has_post_start_commit = false;
136
+ try {
137
+ has_post_start_commit = hasPostStartCommitEvidence(run);
138
+ }
139
+ catch { /* defensive */ }
140
+ let claim_released = false;
141
+ try {
142
+ const claim = loadClaim(run.claim_id, cwd);
143
+ claim_released = claim?.status === 'released';
144
+ }
145
+ catch { /* defensive */ }
146
+ let assignment_completed = false;
147
+ try {
148
+ const assignment = loadAssignment(run.assignment_id, cwd);
149
+ assignment_completed = assignment?.status === 'completed';
150
+ }
151
+ catch { /* defensive */ }
152
+ const process_alive = isProcessAlive(run.pid);
153
+ return { age_ms, has_post_start_commit, claim_released, assignment_completed, process_alive };
154
+ }
155
+ function anyCompletionEvidence(evidence) {
156
+ return evidence.has_post_start_commit
157
+ || evidence.claim_released
158
+ || evidence.assignment_completed;
159
+ }
160
+ function describeEvidence(evidence) {
161
+ const reasons = [];
162
+ if (evidence.has_post_start_commit)
163
+ reasons.push('post-start commit on worktree branch');
164
+ if (evidence.claim_released)
165
+ reasons.push('claim released');
166
+ if (evidence.assignment_completed)
167
+ reasons.push('assignment marked completed');
168
+ if (reasons.length === 0) {
169
+ if (evidence.process_alive === false)
170
+ reasons.push('process dead, no completion signal');
171
+ else if (evidence.process_alive === true)
172
+ reasons.push('process still alive');
173
+ else
174
+ reasons.push('no PID tracked');
175
+ }
176
+ return reasons.join(' + ');
177
+ }
178
+ // ── Synthetic event for unverified spawns ──────────────────────────────────
179
+ function emitUnverifiedEvent(run, evidence, actor, cwd) {
180
+ try {
181
+ createRuntimeEvent({
182
+ agent: actor,
183
+ session_id: run.session_id,
184
+ event_type: 'run_running',
185
+ text: `Spawn unverified after ${Math.round(evidence.age_ms / 1000)}s — no life-sign detected (process_alive=${evidence.process_alive}, post_start_commit=${evidence.has_post_start_commit}, claim_released=${evidence.claim_released})`,
186
+ tags: ['agent-runtime', 'run', 'reconciler', 'health-check'],
187
+ assignment_id: run.assignment_id,
188
+ run_id: run.id,
189
+ claim_id: run.claim_id,
190
+ plan_id: run.plan_id,
191
+ sequence_id: run.sequence_id,
192
+ scope: run.scope,
193
+ transport: run.transport,
194
+ status: run.status,
195
+ status_reason: 'delivered_but_unverified',
196
+ related_paths: run.scope ? [run.scope] : [],
197
+ metadata: {
198
+ reconciler: true,
199
+ evidence_age_ms: evidence.age_ms,
200
+ protocol: 'brainclaw.agent_runtime.reconciler.v0',
201
+ },
202
+ }, cwd);
203
+ }
204
+ catch { /* best-effort */ }
205
+ }
206
+ // ── Public API ─────────────────────────────────────────────────────────────
207
+ /**
208
+ * Reconcile a single agent_run against runtime evidence.
209
+ *
210
+ * No-op for terminal statuses or runs younger than the grace window.
211
+ * For older runs:
212
+ * - If any completion evidence exists → transition to `completed` with
213
+ * `status_reason='inferred=true; …'`. This unblocks loops waiting on
214
+ * `run_completed` even when the worker forgot to call
215
+ * bclaw_assignment_update.
216
+ * - Else if process is provably dead and run is past the stale threshold
217
+ * → transition to `failed` with `status_reason='silent_termination_no_evidence'`.
218
+ * - Else if past grace but not yet stale, no evidence either way → emit a
219
+ * non-mutating `delivered_but_unverified` runtime event so callers can
220
+ * surface the uncertainty.
221
+ *
222
+ * The function is pure-evidence: it never inspects in-memory state of the
223
+ * dispatcher, so it can be called from any process / any session that has
224
+ * read access to the brainclaw store.
225
+ */
226
+ export function reconcileAgentRun(runId, cwd, options = {}) {
227
+ const run = loadAgentRun(runId, cwd);
228
+ if (!run) {
229
+ const evidence = {
230
+ age_ms: 0, has_post_start_commit: false, claim_released: false,
231
+ assignment_completed: false, process_alive: undefined,
232
+ };
233
+ return {
234
+ run_id: runId, action: 'no_op', reason: 'run not found', evidence,
235
+ previous_status: 'created', current_status: 'created',
236
+ };
237
+ }
238
+ const previous_status = run.status;
239
+ const evidence = collectEvidence(run, cwd, { nowMs: options.nowMs });
240
+ // Never touch terminal runs — they already converged.
241
+ if (TERMINAL_STATUSES.has(run.status)) {
242
+ return {
243
+ run_id: runId, action: 'no_op', reason: `run already terminal (${run.status})`,
244
+ evidence, previous_status, current_status: run.status,
245
+ };
246
+ }
247
+ const grace = options.healthCheckGraceMs ?? DEFAULT_HEALTH_CHECK_GRACE_MS;
248
+ const stale = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
249
+ const actor = options.actor ?? 'reconciler';
250
+ // Below grace window: too early to draw any conclusion. Caller should
251
+ // re-poll later.
252
+ if (evidence.age_ms < grace) {
253
+ return {
254
+ run_id: runId, action: 'no_op', reason: `under grace window (${grace}ms)`,
255
+ evidence, previous_status, current_status: run.status,
256
+ };
257
+ }
258
+ // Recovery: any completion evidence outside the grace window → infer success.
259
+ if (anyCompletionEvidence(evidence)) {
260
+ try {
261
+ transitionAgentRun(runId, 'completed', {
262
+ actor,
263
+ status_reason: `inferred=true; evidence: ${describeEvidence(evidence)}`,
264
+ }, cwd);
265
+ return {
266
+ run_id: runId, action: 'inferred_completed',
267
+ reason: `inferred=true; ${describeEvidence(evidence)}`,
268
+ evidence, previous_status, current_status: 'completed',
269
+ };
270
+ }
271
+ catch (err) {
272
+ // Transition might fail if the run is no longer in a transitionable
273
+ // state (race with explicit completion). Treat as no-op.
274
+ return {
275
+ run_id: runId, action: 'no_op',
276
+ reason: `transition rejected: ${err instanceof Error ? err.message : String(err)}`,
277
+ evidence, previous_status, current_status: run.status,
278
+ };
279
+ }
280
+ }
281
+ // Failure inference: stale + dead process + no evidence.
282
+ if (evidence.age_ms >= stale && evidence.process_alive === false) {
283
+ try {
284
+ transitionAgentRun(runId, 'failed', {
285
+ actor,
286
+ status_reason: 'silent_termination_no_evidence',
287
+ }, cwd);
288
+ return {
289
+ run_id: runId, action: 'inferred_failed',
290
+ reason: 'silent_termination_no_evidence',
291
+ evidence, previous_status, current_status: 'failed',
292
+ };
293
+ }
294
+ catch (err) {
295
+ return {
296
+ run_id: runId, action: 'no_op',
297
+ reason: `failure transition rejected: ${err instanceof Error ? err.message : String(err)}`,
298
+ evidence, previous_status, current_status: run.status,
299
+ };
300
+ }
301
+ }
302
+ // Health-check window: past grace, not yet stale, no evidence either way.
303
+ // Emit a non-mutating event so callers see the uncertainty without
304
+ // forcing a transition based on incomplete data.
305
+ emitUnverifiedEvent(run, evidence, actor, cwd);
306
+ return {
307
+ run_id: runId, action: 'health_check_unverified',
308
+ reason: `delivered_but_unverified (age=${Math.round(evidence.age_ms / 1000)}s, process_alive=${evidence.process_alive})`,
309
+ evidence, previous_status, current_status: run.status,
310
+ };
311
+ }
312
+ /**
313
+ * Reconcile every non-terminal agent_run matching `filter`. Useful for
314
+ * batch sweeps from `bclaw_assignment_events` or `brainclaw doctor --dispatch`.
315
+ * Errors per-run are isolated — one bad run does not abort the sweep.
316
+ */
317
+ export function reconcileAllOpenRuns(cwd, filter = {}, options = {}) {
318
+ const results = [];
319
+ // Run statuses we consider open / candidates for reconciliation.
320
+ const OPEN = ['created', 'launching', 'waiting_input', 'running', 'blocked'];
321
+ for (const status of OPEN) {
322
+ const runs = listAgentRuns(cwd, { ...filter, status });
323
+ for (const run of runs) {
324
+ try {
325
+ results.push(reconcileAgentRun(run.id, cwd, options));
326
+ }
327
+ catch {
328
+ results.push({
329
+ run_id: run.id, action: 'no_op', reason: 'reconcile threw — skipped',
330
+ evidence: { age_ms: 0, has_post_start_commit: false, claim_released: false, assignment_completed: false, process_alive: undefined },
331
+ previous_status: run.status, current_status: run.status,
332
+ });
333
+ }
334
+ }
335
+ }
336
+ return results;
337
+ }
338
+ // Re-export key helpers for tests.
339
+ export { TERMINAL_STATUSES };
340
+ export const __testing = {
341
+ describeEvidence,
342
+ anyCompletionEvidence,
343
+ };
344
+ void nowISO; // placeholder to keep import alive if a future refactor needs it
345
+ //# sourceMappingURL=agentrun-reconciler.js.map