@adhdev/daemon-core 0.9.82-rc.7 → 0.9.82-rc.70

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 (72) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
  3. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  4. package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
  5. package/dist/commands/router.d.ts +24 -0
  6. package/dist/config/mesh-config.d.ts +66 -1
  7. package/dist/git/git-commands.d.ts +1 -0
  8. package/dist/git/git-status.d.ts +5 -0
  9. package/dist/git/git-types.d.ts +10 -0
  10. package/dist/index.d.ts +13 -6
  11. package/dist/index.js +4619 -1143
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +4582 -1128
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/installer.d.ts +1 -4
  16. package/dist/launch.d.ts +1 -1
  17. package/dist/logging/async-batch-writer.d.ts +10 -0
  18. package/dist/mesh/beads-db.d.ts +18 -0
  19. package/dist/mesh/mesh-active-work.d.ts +48 -0
  20. package/dist/mesh/mesh-events.d.ts +28 -5
  21. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  22. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  23. package/dist/mesh/mesh-ledger.d.ts +38 -1
  24. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  25. package/dist/mesh/refine-config.d.ts +119 -0
  26. package/dist/providers/chat-message-normalization.d.ts +1 -0
  27. package/dist/providers/cli-provider-instance.d.ts +1 -0
  28. package/dist/repo-mesh-types.d.ts +160 -0
  29. package/dist/status/reporter.d.ts +2 -0
  30. package/package.json +3 -1
  31. package/src/boot/daemon-lifecycle.ts +4 -0
  32. package/src/cli-adapters/provider-cli-adapter.ts +91 -3
  33. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  34. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  35. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  36. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  37. package/src/cli-adapters/provider-cli-shared.ts +20 -10
  38. package/src/commands/handler.ts +8 -1
  39. package/src/commands/mesh-coordinator.ts +13 -143
  40. package/src/commands/router.ts +2452 -409
  41. package/src/config/chat-history.ts +9 -7
  42. package/src/config/mesh-config.ts +244 -1
  43. package/src/daemon/dev-cli-debug.ts +10 -1
  44. package/src/detection/ide-detector.ts +26 -16
  45. package/src/git/git-commands.ts +3 -3
  46. package/src/git/git-status.ts +97 -6
  47. package/src/git/git-summary.ts +3 -0
  48. package/src/git/git-types.ts +11 -0
  49. package/src/index.ts +39 -5
  50. package/src/installer.d.ts +1 -1
  51. package/src/installer.ts +8 -6
  52. package/src/launch.d.ts +1 -1
  53. package/src/launch.ts +37 -28
  54. package/src/logging/async-batch-writer.ts +55 -0
  55. package/src/logging/logger.ts +2 -1
  56. package/src/mesh/beads-db.ts +176 -0
  57. package/src/mesh/coordinator-prompt.ts +4 -2
  58. package/src/mesh/mesh-active-work.ts +205 -0
  59. package/src/mesh/mesh-events.ts +291 -38
  60. package/src/mesh/mesh-fast-forward.ts +430 -0
  61. package/src/mesh/mesh-host-ownership.ts +73 -0
  62. package/src/mesh/mesh-ledger.ts +138 -1
  63. package/src/mesh/mesh-work-queue.ts +199 -137
  64. package/src/mesh/refine-config.ts +306 -0
  65. package/src/providers/chat-message-normalization.ts +3 -1
  66. package/src/providers/cli-provider-instance.ts +66 -1
  67. package/src/providers/ide-provider-instance.ts +17 -3
  68. package/src/providers/provider-loader.ts +10 -4
  69. package/src/providers/version-archive.ts +38 -20
  70. package/src/repo-mesh-types.ts +174 -0
  71. package/src/status/reporter.ts +15 -0
  72. package/src/system/host-memory.ts +29 -12
@@ -38,10 +38,22 @@ import { createInteractionId, getRecentDebugTrace, recordDebugTrace } from '../l
38
38
  import { getSessionHostSurfaceKind, partitionSessionHostRecords } from '../session-host/runtime-surface.js';
39
39
  import { createHermesManualMeshCoordinatorSetup, resolveMeshCoordinatorSetup } from './mesh-coordinator.js';
40
40
  import { buildSessionEntries } from '../status/builders.js';
41
- import { handleMeshForwardEvent, drainPendingMeshCoordinatorEvents } from '../mesh/mesh-events.js';
41
+ import { handleMeshForwardEvent, drainPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent } from '../mesh/mesh-events.js';
42
+ import { buildMeshHostRequiredFailure, normalizeMeshDaemonRole, resolveMeshHostStatus } from '../mesh/mesh-host-ownership.js';
43
+ import { fastForwardMeshNode } from '../mesh/mesh-fast-forward.js';
44
+ import {
45
+ MESH_REFINE_CONFIG_LOCATIONS,
46
+ MESH_REFINE_CONFIG_SCHEMA,
47
+ loadMeshRefineConfig,
48
+ resolveMeshRefineValidationPlan,
49
+ suggestMeshRefineConfig,
50
+ validateMeshRefineConfig,
51
+ type MeshRefineValidationCommandPlan,
52
+ } from '../mesh/refine-config.js';
42
53
  import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
43
54
  import { getSessionCompletionMarker } from '../status/snapshot.js';
44
55
  import { execNpmCommandSync, resolveCurrentGlobalInstallSurface, spawnDetachedDaemonUpgradeHelper } from './upgrade-helper.js';
56
+ import { getMeshQueueRevision } from '../mesh/mesh-work-queue.js';
45
57
  import type { RepoMeshSessionCleanupMode } from '../repo-mesh-types.js';
46
58
  import { homedir } from 'os';
47
59
  import { join as pathJoin, resolve as pathResolve } from 'path';
@@ -114,14 +126,95 @@ function readBooleanValue(...values: unknown[]): boolean | undefined {
114
126
  return undefined;
115
127
  }
116
128
 
117
- function readGitSubmodules(value: unknown): GitSubmoduleStatus[] | undefined {
129
+ function summarizeRepoMeshDebugGit(git: unknown): Record<string, unknown> | null {
130
+ const record = readObjectRecord(git);
131
+ if (!Object.keys(record).length) return null;
132
+ const submodules = Array.isArray(record.submodules)
133
+ ? record.submodules.map((entry: any) => ({
134
+ path: readStringValue(entry?.path) ?? null,
135
+ commit: readStringValue(entry?.commit)?.slice(0, 12) ?? null,
136
+ dirty: readBooleanValue(entry?.dirty) ?? false,
137
+ outOfSync: readBooleanValue(entry?.outOfSync, entry?.out_of_sync) ?? false,
138
+ }))
139
+ : [];
140
+ return {
141
+ isGitRepo: readBooleanValue(record.isGitRepo),
142
+ workspace: readStringValue(record.workspace) ?? null,
143
+ repoRoot: readStringValue(record.repoRoot, record.repo_root) ?? null,
144
+ branch: readStringValue(record.branch) ?? null,
145
+ upstream: readStringValue(record.upstream) ?? null,
146
+ upstreamStatus: readStringValue(record.upstreamStatus, record.upstream_status) ?? null,
147
+ headCommit: readStringValue(record.headCommit, record.head_commit)?.slice(0, 12) ?? null,
148
+ ahead: readNumberValue(record.ahead) ?? null,
149
+ behind: readNumberValue(record.behind) ?? null,
150
+ dirtyCounts: {
151
+ staged: readNumberValue(record.staged) ?? 0,
152
+ modified: readNumberValue(record.modified) ?? 0,
153
+ untracked: readNumberValue(record.untracked) ?? 0,
154
+ deleted: readNumberValue(record.deleted) ?? 0,
155
+ renamed: readNumberValue(record.renamed) ?? 0,
156
+ },
157
+ lastCheckedAt: readNumberValue(record.lastCheckedAt, record.last_checked_at) ?? null,
158
+ submoduleCount: submodules.length,
159
+ submodules,
160
+ };
161
+ }
162
+
163
+ function summarizeRepoMeshStatusDebug(status: any): Record<string, unknown> {
164
+ const nodes = Array.isArray(status?.nodes) ? status.nodes : [];
165
+ return {
166
+ success: status?.success,
167
+ meshId: readStringValue(status?.meshId, status?.mesh_id) ?? null,
168
+ refreshedAt: readStringValue(status?.refreshedAt, status?.refreshed_at) ?? null,
169
+ sourceOfTruth: status?.sourceOfTruth ?? null,
170
+ branchConvergenceSummary: status?.branchConvergenceSummary ?? status?.branch_convergence_summary ?? null,
171
+ nodeCount: nodes.length,
172
+ nodes: nodes.map((node: any) => ({
173
+ nodeId: readStringValue(node?.nodeId, node?.id) ?? null,
174
+ daemonId: readStringValue(node?.daemonId, node?.daemon_id) ?? null,
175
+ workspace: readStringValue(node?.workspace, node?.git?.workspace) ?? null,
176
+ health: readStringValue(node?.health) ?? null,
177
+ machineStatus: readStringValue(node?.machineStatus, node?.machine_status) ?? null,
178
+ connection: node?.connection && typeof node.connection === 'object' ? {
179
+ state: readStringValue(node.connection.state) ?? null,
180
+ transport: readStringValue(node.connection.transport) ?? null,
181
+ source: readStringValue(node.connection.source) ?? null,
182
+ reported: readBooleanValue(node.connection.reported) ?? null,
183
+ } : null,
184
+ gitProbePending: node?.gitProbePending === true,
185
+ launchReady: node?.launchReady === true,
186
+ git: summarizeRepoMeshDebugGit(node?.git),
187
+ branchConvergence: node?.branchConvergence ?? node?.branch_convergence ?? null,
188
+ })),
189
+ };
190
+ }
191
+
192
+ function logRepoMeshStatusDebug(event: string, fields: Record<string, unknown>): void {
193
+ try {
194
+ LOG.info('MeshStatusDebug', `[RepoMeshStatusDebug] ${JSON.stringify({ event, ...fields })}`);
195
+ } catch {
196
+ LOG.info('MeshStatusDebug', `[RepoMeshStatusDebug] ${event}`);
197
+ }
198
+ }
199
+
200
+ function joinRepoPath(root: string | undefined, relativePath: string | undefined): string | undefined {
201
+ const normalizedRoot = typeof root === 'string' ? root.trim().replace(/[\\/]+$/, '') : '';
202
+ const normalizedPath = typeof relativePath === 'string' ? relativePath.trim() : '';
203
+ if (!normalizedPath) return undefined;
204
+ if (/^(?:[A-Za-z]:[\\/]|\/)/.test(normalizedPath)) return normalizedPath;
205
+ if (!normalizedRoot) return undefined;
206
+ return `${normalizedRoot}/${normalizedPath.replace(/^[\\/]+/, '')}`;
207
+ }
208
+
209
+ function readGitSubmodules(value: unknown, parentRepoRoot?: string): GitSubmoduleStatus[] | undefined {
118
210
  if (!Array.isArray(value)) return undefined;
119
211
  const submodules = value
120
212
  .map(entry => {
121
213
  const submodule = readObjectRecord(entry);
122
214
  const path = readStringValue(submodule.path);
123
215
  const commit = readStringValue(submodule.commit);
124
- const repoPath = readStringValue(submodule.repoPath, submodule.repo_root);
216
+ const repoPath = readStringValue(submodule.repoPath, submodule.repo_root)
217
+ ?? joinRepoPath(parentRepoRoot, path);
125
218
  if (!path || !commit || !repoPath) return null;
126
219
  return {
127
220
  path,
@@ -137,60 +230,11 @@ function readGitSubmodules(value: unknown): GitSubmoduleStatus[] | undefined {
137
230
  return submodules.length > 0 ? submodules : undefined;
138
231
  }
139
232
 
140
- function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | undefined {
141
- const cachedStatus = readObjectRecord(node?.cachedStatus);
142
- const cachedGit = readObjectRecord(cachedStatus.git);
143
- if (Object.keys(cachedGit).length) {
144
- const conflictFiles = Array.isArray(cachedGit.conflictFiles)
145
- ? cachedGit.conflictFiles.filter((value: unknown): value is string => typeof value === 'string')
146
- : [];
147
- const conflictCount = readNumberValue(cachedGit.conflicts) ?? conflictFiles.length;
148
- const hasConflicts = readBooleanValue(cachedGit.hasConflicts) ?? conflictCount > 0;
149
- const isGitRepo = readBooleanValue(cachedGit.isGitRepo);
150
- if (isGitRepo !== undefined) {
151
- const submodules = readGitSubmodules(cachedGit.submodules);
152
- return {
153
- workspace: readStringValue(cachedGit.workspace, node?.workspace) || '',
154
- repoRoot: readStringValue(cachedGit.repoRoot, node?.repoRoot, node?.workspace) || null,
155
- isGitRepo,
156
- branch: readStringValue(cachedGit.branch) ?? null,
157
- headCommit: readStringValue(cachedGit.headCommit) ?? null,
158
- headMessage: readStringValue(cachedGit.headMessage) ?? null,
159
- upstream: readStringValue(cachedGit.upstream) ?? null,
160
- ahead: readNumberValue(cachedGit.ahead) ?? 0,
161
- behind: readNumberValue(cachedGit.behind) ?? 0,
162
- staged: readNumberValue(cachedGit.staged) ?? 0,
163
- modified: readNumberValue(cachedGit.modified) ?? 0,
164
- untracked: readNumberValue(cachedGit.untracked) ?? 0,
165
- deleted: readNumberValue(cachedGit.deleted) ?? 0,
166
- renamed: readNumberValue(cachedGit.renamed) ?? 0,
167
- hasConflicts,
168
- conflictFiles,
169
- stashCount: readNumberValue(cachedGit.stashCount) ?? 0,
170
- lastCheckedAt: readNumberValue(cachedGit.lastCheckedAt) ?? Date.now(),
171
- ...(submodules ? { submodules } : {}),
172
- };
173
- }
174
- }
175
-
176
- const rawGit = readObjectRecord(node?.lastGit ?? node?.last_git);
177
- const gitResult = readObjectRecord(rawGit.result);
178
- const directStatus = readObjectRecord(rawGit.status);
179
- const nestedStatus = readObjectRecord(gitResult.status);
180
- const rawProbe = readObjectRecord(node?.lastProbe ?? node?.last_probe);
181
- const probeGit = readObjectRecord(rawProbe.git);
182
- const probeGitResult = readObjectRecord(probeGit.result);
183
- const probeDirectStatus = readObjectRecord(probeGit.status);
184
- const probeNestedStatus = readObjectRecord(probeGitResult.status);
185
- const status = Object.keys(directStatus).length
186
- ? directStatus
187
- : Object.keys(nestedStatus).length
188
- ? nestedStatus
189
- : Object.keys(probeDirectStatus).length
190
- ? probeDirectStatus
191
- : Object.keys(probeNestedStatus).length
192
- ? probeNestedStatus
193
- : {};
233
+ function normalizeInlineMeshGitStatus(
234
+ status: Record<string, unknown>,
235
+ node: any,
236
+ options?: { lastCheckedAt?: number },
237
+ ): Record<string, unknown> | undefined {
194
238
  const isGitRepo = readBooleanValue(status.isGitRepo);
195
239
  if (!Object.keys(status).length || isGitRepo === undefined) return undefined;
196
240
  const conflictFiles = Array.isArray(status.conflictFiles)
@@ -198,15 +242,20 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
198
242
  : [];
199
243
  const conflictCount = readNumberValue(status.conflicts) ?? conflictFiles.length;
200
244
  const hasConflicts = readBooleanValue(status.hasConflicts) ?? conflictCount > 0;
201
- const submodules = readGitSubmodules(status.submodules);
245
+ const repoRoot = readStringValue(status.repoRoot, status.repo_root, node?.repoRoot, node?.repo_root, status.workspace, node?.workspace) || undefined;
246
+ const submodules = readGitSubmodules(status.submodules, repoRoot);
202
247
  return {
203
248
  workspace: readStringValue(status.workspace, node?.workspace) || '',
204
- repoRoot: readStringValue(status.repoRoot, node?.repoRoot, node?.workspace) || null,
249
+ repoRoot: repoRoot ?? null,
205
250
  isGitRepo,
206
251
  branch: readStringValue(status.branch) ?? null,
207
252
  headCommit: readStringValue(status.headCommit) ?? null,
208
253
  headMessage: readStringValue(status.headMessage) ?? null,
209
254
  upstream: readStringValue(status.upstream) ?? null,
255
+ upstreamStatus: readStringValue(status.upstreamStatus, status.upstream_status)
256
+ ?? (readStringValue(status.upstream) ? 'unchecked' : 'no_upstream'),
257
+ upstreamFetchedAt: readNumberValue(status.upstreamFetchedAt, status.upstream_fetched_at),
258
+ upstreamFetchError: readStringValue(status.upstreamFetchError, status.upstream_fetch_error),
210
259
  ahead: readNumberValue(status.ahead) ?? 0,
211
260
  behind: readNumberValue(status.behind) ?? 0,
212
261
  staged: readNumberValue(status.staged) ?? 0,
@@ -217,14 +266,247 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
217
266
  hasConflicts,
218
267
  conflictFiles,
219
268
  stashCount: readNumberValue(status.stashCount) ?? 0,
220
- lastCheckedAt: Date.now(),
269
+ lastCheckedAt: options?.lastCheckedAt ?? readNumberValue(status.lastCheckedAt) ?? Date.now(),
221
270
  ...(submodules ? { submodules } : {}),
222
271
  };
223
272
  }
224
273
 
274
+ function scoreInlineMeshGitStatus(git: Record<string, unknown> | undefined): number {
275
+ if (!git) return Number.NEGATIVE_INFINITY;
276
+ let score = 0;
277
+ if (readBooleanValue(git.isGitRepo) === true) score += 50;
278
+ if (readBooleanValue(git.isGitRepo) === false) score -= 10;
279
+ if (readStringValue(git.branch)) score += 20;
280
+ if (readStringValue(git.headCommit)) score += 20;
281
+ if (readStringValue(git.upstream)) score += 10;
282
+ if (readStringValue(git.upstreamStatus)) score += 5;
283
+ if (readNumberValue(git.ahead) !== undefined) score += 2;
284
+ if (readNumberValue(git.behind) !== undefined) score += 2;
285
+ if (Array.isArray(git.submodules) && git.submodules.length > 0) score += 4 + git.submodules.length;
286
+ if (readStringValue(git.error)) score -= 20;
287
+ return score;
288
+ }
289
+
290
+ function buildInlineMeshTransitGitStatus(node: any): Record<string, unknown> | undefined {
291
+ const rawGit = readObjectRecord(node?.lastGit ?? node?.last_git);
292
+ const gitResult = readObjectRecord(rawGit.result);
293
+ const directStatus = readObjectRecord(rawGit.status);
294
+ const nestedStatus = readObjectRecord(gitResult.status);
295
+ const rawProbe = readObjectRecord(node?.lastProbe ?? node?.last_probe);
296
+ const probeGit = readObjectRecord(rawProbe.git);
297
+ const probeGitResult = readObjectRecord(probeGit.result);
298
+ const probeDirectStatus = readObjectRecord(probeGit.status);
299
+ const probeNestedStatus = readObjectRecord(probeGitResult.status);
300
+ const candidates = [directStatus, nestedStatus, probeDirectStatus, probeNestedStatus];
301
+ let best: { git: Record<string, unknown>; score: number } | null = null;
302
+ for (const status of candidates) {
303
+ const normalized = normalizeInlineMeshGitStatus(status, node, { lastCheckedAt: Date.now() });
304
+ if (!normalized) continue;
305
+ const score = scoreInlineMeshGitStatus(normalized);
306
+ if (!best || score > best.score) best = { git: normalized, score };
307
+ }
308
+ return best?.git;
309
+ }
310
+
311
+ function shouldRefreshStalePendingAggregate(snapshot: any, options?: { requireDirectPeerTruth?: boolean }): boolean {
312
+ if (options?.requireDirectPeerTruth !== true || !Array.isArray(snapshot?.nodes)) return false;
313
+ return snapshot.nodes.some((node: any) => {
314
+ if (node?.gitProbePending !== true) return false;
315
+ const git = readObjectRecord(node?.git);
316
+ return !readBooleanValue(git.isGitRepo) && !readStringValue(git.branch, git.headCommit, git.upstream);
317
+ });
318
+ }
319
+
320
+ function buildLivePeerGitConnection(connection: Record<string, unknown>, timestamp = new Date().toISOString()): Record<string, unknown> {
321
+ const source = readStringValue(connection.source);
322
+ const transport = readStringValue(connection.transport);
323
+ return {
324
+ ...connection,
325
+ perspective: readStringValue(connection.perspective) ?? 'selected_coordinator',
326
+ source: source && source !== 'not_reported' ? source : 'mesh_peer_status',
327
+ state: 'connected',
328
+ transport: transport && transport !== 'unknown' ? transport : 'direct',
329
+ reported: true,
330
+ reason: 'Live peer git snapshot reported by the selected coordinator.',
331
+ lastStateChangeAt: readStringValue(connection.lastStateChangeAt) ?? timestamp,
332
+ };
333
+ }
334
+
335
+ function recordInlineMeshDirectGitTruth(
336
+ node: any,
337
+ git: Record<string, unknown>,
338
+ source: 'selected_coordinator_local_git' | 'selected_coordinator_mesh_p2p_git',
339
+ ): void {
340
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return;
341
+ const checkedAt = readNumberValue(git.lastCheckedAt) ?? Date.now();
342
+ const updatedAt = new Date(checkedAt).toISOString();
343
+ const nextGit: Record<string, unknown> = {
344
+ ...git,
345
+ lastCheckedAt: checkedAt,
346
+ };
347
+ node.lastGit = {
348
+ source,
349
+ checkedAt,
350
+ status: nextGit,
351
+ };
352
+ node.last_git = node.lastGit;
353
+ node.machineStatus = 'online';
354
+ node.updatedAt = updatedAt;
355
+ node.lastSeenAt = updatedAt;
356
+ const repoRoot = readStringValue(nextGit.repoRoot);
357
+ if (repoRoot && !readStringValue(node.repoRoot)) node.repoRoot = repoRoot;
358
+ }
359
+
360
+ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | undefined {
361
+ const liveGit = buildInlineMeshTransitGitStatus(node);
362
+ if (liveGit) return liveGit;
363
+
364
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
365
+ const cachedGit = readObjectRecord(cachedStatus.git);
366
+ if (!Object.keys(cachedGit).length) return undefined;
367
+ return normalizeInlineMeshGitStatus(cachedGit, node);
368
+ }
369
+
370
+ function shouldDiscardCachedInlineMeshStatus(node: any): boolean {
371
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
372
+ if (!Object.keys(cachedStatus).length) return false;
373
+ const cachedGit = readObjectRecord(cachedStatus.git);
374
+ const workspaceError = readStringValue(cachedStatus.error, node?.error);
375
+ if (workspaceError && /workspace must be an existing directory/i.test(workspaceError)) return true;
376
+ const isGitRepo = readBooleanValue(cachedGit.isGitRepo);
377
+ const branch = readStringValue(cachedGit.branch);
378
+ const headCommit = readStringValue(cachedGit.headCommit);
379
+ return isGitRepo === false && !branch && !headCommit;
380
+ }
381
+
382
+ function stripInlineMeshTransientNodeState(node: any): any {
383
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return node;
384
+ const {
385
+ cachedStatus,
386
+ lastGit: _lastGit,
387
+ last_git: _lastGitLegacy,
388
+ lastProbe: _lastProbe,
389
+ last_probe: _lastProbeLegacy,
390
+ error: _error,
391
+ health: _health,
392
+ machineStatus: _machineStatus,
393
+ lastSeenAt: _lastSeenAt,
394
+ last_seen_at: _lastSeenAtLegacy,
395
+ updatedAt: _updatedAt,
396
+ updated_at: _updatedAtLegacy,
397
+ activeSession: _activeSession,
398
+ active_session: _activeSessionLegacy,
399
+ activeSessionId: _activeSessionId,
400
+ active_session_id: _activeSessionIdLegacy,
401
+ sessionId: _sessionId,
402
+ session_id: _sessionIdLegacy,
403
+ providerType: _providerType,
404
+ provider_type: _providerTypeLegacy,
405
+ ...rest
406
+ } = node as Record<string, unknown>;
407
+ if (cachedStatus && !shouldDiscardCachedInlineMeshStatus(node)) {
408
+ return { ...rest, cachedStatus };
409
+ }
410
+ return rest;
411
+ }
412
+
413
+ function hasInlineMeshTransientNodeState(node: any): boolean {
414
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return false;
415
+ return 'cachedStatus' in node
416
+ || 'lastGit' in node
417
+ || 'last_git' in node
418
+ || 'lastProbe' in node
419
+ || 'last_probe' in node
420
+ || 'error' in node
421
+ || 'health' in node
422
+ || 'machineStatus' in node
423
+ || 'lastSeenAt' in node
424
+ || 'last_seen_at' in node
425
+ || 'updatedAt' in node
426
+ || 'updated_at' in node
427
+ || 'activeSession' in node
428
+ || 'active_session' in node
429
+ || 'activeSessionId' in node
430
+ || 'active_session_id' in node
431
+ || 'sessionId' in node
432
+ || 'session_id' in node
433
+ || 'providerType' in node
434
+ || 'provider_type' in node;
435
+ }
436
+
437
+ function inlineMeshCarriesTransientNodeTruth(inlineMesh: any): boolean {
438
+ if (!inlineMesh || typeof inlineMesh !== 'object' || Array.isArray(inlineMesh)) return false;
439
+ if (!Array.isArray(inlineMesh.nodes) || inlineMesh.nodes.length === 0) return false;
440
+ return inlineMesh.nodes.some((node: any) => hasInlineMeshTransientNodeState(node));
441
+ }
442
+
443
+ function readInlineMeshNodeId(node: any): string {
444
+ return readStringValue(node?.id, node?.nodeId) || '';
445
+ }
446
+
447
+ function sanitizeInlineMesh(inlineMesh: any): any {
448
+ if (!inlineMesh || typeof inlineMesh !== 'object' || Array.isArray(inlineMesh)) return inlineMesh;
449
+ if (!Array.isArray(inlineMesh.nodes)) return inlineMesh;
450
+ let changed = false;
451
+ const nodes = inlineMesh.nodes.map((node: any) => {
452
+ if (!hasInlineMeshTransientNodeState(node)) return node;
453
+ changed = true;
454
+ return stripInlineMeshTransientNodeState(node);
455
+ });
456
+ if (!changed) return inlineMesh;
457
+ return {
458
+ ...inlineMesh,
459
+ nodes,
460
+ };
461
+ }
462
+
463
+ function reconcileInlineMeshCache(cached: any, incoming: any): any {
464
+ if (!cached || typeof cached !== 'object' || Array.isArray(cached)) return incoming;
465
+ if (!incoming || typeof incoming !== 'object' || Array.isArray(incoming)) return cached;
466
+ const cachedNodes = Array.isArray(cached.nodes) ? cached.nodes : [];
467
+ const incomingNodes = Array.isArray(incoming.nodes) ? incoming.nodes : [];
468
+ if (!cachedNodes.length || !incomingNodes.length) return { ...cached, ...incoming };
469
+
470
+ const cachedUpdatedAt = Date.parse(readStringValue(cached.updatedAt, cached.updated_at) || '');
471
+ const incomingUpdatedAt = Date.parse(readStringValue(incoming.updatedAt, incoming.updated_at) || '');
472
+ const preserveCachedMembership = Number.isFinite(cachedUpdatedAt)
473
+ && (!Number.isFinite(incomingUpdatedAt) || cachedUpdatedAt > incomingUpdatedAt);
474
+
475
+ const cachedById = new Map<string, any>();
476
+ for (const node of cachedNodes) {
477
+ const nodeId = readInlineMeshNodeId(node);
478
+ if (nodeId) cachedById.set(nodeId, node);
479
+ }
480
+
481
+ const nodes = incomingNodes.map((incomingNode: any) => {
482
+ const nodeId = readInlineMeshNodeId(incomingNode);
483
+ const cachedNode = nodeId ? cachedById.get(nodeId) : undefined;
484
+ if (!cachedNode && preserveCachedMembership) return null;
485
+ if (!cachedNode) return incomingNode;
486
+ if (hasInlineMeshTransientNodeState(incomingNode)) {
487
+ return { ...cachedNode, ...incomingNode };
488
+ }
489
+ return { ...stripInlineMeshTransientNodeState(cachedNode), ...incomingNode };
490
+ }).filter(Boolean);
491
+
492
+ return {
493
+ ...cached,
494
+ ...incoming,
495
+ nodes,
496
+ };
497
+ }
498
+
225
499
  function hasGitWorktreeChanges(git: Record<string, unknown> | null | undefined): boolean {
226
- if (!git) return false;
227
- return Number(git.staged || 0) + Number(git.modified || 0) + Number(git.untracked || 0) + Number(git.deleted || 0) + Number(git.renamed || 0) > 0;
500
+ return countGitWorktreeChanges(git) > 0;
501
+ }
502
+
503
+ function countGitWorktreeChanges(git: Record<string, unknown> | null | undefined): number {
504
+ if (!git) return 0;
505
+ return Number(git.staged || 0)
506
+ + Number(git.modified || 0)
507
+ + Number(git.untracked || 0)
508
+ + Number(git.deleted || 0)
509
+ + Number(git.renamed || 0);
228
510
  }
229
511
 
230
512
  function getGitSubmoduleDriftState(git: Record<string, unknown> | null | undefined): { dirty: boolean; outOfSync: boolean } {
@@ -249,6 +531,167 @@ function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undef
249
531
  return 'online';
250
532
  }
251
533
 
534
+ function readMeshNodeLabel(status: Record<string, unknown>, node: any): string {
535
+ return readStringValue(status.nodeId, node?.id, node?.nodeId) ?? 'unknown';
536
+ }
537
+
538
+ function buildInlineMeshBranchConvergence(args: {
539
+ mesh: any;
540
+ node: any;
541
+ status: Record<string, unknown>;
542
+ }): Record<string, unknown> {
543
+ const git = readObjectRecord(args.status.git);
544
+ const nodeLabel = readMeshNodeLabel(args.status, args.node);
545
+ const defaultBranch = readStringValue(args.mesh?.defaultBranch) ?? 'main';
546
+ const branch = readStringValue(git.branch, args.node?.worktreeBranch) ?? null;
547
+ const upstream = readStringValue(git.upstream) ?? null;
548
+ const upstreamStatus = readStringValue(git.upstreamStatus, git.upstream_status)
549
+ ?? (upstream ? 'unchecked' : 'no_upstream');
550
+ const ahead = readNumberValue(git.ahead) ?? 0;
551
+ const behind = readNumberValue(git.behind) ?? 0;
552
+ const uncommittedChanges = countGitWorktreeChanges(git);
553
+ const hasConflicts = readBooleanValue(git.hasConflicts)
554
+ ?? (Array.isArray(git.conflictFiles) && git.conflictFiles.length > 0);
555
+ const base = {
556
+ defaultBranch,
557
+ branch,
558
+ upstream,
559
+ upstreamStatus,
560
+ ahead,
561
+ behind,
562
+ isWorktree: args.node?.isLocalWorktree === true || args.status.isLocalWorktree === true,
563
+ isDefaultBranch: branch === defaultBranch,
564
+ };
565
+
566
+ if (readBooleanValue(git.isGitRepo) !== true) {
567
+ return {
568
+ ...base,
569
+ status: 'blocked_review',
570
+ needsConvergence: true,
571
+ reason: 'git_status_unavailable',
572
+ nextStep: `Resolve git status for node '${nodeLabel}' before marking the task complete.`,
573
+ };
574
+ }
575
+
576
+ if (!branch) {
577
+ return {
578
+ ...base,
579
+ status: 'blocked_review',
580
+ needsConvergence: true,
581
+ reason: 'branch_unknown',
582
+ nextStep: `Inspect node '${nodeLabel}' git branch before deciding whether it is merged to ${defaultBranch}.`,
583
+ };
584
+ }
585
+
586
+ if (hasConflicts || uncommittedChanges > 0) {
587
+ return {
588
+ ...base,
589
+ status: 'not_mergeable',
590
+ needsConvergence: true,
591
+ reason: hasConflicts ? 'conflicts_present' : 'dirty_workspace',
592
+ nextStep: `Commit, checkpoint, or resolve node '${nodeLabel}' before any main convergence step.`,
593
+ };
594
+ }
595
+
596
+ if (branch === defaultBranch) {
597
+ if (upstream && upstreamStatus !== 'fresh') {
598
+ return {
599
+ ...base,
600
+ status: 'blocked_review',
601
+ needsConvergence: true,
602
+ reason: 'default_branch_upstream_unverified',
603
+ nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${nodeLabel}'.`,
604
+ };
605
+ }
606
+ if (ahead > 0 || behind > 0) {
607
+ return {
608
+ ...base,
609
+ status: 'blocked_review',
610
+ needsConvergence: true,
611
+ reason: 'default_branch_not_even_with_upstream',
612
+ nextStep: `Bring ${defaultBranch} even with its upstream before declaring convergence complete.`,
613
+ };
614
+ }
615
+ return {
616
+ ...base,
617
+ status: 'merged_to_main',
618
+ needsConvergence: false,
619
+ reason: 'clean_default_branch',
620
+ nextStep: null,
621
+ };
622
+ }
623
+
624
+ if (args.node?.isLocalWorktree === true || args.status.isLocalWorktree === true) {
625
+ return {
626
+ ...base,
627
+ status: 'cleanup_candidate',
628
+ needsConvergence: true,
629
+ reason: 'clean_non_default_worktree_branch',
630
+ nextStep: `Run mesh_refine_node(node_id: "${nodeLabel}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`,
631
+ };
632
+ }
633
+
634
+ if (upstream && upstreamStatus !== 'fresh') {
635
+ return {
636
+ ...base,
637
+ status: 'blocked_review',
638
+ needsConvergence: true,
639
+ reason: 'feature_branch_upstream_unverified',
640
+ nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`,
641
+ };
642
+ }
643
+
644
+ if (!upstream || ahead > 0 || behind > 0) {
645
+ return {
646
+ ...base,
647
+ status: 'blocked_review',
648
+ needsConvergence: true,
649
+ reason: !upstream ? 'feature_branch_missing_upstream' : 'feature_branch_not_even_with_upstream',
650
+ nextStep: `Push or reconcile branch '${branch}', then merge it into ${defaultBranch} or mark it not_mergeable with a reason.`,
651
+ };
652
+ }
653
+
654
+ return {
655
+ ...base,
656
+ status: 'pushed_feature_branch_needs_merge',
657
+ needsConvergence: true,
658
+ reason: 'clean_non_default_branch',
659
+ nextStep: `Review and merge branch '${branch}' into ${defaultBranch}; do not report the task as fully complete while it remains off main.`,
660
+ };
661
+ }
662
+
663
+ function applyInlineMeshBranchConvergence(mesh: any, node: any, status: Record<string, unknown>): void {
664
+ const git = readObjectRecord(status.git);
665
+ if (Object.keys(git).length === 0 && !status.gitProbePending) return;
666
+ const uncommittedChanges = countGitWorktreeChanges(git);
667
+ status.isDirty = uncommittedChanges > 0;
668
+ status.uncommittedChanges = uncommittedChanges;
669
+ status.branchConvergence = buildInlineMeshBranchConvergence({ mesh, node, status });
670
+ }
671
+
672
+ function summarizeInlineMeshBranchConvergence(nodes: Array<Record<string, unknown>>): Record<string, unknown> {
673
+ const followUps = nodes
674
+ .filter(node => readObjectRecord(node.branchConvergence).needsConvergence === true)
675
+ .map(node => {
676
+ const convergence = readObjectRecord(node.branchConvergence);
677
+ return {
678
+ nodeId: node.nodeId,
679
+ workspace: node.workspace,
680
+ branch: convergence.branch,
681
+ status: convergence.status,
682
+ reason: convergence.reason,
683
+ nextStep: convergence.nextStep,
684
+ };
685
+ });
686
+
687
+ return {
688
+ needsFollowUp: followUps.length > 0,
689
+ unresolvedCount: followUps.length,
690
+ requiredFinalStates: ['merged_to_main', 'pushed_feature_branch_needs_merge', 'blocked_review', 'cleanup_candidate', 'not_mergeable'],
691
+ followUps,
692
+ };
693
+ }
694
+
252
695
  function readCachedInlineMeshActiveSessions(node: any): string[] {
253
696
  const cachedStatus = readObjectRecord(node?.cachedStatus);
254
697
  const activeSession = readObjectRecord(cachedStatus.activeSession);
@@ -259,17 +702,313 @@ function readCachedInlineMeshActiveSessions(node: any): string[] {
259
702
  return sessionId ? [sessionId] : [];
260
703
  }
261
704
 
262
- function applyCachedInlineMeshNodeStatus(status: Record<string, unknown>, node: any): boolean {
705
+ function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<string, unknown>> {
706
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
707
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
708
+ const fallbackSession = Object.keys(activeSession).length
709
+ ? activeSession
710
+ : readObjectRecord(node?.activeSession ?? node?.active_session);
711
+ const sessionId = readStringValue(
712
+ fallbackSession.id,
713
+ fallbackSession.sessionId,
714
+ fallbackSession.session_id,
715
+ node?.activeSessionId,
716
+ node?.active_session_id,
717
+ node?.sessionId,
718
+ node?.session_id,
719
+ );
720
+ if (!sessionId) return [];
721
+ return [{
722
+ sessionId,
723
+ providerType: readStringValue(
724
+ fallbackSession.providerType,
725
+ fallbackSession.provider_type,
726
+ fallbackSession.cliType,
727
+ fallbackSession.cli_type,
728
+ fallbackSession.provider,
729
+ node?.providerType,
730
+ node?.provider_type,
731
+ ),
732
+ state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
733
+ lifecycle: readStringValue(fallbackSession.lifecycle),
734
+ title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
735
+ workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
736
+ lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
737
+ recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
738
+ isCached: true,
739
+ }];
740
+ }
741
+
742
+ function readLiveMeshSessionState(record: any): string | undefined {
743
+ return readStringValue(
744
+ record?.meta?.sessionStatus,
745
+ record?.meta?.status,
746
+ record?.meta?.providerStatus,
747
+ record?.status,
748
+ record?.state,
749
+ record?.lifecycle,
750
+ );
751
+ }
752
+
753
+ function toIsoTimestamp(value: unknown): string | null {
754
+ if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString();
755
+ const stringValue = readStringValue(value);
756
+ return stringValue || null;
757
+ }
758
+
759
+ function synthesizeMeshNodeFreshnessFromConnection(status: Record<string, unknown>): void {
760
+ const connection = readObjectRecord(status.connection);
761
+ const connectionFreshAt = toIsoTimestamp(connection.lastCommandAt ?? connection.lastConnectedAt ?? connection.lastStateChangeAt);
762
+ const git = readObjectRecord(status.git);
763
+ const gitCheckedAt = toIsoTimestamp(git.lastCheckedAt);
764
+ if (!status.lastSeenAt && connectionFreshAt) status.lastSeenAt = connectionFreshAt;
765
+ if (!status.updatedAt && (gitCheckedAt || connectionFreshAt)) {
766
+ status.updatedAt = gitCheckedAt ?? connectionFreshAt;
767
+ }
768
+ }
769
+
770
+ function finalizeMeshNodeStatus(args: {
771
+ status: Record<string, unknown>;
772
+ node: any;
773
+ daemonId?: string;
774
+ isSelfNode: boolean;
775
+ }): void {
776
+ const { status, node, daemonId, isSelfNode } = args;
777
+ if (!readStringValue(status.machineStatus)) {
778
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
779
+ const machineStatus = readStringValue(cachedStatus.machineStatus, cachedStatus.machine_status, node?.machineStatus);
780
+ if (machineStatus) status.machineStatus = machineStatus;
781
+ }
782
+ synthesizeMeshNodeFreshnessFromConnection(status);
783
+ const connectionState = readStringValue(readObjectRecord(status.connection).state);
784
+ status.launchReady = !!daemonId && (
785
+ readStringValue(status.machineStatus) === 'online'
786
+ || connectionState === 'connected'
787
+ || isSelfNode
788
+ );
789
+ }
790
+
791
+ async function probeRemoteMeshGitStatus(args: {
792
+ dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
793
+ daemonId: string;
794
+ workspace: string;
795
+ timeoutMs: number;
796
+ }): Promise<Record<string, unknown> | null> {
797
+ if (!args.dispatchMeshCommand) return null;
798
+ const remoteResult = await Promise.race([
799
+ args.dispatchMeshCommand(args.daemonId, 'git_status', { workspace: args.workspace, refreshUpstream: true }),
800
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), args.timeoutMs)),
801
+ ]) as any;
802
+ const remoteGit = remoteResult?.status ?? remoteResult?.git ?? remoteResult;
803
+ return remoteGit && typeof remoteGit === 'object' && typeof remoteGit.isGitRepo === 'boolean'
804
+ ? remoteGit as Record<string, unknown>
805
+ : null;
806
+ }
807
+
808
+ async function hydrateInlineMeshDirectTruth(args: {
809
+ mesh: any;
810
+ meshSource: 'inline_cache' | 'inline_bootstrap' | 'local_config';
811
+ dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
812
+ statusInstanceId?: string;
813
+ localMachineId?: string;
814
+ }): Promise<{
815
+ directEvidenceCount: number;
816
+ localConfirmedCount: number;
817
+ peerAttemptedCount: number;
818
+ peerConfirmedCount: number;
819
+ unavailableNodeIds: string[];
820
+ }> {
821
+ const nodes = Array.isArray(args.mesh?.nodes) ? args.mesh.nodes : [];
822
+ if (!nodes.length) {
823
+ return {
824
+ directEvidenceCount: 0,
825
+ localConfirmedCount: 0,
826
+ peerAttemptedCount: 0,
827
+ peerConfirmedCount: 0,
828
+ unavailableNodeIds: [],
829
+ };
830
+ }
831
+
832
+ const selectedCoordinatorNodeId = readStringValue(
833
+ args.mesh?.coordinator?.preferredNodeId,
834
+ nodes[0]?.id,
835
+ nodes[0]?.nodeId,
836
+ );
837
+
838
+ let localConfirmedCount = 0;
839
+ let peerAttemptedCount = 0;
840
+ let peerConfirmedCount = 0;
841
+ const unavailableNodeIds: string[] = [];
842
+
843
+ for (const [nodeIndex, node] of nodes.entries()) {
844
+ const nodeId = readStringValue(node?.id, node?.nodeId) || `node_${nodeIndex}`;
845
+ const workspace = readStringValue(node?.workspace);
846
+ const daemonId = readStringValue(node?.daemonId);
847
+ const isSelfNode = Boolean(
848
+ nodeId && selectedCoordinatorNodeId && nodeId === selectedCoordinatorNodeId,
849
+ ) || Boolean(
850
+ daemonId && (daemonId === args.localMachineId || daemonId === args.statusInstanceId),
851
+ ) || Boolean(args.meshSource !== 'local_config' && nodeIndex === 0);
852
+
853
+ if (!workspace) {
854
+ if (!isSelfNode && daemonId) unavailableNodeIds.push(nodeId);
855
+ continue;
856
+ }
857
+
858
+ if (fs.existsSync(workspace)) {
859
+ try {
860
+ const localGit = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
861
+ if (localGit?.isGitRepo) {
862
+ recordInlineMeshDirectGitTruth(node, localGit as unknown as Record<string, unknown>, 'selected_coordinator_local_git');
863
+ localConfirmedCount += 1;
864
+ continue;
865
+ }
866
+ } catch {
867
+ // Fall through to remote classification.
868
+ }
869
+ }
870
+
871
+ if (!daemonId || !args.dispatchMeshCommand) {
872
+ if (!isSelfNode) unavailableNodeIds.push(nodeId);
873
+ continue;
874
+ }
875
+
876
+ peerAttemptedCount += 1;
877
+ try {
878
+ const remoteGit = await probeRemoteMeshGitStatus({
879
+ dispatchMeshCommand: args.dispatchMeshCommand,
880
+ daemonId,
881
+ workspace,
882
+ timeoutMs: 8_000,
883
+ });
884
+ if (remoteGit) {
885
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
886
+ peerConfirmedCount += 1;
887
+ continue;
888
+ }
889
+ } catch {
890
+ // Strict direct-only path: do not fall back to persisted cloud truth here.
891
+ }
892
+
893
+ unavailableNodeIds.push(nodeId);
894
+ }
895
+
896
+ return {
897
+ directEvidenceCount: localConfirmedCount + peerConfirmedCount,
898
+ localConfirmedCount,
899
+ peerAttemptedCount,
900
+ peerConfirmedCount,
901
+ unavailableNodeIds,
902
+ };
903
+ }
904
+
905
+ function summarizeMeshSessionRecord(record: any): Record<string, unknown> {
906
+ return {
907
+ sessionId: readStringValue(record?.sessionId) || 'unknown',
908
+ providerType: readStringValue(record?.providerType),
909
+ state: readLiveMeshSessionState(record),
910
+ lifecycle: readStringValue(record?.lifecycle),
911
+ surfaceKind: getSessionHostSurfaceKind(record as any),
912
+ recoveryState: readStringValue(record?.meta?.runtimeRecoveryState) ?? null,
913
+ workspace: readStringValue(record?.workspace) ?? null,
914
+ title: readStringValue(record?.displayName, record?.workspaceLabel) ?? null,
915
+ lastActivityAt: toIsoTimestamp(record?.updatedAt ?? record?.lastActivityAt ?? record?.last_activity_at),
916
+ isCached: false,
917
+ };
918
+ }
919
+
920
+ function liveSessionRecordMatchesMeshNode(record: any, meshId: string, nodeId: string): boolean {
921
+ const recordNodeId = readStringValue(record?.meta?.meshNodeId);
922
+ if (!recordNodeId || recordNodeId !== nodeId) return false;
923
+ const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
924
+ return !recordMeshId || recordMeshId === meshId;
925
+ }
926
+
927
+ function liveSessionRecordMatchesMeshWorkspace(record: any, meshId: string, workspace: string): boolean {
928
+ const recordWorkspace = readStringValue(record?.workspace);
929
+ if (!recordWorkspace || !workspace || recordWorkspace !== workspace) return false;
930
+
931
+ const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
932
+ if (recordMeshId) return recordMeshId === meshId;
933
+
934
+ return record?.meta?.launchedByCoordinator === true || !!readStringValue(record?.meta?.meshNodeId);
935
+ }
936
+
937
+ function readLiveMeshNodeWorkspace(args: {
938
+ meshId: string;
939
+ nodeId: string;
940
+ liveSessionRecords: any[];
941
+ allowCoordinatorSession?: boolean;
942
+ }): string {
943
+ const directNodeWorkspace = args.liveSessionRecords.find((record) => (
944
+ liveSessionRecordMatchesMeshNode(record, args.meshId, args.nodeId)
945
+ && readStringValue(record?.workspace)
946
+ ));
947
+ if (directNodeWorkspace) {
948
+ return readStringValue(directNodeWorkspace.workspace) || '';
949
+ }
950
+
951
+ if (args.allowCoordinatorSession) {
952
+ const coordinatorWorkspace = args.liveSessionRecords.find((record) => (
953
+ readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId
954
+ && readStringValue(record?.workspace)
955
+ ));
956
+ if (coordinatorWorkspace) {
957
+ return readStringValue(coordinatorWorkspace.workspace) || '';
958
+ }
959
+ }
960
+
961
+ return '';
962
+ }
963
+
964
+ function collectLiveMeshSessionRecords(args: {
965
+ meshId: string;
966
+ node: any;
967
+ nodeId: string;
968
+ liveSessionRecords: any[];
969
+ allowCoordinatorSession?: boolean;
970
+ }): any[] {
971
+ const matches = args.liveSessionRecords.filter((record) => {
972
+ const nodeWorkspace = readStringValue(args.node?.workspace);
973
+ if (liveSessionRecordMatchesMeshNode(record, args.meshId, args.nodeId)) return true;
974
+ return !!nodeWorkspace && liveSessionRecordMatchesMeshWorkspace(record, args.meshId, nodeWorkspace);
975
+ });
976
+
977
+ if (args.allowCoordinatorSession) {
978
+ for (const record of args.liveSessionRecords) {
979
+ if (readStringValue(record?.meta?.meshCoordinatorFor) !== args.meshId) continue;
980
+ const sessionId = readStringValue(record?.sessionId);
981
+ if (sessionId && matches.some((entry) => readStringValue(entry?.sessionId) === sessionId)) continue;
982
+ matches.push(record);
983
+ }
984
+ }
985
+
986
+ return matches;
987
+ }
988
+
989
+ function applyCachedInlineMeshNodeStatus(
990
+ status: Record<string, unknown>,
991
+ node: any,
992
+ options?: { skipGit?: boolean; skipError?: boolean; skipHealth?: boolean },
993
+ ): boolean {
263
994
  const cachedStatus = readObjectRecord(node?.cachedStatus);
264
- const git = buildCachedInlineMeshGitStatus(node);
265
- const error = readStringValue(cachedStatus.error, node?.error);
266
- const health = readStringValue(cachedStatus.health, node?.health);
995
+ const liveGit = buildInlineMeshTransitGitStatus(node);
996
+ const git = options?.skipGit ? undefined : (liveGit ?? buildCachedInlineMeshGitStatus(node));
997
+ const error = options?.skipError ? undefined : (liveGit ? undefined : readStringValue(cachedStatus.error, node?.error));
998
+ const health = options?.skipHealth ? undefined : (liveGit ? undefined : readStringValue(cachedStatus.health, node?.health));
267
999
  const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
1000
+ const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
1001
+ const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
268
1002
  const activeSessions = readCachedInlineMeshActiveSessions(node);
269
- if (!git && !error && !health && !machineStatus && activeSessions.length === 0) return false;
1003
+ const activeSessionDetails = readCachedInlineMeshActiveSessionDetails(node);
1004
+ if (!git && !error && !health && !machineStatus && !lastSeenAt && !updatedAt && activeSessions.length === 0) return false;
270
1005
  if (git) status.git = git;
271
1006
  if (error) status.error = error;
1007
+ if (machineStatus) status.machineStatus = machineStatus;
1008
+ if (lastSeenAt) status.lastSeenAt = lastSeenAt;
1009
+ if (updatedAt) status.updatedAt = updatedAt;
272
1010
  if (activeSessions.length > 0) status.activeSessions = activeSessions;
1011
+ if (activeSessionDetails.length > 0) status.activeSessionDetails = activeSessionDetails;
273
1012
  if (health) {
274
1013
  status.health = health;
275
1014
  return true;
@@ -278,7 +1017,7 @@ function applyCachedInlineMeshNodeStatus(status: Record<string, unknown>, node:
278
1017
  status.health = deriveMeshNodeHealthFromGit(git);
279
1018
  return true;
280
1019
  }
281
- return activeSessions.length > 0 || !!machineStatus;
1020
+ return activeSessions.length > 0 || !!machineStatus || !!lastSeenAt || !!updatedAt;
282
1021
  }
283
1022
 
284
1023
  async function resolveProviderTypeFromPriority(args: {
@@ -313,13 +1052,7 @@ async function resolveProviderTypeFromPriority(args: {
313
1052
  }
314
1053
  type MeshCoordinatorConfigFormat = 'claude_mcp_json' | 'hermes_config_yaml';
315
1054
  type MeshRefineValidationStatus = 'passed' | 'failed' | 'skipped';
316
- type MeshRefineValidationCommand = {
317
- command: string;
318
- args: string[];
319
- displayCommand: string;
320
- category: string;
321
- source: string;
322
- };
1055
+ type MeshRefineValidationCommand = MeshRefineValidationCommandPlan;
323
1056
 
324
1057
  type MeshRefineValidationSummary = {
325
1058
  status: MeshRefineValidationStatus;
@@ -329,13 +1062,83 @@ type MeshRefineValidationSummary = {
329
1062
  skippedReason?: string;
330
1063
  timeoutMs: number;
331
1064
  outputLimitBytes: number;
1065
+ configSource?: string;
1066
+ configSourceType?: string;
1067
+ suggestions?: unknown[];
1068
+ suggestedConfig?: unknown;
1069
+ };
1070
+
1071
+ type MeshRefineStageStatus = 'passed' | 'failed' | 'skipped';
1072
+
1073
+ type MeshRefinePatchEquivalenceSummary = {
1074
+ status: MeshRefineStageStatus;
1075
+ equivalent: boolean;
1076
+ baseHead: string;
1077
+ branchHead: string;
1078
+ mergeBase?: string;
1079
+ mergedTree?: string;
1080
+ expectedPatchId?: string;
1081
+ actualPatchId?: string;
1082
+ durationMs: number;
1083
+ error?: string;
1084
+ stdout?: string;
1085
+ stderr?: string;
1086
+ };
1087
+
1088
+ type MeshRefineSubmoduleReachabilityEntry = {
1089
+ path: string;
1090
+ commit: string;
1091
+ reachable: boolean;
1092
+ checkedLocal?: boolean;
1093
+ fetchedFromOrigin?: boolean;
1094
+ error?: string;
1095
+ };
1096
+
1097
+ type MeshRefineSubmoduleReachabilitySummary = {
1098
+ status: MeshRefineStageStatus;
1099
+ checked: number;
1100
+ unreachable: MeshRefineSubmoduleReachabilityEntry[];
1101
+ entries: MeshRefineSubmoduleReachabilityEntry[];
1102
+ durationMs: number;
1103
+ error?: string;
1104
+ };
1105
+
1106
+ type MeshRefineAsyncJobStatus = 'accepted' | 'completed' | 'failed';
1107
+
1108
+ type MeshRefineJobHandle = {
1109
+ success: true;
1110
+ async: true;
1111
+ status: MeshRefineAsyncJobStatus;
1112
+ jobId: string;
1113
+ interactionId: string;
1114
+ meshId: string;
1115
+ nodeId: string;
1116
+ targetNodeId: string;
1117
+ targetDaemonId?: string;
1118
+ workspace?: string;
1119
+ startedAt: string;
1120
+ completedAt?: string;
1121
+ duplicate?: boolean;
1122
+ retryOfJobId?: string;
1123
+ eventDelivery: {
1124
+ pendingEvents: true;
1125
+ ledger: true;
1126
+ };
1127
+ evidence: {
1128
+ pendingEventsCommand: 'get_pending_mesh_events';
1129
+ ledgerCommand: 'get_mesh_ledger_slice';
1130
+ taskHistoryKind: 'task_dispatched' | 'task_completed' | 'task_failed';
1131
+ };
332
1132
  };
333
1133
 
1134
+ type MeshRefineTerminalJob = MeshRefineJobHandle & { result?: Record<string, unknown> };
1135
+
334
1136
  const REFINE_VALIDATION_CATEGORIES = ['typecheck', 'test', 'lint', 'build'] as const;
335
1137
  const REFINE_VALIDATION_TIMEOUT_MS = 120_000;
336
1138
  const REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
337
1139
  const REFINE_VALIDATION_SUMMARY_CHARS = 2_000;
338
1140
  const REFINE_VALIDATION_MAX_COMMANDS = 4;
1141
+ const REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES = 4 * 1024 * 1024;
339
1142
 
340
1143
  function truncateValidationOutput(value: unknown): string {
341
1144
  const text = typeof value === 'string' ? value : value == null ? '' : String(value);
@@ -343,171 +1146,203 @@ function truncateValidationOutput(value: unknown): string {
343
1146
  return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}\n[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
344
1147
  }
345
1148
 
346
- function readPackageScripts(workspace: string): Record<string, string> {
347
- try {
348
- const packageJsonPath = pathJoin(workspace, 'package.json');
349
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
350
- return parsed?.scripts && typeof parsed.scripts === 'object' && !Array.isArray(parsed.scripts)
351
- ? parsed.scripts as Record<string, string>
352
- : {};
353
- } catch {
354
- return {};
355
- }
356
- }
357
-
358
- function tokenizeValidationCommand(command: string): string[] | null {
359
- const trimmed = command.trim();
360
- if (!trimmed) return null;
361
- // Fail closed: the gate never hands shell syntax to a shell. Package-manager
362
- // scripts are invoked via execFile(binary, args), and metacharacters/quotes are
363
- // rejected before tokenization so `npm run test && rm -rf` cannot be smuggled in.
364
- if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
365
- const tokens = trimmed.split(/\s+/).filter(Boolean);
366
- if (!tokens.length) return null;
367
- if (tokens.some(token => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
368
- return tokens;
1149
+ function recordMeshRefineStage(
1150
+ stages: Array<Record<string, unknown>>,
1151
+ stage: string,
1152
+ status: MeshRefineStageStatus,
1153
+ startedAt: number,
1154
+ details?: Record<string, unknown>,
1155
+ ): void {
1156
+ stages.push({
1157
+ stage,
1158
+ status,
1159
+ durationMs: Date.now() - startedAt,
1160
+ ...(details || {}),
1161
+ });
369
1162
  }
370
1163
 
371
- function scriptMatchesValidationCategory(scriptName: string, category: string): boolean {
372
- return scriptName === category || scriptName.startsWith(`${category}:`);
1164
+ async function computeGitPatchId(cwd: string, fromRef: string, toRef: string): Promise<string> {
1165
+ const { execFileSync } = await import('node:child_process');
1166
+ const diff = execFileSync('git', ['diff', '--patch', '--full-index', fromRef, toRef], {
1167
+ cwd,
1168
+ encoding: 'utf8',
1169
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1170
+ });
1171
+ if (!diff.trim()) return '';
1172
+ const patchId = execFileSync('git', ['patch-id', '--stable'], {
1173
+ cwd,
1174
+ input: diff,
1175
+ encoding: 'utf8',
1176
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1177
+ }).trim();
1178
+ return patchId.split(/\s+/)[0] || '';
373
1179
  }
374
1180
 
375
- function parsePackageManagerValidationCommand(
376
- rawCommand: string,
377
- category: string,
378
- scripts: Record<string, string>,
379
- source: string,
380
- ): { command?: MeshRefineValidationCommand; rejected?: Record<string, unknown> } {
381
- const tokens = tokenizeValidationCommand(rawCommand);
382
- if (!tokens) {
383
- return { rejected: { command: rawCommand, category, source, reason: 'unsafe command string is not allowlisted' } };
384
- }
385
-
386
- const [binary, second, third, ...rest] = tokens;
387
- let scriptName = '';
388
- let command = binary;
389
- let args: string[] = [];
390
-
391
- if ((binary === 'npm' || binary === 'pnpm' || binary === 'bun') && second === 'run' && third) {
392
- scriptName = third;
393
- args = ['run', scriptName, ...rest];
394
- } else if (binary === 'npm' && second === 'test' && !third) {
395
- scriptName = 'test';
396
- args = ['test'];
397
- } else if (binary === 'yarn' && second === 'run' && third) {
398
- scriptName = third;
399
- args = ['run', scriptName, ...rest];
400
- } else if (binary === 'yarn' && second && !third) {
401
- scriptName = second;
402
- args = [scriptName];
403
- } else {
404
- return { rejected: { command: rawCommand, category, source, reason: 'command is not a supported package-manager script invocation' } };
405
- }
406
-
407
- if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
408
- return { rejected: { command: rawCommand, category, source, script: scriptName, reason: 'script is not declared in package.json' } };
409
- }
410
- if (!scriptMatchesValidationCategory(scriptName, category)) {
411
- return { rejected: { command: rawCommand, category, source, script: scriptName, reason: 'script name is outside the validation category allowlist' } };
1181
+ async function runMeshRefinePatchEquivalenceGate(
1182
+ repoRoot: string,
1183
+ baseHead: string,
1184
+ branchHead: string,
1185
+ ): Promise<MeshRefinePatchEquivalenceSummary> {
1186
+ const startedAt = Date.now();
1187
+ try {
1188
+ const { execFileSync } = await import('node:child_process');
1189
+ const git = (args: string[]) => execFileSync('git', args, {
1190
+ cwd: repoRoot,
1191
+ encoding: 'utf8',
1192
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1193
+ });
1194
+ const mergeBase = git(['merge-base', baseHead, branchHead]).trim();
1195
+ const mergeTreeStdout = git(['merge-tree', '--write-tree', baseHead, branchHead]);
1196
+ const mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || '';
1197
+ if (!mergeBase || !mergedTree) {
1198
+ return {
1199
+ status: 'failed',
1200
+ equivalent: false,
1201
+ baseHead,
1202
+ branchHead,
1203
+ mergeBase: mergeBase || undefined,
1204
+ mergedTree: mergedTree || undefined,
1205
+ durationMs: Date.now() - startedAt,
1206
+ error: 'patch equivalence preflight could not resolve merge-base or synthetic merge tree',
1207
+ stdout: truncateValidationOutput(mergeTreeStdout),
1208
+ };
1209
+ }
1210
+ const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
1211
+ const actualPatchId = await computeGitPatchId(repoRoot, baseHead, mergedTree);
1212
+ const equivalent = expectedPatchId === actualPatchId;
1213
+ return {
1214
+ status: equivalent ? 'passed' : 'failed',
1215
+ equivalent,
1216
+ baseHead,
1217
+ branchHead,
1218
+ mergeBase,
1219
+ mergedTree,
1220
+ expectedPatchId,
1221
+ actualPatchId,
1222
+ durationMs: Date.now() - startedAt,
1223
+ };
1224
+ } catch (e: any) {
1225
+ return {
1226
+ status: 'failed',
1227
+ equivalent: false,
1228
+ baseHead,
1229
+ branchHead,
1230
+ durationMs: Date.now() - startedAt,
1231
+ error: e?.message || String(e),
1232
+ stdout: truncateValidationOutput(e?.stdout),
1233
+ stderr: truncateValidationOutput(e?.stderr),
1234
+ };
412
1235
  }
413
-
414
- return {
415
- command: {
416
- command,
417
- args,
418
- displayCommand: [command, ...args].join(' '),
419
- category,
420
- source,
421
- },
422
- };
423
1236
  }
424
1237
 
425
- function collectProjectContextValidationCandidates(mesh: any): Array<{ command: string; category: string; source: string; confidence?: string }> {
426
- const commands = mesh?.projectContext?.commands;
427
- if (!commands || typeof commands !== 'object' || Array.isArray(commands)) return [];
428
- const candidates: Array<{ command: string; category: string; source: string; confidence?: string }> = [];
429
- for (const category of REFINE_VALIDATION_CATEGORIES) {
430
- const entries = Array.isArray(commands[category]) ? commands[category] : [];
431
- for (const entry of entries) {
432
- if (typeof entry?.command !== 'string') continue;
433
- candidates.push({
434
- command: entry.command,
435
- category,
436
- source: typeof entry.sourcePath === 'string' ? entry.sourcePath : 'projectContext.commands',
437
- confidence: typeof entry.confidence === 'string' ? entry.confidence : undefined,
1238
+ async function runMeshRefineSubmoduleReachabilityGate(
1239
+ repoRoot: string,
1240
+ mergedTree: string,
1241
+ ): Promise<MeshRefineSubmoduleReachabilitySummary> {
1242
+ const startedAt = Date.now();
1243
+ const entries: MeshRefineSubmoduleReachabilityEntry[] = [];
1244
+ try {
1245
+ const { execFile } = await import('node:child_process');
1246
+ const { promisify } = await import('node:util');
1247
+ const execFileAsync = promisify(execFile);
1248
+ const runGit = async (cwd: string, args: string[]): Promise<string> => {
1249
+ const { stdout } = await execFileAsync('git', args, {
1250
+ cwd,
1251
+ encoding: 'utf8',
1252
+ timeout: 30_000,
1253
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1254
+ windowsHide: true,
438
1255
  });
439
- }
440
- }
441
- return candidates.sort((a, b) => {
442
- const rank = (value?: string) => value === 'high' ? 0 : value === 'medium' ? 1 : 2;
443
- return rank(a.confidence) - rank(b.confidence);
444
- });
445
- }
1256
+ return String(stdout || '');
1257
+ };
446
1258
 
447
- function collectPolicyValidationCandidates(mesh: any): Array<{ command: string; category: string; source: string }> {
448
- const policy = mesh?.policy && typeof mesh.policy === 'object' && !Array.isArray(mesh.policy) ? mesh.policy : {};
449
- const configured = Array.isArray(policy.validationCommands)
450
- ? policy.validationCommands
451
- : Array.isArray(policy.validationGate?.commands)
452
- ? policy.validationGate.commands
453
- : [];
454
- return configured
455
- .map((entry: any) => typeof entry === 'string' ? { command: entry, category: '', source: 'mesh.policy.validationCommands' } : entry)
456
- .filter((entry: any) => entry && typeof entry.command === 'string')
457
- .map((entry: any) => {
458
- const commandText = entry.command.trim();
459
- const category = REFINE_VALIDATION_CATEGORIES.find(cat => commandText.includes(` ${cat}`)) ?? '';
460
- return { command: commandText, category, source: 'mesh.policy.validationCommands' };
461
- })
462
- .filter((entry: any) => !!entry.category);
463
- }
1259
+ const treeOutput = await runGit(repoRoot, ['ls-tree', '-r', '-z', mergedTree]);
1260
+ const gitlinks = treeOutput
1261
+ .split('\0')
1262
+ .filter(Boolean)
1263
+ .map(record => {
1264
+ const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record);
1265
+ return match ? { commit: match[1], path: match[2] } : null;
1266
+ })
1267
+ .filter((entry): entry is { commit: string; path: string } => !!entry);
1268
+
1269
+ for (const gitlink of gitlinks) {
1270
+ const submodulePath = pathResolve(repoRoot, gitlink.path);
1271
+ const entry: MeshRefineSubmoduleReachabilityEntry = {
1272
+ path: gitlink.path,
1273
+ commit: gitlink.commit,
1274
+ reachable: false,
1275
+ };
1276
+ try {
1277
+ if (!fs.existsSync(submodulePath)) {
1278
+ entry.error = `Submodule checkout missing at ${gitlink.path}`;
1279
+ entries.push(entry);
1280
+ continue;
1281
+ }
464
1282
 
465
- function selectMeshRefineValidationCommands(mesh: any, workspace: string): { commands: MeshRefineValidationCommand[]; rejectedCommands: Array<Record<string, unknown>>; source: string } {
466
- const scripts = readPackageScripts(workspace);
467
- const rejectedCommands: Array<Record<string, unknown>> = [];
468
- const selected: MeshRefineValidationCommand[] = [];
469
- const seen = new Set<string>();
470
- const candidates = [
471
- ...collectPolicyValidationCandidates(mesh),
472
- ...collectProjectContextValidationCandidates(mesh),
473
- ];
474
-
475
- for (const candidate of candidates) {
476
- const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
477
- if (parsed.rejected) {
478
- rejectedCommands.push(parsed.rejected);
479
- continue;
480
- }
481
- if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
482
- selected.push(parsed.command);
483
- seen.add(parsed.command.displayCommand);
484
- if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
485
- }
486
-
487
- if (!selected.length && candidates.length === 0) {
488
- for (const category of REFINE_VALIDATION_CATEGORIES) {
489
- if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
490
- const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, 'package.json:scripts');
491
- if (fallback.command && !seen.has(fallback.command.displayCommand)) {
492
- selected.push(fallback.command);
493
- seen.add(fallback.command.displayCommand);
494
- } else if (fallback.rejected) {
495
- rejectedCommands.push(fallback.rejected);
496
- }
497
- if (selected.length >= 2) break;
1283
+ entry.checkedLocal = true;
1284
+ try {
1285
+ await runGit(submodulePath, ['cat-file', '-e', `${gitlink.commit}^{commit}`]);
1286
+ entry.reachable = true;
1287
+ entries.push(entry);
1288
+ continue;
1289
+ } catch {
1290
+ // Probe the submodule remote before allowing cleanup/completion.
1291
+ }
1292
+
1293
+ try {
1294
+ await runGit(submodulePath, ['fetch', 'origin', gitlink.commit]);
1295
+ entry.fetchedFromOrigin = true;
1296
+ await runGit(submodulePath, ['cat-file', '-e', `${gitlink.commit}^{commit}`]);
1297
+ entry.reachable = true;
1298
+ } catch (e: any) {
1299
+ entry.error = truncateValidationOutput(e?.stderr || e?.message || String(e));
1300
+ }
1301
+ } catch (e: any) {
1302
+ entry.error = truncateValidationOutput(e?.message || String(e));
1303
+ }
1304
+ entries.push(entry);
498
1305
  }
1306
+
1307
+ const unreachable = entries.filter(entry => !entry.reachable);
1308
+ return {
1309
+ status: unreachable.length ? 'failed' : 'passed',
1310
+ checked: entries.length,
1311
+ unreachable,
1312
+ entries,
1313
+ durationMs: Date.now() - startedAt,
1314
+ };
1315
+ } catch (e: any) {
1316
+ return {
1317
+ status: 'failed',
1318
+ checked: entries.length,
1319
+ unreachable: entries.filter(entry => !entry.reachable),
1320
+ entries,
1321
+ durationMs: Date.now() - startedAt,
1322
+ error: truncateValidationOutput(e?.message || String(e)),
1323
+ };
499
1324
  }
1325
+ }
500
1326
 
1327
+ function buildMeshRefineValidationPlan(mesh: any, workspace: string): Record<string, unknown> {
1328
+ const plan = resolveMeshRefineValidationPlan(mesh, workspace);
501
1329
  return {
502
- commands: selected,
503
- rejectedCommands,
504
- source: selected.some(command => command.source === 'mesh.policy.validationCommands')
505
- ? 'mesh_policy'
506
- : selected.some(command => command.source !== 'package.json:scripts')
507
- ? 'project_context'
508
- : selected.length
509
- ? 'package_json_scripts'
510
- : 'unavailable',
1330
+ source: plan.source,
1331
+ sourceType: plan.sourceType,
1332
+ commands: plan.commands.map(command => ({
1333
+ displayCommand: command.displayCommand,
1334
+ category: command.category,
1335
+ source: command.source,
1336
+ cwd: command.cwd,
1337
+ timeoutMs: command.timeoutMs,
1338
+ })),
1339
+ unavailableReason: plan.unavailableReason,
1340
+ rejectedCommands: plan.rejectedCommands,
1341
+ suggestions: plan.suggestions,
1342
+ suggestedConfig: plan.suggestedConfig,
1343
+ note: plan.sourceType === 'unavailable'
1344
+ ? 'No validation command will be executed until a repo mesh/refine config is provided. Heuristics are suggestions only.'
1345
+ : 'Validation commands are resolved from repo mesh/refine config; heuristics are suggestions only.',
511
1346
  };
512
1347
  }
513
1348
 
@@ -515,7 +1350,7 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
515
1350
  const { execFile } = await import('node:child_process');
516
1351
  const { promisify } = await import('node:util');
517
1352
  const execFileAsync = promisify(execFile);
518
- const selection = selectMeshRefineValidationCommands(mesh, workspace);
1353
+ const selection = resolveMeshRefineValidationPlan(mesh, workspace);
519
1354
  const summary: MeshRefineValidationSummary = {
520
1355
  status: 'skipped',
521
1356
  required: true,
@@ -524,22 +1359,28 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
524
1359
  skippedReason: undefined,
525
1360
  timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
526
1361
  outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
1362
+ configSource: selection.source,
1363
+ configSourceType: selection.sourceType,
1364
+ suggestions: selection.suggestions,
1365
+ suggestedConfig: selection.suggestedConfig,
527
1366
  };
528
1367
 
529
1368
  if (!selection.commands.length) {
530
- summary.skippedReason = 'validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available';
1369
+ summary.skippedReason = selection.unavailableReason || 'validation_unavailable: repo mesh/refine config did not provide executable validation.commands';
531
1370
  return summary;
532
1371
  }
533
1372
 
534
1373
  for (const candidate of selection.commands) {
535
1374
  const startedAt = Date.now();
1375
+ const cwd = candidate.cwd ? pathResolve(workspace, candidate.cwd) : workspace;
1376
+ const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
536
1377
  try {
537
1378
  const result = await execFileAsync(candidate.command, candidate.args, {
538
- cwd: workspace,
1379
+ cwd,
539
1380
  encoding: 'utf8',
540
- timeout: REFINE_VALIDATION_TIMEOUT_MS,
1381
+ timeout,
541
1382
  maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
542
- env: { ...process.env, CI: process.env.CI || '1' },
1383
+ env: { ...process.env, CI: process.env.CI || '1', ...(candidate.env || {}) },
543
1384
  });
544
1385
  summary.commandsRun.push({
545
1386
  command: candidate.command,
@@ -547,6 +1388,7 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
547
1388
  displayCommand: candidate.displayCommand,
548
1389
  category: candidate.category,
549
1390
  source: candidate.source,
1391
+ cwd,
550
1392
  passed: true,
551
1393
  exitCode: 0,
552
1394
  durationMs: Date.now() - startedAt,
@@ -560,6 +1402,7 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
560
1402
  displayCommand: candidate.displayCommand,
561
1403
  category: candidate.category,
562
1404
  source: candidate.source,
1405
+ cwd,
563
1406
  passed: false,
564
1407
  exitCode: typeof error?.code === 'number' ? error.code : null,
565
1408
  signal: typeof error?.signal === 'string' ? error.signal : null,
@@ -698,6 +1541,10 @@ export interface CommandRouterDeps {
698
1541
  statusVersion?: string;
699
1542
  /** Session host control plane */
700
1543
  sessionHostControl?: SessionHostControlPlane | null;
1544
+ /** Selected-coordinator mesh peer telemetry surface for target daemons, when supported by the runtime. */
1545
+ getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
1546
+ /** Dispatch a command to a remote mesh node via P2P/relay. Injected by cloud runtime; absent in standalone. */
1547
+ dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
701
1548
  }
702
1549
 
703
1550
  export interface CommandRouterResult {
@@ -809,42 +1656,262 @@ function summarizeSessionHostPruneResult(result: unknown): Record<string, unknow
809
1656
  };
810
1657
  }
811
1658
 
1659
+ function normalizeStandaloneHostCommandUrl(hostAddress: string): string {
1660
+ const raw = hostAddress.trim();
1661
+ if (!raw) throw new Error('hostAddress required');
1662
+ const url = new URL(raw.replace(/^ws:/, 'http:').replace(/^wss:/, 'https:'));
1663
+ url.pathname = '/api/v1/command';
1664
+ url.search = '';
1665
+ url.hash = '';
1666
+ return url.toString();
1667
+ }
1668
+
1669
+ function buildMemberJoinNode(mesh: any, args: any, fallbackDaemonId?: string): Record<string, unknown> | null {
1670
+ const requestedNodeId = typeof args?.memberNodeId === 'string' ? args.memberNodeId.trim() : '';
1671
+ const explicit = args?.memberNode && typeof args.memberNode === 'object' && !Array.isArray(args.memberNode)
1672
+ ? args.memberNode as Record<string, any>
1673
+ : null;
1674
+ const configured = Array.isArray(mesh?.nodes)
1675
+ ? (requestedNodeId
1676
+ ? mesh.nodes.find((node: any) => node?.id === requestedNodeId || node?.nodeId === requestedNodeId)
1677
+ : mesh.nodes[0])
1678
+ : null;
1679
+ const source = explicit || configured;
1680
+ const workspace = typeof source?.workspace === 'string' && source.workspace.trim()
1681
+ ? source.workspace.trim()
1682
+ : typeof args?.workspace === 'string' && args.workspace.trim()
1683
+ ? args.workspace.trim()
1684
+ : process.cwd();
1685
+ if (!workspace) return null;
1686
+ const nodeId = typeof source?.id === 'string' && source.id.trim()
1687
+ ? source.id.trim()
1688
+ : typeof source?.nodeId === 'string' && source.nodeId.trim()
1689
+ ? source.nodeId.trim()
1690
+ : undefined;
1691
+ return {
1692
+ ...(nodeId ? { id: nodeId } : {}),
1693
+ workspace,
1694
+ ...(typeof source?.repoRoot === 'string' && source.repoRoot.trim() ? { repoRoot: source.repoRoot.trim() } : {}),
1695
+ ...(typeof source?.daemonId === 'string' && source.daemonId.trim() ? { daemonId: source.daemonId.trim() } : fallbackDaemonId ? { daemonId: fallbackDaemonId } : {}),
1696
+ ...(typeof source?.machineId === 'string' && source.machineId.trim() ? { machineId: source.machineId.trim() } : {}),
1697
+ userOverrides: source?.userOverrides && typeof source.userOverrides === 'object' && !Array.isArray(source.userOverrides) ? source.userOverrides : {},
1698
+ policy: source?.policy && typeof source.policy === 'object' && !Array.isArray(source.policy) ? source.policy : {},
1699
+ role: 'member',
1700
+ };
1701
+ }
1702
+
812
1703
  export class DaemonCommandRouter {
813
1704
  private deps: CommandRouterDeps;
814
1705
  /** In-memory cache for cloud-originating meshes passed via inlineMesh.
815
1706
  * Allows the MCP server to query mesh data via get_mesh even when
816
1707
  * the mesh doesn't exist in the local meshes.json file. */
817
1708
  private inlineMeshCache = new Map<string, any>();
1709
+ /** Coordinator-owned whole-mesh aggregate status snapshots. Browser callers read this by default. */
1710
+ private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any; queueRevision: string }>();
1711
+ /** In-memory async Refinery jobs keyed by meshId:nodeId to reject/return duplicate in-flight requests. */
1712
+ private runningRefineJobs = new Map<string, MeshRefineJobHandle>();
1713
+ /** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
1714
+ private terminalRefineJobs = new Map<string, MeshRefineTerminalJob>();
818
1715
 
819
1716
  constructor(deps: CommandRouterDeps) {
820
1717
  this.deps = deps;
821
1718
  }
822
1719
 
1720
+ private cloneJsonValue<T>(value: T): T {
1721
+ if (typeof structuredClone === 'function') return structuredClone(value);
1722
+ return JSON.parse(JSON.stringify(value)) as T;
1723
+ }
1724
+
1725
+ private hydrateCachedAggregateMeshStatusFromInline(snapshot: any, mesh: any, options?: { requireDirectPeerTruth?: boolean }): any {
1726
+ if (!mesh || typeof mesh !== 'object' || !Array.isArray(mesh.nodes) || !Array.isArray(snapshot?.nodes)) return snapshot;
1727
+ const inlineNodesById = new Map<string, any>();
1728
+ for (const node of mesh.nodes) {
1729
+ const nodeId = readInlineMeshNodeId(node);
1730
+ if (nodeId) inlineNodesById.set(nodeId, node);
1731
+ }
1732
+ if (!inlineNodesById.size) return snapshot;
1733
+
1734
+ let changed = false;
1735
+ const unavailableNodeIds = new Set<string>();
1736
+ const sourceOfTruth = readObjectRecord(snapshot.sourceOfTruth);
1737
+ const directPeerTruth = readObjectRecord(sourceOfTruth.directPeerTruth);
1738
+ for (const entry of Array.isArray(directPeerTruth.unavailableNodeIds) ? directPeerTruth.unavailableNodeIds : []) {
1739
+ const nodeId = readStringValue(entry);
1740
+ if (nodeId) unavailableNodeIds.add(nodeId);
1741
+ }
1742
+
1743
+ const nodes = snapshot.nodes.map((statusNode: any) => {
1744
+ const nodeId = readStringValue(statusNode?.nodeId, statusNode?.id);
1745
+ const inlineNode = nodeId ? inlineNodesById.get(nodeId) : undefined;
1746
+ if (!inlineNode) return statusNode;
1747
+ const liveGit = buildInlineMeshTransitGitStatus(inlineNode);
1748
+ if (!liveGit) return statusNode;
1749
+ const nextStatus = { ...statusNode };
1750
+ nextStatus.git = liveGit;
1751
+ nextStatus.health = deriveMeshNodeHealthFromGit(liveGit);
1752
+ applyInlineMeshBranchConvergence(mesh, inlineNode, nextStatus);
1753
+ nextStatus.launchReady = readBooleanValue(nextStatus.launchReady) ?? true;
1754
+ const connection = readObjectRecord(nextStatus.connection);
1755
+ const connectionState = readStringValue(connection.state);
1756
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
1757
+ if (!connectionReported || connectionState === 'unknown') {
1758
+ nextStatus.connection = buildLivePeerGitConnection(connection);
1759
+ }
1760
+ delete nextStatus.gitProbePending;
1761
+ const error = readStringValue(nextStatus.error);
1762
+ if (error && /pending_git|git probe|live peer git snapshot|no peer git snapshot/i.test(error)) delete nextStatus.error;
1763
+ if (!readStringValue(nextStatus.machineStatus)) nextStatus.machineStatus = 'online';
1764
+ if (nodeId) unavailableNodeIds.delete(nodeId);
1765
+ changed = true;
1766
+ return nextStatus;
1767
+ });
1768
+
1769
+ if (!changed && !(options?.requireDirectPeerTruth && unavailableNodeIds.size > 0)) return snapshot;
1770
+ const nextSourceOfTruth = {
1771
+ ...sourceOfTruth,
1772
+ ...(Object.keys(directPeerTruth).length ? {
1773
+ directPeerTruth: {
1774
+ ...directPeerTruth,
1775
+ satisfied: options?.requireDirectPeerTruth === true ? unavailableNodeIds.size === 0 : directPeerTruth.satisfied,
1776
+ unavailableNodeIds: [...unavailableNodeIds],
1777
+ },
1778
+ ...(options?.requireDirectPeerTruth === true ? {
1779
+ coordinatorOwnsLiveTruth: unavailableNodeIds.size === 0,
1780
+ currentStatus: unavailableNodeIds.size === 0 ? 'live_git_and_session_probes' : 'direct_peer_truth_unavailable',
1781
+ } : {}),
1782
+ } : {}),
1783
+ };
1784
+ return {
1785
+ ...snapshot,
1786
+ ...(options?.requireDirectPeerTruth === true && unavailableNodeIds.size > 0 ? {
1787
+ success: false,
1788
+ code: 'mesh_direct_peer_truth_unavailable',
1789
+ error: 'Selected coordinator could not confirm direct mesh truth for every remote node yet.',
1790
+ } : {}),
1791
+ sourceOfTruth: nextSourceOfTruth,
1792
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodes),
1793
+ nodes,
1794
+ };
1795
+ }
1796
+
1797
+ private getCachedAggregateMeshStatus(meshId: string, mesh?: any, options?: { requireDirectPeerTruth?: boolean }): any | null {
1798
+ const cached = this.aggregateMeshStatusCache.get(meshId);
1799
+ if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
1800
+ if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
1801
+ let snapshot = this.cloneJsonValue(cached.snapshot);
1802
+ snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
1803
+ if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
1804
+ const ageMs = Math.max(0, Date.now() - cached.builtAt);
1805
+ const sourceOfTruth = snapshot.sourceOfTruth && typeof snapshot.sourceOfTruth === 'object'
1806
+ ? snapshot.sourceOfTruth
1807
+ : {};
1808
+ snapshot.sourceOfTruth = {
1809
+ ...sourceOfTruth,
1810
+ aggregateSnapshot: {
1811
+ ...(sourceOfTruth.aggregateSnapshot && typeof sourceOfTruth.aggregateSnapshot === 'object'
1812
+ ? sourceOfTruth.aggregateSnapshot
1813
+ : {}),
1814
+ owner: 'coordinator_daemon_memory',
1815
+ cached: true,
1816
+ source: 'memory',
1817
+ refreshReason: 'memory_cache_hit',
1818
+ ageMs,
1819
+ cachedAt: new Date(cached.builtAt).toISOString(),
1820
+ returnedAt: new Date().toISOString(),
1821
+ },
1822
+ };
1823
+ return snapshot;
1824
+ }
1825
+
1826
+ private rememberAggregateMeshStatus(meshId: string, snapshot: any, refreshReason: string): any {
1827
+ if (!snapshot || typeof snapshot !== 'object' || snapshot.success !== true || !Array.isArray(snapshot.nodes)) return snapshot;
1828
+ const builtAt = Date.now();
1829
+ const next = this.cloneJsonValue(snapshot);
1830
+ const sourceOfTruth = next.sourceOfTruth && typeof next.sourceOfTruth === 'object'
1831
+ ? next.sourceOfTruth
1832
+ : {};
1833
+ next.sourceOfTruth = {
1834
+ ...sourceOfTruth,
1835
+ aggregateSnapshot: {
1836
+ owner: 'coordinator_daemon_memory',
1837
+ cached: false,
1838
+ source: 'live_refresh',
1839
+ refreshReason,
1840
+ ageMs: 0,
1841
+ cachedAt: new Date(builtAt).toISOString(),
1842
+ returnedAt: new Date(builtAt).toISOString(),
1843
+ },
1844
+ };
1845
+ this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next), queueRevision: getMeshQueueRevision(meshId) });
1846
+ return next;
1847
+ }
1848
+
823
1849
  public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
824
1850
  if (inlineMesh && typeof inlineMesh === 'object') {
825
- this.inlineMeshCache.set(meshId, inlineMesh as any);
826
- return inlineMesh as any;
1851
+ return this.warmInlineMeshCache(meshId, inlineMesh);
827
1852
  }
828
1853
  return this.inlineMeshCache.get(meshId);
829
1854
  }
830
1855
 
1856
+ private warmInlineMeshCache(meshId: string, inlineMesh?: unknown): any | undefined {
1857
+ if (!inlineMesh || typeof inlineMesh !== 'object') return undefined;
1858
+ const sanitizedInlineMesh = sanitizeInlineMesh(inlineMesh as any);
1859
+ const cached = this.inlineMeshCache.get(meshId);
1860
+ if (cached) {
1861
+ const merged = reconcileInlineMeshCache(cached, sanitizedInlineMesh);
1862
+ this.inlineMeshCache.set(meshId, merged);
1863
+ return merged;
1864
+ }
1865
+ this.inlineMeshCache.set(meshId, sanitizedInlineMesh as any);
1866
+ return sanitizedInlineMesh as any;
1867
+ }
1868
+
831
1869
  private async getMeshForCommand(
832
1870
  meshId: string,
833
1871
  inlineMesh?: unknown,
834
1872
  options?: { preferInline?: boolean },
835
- ): Promise<{ mesh: any; inline: boolean } | null> {
1873
+ ): Promise<{ mesh: any; inline: boolean; source: 'inline_cache' | 'inline_bootstrap' | 'local_config' } | null> {
836
1874
  const preferInline = options?.preferInline === true;
837
1875
  if (preferInline) {
838
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
839
- if (cached) return { mesh: cached, inline: true };
1876
+ const cached = this.getCachedInlineMesh(meshId);
1877
+ if (cached) {
1878
+ if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
1879
+ const merged = reconcileInlineMeshCache(cached, inlineMesh as any);
1880
+ this.inlineMeshCache.set(meshId, sanitizeInlineMesh(merged));
1881
+ return { mesh: merged, inline: true, source: 'inline_cache' };
1882
+ }
1883
+ return { mesh: cached, inline: true, source: 'inline_cache' };
1884
+ }
1885
+ if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
1886
+ this.warmInlineMeshCache(meshId, inlineMesh);
1887
+ return { mesh: inlineMesh, inline: true, source: 'inline_bootstrap' };
1888
+ }
840
1889
  }
841
1890
  try {
842
1891
  const { getMesh } = await import('../config/mesh-config.js');
843
1892
  const mesh = getMesh(meshId);
844
- if (mesh) return { mesh, inline: false };
1893
+ if (mesh) return { mesh, inline: false, source: 'local_config' };
845
1894
  } catch { /* fall through to inline cache */ }
846
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
847
- return cached ? { mesh: cached, inline: true } : null;
1895
+ const cached = this.getCachedInlineMesh(meshId);
1896
+ if (cached) return { mesh: cached, inline: true, source: 'inline_cache' };
1897
+ const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
1898
+ return warmedInline ? { mesh: warmedInline, inline: true, source: 'inline_bootstrap' } : null;
1899
+ }
1900
+
1901
+ private invalidateAggregateMeshStatus(meshId: string): void {
1902
+ this.aggregateMeshStatusCache.delete(meshId);
1903
+ }
1904
+
1905
+
1906
+ private async requireMeshHostMutationOwner(meshId: string, inlineMesh: unknown, operation: string): Promise<CommandRouterResult | null> {
1907
+ const meshRecord = await this.getMeshForCommand(meshId, inlineMesh, { preferInline: true });
1908
+ const mesh = meshRecord?.mesh;
1909
+ if (!mesh) return { success: false, error: 'Mesh not found' };
1910
+ const meshHost = resolveMeshHostStatus(mesh);
1911
+ if (!meshHost.canOwnCoordinator || !meshHost.canOwnQueue) {
1912
+ return { ...buildMeshHostRequiredFailure(mesh, operation), success: false, meshId };
1913
+ }
1914
+ return null;
848
1915
  }
849
1916
 
850
1917
  private updateInlineMeshNode(meshId: string, mesh: any, node: any): void {
@@ -854,6 +1921,7 @@ export class DaemonCommandRouter {
854
1921
  else mesh.nodes.push(node);
855
1922
  mesh.updatedAt = new Date().toISOString();
856
1923
  this.inlineMeshCache.set(meshId, mesh);
1924
+ this.invalidateAggregateMeshStatus(meshId);
857
1925
  }
858
1926
 
859
1927
  private removeInlineMeshNode(meshId: string, mesh: any, nodeId: string): boolean {
@@ -863,6 +1931,7 @@ export class DaemonCommandRouter {
863
1931
  mesh.nodes.splice(idx, 1);
864
1932
  mesh.updatedAt = new Date().toISOString();
865
1933
  this.inlineMeshCache.set(meshId, mesh);
1934
+ this.invalidateAggregateMeshStatus(meshId);
866
1935
  return true;
867
1936
  }
868
1937
 
@@ -1141,6 +2210,7 @@ export class DaemonCommandRouter {
1141
2210
  const deletedSessionIds: string[] = [];
1142
2211
  const skippedSessionIds: string[] = [];
1143
2212
  const skippedLiveSessionIds: string[] = [];
2213
+ const skippedCoordinatorSessionIds: string[] = [];
1144
2214
  const deleteUnsupportedSessionIds: string[] = [];
1145
2215
  const recordsRemainSessionIds: string[] = [];
1146
2216
  const errors: Array<{ sessionId: string; error: string }> = [];
@@ -1175,6 +2245,12 @@ export class DaemonCommandRouter {
1175
2245
  const completed = this.isCompletedHostedSession(record);
1176
2246
  const surfaceKind = getSessionHostSurfaceKind(record);
1177
2247
  const liveRuntime = surfaceKind === 'live_runtime';
2248
+ const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
2249
+ if (!hasExplicitSessionIds && coordinatorSession) {
2250
+ skippedSessionIds.push(sessionId);
2251
+ skippedCoordinatorSessionIds.push(sessionId);
2252
+ continue;
2253
+ }
1178
2254
  if (!hasExplicitSessionIds && liveRuntime) {
1179
2255
  skippedSessionIds.push(sessionId);
1180
2256
  skippedLiveSessionIds.push(sessionId);
@@ -1244,6 +2320,7 @@ export class DaemonCommandRouter {
1244
2320
  deletedSessionIds,
1245
2321
  skippedSessionIds,
1246
2322
  skippedLiveSessionIds,
2323
+ skippedCoordinatorSessionIds,
1247
2324
  ...(deleteUnsupported ? {
1248
2325
  deleteUnsupported: true,
1249
2326
  effectiveCleanup: args.mode === 'stop_and_delete'
@@ -1365,23 +2442,430 @@ export class DaemonCommandRouter {
1365
2442
  payload: { cmd, source: logSource, success: handlerResult.success, durationMs: Date.now() - cmdStart },
1366
2443
  });
1367
2444
 
1368
- // 3. Post-chat command callback
1369
- if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
1370
- this.deps.onPostChatCommand();
2445
+ // 3. Post-chat command callback
2446
+ if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
2447
+ this.deps.onPostChatCommand();
2448
+ }
2449
+
2450
+ return handlerResult;
2451
+ } catch (e: any) {
2452
+ logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: false, error: e.message, durationMs: Date.now() - cmdStart });
2453
+ recordDebugTrace({
2454
+ interactionId,
2455
+ category: 'command',
2456
+ stage: 'failed',
2457
+ level: 'error',
2458
+ payload: { cmd, source: logSource, error: e?.message || String(e), durationMs: Date.now() - cmdStart },
2459
+ });
2460
+ throw e;
2461
+ }
2462
+ }
2463
+
2464
+
2465
+ private buildRefineJobKey(meshId: string, nodeId: string): string {
2466
+ return `${meshId}:${nodeId}`;
2467
+ }
2468
+
2469
+ private buildRefineJobHandle(args: {
2470
+ meshId: string;
2471
+ nodeId: string;
2472
+ node?: any;
2473
+ status?: MeshRefineAsyncJobStatus;
2474
+ startedAt?: string;
2475
+ completedAt?: string;
2476
+ jobId?: string;
2477
+ interactionId?: string;
2478
+ retryOfJobId?: string;
2479
+ }): MeshRefineJobHandle {
2480
+ return {
2481
+ success: true,
2482
+ async: true,
2483
+ status: args.status || 'accepted',
2484
+ jobId: args.jobId || `refine_${createInteractionId()}`,
2485
+ interactionId: args.interactionId || createInteractionId(),
2486
+ meshId: args.meshId,
2487
+ nodeId: args.nodeId,
2488
+ targetNodeId: args.nodeId,
2489
+ targetDaemonId: readStringValue(args.node?.daemonId),
2490
+ workspace: readStringValue(args.node?.workspace),
2491
+ startedAt: args.startedAt || new Date().toISOString(),
2492
+ ...(args.completedAt ? { completedAt: args.completedAt } : {}),
2493
+ ...(args.retryOfJobId ? { retryOfJobId: args.retryOfJobId } : {}),
2494
+ eventDelivery: { pendingEvents: true, ledger: true },
2495
+ evidence: {
2496
+ pendingEventsCommand: 'get_pending_mesh_events',
2497
+ ledgerCommand: 'get_mesh_ledger_slice',
2498
+ taskHistoryKind: args.status === 'completed' ? 'task_completed' : args.status === 'failed' ? 'task_failed' : 'task_dispatched',
2499
+ },
2500
+ };
2501
+ }
2502
+
2503
+ private queueRefineJobEvent(event: 'refine:accepted' | 'refine:completed' | 'refine:failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): void {
2504
+ queuePendingMeshCoordinatorEvent({
2505
+ event,
2506
+ meshId: handle.meshId,
2507
+ nodeLabel: handle.targetNodeId,
2508
+ nodeId: handle.targetNodeId,
2509
+ workspace: handle.workspace,
2510
+ metadataEvent: {
2511
+ source: 'refine_mesh_node_async_job',
2512
+ jobId: handle.jobId,
2513
+ interactionId: handle.interactionId,
2514
+ meshId: handle.meshId,
2515
+ nodeId: handle.targetNodeId,
2516
+ targetDaemonId: handle.targetDaemonId,
2517
+ workspace: handle.workspace,
2518
+ status: handle.status,
2519
+ startedAt: handle.startedAt,
2520
+ completedAt: handle.completedAt,
2521
+ retryOfJobId: handle.retryOfJobId,
2522
+ ...(result ? { result } : {}),
2523
+ },
2524
+ queuedAt: Date.now(),
2525
+ });
2526
+ }
2527
+
2528
+ private async appendRefineJobLedger(kind: 'task_dispatched' | 'task_completed' | 'task_failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): Promise<void> {
2529
+ try {
2530
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2531
+ appendLedgerEntry(handle.meshId, {
2532
+ kind,
2533
+ nodeId: handle.targetNodeId,
2534
+ payload: {
2535
+ source: 'refine_mesh_node_async_job',
2536
+ refineJob: {
2537
+ jobId: handle.jobId,
2538
+ interactionId: handle.interactionId,
2539
+ status: handle.status,
2540
+ meshId: handle.meshId,
2541
+ nodeId: handle.targetNodeId,
2542
+ targetDaemonId: handle.targetDaemonId,
2543
+ workspace: handle.workspace,
2544
+ startedAt: handle.startedAt,
2545
+ completedAt: handle.completedAt,
2546
+ retryOfJobId: handle.retryOfJobId,
2547
+ },
2548
+ async: true,
2549
+ retryOfJobId: handle.retryOfJobId,
2550
+ ...(result ? {
2551
+ success: result.success === true,
2552
+ result,
2553
+ finalBranchConvergenceState: result.finalBranchConvergenceState,
2554
+ } : {}),
2555
+ },
2556
+ });
2557
+ } catch (e: any) {
2558
+ LOG.warn('Mesh', `[Refinery] Failed to append async refine ledger entry: ${e?.message || e}`);
2559
+ }
2560
+ }
2561
+
2562
+ private async executeMeshRefineNodeSynchronously(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
2563
+ const refineStages: Array<Record<string, unknown>> = [];
2564
+ try {
2565
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2566
+ const mesh = meshRecord?.mesh;
2567
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2568
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh`, refineStages };
2569
+
2570
+ if (!node.isLocalWorktree || !node.workspace) {
2571
+ return { success: false, error: `Refinery requires a local worktree node`, refineStages };
2572
+ }
2573
+
2574
+ const sourceNode = node.clonedFromNodeId
2575
+ ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2576
+ : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2577
+ const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2578
+ if (!repoRoot) return { success: false, error: 'Source node repoRoot not found', refineStages };
2579
+
2580
+ const { execFile } = await import('node:child_process');
2581
+ const { promisify } = await import('node:util');
2582
+ const execFileAsync = promisify(execFile);
2583
+
2584
+ const resolveStarted = Date.now();
2585
+ const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2586
+ const branch = branchStdout.trim();
2587
+ if (!branch) return { success: false, error: 'Could not determine branch of the worktree node', refineStages };
2588
+
2589
+ const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2590
+ const baseBranch = baseBranchStdout.trim();
2591
+ const { stdout: baseHeadStdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
2592
+ const { stdout: branchHeadStdout } = await execFileAsync('git', ['rev-parse', branch], { cwd: node.workspace, encoding: 'utf8' });
2593
+ const baseHead = baseHeadStdout.trim();
2594
+ const branchHead = branchHeadStdout.trim();
2595
+ recordMeshRefineStage(refineStages, 'resolve_refs', 'passed', resolveStarted, { branch, baseBranch, baseHead, branchHead });
2596
+
2597
+ const validationStarted = Date.now();
2598
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2599
+ recordMeshRefineStage(
2600
+ refineStages,
2601
+ 'validation',
2602
+ validationSummary.status === 'passed' ? 'passed' : validationSummary.status === 'failed' ? 'failed' : 'skipped',
2603
+ validationStarted,
2604
+ { validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length },
2605
+ );
2606
+ if (validationSummary.status === 'failed') {
2607
+ return {
2608
+ success: false,
2609
+ code: 'validation_failed',
2610
+ convergenceStatus: 'blocked_review',
2611
+ error: 'Refinery validation gate failed; merge/refine was not attempted.',
2612
+ branch,
2613
+ into: baseBranch,
2614
+ validationSummary,
2615
+ refineStages,
2616
+ finalBranchConvergenceState: {
2617
+ branch,
2618
+ baseBranch,
2619
+ merged: false,
2620
+ removed: false,
2621
+ validation: 'failed',
2622
+ status: 'blocked_review',
2623
+ },
2624
+ };
2625
+ }
2626
+ if (validationSummary.status === 'skipped') {
2627
+ return {
2628
+ success: false,
2629
+ code: 'validation_unavailable',
2630
+ convergenceStatus: 'blocked_review',
2631
+ error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
2632
+ branch,
2633
+ into: baseBranch,
2634
+ validationSummary,
2635
+ refineStages,
2636
+ finalBranchConvergenceState: {
2637
+ branch,
2638
+ baseBranch,
2639
+ merged: false,
2640
+ removed: false,
2641
+ validation: 'unavailable',
2642
+ status: 'blocked_review',
2643
+ },
2644
+ };
2645
+ }
2646
+
2647
+ const patchEquivalenceStarted = Date.now();
2648
+ const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
2649
+ recordMeshRefineStage(refineStages, 'patch_equivalence', patchEquivalence.status, patchEquivalenceStarted, {
2650
+ equivalent: patchEquivalence.equivalent,
2651
+ expectedPatchId: patchEquivalence.expectedPatchId,
2652
+ actualPatchId: patchEquivalence.actualPatchId,
2653
+ error: patchEquivalence.error,
2654
+ });
2655
+ if (!patchEquivalence.equivalent) {
2656
+ return {
2657
+ success: false,
2658
+ code: 'patch_equivalence_failed',
2659
+ convergenceStatus: 'blocked_review',
2660
+ error: 'Refinery patch-equivalence preflight failed; merge/refine was not attempted.',
2661
+ branch,
2662
+ into: baseBranch,
2663
+ validationSummary,
2664
+ patchEquivalence,
2665
+ refineStages,
2666
+ finalBranchConvergenceState: {
2667
+ branch,
2668
+ baseBranch,
2669
+ merged: false,
2670
+ removed: false,
2671
+ validation: 'passed',
2672
+ patchEquivalence: 'failed',
2673
+ status: 'blocked_review',
2674
+ },
2675
+ };
2676
+ }
2677
+
2678
+ const submoduleReachabilityStarted = Date.now();
2679
+ const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead);
2680
+ recordMeshRefineStage(refineStages, 'submodule_reachability', submoduleReachability.status, submoduleReachabilityStarted, {
2681
+ checked: submoduleReachability.checked,
2682
+ unreachable: submoduleReachability.unreachable.map(entry => ({ path: entry.path, commit: entry.commit, error: entry.error })),
2683
+ error: submoduleReachability.error,
2684
+ });
2685
+ if (submoduleReachability.status === 'failed') {
2686
+ return {
2687
+ success: false,
2688
+ code: 'submodule_reachability_failed',
2689
+ convergenceStatus: 'blocked_review',
2690
+ error: 'Refinery submodule reachability preflight failed; merge/refine cleanup was not attempted.',
2691
+ branch,
2692
+ into: baseBranch,
2693
+ validationSummary,
2694
+ patchEquivalence,
2695
+ submoduleReachability,
2696
+ refineStages,
2697
+ finalBranchConvergenceState: {
2698
+ branch,
2699
+ baseBranch,
2700
+ merged: false,
2701
+ removed: false,
2702
+ validation: 'passed',
2703
+ patchEquivalence: 'passed',
2704
+ submoduleReachability: 'failed',
2705
+ status: 'blocked_review',
2706
+ },
2707
+ };
2708
+ }
2709
+
2710
+ let mergeResult: Record<string, unknown> | undefined;
2711
+ const mergeStarted = Date.now();
2712
+ try {
2713
+ const result = await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2714
+ mergeResult = {
2715
+ stdout: truncateValidationOutput(result.stdout),
2716
+ stderr: truncateValidationOutput(result.stderr),
2717
+ durationMs: Date.now() - mergeStarted,
2718
+ };
2719
+ recordMeshRefineStage(refineStages, 'merge', 'passed', mergeStarted, mergeResult);
2720
+ } catch (e: any) {
2721
+ recordMeshRefineStage(refineStages, 'merge', 'failed', mergeStarted, {
2722
+ error: e?.message || String(e),
2723
+ stdout: truncateValidationOutput(e?.stdout),
2724
+ stderr: truncateValidationOutput(e?.stderr),
2725
+ });
2726
+ return {
2727
+ success: false,
2728
+ error: `Merge failed (conflicts?): ${e.message}`,
2729
+ validationSummary,
2730
+ patchEquivalence,
2731
+ refineStages,
2732
+ finalBranchConvergenceState: {
2733
+ branch,
2734
+ baseBranch,
2735
+ merged: false,
2736
+ removed: false,
2737
+ validation: 'passed',
2738
+ patchEquivalence: 'passed',
2739
+ status: 'not_mergeable',
2740
+ },
2741
+ };
2742
+ }
2743
+
2744
+ const cleanupStarted = Date.now();
2745
+ const removeResult = await this.execute('remove_mesh_node', {
2746
+ meshId,
2747
+ nodeId,
2748
+ sessionCleanupMode: 'preserve',
2749
+ inlineMesh: args?.inlineMesh,
2750
+ });
2751
+ recordMeshRefineStage(refineStages, 'cleanup', removeResult?.success === false ? 'failed' : 'passed', cleanupStarted, {
2752
+ removed: removeResult?.removed,
2753
+ code: removeResult?.code,
2754
+ error: removeResult?.error,
2755
+ });
2756
+
2757
+ let ledgerError: string | undefined;
2758
+ const ledgerStarted = Date.now();
2759
+ try {
2760
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2761
+ appendLedgerEntry(meshId, {
2762
+ kind: 'node_removed',
2763
+ nodeId,
2764
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence },
2765
+ });
2766
+ recordMeshRefineStage(refineStages, 'ledger', 'passed', ledgerStarted);
2767
+ } catch (e: any) {
2768
+ ledgerError = e?.message || String(e);
2769
+ recordMeshRefineStage(refineStages, 'ledger', 'failed', ledgerStarted, { error: ledgerError });
1371
2770
  }
1372
2771
 
1373
- return handlerResult;
2772
+ const finalBranchConvergenceState = {
2773
+ branch: baseBranch,
2774
+ mergedBranch: branch,
2775
+ baseBranch,
2776
+ merged: true,
2777
+ removed: removeResult?.success !== false,
2778
+ validation: 'passed',
2779
+ patchEquivalence: 'passed',
2780
+ status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
2781
+ };
2782
+
2783
+ if (removeResult?.success === false) {
2784
+ return {
2785
+ success: false,
2786
+ code: 'cleanup_failed',
2787
+ error: 'Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.',
2788
+ merged: true,
2789
+ branch,
2790
+ into: baseBranch,
2791
+ removeResult,
2792
+ validationSummary,
2793
+ patchEquivalence,
2794
+ mergeResult,
2795
+ refineStages,
2796
+ ...(ledgerError ? { ledgerError } : {}),
2797
+ finalBranchConvergenceState,
2798
+ };
2799
+ }
2800
+
2801
+ return {
2802
+ success: true,
2803
+ merged: true,
2804
+ branch,
2805
+ into: baseBranch,
2806
+ removeResult,
2807
+ validationSummary,
2808
+ patchEquivalence,
2809
+ mergeResult,
2810
+ refineStages,
2811
+ ...(ledgerError ? { ledgerError } : {}),
2812
+ finalBranchConvergenceState,
2813
+ };
1374
2814
  } catch (e: any) {
1375
- logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: false, error: e.message, durationMs: Date.now() - cmdStart });
1376
- recordDebugTrace({
1377
- interactionId,
1378
- category: 'command',
1379
- stage: 'failed',
1380
- level: 'error',
1381
- payload: { cmd, source: logSource, error: e?.message || String(e), durationMs: Date.now() - cmdStart },
1382
- });
1383
- throw e;
2815
+ return { success: false, error: e.message, refineStages };
2816
+ }
2817
+ }
2818
+
2819
+ private async finishMeshRefineJob(handle: MeshRefineJobHandle, args: any): Promise<void> {
2820
+ const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
2821
+ let result: Record<string, unknown>;
2822
+ try {
2823
+ result = await this.executeMeshRefineNodeSynchronously(handle.meshId, handle.targetNodeId, args) as Record<string, unknown>;
2824
+ } catch (e: any) {
2825
+ result = { success: false, error: e?.message || String(e) };
1384
2826
  }
2827
+ const completedAt = new Date().toISOString();
2828
+ const terminalHandle = this.buildRefineJobHandle({
2829
+ meshId: handle.meshId,
2830
+ nodeId: handle.targetNodeId,
2831
+ status: result.success === true ? 'completed' : 'failed',
2832
+ startedAt: handle.startedAt,
2833
+ completedAt,
2834
+ jobId: handle.jobId,
2835
+ interactionId: handle.interactionId,
2836
+ retryOfJobId: handle.retryOfJobId,
2837
+ node: { daemonId: handle.targetDaemonId, workspace: handle.workspace },
2838
+ });
2839
+ const terminal: MeshRefineTerminalJob = { ...terminalHandle, result };
2840
+ this.terminalRefineJobs.set(key, terminal);
2841
+ this.runningRefineJobs.delete(key);
2842
+ this.invalidateAggregateMeshStatus(handle.meshId);
2843
+ await this.appendRefineJobLedger(result.success === true ? 'task_completed' : 'task_failed', terminalHandle, result);
2844
+ this.queueRefineJobEvent(result.success === true ? 'refine:completed' : 'refine:failed', terminalHandle, result);
2845
+ }
2846
+
2847
+ private async startMeshRefineJob(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
2848
+ const key = this.buildRefineJobKey(meshId, nodeId);
2849
+ const running = this.runningRefineJobs.get(key);
2850
+ if (running) return { ...running, duplicate: true };
2851
+ const terminal = this.terminalRefineJobs.get(key);
2852
+
2853
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2854
+ const mesh = meshRecord?.mesh;
2855
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2856
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2857
+ if (!node.isLocalWorktree || !node.workspace) return { success: false, error: `Refinery requires a local worktree node` };
2858
+
2859
+ const handle = this.buildRefineJobHandle({ meshId, nodeId, node, retryOfJobId: terminal?.jobId });
2860
+ this.runningRefineJobs.set(key, handle);
2861
+ await this.appendRefineJobLedger('task_dispatched', handle);
2862
+ this.queueRefineJobEvent('refine:accepted', handle);
2863
+
2864
+ setImmediate(() => {
2865
+ void this.finishMeshRefineJob(handle, args);
2866
+ });
2867
+
2868
+ return handle;
1385
2869
  }
1386
2870
 
1387
2871
  // ─── Daemon-level command core ───────────────────
@@ -1398,7 +2882,8 @@ export class DaemonCommandRouter {
1398
2882
  }
1399
2883
 
1400
2884
  case 'get_pending_mesh_events': {
1401
- const events = drainPendingMeshCoordinatorEvents();
2885
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2886
+ const events = drainPendingMeshCoordinatorEvents(meshId || undefined);
1402
2887
  return { success: true, events };
1403
2888
  }
1404
2889
 
@@ -2003,15 +3488,44 @@ export class DaemonCommandRouter {
2003
3488
  case 'get_mesh': {
2004
3489
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2005
3490
  if (!meshId) return { success: false, error: 'meshId required' };
2006
- try {
2007
- const { getMesh } = await import('../config/mesh-config.js');
2008
- const mesh = getMesh(meshId);
2009
- if (mesh) return { success: true, mesh };
2010
- } catch { /* fall through to inline cache */ }
2011
- // Fallback: check in-memory cache for cloud-originating meshes
2012
- const cached = this.inlineMeshCache.get(meshId);
2013
- if (cached) return { success: true, mesh: cached };
2014
- return { success: false, error: 'Mesh not found' };
3491
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3492
+ if (!meshRecord?.mesh) return { success: false, error: 'Mesh not found' };
3493
+
3494
+ const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
3495
+ const directTruth = await hydrateInlineMeshDirectTruth({
3496
+ mesh: meshRecord.mesh,
3497
+ meshSource: meshRecord.source,
3498
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
3499
+ statusInstanceId: this.deps.statusInstanceId,
3500
+ localMachineId: loadConfig().machineId || '',
3501
+ });
3502
+ const directTruthSatisfied = meshRecord.source !== 'inline_bootstrap' || directTruth.directEvidenceCount > 0;
3503
+ const sourceOfTruth = {
3504
+ membership: meshRecord.source === 'inline_cache'
3505
+ ? 'coordinator_inline_mesh_cache'
3506
+ : meshRecord.source === 'local_config'
3507
+ ? 'local_mesh_config'
3508
+ : 'inline_bootstrap_snapshot',
3509
+ coordinatorOwnsLiveTruth: directTruthSatisfied,
3510
+ directPeerTruth: {
3511
+ required: requireDirectPeerTruth,
3512
+ satisfied: directTruthSatisfied,
3513
+ directEvidenceCount: directTruth.directEvidenceCount,
3514
+ localConfirmedCount: directTruth.localConfirmedCount,
3515
+ peerAttemptedCount: directTruth.peerAttemptedCount,
3516
+ peerConfirmedCount: directTruth.peerConfirmedCount,
3517
+ unavailableNodeIds: directTruth.unavailableNodeIds,
3518
+ },
3519
+ };
3520
+ if (requireDirectPeerTruth && !directTruthSatisfied) {
3521
+ return {
3522
+ success: false,
3523
+ code: 'mesh_direct_peer_truth_unavailable',
3524
+ error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct get_mesh probes succeed.',
3525
+ sourceOfTruth,
3526
+ };
3527
+ }
3528
+ return { success: true, mesh: meshRecord.mesh, sourceOfTruth };
2015
3529
  }
2016
3530
 
2017
3531
  case 'create_mesh': {
@@ -2022,7 +3536,10 @@ export class DaemonCommandRouter {
2022
3536
  if (!name) return { success: false, error: 'name required' };
2023
3537
  try {
2024
3538
  const { createMesh } = await import('../config/mesh-config.js');
2025
- const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy });
3539
+ const meshHost = args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)
3540
+ ? args.meshHost
3541
+ : undefined;
3542
+ const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy, meshHost });
2026
3543
  return { success: true, mesh };
2027
3544
  } catch (e: any) {
2028
3545
  return { success: false, error: e.message };
@@ -2039,16 +3556,237 @@ export class DaemonCommandRouter {
2039
3556
  if (typeof args?.defaultBranch === 'string') patch.defaultBranch = args.defaultBranch;
2040
3557
  if (args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)) patch.policy = args.policy;
2041
3558
  if (args?.coordinator && typeof args.coordinator === 'object' && !Array.isArray(args.coordinator)) patch.coordinator = args.coordinator;
3559
+ if (args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)) patch.meshHost = args.meshHost;
2042
3560
  if (!Object.keys(patch).length) return { success: false, error: 'No updates provided' };
2043
3561
  const mesh = updateMesh(meshId, patch as any);
2044
3562
  if (!mesh) return { success: false, error: 'Mesh not found' };
2045
3563
  this.inlineMeshCache.set(meshId, mesh);
3564
+ this.invalidateAggregateMeshStatus(meshId);
2046
3565
  return { success: true, mesh };
2047
3566
  } catch (e: any) {
2048
3567
  return { success: false, error: e.message };
2049
3568
  }
2050
3569
  }
2051
3570
 
3571
+ case 'get_mesh_host_pairing': {
3572
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3573
+ if (!meshId) return { success: false, error: 'meshId required' };
3574
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3575
+ const mesh = meshRecord?.mesh;
3576
+ if (!mesh) return { success: false, error: 'Mesh not found' };
3577
+ const meshHost = resolveMeshHostStatus(mesh);
3578
+ const pairingStatus = meshHost.pairing?.status || 'not_configured';
3579
+ return {
3580
+ success: true,
3581
+ code: pairingStatus === 'not_configured' ? 'mesh_host_pairing_not_configured' : 'mesh_host_pairing_pending',
3582
+ meshId,
3583
+ hostAddress: meshHost.hostAddress,
3584
+ meshHost,
3585
+ manualPairing: {
3586
+ status: pairingStatus,
3587
+ joinImplemented: true,
3588
+ protocol: 'standalone_command_direct_v1',
3589
+ description: 'Standalone manual pairing can save address/token metadata, apply a host join over direct standalone command HTTP or injected mesh command dispatch, and check persisted status. P2P signaling remains outside this slice.',
3590
+ },
3591
+ };
3592
+ }
3593
+
3594
+ case 'configure_mesh_host_pairing': {
3595
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3596
+ const hostAddress = typeof args?.hostAddress === 'string' ? args.hostAddress.trim() : '';
3597
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
3598
+ if (!meshId) return { success: false, error: 'meshId required' };
3599
+ if (!hostAddress || !token) return { success: false, error: 'hostAddress and token required' };
3600
+ try {
3601
+ const { configureMeshHostPairing } = await import('../config/mesh-config.js');
3602
+ const configured = configureMeshHostPairing(meshId, { hostAddress, token });
3603
+ if (!configured) return { success: false, error: 'Mesh not found' };
3604
+ this.inlineMeshCache.set(meshId, configured.mesh);
3605
+ const meshHost = resolveMeshHostStatus(configured.mesh);
3606
+ return {
3607
+ success: true,
3608
+ code: 'mesh_host_pairing_pending',
3609
+ meshId,
3610
+ hostAddress: configured.hostAddress,
3611
+ meshHost,
3612
+ manualPairing: {
3613
+ status: meshHost.pairing?.status || 'pairing',
3614
+ joinImplemented: true,
3615
+ protocol: 'standalone_command_direct_v1',
3616
+ description: 'Manual Mesh Host pairing config was saved locally. Use join_mesh_host_pairing to apply it to the host. Raw token was not persisted.',
3617
+ },
3618
+ };
3619
+ } catch (e: any) {
3620
+ return { success: false, code: 'mesh_host_pairing_invalid', meshId, hostAddress, error: e.message };
3621
+ }
3622
+ }
3623
+
3624
+ case 'create_mesh_host_pairing_token': {
3625
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3626
+ if (!meshId) return { success: false, error: 'meshId required' };
3627
+ try {
3628
+ const { createMeshHostPairingToken } = await import('../config/mesh-config.js');
3629
+ const created = createMeshHostPairingToken(meshId, {
3630
+ token: typeof args?.token === 'string' ? args.token : undefined,
3631
+ expiresAt: typeof args?.expiresAt === 'string' ? args.expiresAt : undefined,
3632
+ });
3633
+ if (!created) return { success: false, error: 'Mesh not found' };
3634
+ this.inlineMeshCache.set(meshId, created.mesh);
3635
+ this.invalidateAggregateMeshStatus(meshId);
3636
+ return {
3637
+ success: true,
3638
+ code: 'mesh_host_pairing_token_created',
3639
+ meshId,
3640
+ token: created.token,
3641
+ tokenId: created.tokenId,
3642
+ expiresAt: created.expiresAt,
3643
+ meshHost: resolveMeshHostStatus(created.mesh),
3644
+ warning: 'Raw token is returned once and is not persisted; share it with member daemons over a trusted channel.',
3645
+ };
3646
+ } catch (e: any) {
3647
+ return { success: false, code: 'mesh_host_pairing_token_invalid', meshId, error: e.message };
3648
+ }
3649
+ }
3650
+
3651
+ case 'apply_mesh_host_join': {
3652
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3653
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
3654
+ const memberNode = args?.memberNode && typeof args.memberNode === 'object' && !Array.isArray(args.memberNode)
3655
+ ? args.memberNode
3656
+ : null;
3657
+ if (!meshId) return { success: false, error: 'meshId required' };
3658
+ if (!token || !memberNode) return { success: false, error: 'token and memberNode required' };
3659
+ try {
3660
+ const { applyMeshHostJoinRequest } = await import('../config/mesh-config.js');
3661
+ const applied = applyMeshHostJoinRequest(meshId, {
3662
+ token,
3663
+ memberNode: memberNode as any,
3664
+ memberMeshId: typeof args?.memberMeshId === 'string' ? args.memberMeshId : undefined,
3665
+ });
3666
+ if (!applied) return { success: false, error: 'Mesh not found' };
3667
+ if (!applied.accepted) {
3668
+ return {
3669
+ success: false,
3670
+ code: 'mesh_host_join_rejected',
3671
+ meshId,
3672
+ tokenId: applied.tokenId,
3673
+ meshHost: applied.meshHost ? resolveMeshHostStatus({ meshHost: applied.meshHost }) : undefined,
3674
+ error: applied.reason,
3675
+ };
3676
+ }
3677
+ this.inlineMeshCache.set(meshId, applied.mesh);
3678
+ this.invalidateAggregateMeshStatus(meshId);
3679
+ try {
3680
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
3681
+ appendLedgerEntry(meshId, {
3682
+ kind: 'node_joined',
3683
+ nodeId: applied.node.id,
3684
+ payload: { role: 'member', tokenId: applied.tokenId, workspace: applied.node.workspace },
3685
+ });
3686
+ } catch { /* ledger append is best-effort */ }
3687
+ return {
3688
+ success: true,
3689
+ code: 'mesh_host_join_accepted',
3690
+ meshId,
3691
+ node: applied.node,
3692
+ tokenId: applied.tokenId,
3693
+ meshHost: resolveMeshHostStatus(applied.mesh),
3694
+ };
3695
+ } catch (e: any) {
3696
+ return { success: false, code: 'mesh_host_join_failed', meshId, error: e.message };
3697
+ }
3698
+ }
3699
+
3700
+ case 'join_mesh_host_pairing': {
3701
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3702
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
3703
+ if (!meshId) return { success: false, error: 'meshId required' };
3704
+ if (!token) return { success: false, error: 'token required because raw pairing tokens are not persisted' };
3705
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3706
+ const mesh = meshRecord?.mesh;
3707
+ if (!mesh) return { success: false, error: 'Mesh not found' };
3708
+ const meshHost = resolveMeshHostStatus(mesh);
3709
+ if (meshHost.role !== 'member') {
3710
+ return { success: false, code: 'mesh_host_join_not_member', meshId, meshHost, error: 'join_mesh_host_pairing must run from a member daemon configured with a Mesh Host address/token.' };
3711
+ }
3712
+ try {
3713
+ const { tokenIdForManualPairing, markMeshHostPairingJoined } = await import('../config/mesh-config.js');
3714
+ const tokenId = tokenIdForManualPairing(token);
3715
+ if (meshHost.pairing?.tokenId && meshHost.pairing.tokenId !== tokenId) {
3716
+ return { success: false, code: 'mesh_host_join_rejected', meshId, tokenId, meshHost, error: 'invalid pairing token' };
3717
+ }
3718
+ const memberNode = buildMemberJoinNode(mesh, args, this.deps.statusInstanceId);
3719
+ if (!memberNode) return { success: false, error: 'member node metadata unavailable' };
3720
+ const hostMeshId = typeof args?.hostMeshId === 'string' && args.hostMeshId.trim() ? args.hostMeshId.trim() : meshId;
3721
+ const hostDaemonId = typeof args?.hostDaemonId === 'string' && args.hostDaemonId.trim()
3722
+ ? args.hostDaemonId.trim()
3723
+ : meshHost.hostDaemonId;
3724
+ let hostResult: any;
3725
+ let transport: string;
3726
+ if (hostDaemonId && this.deps.dispatchMeshCommand) {
3727
+ transport = 'mesh_command_dispatch';
3728
+ hostResult = await this.deps.dispatchMeshCommand(hostDaemonId, 'apply_mesh_host_join', {
3729
+ meshId: hostMeshId,
3730
+ token,
3731
+ memberMeshId: meshId,
3732
+ memberNode,
3733
+ });
3734
+ } else if (meshHost.hostAddress) {
3735
+ transport = 'standalone_http_command';
3736
+ const commandUrl = normalizeStandaloneHostCommandUrl(meshHost.hostAddress);
3737
+ const response = await fetch(commandUrl, {
3738
+ method: 'POST',
3739
+ headers: { 'Content-Type': 'application/json' },
3740
+ body: JSON.stringify({ type: 'apply_mesh_host_join', payload: { meshId: hostMeshId, token, memberMeshId: meshId, memberNode } }),
3741
+ });
3742
+ hostResult = await response.json().catch(() => ({ success: false, error: `Host returned HTTP ${response.status}` }));
3743
+ if (!response.ok && hostResult?.success !== false) hostResult = { success: false, error: `Host returned HTTP ${response.status}` };
3744
+ } else {
3745
+ return {
3746
+ success: false,
3747
+ code: 'mesh_host_join_transport_unavailable',
3748
+ meshId,
3749
+ meshHost,
3750
+ error: 'No hostDaemonId dispatch path or hostAddress HTTP command path is available. P2P signaling join is not implemented in this slice.',
3751
+ };
3752
+ }
3753
+ if (!hostResult?.success) {
3754
+ return { success: false, code: hostResult?.code || 'mesh_host_join_rejected', meshId, meshHost, transport, error: hostResult?.error || 'Mesh Host rejected join request', hostResult };
3755
+ }
3756
+ const joined = meshRecord.inline
3757
+ ? null
3758
+ : markMeshHostPairingJoined(meshId, {
3759
+ tokenId: hostResult.tokenId || tokenId,
3760
+ hostDaemonId: hostResult.meshHost?.hostDaemonId || hostDaemonId,
3761
+ hostNodeId: hostResult.meshHost?.hostNodeId,
3762
+ joinedAt: hostResult.meshHost?.pairing?.joinedAt,
3763
+ });
3764
+ if (joined) {
3765
+ this.inlineMeshCache.set(meshId, joined.mesh);
3766
+ this.invalidateAggregateMeshStatus(meshId);
3767
+ }
3768
+ return {
3769
+ success: true,
3770
+ code: 'mesh_host_join_applied',
3771
+ meshId,
3772
+ hostMeshId,
3773
+ transport,
3774
+ node: hostResult.node,
3775
+ tokenId: hostResult.tokenId || tokenId,
3776
+ meshHost: joined ? resolveMeshHostStatus(joined.mesh) : { ...meshHost, pairing: { ...(meshHost.pairing || {}), status: 'paired', tokenId: hostResult.tokenId || tokenId } },
3777
+ hostResult,
3778
+ manualPairing: {
3779
+ status: 'paired',
3780
+ joinImplemented: true,
3781
+ protocol: 'standalone_command_direct_v1',
3782
+ description: 'Mesh Host accepted the join and local member pairing status was marked paired. P2P runtime signaling remains outside this slice.',
3783
+ },
3784
+ };
3785
+ } catch (e: any) {
3786
+ return { success: false, code: 'mesh_host_join_failed', meshId, meshHost, error: e.message };
3787
+ }
3788
+ }
3789
+
2052
3790
  case 'delete_mesh': {
2053
3791
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2054
3792
  if (!meshId) return { success: false, error: 'meshId required' };
@@ -2142,6 +3880,8 @@ export class DaemonCommandRouter {
2142
3880
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2143
3881
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
2144
3882
  if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
3883
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue cancellation');
3884
+ if (ownerFailure) return ownerFailure;
2145
3885
  try {
2146
3886
  const { cancelTask } = await import('../mesh/mesh-work-queue.js');
2147
3887
  const reason = typeof args?.reason === 'string' ? args.reason : undefined;
@@ -2157,6 +3897,8 @@ export class DaemonCommandRouter {
2157
3897
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2158
3898
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
2159
3899
  if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
3900
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue requeue');
3901
+ if (ownerFailure) return ownerFailure;
2160
3902
  try {
2161
3903
  const { requeueTask } = await import('../mesh/mesh-work-queue.js');
2162
3904
  const task = requeueTask(meshId, taskId, {
@@ -2178,6 +3920,8 @@ export class DaemonCommandRouter {
2178
3920
  const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
2179
3921
  if (!meshId) return { success: false, error: 'meshId required' };
2180
3922
  if (!workspace) return { success: false, error: 'workspace required' };
3923
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node addition');
3924
+ if (ownerFailure) return ownerFailure;
2181
3925
  try {
2182
3926
  const { addNode } = await import('../config/mesh-config.js');
2183
3927
  const providerPriority = Array.isArray(args?.providerPriority)
@@ -2188,7 +3932,18 @@ export class DaemonCommandRouter {
2188
3932
  ...(readOnly ? { readOnly: true } : {}),
2189
3933
  ...(providerPriority.length ? { providerPriority } : {}),
2190
3934
  };
2191
- const node = addNode(meshId, { workspace, ...(policy ? { policy } : {}) });
3935
+ const role = normalizeMeshDaemonRole(args?.role);
3936
+ const daemonId = typeof args?.daemonId === 'string' && args.daemonId.trim() ? args.daemonId.trim() : undefined;
3937
+ const machineId = typeof args?.machineId === 'string' && args.machineId.trim() ? args.machineId.trim() : undefined;
3938
+ const repoRoot = typeof args?.repoRoot === 'string' && args.repoRoot.trim() ? args.repoRoot.trim() : undefined;
3939
+ const node = addNode(meshId, {
3940
+ workspace,
3941
+ ...(repoRoot ? { repoRoot } : {}),
3942
+ ...(daemonId ? { daemonId } : {}),
3943
+ ...(machineId ? { machineId } : {}),
3944
+ ...(policy ? { policy } : {}),
3945
+ ...(role ? { role } : {}),
3946
+ });
2192
3947
  if (!node) return { success: false, error: 'Mesh not found' };
2193
3948
  return { success: true, node };
2194
3949
  } catch (e: any) {
@@ -2200,6 +3955,8 @@ export class DaemonCommandRouter {
2200
3955
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2201
3956
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2202
3957
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
3958
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node update');
3959
+ if (ownerFailure) return ownerFailure;
2203
3960
  try {
2204
3961
  const { updateNode } = await import('../config/mesh-config.js');
2205
3962
  const policy = args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)
@@ -2228,6 +3985,8 @@ export class DaemonCommandRouter {
2228
3985
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2229
3986
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2230
3987
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
3988
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node removal');
3989
+ if (ownerFailure) return ownerFailure;
2231
3990
  try {
2232
3991
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2233
3992
  const mesh = meshRecord?.mesh;
@@ -2253,131 +4012,91 @@ export class DaemonCommandRouter {
2253
4012
  }
2254
4013
  }
2255
4014
 
2256
- case 'refine_mesh_node': {
4015
+ case 'get_mesh_refine_config_schema': {
4016
+ return {
4017
+ success: true,
4018
+ schema: MESH_REFINE_CONFIG_SCHEMA,
4019
+ locations: MESH_REFINE_CONFIG_LOCATIONS,
4020
+ sourceOfTruth: 'repo mesh/refine config',
4021
+ heuristicRole: 'suggestions_only_not_execution_path',
4022
+ };
4023
+ }
4024
+
4025
+ case 'validate_mesh_refine_config': {
4026
+ const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
4027
+ const mesh = args?.inlineMesh || {};
4028
+ const loaded = args?.config !== undefined
4029
+ ? { config: args.config, source: 'inline', sourceType: 'mesh_policy' as const }
4030
+ : loadMeshRefineConfig(mesh, workspace);
4031
+ const validation = loaded.config
4032
+ ? validateMeshRefineConfig(loaded.config, loaded.source)
4033
+ : { valid: false, errors: [((loaded as { error?: string }).error) || 'repo mesh/refine config unavailable'], commands: [], rejectedCommands: [] };
4034
+ return { success: validation.valid, ...loaded, ...validation };
4035
+ }
4036
+
4037
+ case 'suggest_mesh_refine_config': {
4038
+ const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
4039
+ const mesh = args?.inlineMesh || {};
4040
+ return {
4041
+ success: true,
4042
+ ...suggestMeshRefineConfig(mesh, workspace),
4043
+ note: 'Suggestions are heuristic scaffold only; Refinery will not execute them until saved into repo mesh/refine config.',
4044
+ };
4045
+ }
4046
+
4047
+ case 'plan_mesh_refine_node': {
2257
4048
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2258
4049
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2259
4050
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
2260
- try {
4051
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
4052
+ const mesh = meshRecord?.mesh;
4053
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
4054
+ if (!node?.workspace) return { success: false, error: `Node '${nodeId}' workspace not found` };
4055
+ return {
4056
+ success: true,
4057
+ dryRun: true,
4058
+ nodeId,
4059
+ workspace: node.workspace,
4060
+ validationPlan: buildMeshRefineValidationPlan(mesh, node.workspace),
4061
+ mergeWillRun: false,
4062
+ cleanupWillRun: false,
4063
+ };
4064
+ }
4065
+
4066
+ case 'fast_forward_mesh_node': {
4067
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4068
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
4069
+ let workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
4070
+ let submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths)
4071
+ ? args.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string')
4072
+ : undefined;
4073
+ if (!workspace && meshId && nodeId) {
2261
4074
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2262
4075
  const mesh = meshRecord?.mesh;
2263
4076
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2264
- if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2265
-
2266
- if (!node.isLocalWorktree || !node.workspace) {
2267
- return { success: false, error: `Refinery requires a local worktree node` };
2268
- }
2269
-
2270
- const sourceNode = node.clonedFromNodeId
2271
- ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2272
- : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2273
- const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2274
- if (!repoRoot) return { success: false, error: 'Source node repoRoot not found' };
2275
-
2276
- const { execFile } = await import('node:child_process');
2277
- const { promisify } = await import('node:util');
2278
- const execFileAsync = promisify(execFile);
2279
-
2280
- const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2281
- const branch = branchStdout.trim();
2282
- if (!branch) return { success: false, error: 'Could not determine branch of the worktree node' };
2283
-
2284
- const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2285
- const baseBranch = baseBranchStdout.trim();
2286
-
2287
- const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2288
- if (validationSummary.status === 'failed') {
2289
- return {
2290
- success: false,
2291
- code: 'validation_failed',
2292
- convergenceStatus: 'blocked_review',
2293
- error: 'Refinery validation gate failed; merge/refine was not attempted.',
2294
- branch,
2295
- into: baseBranch,
2296
- validationSummary,
2297
- finalBranchConvergenceState: {
2298
- branch,
2299
- baseBranch,
2300
- merged: false,
2301
- removed: false,
2302
- validation: 'failed',
2303
- status: 'blocked_review',
2304
- },
2305
- };
4077
+ workspace = typeof node?.workspace === 'string' ? node.workspace.trim() : '';
4078
+ if (!submoduleIgnorePaths && Array.isArray(node?.policy?.submoduleIgnorePaths)) {
4079
+ submoduleIgnorePaths = node.policy.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string');
2306
4080
  }
2307
- if (validationSummary.status === 'skipped') {
2308
- return {
2309
- success: false,
2310
- code: 'validation_unavailable',
2311
- convergenceStatus: 'blocked_review',
2312
- error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
2313
- branch,
2314
- into: baseBranch,
2315
- validationSummary,
2316
- finalBranchConvergenceState: {
2317
- branch,
2318
- baseBranch,
2319
- merged: false,
2320
- removed: false,
2321
- validation: 'unavailable',
2322
- status: 'blocked_review',
2323
- },
2324
- };
2325
- }
2326
-
2327
- try {
2328
- await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2329
- } catch (e: any) {
2330
- return {
2331
- success: false,
2332
- error: `Merge failed (conflicts?): ${e.message}`,
2333
- validationSummary,
2334
- finalBranchConvergenceState: {
2335
- branch,
2336
- baseBranch,
2337
- merged: false,
2338
- removed: false,
2339
- validation: 'passed',
2340
- status: 'not_mergeable',
2341
- },
2342
- };
2343
- }
2344
-
2345
- const removeResult = await this.execute('remove_mesh_node', {
2346
- meshId,
2347
- nodeId,
2348
- sessionCleanupMode: 'kill',
2349
- inlineMesh: args?.inlineMesh,
2350
- });
2351
-
2352
- try {
2353
- const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2354
- appendLedgerEntry(meshId, {
2355
- kind: 'node_removed',
2356
- nodeId,
2357
- payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary },
2358
- });
2359
- } catch {}
2360
-
2361
- return {
2362
- success: true,
2363
- merged: true,
2364
- branch,
2365
- into: baseBranch,
2366
- removeResult,
2367
- validationSummary,
2368
- finalBranchConvergenceState: {
2369
- branch: baseBranch,
2370
- mergedBranch: branch,
2371
- baseBranch,
2372
- merged: true,
2373
- removed: removeResult?.success !== false,
2374
- validation: 'passed',
2375
- status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
2376
- },
2377
- };
2378
- } catch (e: any) {
2379
- return { success: false, error: e.message };
2380
4081
  }
4082
+ const result = await (fastForwardMeshNode({
4083
+ meshId: meshId || undefined,
4084
+ nodeId: nodeId || undefined,
4085
+ workspace,
4086
+ branch: typeof args?.branch === 'string' ? args.branch : undefined,
4087
+ execute: args?.execute === true,
4088
+ dryRun: args?.dryRun === true,
4089
+ updateSubmodules: args?.updateSubmodules === true,
4090
+ submoduleIgnorePaths,
4091
+ }) as Promise<unknown>);
4092
+ return result as CommandRouterResult;
4093
+ }
4094
+
4095
+ case 'refine_mesh_node': {
4096
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4097
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
4098
+ if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
4099
+ return this.startMeshRefineJob(meshId, nodeId, args);
2381
4100
  }
2382
4101
 
2383
4102
  case 'remove_mesh_node': {
@@ -2421,6 +4140,7 @@ export class DaemonCommandRouter {
2421
4140
  } else {
2422
4141
  const { removeNode } = await import('../config/mesh-config.js');
2423
4142
  removed = removeNode(meshId, nodeId);
4143
+ if (removed) this.invalidateAggregateMeshStatus(meshId);
2424
4144
  }
2425
4145
 
2426
4146
  // Record in task ledger
@@ -2458,6 +4178,8 @@ export class DaemonCommandRouter {
2458
4178
  if (!meshId) return { success: false, error: 'meshId required' };
2459
4179
  if (!sourceNodeId) return { success: false, error: 'sourceNodeId required' };
2460
4180
  if (!branch) return { success: false, error: 'branch required' };
4181
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'worktree clone');
4182
+ if (ownerFailure) return ownerFailure;
2461
4183
 
2462
4184
  try {
2463
4185
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
@@ -2506,6 +4228,7 @@ export class DaemonCommandRouter {
2506
4228
  policy: { ...(sourceNode.policy || {}) },
2507
4229
  });
2508
4230
  if (!node) return { success: false, error: 'Failed to register worktree node' };
4231
+ this.invalidateAggregateMeshStatus(meshId);
2509
4232
  }
2510
4233
 
2511
4234
  // Initialize submodules if policy allows (default: true)
@@ -2547,6 +4270,8 @@ export class DaemonCommandRouter {
2547
4270
  case 'trigger_mesh_queue': {
2548
4271
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2549
4272
  if (!meshId) return { success: false, error: 'meshId required' };
4273
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue trigger');
4274
+ if (ownerFailure) return ownerFailure;
2550
4275
  try {
2551
4276
  const { triggerMeshQueue } = await import('../mesh/mesh-events.js');
2552
4277
  if (meshId) {
@@ -2578,6 +4303,15 @@ export class DaemonCommandRouter {
2578
4303
  mesh = getMesh(meshId);
2579
4304
  }
2580
4305
  if (!mesh) return { success: false, error: 'Mesh not found' };
4306
+ const meshHost = resolveMeshHostStatus(mesh);
4307
+ if (!meshHost.canOwnCoordinator) {
4308
+ return {
4309
+ success: false,
4310
+ ...buildMeshHostRequiredFailure(mesh, 'coordinator launch'),
4311
+ meshId,
4312
+ cliType,
4313
+ };
4314
+ }
2581
4315
  if (!Array.isArray(mesh.nodes) || mesh.nodes.length === 0) return { success: false, error: 'No nodes in mesh' };
2582
4316
 
2583
4317
  const requestedCoordinatorNodeId = typeof args?.coordinatorNodeId === 'string'
@@ -2597,7 +4331,16 @@ export class DaemonCommandRouter {
2597
4331
  cliType,
2598
4332
  };
2599
4333
  }
2600
- const workspace = typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '';
4334
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions
4335
+ ? await this.deps.sessionHostControl.listSessions().catch(() => [])
4336
+ : [];
4337
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
4338
+ const workspace = readLiveMeshNodeWorkspace({
4339
+ meshId,
4340
+ nodeId: String(coordinatorNode.id || coordinatorNode.nodeId || preferredCoordinatorNodeId || ''),
4341
+ liveSessionRecords: liveMeshSessions,
4342
+ allowCoordinatorSession: true,
4343
+ }) || (typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '');
2601
4344
  if (!workspace) return { success: false, error: 'Coordinator node workspace required', meshId, cliType };
2602
4345
  if (!cliType) {
2603
4346
  const resolved = await resolveProviderTypeFromPriority({
@@ -2942,6 +4685,27 @@ export class DaemonCommandRouter {
2942
4685
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
2943
4686
  const mesh = meshRecord?.mesh;
2944
4687
  if (!mesh) return { success: false, error: 'Mesh not found' };
4688
+ const meshHost = resolveMeshHostStatus(mesh);
4689
+
4690
+ const refreshRequested = args?.refresh === true || args?.forceRefresh === true;
4691
+ const hadAggregateCache = this.aggregateMeshStatusCache.has(meshId);
4692
+ if (!refreshRequested) {
4693
+ const cachedStatus = this.getCachedAggregateMeshStatus(meshId, mesh, { requireDirectPeerTruth: args?.requireDirectPeerTruth === true });
4694
+ if (cachedStatus) {
4695
+ logRepoMeshStatusDebug('return_cached', {
4696
+ meshId,
4697
+ command: 'mesh_status',
4698
+ refreshRequested,
4699
+ summary: summarizeRepoMeshStatusDebug(cachedStatus),
4700
+ });
4701
+ return cachedStatus;
4702
+ }
4703
+ }
4704
+ const refreshReason = refreshRequested
4705
+ ? 'explicit_refresh'
4706
+ : hadAggregateCache
4707
+ ? 'stale_pending_cache_refresh'
4708
+ : 'cold_cache_miss';
2945
4709
 
2946
4710
  const { getMeshQueueStats, getQueue } = await import('../mesh/mesh-work-queue.js');
2947
4711
  const queue = getQueue(meshId);
@@ -2955,64 +4719,343 @@ export class DaemonCommandRouter {
2955
4719
  : [];
2956
4720
  const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
2957
4721
 
4722
+ const localMachineId = loadConfig().machineId || '';
4723
+ const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
4724
+ const directTruth = requireDirectPeerTruth
4725
+ ? await hydrateInlineMeshDirectTruth({
4726
+ mesh,
4727
+ meshSource: meshRecord.source,
4728
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
4729
+ statusInstanceId: this.deps.statusInstanceId,
4730
+ localMachineId,
4731
+ })
4732
+ : {
4733
+ directEvidenceCount: 0,
4734
+ localConfirmedCount: 0,
4735
+ peerAttemptedCount: 0,
4736
+ peerConfirmedCount: 0,
4737
+ unavailableNodeIds: [] as string[],
4738
+ };
4739
+ // Default/cached loads may not attempt a remote peer probe yet; do not surface that as
4740
+ // a direct mesh truth failure until an explicit probe attempt actually fails.
4741
+ const passivePeerTruthNotAttempted = requireDirectPeerTruth
4742
+ && !refreshRequested
4743
+ && directTruth.directEvidenceCount > 0
4744
+ && directTruth.peerAttemptedCount === 0;
4745
+ const effectiveDirectTruth = passivePeerTruthNotAttempted
4746
+ ? { ...directTruth, unavailableNodeIds: [] as string[] }
4747
+ : directTruth;
4748
+ const directTruthSatisfied = !requireDirectPeerTruth
4749
+ || (effectiveDirectTruth.directEvidenceCount > 0 && effectiveDirectTruth.unavailableNodeIds.length === 0);
4750
+ if (requireDirectPeerTruth && !directTruthSatisfied) {
4751
+ const failureResult = {
4752
+ success: false,
4753
+ code: 'mesh_direct_peer_truth_unavailable',
4754
+ error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct mesh_status probes succeed.',
4755
+ sourceOfTruth: {
4756
+ membership: meshRecord.source === 'inline_cache'
4757
+ ? 'coordinator_inline_mesh_cache'
4758
+ : meshRecord.source === 'local_config'
4759
+ ? 'local_mesh_config'
4760
+ : 'inline_bootstrap_snapshot',
4761
+ coordinatorOwnsLiveTruth: false,
4762
+ currentStatus: 'direct_peer_truth_unavailable',
4763
+ directPeerTruth: {
4764
+ required: true,
4765
+ satisfied: false,
4766
+ directEvidenceCount: directTruth.directEvidenceCount,
4767
+ localConfirmedCount: directTruth.localConfirmedCount,
4768
+ peerAttemptedCount: directTruth.peerAttemptedCount,
4769
+ peerConfirmedCount: directTruth.peerConfirmedCount,
4770
+ unavailableNodeIds: directTruth.unavailableNodeIds,
4771
+ },
4772
+ },
4773
+ };
4774
+ logRepoMeshStatusDebug('direct_truth_unavailable', {
4775
+ meshId,
4776
+ command: 'mesh_status',
4777
+ refreshRequested,
4778
+ meshSource: meshRecord.source,
4779
+ directTruth,
4780
+ });
4781
+ return failureResult;
4782
+ }
4783
+ const directTruthUnavailableNodeIds = new Set(effectiveDirectTruth.unavailableNodeIds);
4784
+ const selectedCoordinatorNodeId = readStringValue(
4785
+ mesh.coordinator?.preferredNodeId,
4786
+ (mesh.nodes?.[0] as any)?.id,
4787
+ (mesh.nodes?.[0] as any)?.nodeId,
4788
+ );
4789
+ const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes)
4790
+ ? selectedCoordinatorNodeId
4791
+ : undefined;
4792
+ const refreshedAt = new Date().toISOString();
2958
4793
  const nodeStatuses = [];
2959
- for (const node of mesh.nodes || []) {
4794
+ for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
4795
+ const nodeId = String(node.id || node.nodeId || '');
4796
+ const daemonId = readStringValue(node.daemonId);
4797
+ const providerPriority = readProviderPriorityFromPolicy(node.policy);
4798
+ const isSelfNode = Boolean(
4799
+ nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId,
4800
+ ) || Boolean(
4801
+ daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId),
4802
+ ) || Boolean(meshRecord?.inline && nodeIndex === 0);
2960
4803
  const status: Record<string, unknown> = {
2961
- nodeId: node.id || node.nodeId,
4804
+ nodeId,
2962
4805
  machineLabel: node.machineLabel || node.id || node.nodeId,
2963
4806
  workspace: node.workspace,
2964
4807
  repoRoot: node.repoRoot,
2965
4808
  isLocalWorktree: node.isLocalWorktree,
2966
4809
  worktreeBranch: node.worktreeBranch,
2967
- daemonId: node.daemonId,
4810
+ role: normalizeMeshDaemonRole(node.role) || (meshHost.hostNodeId && nodeId === meshHost.hostNodeId ? 'host' : undefined),
4811
+ daemonId,
2968
4812
  machineId: node.machineId,
4813
+ machineStatus: node.machineStatus,
2969
4814
  health: 'unknown',
2970
4815
  providers: node.providers || [],
4816
+ providerPriority,
2971
4817
  activeSessions: [],
4818
+ activeSessionDetails: [],
4819
+ launchReady: false,
2972
4820
  };
2973
- const nodeId = String(node.id || node.nodeId || '');
2974
- const matchedLiveSessions = liveMeshSessions
2975
- .filter((record) => this.sessionMatchesMeshNode(record, node, nodeId))
2976
- .map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
2977
- .filter(Boolean);
2978
- if (matchedLiveSessions.length > 0) {
2979
- status.activeSessions = matchedLiveSessions;
4821
+ if (isSelfNode) {
4822
+ status.connection = {
4823
+ perspective: 'selected_coordinator',
4824
+ source: 'mesh_peer_status',
4825
+ state: 'self',
4826
+ transport: 'local',
4827
+ reported: true,
4828
+ reason: 'Selected coordinator daemon',
4829
+ lastStateChangeAt: refreshedAt,
4830
+ };
4831
+ } else if (daemonId) {
4832
+ const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
4833
+ status.connection = connection ?? {
4834
+ perspective: 'selected_coordinator',
4835
+ source: 'not_reported',
4836
+ state: 'unknown',
4837
+ transport: 'unknown',
4838
+ reported: false,
4839
+ reason: 'No live mesh peer telemetry reported by the selected coordinator yet.',
4840
+ };
4841
+ } else {
4842
+ status.connection = {
4843
+ perspective: 'selected_coordinator',
4844
+ source: 'not_reported',
4845
+ state: 'unknown',
4846
+ transport: 'unknown',
4847
+ reported: false,
4848
+ reason: 'Node has no daemon id, so mesh transport cannot be reported from the selected coordinator.',
4849
+ };
2980
4850
  }
2981
- if (node.workspace && typeof node.workspace === 'string') {
2982
- if (!fs.existsSync(node.workspace as string) && applyCachedInlineMeshNodeStatus(status, node)) {
2983
- nodeStatuses.push(status);
2984
- continue;
4851
+ const matchedLiveSessionRecords = collectLiveMeshSessionRecords({
4852
+ meshId,
4853
+ node,
4854
+ nodeId,
4855
+ liveSessionRecords: liveMeshSessions,
4856
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
4857
+ });
4858
+ const workspace = readLiveMeshNodeWorkspace({
4859
+ meshId,
4860
+ nodeId,
4861
+ liveSessionRecords: matchedLiveSessionRecords,
4862
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
4863
+ }) || (typeof node.workspace === 'string' ? node.workspace : '');
4864
+ status.workspace = workspace || node.workspace;
4865
+ if (matchedLiveSessionRecords.length > 0) {
4866
+ const sessionIds = matchedLiveSessionRecords
4867
+ .map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
4868
+ .filter(Boolean);
4869
+ const providerTypes = matchedLiveSessionRecords
4870
+ .map((record: any) => readStringValue(record?.providerType))
4871
+ .filter(Boolean) as string[];
4872
+ status.activeSessions = sessionIds;
4873
+ status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
4874
+ if (providerTypes.length > 0) {
4875
+ status.providers = Array.from(new Set([...(Array.isArray(status.providers) ? status.providers as string[] : []), ...providerTypes]));
2985
4876
  }
2986
- try {
2987
- const gitStatus = await getGitRepoStatus(node.workspace as string, { timeoutMs: 10_000 });
2988
- status.git = gitStatus;
2989
- if (gitStatus.isGitRepo) {
2990
- status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
2991
- } else {
2992
- status.health = 'degraded';
2993
- if (gitStatus.error && !status.error) status.error = gitStatus.error;
4877
+ }
4878
+ if (workspace) {
4879
+ if (!fs.existsSync(workspace)) {
4880
+ // Workspace not local — prefer direct live inline truth, then attempt a P2P git probe.
4881
+ const inlineTransitGit = buildInlineMeshTransitGitStatus(node);
4882
+ let remoteProbeApplied = false;
4883
+ if (inlineTransitGit) {
4884
+ status.git = inlineTransitGit;
4885
+ status.health = inlineTransitGit.isGitRepo
4886
+ ? deriveMeshNodeHealthFromGit(inlineTransitGit as unknown as Record<string, unknown>)
4887
+ : 'degraded';
4888
+ const connection = readObjectRecord(status.connection);
4889
+ const connectionState = readStringValue(connection.state);
4890
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
4891
+ if (!connectionReported || connectionState === 'unknown') {
4892
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
4893
+ }
4894
+ remoteProbeApplied = true;
4895
+ } else if (!isSelfNode && daemonId && this.deps.dispatchMeshCommand && !directTruthUnavailableNodeIds.has(nodeId)) {
4896
+ try {
4897
+ const remoteGit = await probeRemoteMeshGitStatus({
4898
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
4899
+ daemonId,
4900
+ workspace,
4901
+ timeoutMs: 8000,
4902
+ });
4903
+ if (remoteGit) {
4904
+ status.git = remoteGit;
4905
+ status.health = remoteGit.isGitRepo
4906
+ ? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
4907
+ : 'degraded';
4908
+ const connection = readObjectRecord(status.connection);
4909
+ const connectionState = readStringValue(connection.state);
4910
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
4911
+ if (!connectionReported || connectionState === 'unknown') {
4912
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
4913
+ }
4914
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
4915
+ remoteProbeApplied = true;
4916
+ }
4917
+ } catch {
4918
+ const refreshedConnection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
4919
+ const refreshedConnectionState = readStringValue(refreshedConnection?.state);
4920
+ if (refreshedConnection && refreshedConnectionState === 'connected') {
4921
+ status.connection = refreshedConnection;
4922
+ try {
4923
+ const remoteGit = await probeRemoteMeshGitStatus({
4924
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
4925
+ daemonId,
4926
+ workspace,
4927
+ timeoutMs: 12000,
4928
+ });
4929
+ if (remoteGit) {
4930
+ status.git = remoteGit;
4931
+ status.health = remoteGit.isGitRepo
4932
+ ? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
4933
+ : 'degraded';
4934
+ const connection = readObjectRecord(status.connection);
4935
+ const connectionState = readStringValue(connection.state);
4936
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
4937
+ if (!connectionReported || connectionState === 'unknown') {
4938
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
4939
+ }
4940
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
4941
+ remoteProbeApplied = true;
4942
+ }
4943
+ } catch {
4944
+ // Probe timed out again or P2P unavailable — fall back to cached status
4945
+ }
4946
+ }
4947
+ }
2994
4948
  }
2995
- } catch {
2996
- if (!applyCachedInlineMeshNodeStatus(status, node)) {
2997
- status.health = 'degraded';
4949
+ if (!remoteProbeApplied) {
4950
+ const connectionState = readStringValue((status.connection as any)?.state);
4951
+ const pendingPeerGitProbe = !inlineTransitGit
4952
+ && !isSelfNode
4953
+ && !!daemonId
4954
+ && (
4955
+ readStringValue(status.machineStatus) === 'online'
4956
+ || readStringValue(status.health) === 'online'
4957
+ || connectionState === 'connecting'
4958
+ || connectionState === 'connected'
4959
+ || connectionState === 'unknown'
4960
+ );
4961
+ if (pendingPeerGitProbe) {
4962
+ status.gitProbePending = true;
4963
+ status.health = 'unknown';
4964
+ }
4965
+ if (applyCachedInlineMeshNodeStatus(
4966
+ status,
4967
+ node,
4968
+ pendingPeerGitProbe ? { skipGit: true, skipError: true, skipHealth: true } : undefined,
4969
+ )) {
4970
+ applyInlineMeshBranchConvergence(mesh, node, status);
4971
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
4972
+ nodeStatuses.push(status);
4973
+ continue;
4974
+ }
4975
+ if (meshRecord?.source === 'inline_cache' && !isSelfNode) {
4976
+ applyInlineMeshBranchConvergence(mesh, node, status);
4977
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
4978
+ nodeStatuses.push(status);
4979
+ continue;
4980
+ }
4981
+ }
4982
+ } else {
4983
+ try {
4984
+ const gitStatus = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
4985
+ status.git = gitStatus;
4986
+ recordInlineMeshDirectGitTruth(node, gitStatus as unknown as Record<string, unknown>, 'selected_coordinator_local_git');
4987
+ if (gitStatus.isGitRepo) {
4988
+ status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
4989
+ } else {
4990
+ status.health = 'degraded';
4991
+ if (gitStatus.error && !status.error) status.error = gitStatus.error;
4992
+ }
4993
+ } catch {
4994
+ if (!applyCachedInlineMeshNodeStatus(status, node)) {
4995
+ status.health = 'degraded';
4996
+ }
2998
4997
  }
2999
4998
  }
3000
4999
  } else {
3001
5000
  applyCachedInlineMeshNodeStatus(status, node);
3002
5001
  }
5002
+ applyInlineMeshBranchConvergence(mesh, node, status);
5003
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
3003
5004
  nodeStatuses.push(status);
3004
5005
  }
3005
5006
 
3006
- return {
5007
+ const statusResult = {
3007
5008
  success: true,
3008
5009
  meshId: mesh.id,
3009
5010
  meshName: mesh.name,
3010
5011
  repoIdentity: mesh.repoIdentity,
3011
5012
  defaultBranch: mesh.defaultBranch,
5013
+ refreshedAt,
5014
+ meshHost,
5015
+ sourceOfTruth: {
5016
+ membership: meshRecord?.source === 'inline_cache'
5017
+ ? 'coordinator_inline_mesh_cache'
5018
+ : meshRecord?.source === 'local_config'
5019
+ ? 'local_mesh_config'
5020
+ : 'inline_bootstrap_snapshot',
5021
+ coordinatorOwnsLiveTruth: directTruthSatisfied,
5022
+ meshHost: {
5023
+ owner: 'mesh_host_daemon',
5024
+ localRole: meshHost.role,
5025
+ hostDaemonId: meshHost.hostDaemonId,
5026
+ hostNodeId: meshHost.hostNodeId,
5027
+ hostAddress: meshHost.hostAddress,
5028
+ },
5029
+ ...(requireDirectPeerTruth ? {
5030
+ currentStatus: directTruthSatisfied ? 'live_git_and_session_probes' : 'direct_peer_truth_unavailable',
5031
+ directPeerTruth: {
5032
+ required: true,
5033
+ satisfied: directTruthSatisfied,
5034
+ directEvidenceCount: effectiveDirectTruth.directEvidenceCount,
5035
+ localConfirmedCount: effectiveDirectTruth.localConfirmedCount,
5036
+ peerAttemptedCount: effectiveDirectTruth.peerAttemptedCount,
5037
+ peerConfirmedCount: effectiveDirectTruth.peerConfirmedCount,
5038
+ unavailableNodeIds: effectiveDirectTruth.unavailableNodeIds,
5039
+ },
5040
+ } : {}),
5041
+ historicalEvidenceOnly: ['recoveryHints', 'ledger.summary', 'queue.summary'],
5042
+ },
5043
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodeStatuses),
3012
5044
  nodes: nodeStatuses,
3013
5045
  queue: { tasks: queue, summary: queueSummary },
3014
5046
  ledger: { entries: ledgerEntries, summary: ledgerSummary },
3015
5047
  };
5048
+ const rememberedStatus = this.rememberAggregateMeshStatus(meshId, statusResult, refreshReason);
5049
+ logRepoMeshStatusDebug('return_live', {
5050
+ meshId,
5051
+ command: 'mesh_status',
5052
+ refreshRequested,
5053
+ refreshReason,
5054
+ meshSource: meshRecord.source,
5055
+ directTruth,
5056
+ summary: summarizeRepoMeshStatusDebug(rememberedStatus),
5057
+ });
5058
+ return rememberedStatus;
3016
5059
  } catch (e: any) {
3017
5060
  return { success: false, error: e.message };
3018
5061
  }
@@ -3070,7 +5113,7 @@ export class DaemonCommandRouter {
3070
5113
 
3071
5114
  // 3. Kill OS process if requested
3072
5115
  if (killProcess) {
3073
- const running = isIdeRunning(ideType);
5116
+ const running = await isIdeRunning(ideType);
3074
5117
  if (running) {
3075
5118
  LOG.info('StopIDE', `Killing IDE process: ${ideType}`);
3076
5119
  const killed = await killIdeProcess(ideType);