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

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 (74) 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 +4888 -1149
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +4852 -1135
  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/chat-commands.ts +242 -7
  39. package/src/commands/cli-manager.ts +19 -0
  40. package/src/commands/handler.ts +8 -1
  41. package/src/commands/mesh-coordinator.ts +13 -143
  42. package/src/commands/router.ts +2518 -408
  43. package/src/config/chat-history.ts +9 -7
  44. package/src/config/mesh-config.ts +244 -1
  45. package/src/daemon/dev-cli-debug.ts +10 -1
  46. package/src/detection/ide-detector.ts +26 -16
  47. package/src/git/git-commands.ts +3 -3
  48. package/src/git/git-status.ts +97 -6
  49. package/src/git/git-summary.ts +3 -0
  50. package/src/git/git-types.ts +11 -0
  51. package/src/index.ts +39 -5
  52. package/src/installer.d.ts +1 -1
  53. package/src/installer.ts +8 -6
  54. package/src/launch.d.ts +1 -1
  55. package/src/launch.ts +37 -28
  56. package/src/logging/async-batch-writer.ts +55 -0
  57. package/src/logging/logger.ts +2 -1
  58. package/src/mesh/beads-db.ts +176 -0
  59. package/src/mesh/coordinator-prompt.ts +5 -2
  60. package/src/mesh/mesh-active-work.ts +205 -0
  61. package/src/mesh/mesh-events.ts +291 -38
  62. package/src/mesh/mesh-fast-forward.ts +430 -0
  63. package/src/mesh/mesh-host-ownership.ts +73 -0
  64. package/src/mesh/mesh-ledger.ts +138 -1
  65. package/src/mesh/mesh-work-queue.ts +199 -137
  66. package/src/mesh/refine-config.ts +306 -0
  67. package/src/providers/chat-message-normalization.ts +3 -1
  68. package/src/providers/cli-provider-instance.ts +68 -1
  69. package/src/providers/ide-provider-instance.ts +17 -3
  70. package/src/providers/provider-loader.ts +10 -4
  71. package/src/providers/version-archive.ts +38 -20
  72. package/src/repo-mesh-types.ts +174 -0
  73. package/src/status/reporter.ts +15 -0
  74. package/src/system/host-memory.ts +29 -12
@@ -38,12 +38,24 @@ 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
- import { homedir } from 'os';
58
+ import { homedir, tmpdir } from 'os';
47
59
  import { join as pathJoin, resolve as pathResolve } from 'path';
48
60
  import * as fs from 'fs';
49
61
 
@@ -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>> {
263
706
  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);
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 {
994
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
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,88 @@ 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
+ publishRequired?: boolean;
1093
+ checkedLocal?: boolean;
1094
+ localReachable?: boolean;
1095
+ remote?: string;
1096
+ remoteUrl?: string;
1097
+ remoteReachable?: boolean;
1098
+ fetchedFromOrigin?: boolean;
1099
+ error?: string;
1100
+ };
1101
+
1102
+ type MeshRefineSubmoduleReachabilitySummary = {
1103
+ status: MeshRefineStageStatus;
1104
+ checked: number;
1105
+ unreachable: MeshRefineSubmoduleReachabilityEntry[];
1106
+ entries: MeshRefineSubmoduleReachabilityEntry[];
1107
+ durationMs: number;
1108
+ error?: string;
1109
+ };
1110
+
1111
+ type MeshRefineAsyncJobStatus = 'accepted' | 'completed' | 'failed';
1112
+
1113
+ type MeshRefineJobHandle = {
1114
+ success: true;
1115
+ async: true;
1116
+ status: MeshRefineAsyncJobStatus;
1117
+ jobId: string;
1118
+ interactionId: string;
1119
+ meshId: string;
1120
+ nodeId: string;
1121
+ targetNodeId: string;
1122
+ targetDaemonId?: string;
1123
+ workspace?: string;
1124
+ startedAt: string;
1125
+ completedAt?: string;
1126
+ duplicate?: boolean;
1127
+ retryOfJobId?: string;
1128
+ eventDelivery: {
1129
+ pendingEvents: true;
1130
+ ledger: true;
1131
+ };
1132
+ evidence: {
1133
+ pendingEventsCommand: 'get_pending_mesh_events';
1134
+ ledgerCommand: 'get_mesh_ledger_slice';
1135
+ taskHistoryKind: 'task_dispatched' | 'task_completed' | 'task_failed';
1136
+ };
332
1137
  };
333
1138
 
1139
+ type MeshRefineTerminalJob = MeshRefineJobHandle & { result?: Record<string, unknown> };
1140
+
334
1141
  const REFINE_VALIDATION_CATEGORIES = ['typecheck', 'test', 'lint', 'build'] as const;
335
1142
  const REFINE_VALIDATION_TIMEOUT_MS = 120_000;
336
1143
  const REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
337
1144
  const REFINE_VALIDATION_SUMMARY_CHARS = 2_000;
338
1145
  const REFINE_VALIDATION_MAX_COMMANDS = 4;
1146
+ const REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES = 4 * 1024 * 1024;
339
1147
 
340
1148
  function truncateValidationOutput(value: unknown): string {
341
1149
  const text = typeof value === 'string' ? value : value == null ? '' : String(value);
@@ -343,171 +1151,237 @@ function truncateValidationOutput(value: unknown): string {
343
1151
  return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}\n[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
344
1152
  }
345
1153
 
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
- }
1154
+ function recordMeshRefineStage(
1155
+ stages: Array<Record<string, unknown>>,
1156
+ stage: string,
1157
+ status: MeshRefineStageStatus,
1158
+ startedAt: number,
1159
+ details?: Record<string, unknown>,
1160
+ ): void {
1161
+ stages.push({
1162
+ stage,
1163
+ status,
1164
+ durationMs: Date.now() - startedAt,
1165
+ ...(details || {}),
1166
+ });
356
1167
  }
357
1168
 
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;
1169
+ function buildSubmodulePublishRequiredNextStep(entries: MeshRefineSubmoduleReachabilityEntry[]): string {
1170
+ const refs = entries
1171
+ .map(entry => `${entry.path}@${entry.commit}`)
1172
+ .join(', ');
1173
+ return `Ask the user for explicit approval to push/publish the unreachable submodule commit(s) (${refs}) to their configured submodule remote(s), then rerun mesh_refine_node. Do not merge the root branch until every submodule gitlink commit is reachable from its configured remote.`;
369
1174
  }
370
1175
 
371
- function scriptMatchesValidationCategory(scriptName: string, category: string): boolean {
372
- return scriptName === category || scriptName.startsWith(`${category}:`);
1176
+ async function computeGitPatchId(cwd: string, fromRef: string, toRef: string): Promise<string> {
1177
+ const { execFileSync } = await import('node:child_process');
1178
+ const diff = execFileSync('git', ['diff', '--patch', '--full-index', fromRef, toRef], {
1179
+ cwd,
1180
+ encoding: 'utf8',
1181
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1182
+ });
1183
+ if (!diff.trim()) return '';
1184
+ const patchId = execFileSync('git', ['patch-id', '--stable'], {
1185
+ cwd,
1186
+ input: diff,
1187
+ encoding: 'utf8',
1188
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1189
+ }).trim();
1190
+ return patchId.split(/\s+/)[0] || '';
373
1191
  }
374
1192
 
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' } };
1193
+ async function runMeshRefinePatchEquivalenceGate(
1194
+ repoRoot: string,
1195
+ baseHead: string,
1196
+ branchHead: string,
1197
+ ): Promise<MeshRefinePatchEquivalenceSummary> {
1198
+ const startedAt = Date.now();
1199
+ try {
1200
+ const { execFileSync } = await import('node:child_process');
1201
+ const git = (args: string[]) => execFileSync('git', args, {
1202
+ cwd: repoRoot,
1203
+ encoding: 'utf8',
1204
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1205
+ });
1206
+ const mergeBase = git(['merge-base', baseHead, branchHead]).trim();
1207
+ const mergeTreeStdout = git(['merge-tree', '--write-tree', baseHead, branchHead]);
1208
+ const mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || '';
1209
+ if (!mergeBase || !mergedTree) {
1210
+ return {
1211
+ status: 'failed',
1212
+ equivalent: false,
1213
+ baseHead,
1214
+ branchHead,
1215
+ mergeBase: mergeBase || undefined,
1216
+ mergedTree: mergedTree || undefined,
1217
+ durationMs: Date.now() - startedAt,
1218
+ error: 'patch equivalence preflight could not resolve merge-base or synthetic merge tree',
1219
+ stdout: truncateValidationOutput(mergeTreeStdout),
1220
+ };
1221
+ }
1222
+ const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
1223
+ const actualPatchId = await computeGitPatchId(repoRoot, baseHead, mergedTree);
1224
+ const equivalent = expectedPatchId === actualPatchId;
1225
+ return {
1226
+ status: equivalent ? 'passed' : 'failed',
1227
+ equivalent,
1228
+ baseHead,
1229
+ branchHead,
1230
+ mergeBase,
1231
+ mergedTree,
1232
+ expectedPatchId,
1233
+ actualPatchId,
1234
+ durationMs: Date.now() - startedAt,
1235
+ };
1236
+ } catch (e: any) {
1237
+ return {
1238
+ status: 'failed',
1239
+ equivalent: false,
1240
+ baseHead,
1241
+ branchHead,
1242
+ durationMs: Date.now() - startedAt,
1243
+ error: e?.message || String(e),
1244
+ stdout: truncateValidationOutput(e?.stdout),
1245
+ stderr: truncateValidationOutput(e?.stderr),
1246
+ };
412
1247
  }
413
-
414
- return {
415
- command: {
416
- command,
417
- args,
418
- displayCommand: [command, ...args].join(' '),
419
- category,
420
- source,
421
- },
422
- };
423
1248
  }
424
1249
 
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,
1250
+ async function runMeshRefineSubmoduleReachabilityGate(
1251
+ repoRoot: string,
1252
+ mergedTree: string,
1253
+ ): Promise<MeshRefineSubmoduleReachabilitySummary> {
1254
+ const startedAt = Date.now();
1255
+ const entries: MeshRefineSubmoduleReachabilityEntry[] = [];
1256
+ try {
1257
+ const { execFile } = await import('node:child_process');
1258
+ const { promisify } = await import('node:util');
1259
+ const execFileAsync = promisify(execFile);
1260
+ const runGit = async (cwd: string, args: string[]): Promise<string> => {
1261
+ const { stdout } = await execFileAsync('git', args, {
1262
+ cwd,
1263
+ encoding: 'utf8',
1264
+ timeout: 30_000,
1265
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1266
+ windowsHide: true,
438
1267
  });
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
- }
1268
+ return String(stdout || '');
1269
+ };
1270
+ const verifyRemoteCommitReachable = async (remoteUrl: string, commit: string): Promise<void> => {
1271
+ const probeDir = fs.mkdtempSync(pathJoin(tmpdir(), 'adhdev-submodule-reachability-'));
1272
+ try {
1273
+ await runGit(probeDir, ['init', '-q']);
1274
+ await runGit(probeDir, ['-c', 'protocol.file.allow=always', 'fetch', '--depth=1', remoteUrl, commit]);
1275
+ await runGit(probeDir, ['cat-file', '-e', `${commit}^{commit}`]);
1276
+ } finally {
1277
+ fs.rmSync(probeDir, { recursive: true, force: true });
1278
+ }
1279
+ };
446
1280
 
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
- }
1281
+ const treeOutput = await runGit(repoRoot, ['ls-tree', '-r', '-z', mergedTree]);
1282
+ const gitlinks = treeOutput
1283
+ .split('\0')
1284
+ .filter(Boolean)
1285
+ .map(record => {
1286
+ const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record);
1287
+ return match ? { commit: match[1], path: match[2] } : null;
1288
+ })
1289
+ .filter((entry): entry is { commit: string; path: string } => !!entry);
1290
+
1291
+ for (const gitlink of gitlinks) {
1292
+ const submodulePath = pathResolve(repoRoot, gitlink.path);
1293
+ const entry: MeshRefineSubmoduleReachabilityEntry = {
1294
+ path: gitlink.path,
1295
+ commit: gitlink.commit,
1296
+ reachable: false,
1297
+ };
1298
+ try {
1299
+ if (!fs.existsSync(submodulePath)) {
1300
+ entry.error = `Submodule checkout missing at ${gitlink.path}`;
1301
+ entry.publishRequired = true;
1302
+ entries.push(entry);
1303
+ continue;
1304
+ }
464
1305
 
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;
1306
+ entry.checkedLocal = true;
1307
+ try {
1308
+ await runGit(submodulePath, ['cat-file', '-e', `${gitlink.commit}^{commit}`]);
1309
+ entry.localReachable = true;
1310
+ } catch {
1311
+ entry.localReachable = false;
1312
+ // Probe the submodule remote before allowing cleanup/completion.
1313
+ }
1314
+
1315
+ try {
1316
+ entry.remote = 'origin';
1317
+ let remoteUrl = '';
1318
+ try {
1319
+ remoteUrl = (await runGit(submodulePath, ['remote', 'get-url', 'origin'])).trim();
1320
+ if (!remoteUrl) throw new Error('origin remote has no URL');
1321
+ entry.remoteUrl = remoteUrl;
1322
+ } catch {
1323
+ entry.error = 'Submodule remote reachability check failed: no configured origin remote';
1324
+ entry.publishRequired = true;
1325
+ entries.push(entry);
1326
+ continue;
1327
+ }
1328
+ await verifyRemoteCommitReachable(remoteUrl, gitlink.commit);
1329
+ entry.fetchedFromOrigin = true;
1330
+ entry.remoteReachable = true;
1331
+ entry.reachable = true;
1332
+ } catch (e: any) {
1333
+ entry.remoteReachable = false;
1334
+ entry.publishRequired = true;
1335
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
1336
+ entry.error = `Submodule remote reachability check failed for origin: ${details}`;
1337
+ }
1338
+ } catch (e: any) {
1339
+ entry.error = truncateValidationOutput(e?.message || String(e));
1340
+ entry.publishRequired = true;
1341
+ }
1342
+ entries.push(entry);
498
1343
  }
1344
+
1345
+ const unreachable = entries.filter(entry => !entry.reachable);
1346
+ return {
1347
+ status: unreachable.length ? 'failed' : 'passed',
1348
+ checked: entries.length,
1349
+ unreachable: unreachable.map(entry => ({ ...entry, publishRequired: entry.publishRequired !== false })),
1350
+ entries: entries.map(entry => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
1351
+ durationMs: Date.now() - startedAt,
1352
+ };
1353
+ } catch (e: any) {
1354
+ const unreachable = entries.filter(entry => !entry.reachable).map(entry => ({ ...entry, publishRequired: true }));
1355
+ return {
1356
+ status: 'failed',
1357
+ checked: entries.length,
1358
+ unreachable,
1359
+ entries: entries.map(entry => entry.reachable ? entry : { ...entry, publishRequired: true }),
1360
+ durationMs: Date.now() - startedAt,
1361
+ error: truncateValidationOutput(e?.message || String(e)),
1362
+ };
499
1363
  }
1364
+ }
500
1365
 
1366
+ function buildMeshRefineValidationPlan(mesh: any, workspace: string): Record<string, unknown> {
1367
+ const plan = resolveMeshRefineValidationPlan(mesh, workspace);
501
1368
  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',
1369
+ source: plan.source,
1370
+ sourceType: plan.sourceType,
1371
+ commands: plan.commands.map(command => ({
1372
+ displayCommand: command.displayCommand,
1373
+ category: command.category,
1374
+ source: command.source,
1375
+ cwd: command.cwd,
1376
+ timeoutMs: command.timeoutMs,
1377
+ })),
1378
+ unavailableReason: plan.unavailableReason,
1379
+ rejectedCommands: plan.rejectedCommands,
1380
+ suggestions: plan.suggestions,
1381
+ suggestedConfig: plan.suggestedConfig,
1382
+ note: plan.sourceType === 'unavailable'
1383
+ ? 'No validation command will be executed until a repo mesh/refine config is provided. Heuristics are suggestions only.'
1384
+ : 'Validation commands are resolved from repo mesh/refine config; heuristics are suggestions only.',
511
1385
  };
512
1386
  }
513
1387
 
@@ -515,7 +1389,7 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
515
1389
  const { execFile } = await import('node:child_process');
516
1390
  const { promisify } = await import('node:util');
517
1391
  const execFileAsync = promisify(execFile);
518
- const selection = selectMeshRefineValidationCommands(mesh, workspace);
1392
+ const selection = resolveMeshRefineValidationPlan(mesh, workspace);
519
1393
  const summary: MeshRefineValidationSummary = {
520
1394
  status: 'skipped',
521
1395
  required: true,
@@ -524,22 +1398,28 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
524
1398
  skippedReason: undefined,
525
1399
  timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
526
1400
  outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
1401
+ configSource: selection.source,
1402
+ configSourceType: selection.sourceType,
1403
+ suggestions: selection.suggestions,
1404
+ suggestedConfig: selection.suggestedConfig,
527
1405
  };
528
1406
 
529
1407
  if (!selection.commands.length) {
530
- summary.skippedReason = 'validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available';
1408
+ summary.skippedReason = selection.unavailableReason || 'validation_unavailable: repo mesh/refine config did not provide executable validation.commands';
531
1409
  return summary;
532
1410
  }
533
1411
 
534
1412
  for (const candidate of selection.commands) {
535
1413
  const startedAt = Date.now();
1414
+ const cwd = candidate.cwd ? pathResolve(workspace, candidate.cwd) : workspace;
1415
+ const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
536
1416
  try {
537
1417
  const result = await execFileAsync(candidate.command, candidate.args, {
538
- cwd: workspace,
1418
+ cwd,
539
1419
  encoding: 'utf8',
540
- timeout: REFINE_VALIDATION_TIMEOUT_MS,
1420
+ timeout,
541
1421
  maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
542
- env: { ...process.env, CI: process.env.CI || '1' },
1422
+ env: { ...process.env, CI: process.env.CI || '1', ...(candidate.env || {}) },
543
1423
  });
544
1424
  summary.commandsRun.push({
545
1425
  command: candidate.command,
@@ -547,6 +1427,7 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
547
1427
  displayCommand: candidate.displayCommand,
548
1428
  category: candidate.category,
549
1429
  source: candidate.source,
1430
+ cwd,
550
1431
  passed: true,
551
1432
  exitCode: 0,
552
1433
  durationMs: Date.now() - startedAt,
@@ -560,6 +1441,7 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
560
1441
  displayCommand: candidate.displayCommand,
561
1442
  category: candidate.category,
562
1443
  source: candidate.source,
1444
+ cwd,
563
1445
  passed: false,
564
1446
  exitCode: typeof error?.code === 'number' ? error.code : null,
565
1447
  signal: typeof error?.signal === 'string' ? error.signal : null,
@@ -698,6 +1580,10 @@ export interface CommandRouterDeps {
698
1580
  statusVersion?: string;
699
1581
  /** Session host control plane */
700
1582
  sessionHostControl?: SessionHostControlPlane | null;
1583
+ /** Selected-coordinator mesh peer telemetry surface for target daemons, when supported by the runtime. */
1584
+ getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
1585
+ /** Dispatch a command to a remote mesh node via P2P/relay. Injected by cloud runtime; absent in standalone. */
1586
+ dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
701
1587
  }
702
1588
 
703
1589
  export interface CommandRouterResult {
@@ -809,42 +1695,262 @@ function summarizeSessionHostPruneResult(result: unknown): Record<string, unknow
809
1695
  };
810
1696
  }
811
1697
 
1698
+ function normalizeStandaloneHostCommandUrl(hostAddress: string): string {
1699
+ const raw = hostAddress.trim();
1700
+ if (!raw) throw new Error('hostAddress required');
1701
+ const url = new URL(raw.replace(/^ws:/, 'http:').replace(/^wss:/, 'https:'));
1702
+ url.pathname = '/api/v1/command';
1703
+ url.search = '';
1704
+ url.hash = '';
1705
+ return url.toString();
1706
+ }
1707
+
1708
+ function buildMemberJoinNode(mesh: any, args: any, fallbackDaemonId?: string): Record<string, unknown> | null {
1709
+ const requestedNodeId = typeof args?.memberNodeId === 'string' ? args.memberNodeId.trim() : '';
1710
+ const explicit = args?.memberNode && typeof args.memberNode === 'object' && !Array.isArray(args.memberNode)
1711
+ ? args.memberNode as Record<string, any>
1712
+ : null;
1713
+ const configured = Array.isArray(mesh?.nodes)
1714
+ ? (requestedNodeId
1715
+ ? mesh.nodes.find((node: any) => node?.id === requestedNodeId || node?.nodeId === requestedNodeId)
1716
+ : mesh.nodes[0])
1717
+ : null;
1718
+ const source = explicit || configured;
1719
+ const workspace = typeof source?.workspace === 'string' && source.workspace.trim()
1720
+ ? source.workspace.trim()
1721
+ : typeof args?.workspace === 'string' && args.workspace.trim()
1722
+ ? args.workspace.trim()
1723
+ : process.cwd();
1724
+ if (!workspace) return null;
1725
+ const nodeId = typeof source?.id === 'string' && source.id.trim()
1726
+ ? source.id.trim()
1727
+ : typeof source?.nodeId === 'string' && source.nodeId.trim()
1728
+ ? source.nodeId.trim()
1729
+ : undefined;
1730
+ return {
1731
+ ...(nodeId ? { id: nodeId } : {}),
1732
+ workspace,
1733
+ ...(typeof source?.repoRoot === 'string' && source.repoRoot.trim() ? { repoRoot: source.repoRoot.trim() } : {}),
1734
+ ...(typeof source?.daemonId === 'string' && source.daemonId.trim() ? { daemonId: source.daemonId.trim() } : fallbackDaemonId ? { daemonId: fallbackDaemonId } : {}),
1735
+ ...(typeof source?.machineId === 'string' && source.machineId.trim() ? { machineId: source.machineId.trim() } : {}),
1736
+ userOverrides: source?.userOverrides && typeof source.userOverrides === 'object' && !Array.isArray(source.userOverrides) ? source.userOverrides : {},
1737
+ policy: source?.policy && typeof source.policy === 'object' && !Array.isArray(source.policy) ? source.policy : {},
1738
+ role: 'member',
1739
+ };
1740
+ }
1741
+
812
1742
  export class DaemonCommandRouter {
813
1743
  private deps: CommandRouterDeps;
814
1744
  /** In-memory cache for cloud-originating meshes passed via inlineMesh.
815
1745
  * Allows the MCP server to query mesh data via get_mesh even when
816
1746
  * the mesh doesn't exist in the local meshes.json file. */
817
1747
  private inlineMeshCache = new Map<string, any>();
1748
+ /** Coordinator-owned whole-mesh aggregate status snapshots. Browser callers read this by default. */
1749
+ private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any; queueRevision: string }>();
1750
+ /** In-memory async Refinery jobs keyed by meshId:nodeId to reject/return duplicate in-flight requests. */
1751
+ private runningRefineJobs = new Map<string, MeshRefineJobHandle>();
1752
+ /** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
1753
+ private terminalRefineJobs = new Map<string, MeshRefineTerminalJob>();
818
1754
 
819
1755
  constructor(deps: CommandRouterDeps) {
820
1756
  this.deps = deps;
821
1757
  }
822
1758
 
1759
+ private cloneJsonValue<T>(value: T): T {
1760
+ if (typeof structuredClone === 'function') return structuredClone(value);
1761
+ return JSON.parse(JSON.stringify(value)) as T;
1762
+ }
1763
+
1764
+ private hydrateCachedAggregateMeshStatusFromInline(snapshot: any, mesh: any, options?: { requireDirectPeerTruth?: boolean }): any {
1765
+ if (!mesh || typeof mesh !== 'object' || !Array.isArray(mesh.nodes) || !Array.isArray(snapshot?.nodes)) return snapshot;
1766
+ const inlineNodesById = new Map<string, any>();
1767
+ for (const node of mesh.nodes) {
1768
+ const nodeId = readInlineMeshNodeId(node);
1769
+ if (nodeId) inlineNodesById.set(nodeId, node);
1770
+ }
1771
+ if (!inlineNodesById.size) return snapshot;
1772
+
1773
+ let changed = false;
1774
+ const unavailableNodeIds = new Set<string>();
1775
+ const sourceOfTruth = readObjectRecord(snapshot.sourceOfTruth);
1776
+ const directPeerTruth = readObjectRecord(sourceOfTruth.directPeerTruth);
1777
+ for (const entry of Array.isArray(directPeerTruth.unavailableNodeIds) ? directPeerTruth.unavailableNodeIds : []) {
1778
+ const nodeId = readStringValue(entry);
1779
+ if (nodeId) unavailableNodeIds.add(nodeId);
1780
+ }
1781
+
1782
+ const nodes = snapshot.nodes.map((statusNode: any) => {
1783
+ const nodeId = readStringValue(statusNode?.nodeId, statusNode?.id);
1784
+ const inlineNode = nodeId ? inlineNodesById.get(nodeId) : undefined;
1785
+ if (!inlineNode) return statusNode;
1786
+ const liveGit = buildInlineMeshTransitGitStatus(inlineNode);
1787
+ if (!liveGit) return statusNode;
1788
+ const nextStatus = { ...statusNode };
1789
+ nextStatus.git = liveGit;
1790
+ nextStatus.health = deriveMeshNodeHealthFromGit(liveGit);
1791
+ applyInlineMeshBranchConvergence(mesh, inlineNode, nextStatus);
1792
+ nextStatus.launchReady = readBooleanValue(nextStatus.launchReady) ?? true;
1793
+ const connection = readObjectRecord(nextStatus.connection);
1794
+ const connectionState = readStringValue(connection.state);
1795
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
1796
+ if (!connectionReported || connectionState === 'unknown') {
1797
+ nextStatus.connection = buildLivePeerGitConnection(connection);
1798
+ }
1799
+ delete nextStatus.gitProbePending;
1800
+ const error = readStringValue(nextStatus.error);
1801
+ if (error && /pending_git|git probe|live peer git snapshot|no peer git snapshot/i.test(error)) delete nextStatus.error;
1802
+ if (!readStringValue(nextStatus.machineStatus)) nextStatus.machineStatus = 'online';
1803
+ if (nodeId) unavailableNodeIds.delete(nodeId);
1804
+ changed = true;
1805
+ return nextStatus;
1806
+ });
1807
+
1808
+ if (!changed && !(options?.requireDirectPeerTruth && unavailableNodeIds.size > 0)) return snapshot;
1809
+ const nextSourceOfTruth = {
1810
+ ...sourceOfTruth,
1811
+ ...(Object.keys(directPeerTruth).length ? {
1812
+ directPeerTruth: {
1813
+ ...directPeerTruth,
1814
+ satisfied: options?.requireDirectPeerTruth === true ? unavailableNodeIds.size === 0 : directPeerTruth.satisfied,
1815
+ unavailableNodeIds: [...unavailableNodeIds],
1816
+ },
1817
+ ...(options?.requireDirectPeerTruth === true ? {
1818
+ coordinatorOwnsLiveTruth: unavailableNodeIds.size === 0,
1819
+ currentStatus: unavailableNodeIds.size === 0 ? 'live_git_and_session_probes' : 'direct_peer_truth_unavailable',
1820
+ } : {}),
1821
+ } : {}),
1822
+ };
1823
+ return {
1824
+ ...snapshot,
1825
+ ...(options?.requireDirectPeerTruth === true && unavailableNodeIds.size > 0 ? {
1826
+ success: false,
1827
+ code: 'mesh_direct_peer_truth_unavailable',
1828
+ error: 'Selected coordinator could not confirm direct mesh truth for every remote node yet.',
1829
+ } : {}),
1830
+ sourceOfTruth: nextSourceOfTruth,
1831
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodes),
1832
+ nodes,
1833
+ };
1834
+ }
1835
+
1836
+ private getCachedAggregateMeshStatus(meshId: string, mesh?: any, options?: { requireDirectPeerTruth?: boolean }): any | null {
1837
+ const cached = this.aggregateMeshStatusCache.get(meshId);
1838
+ if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
1839
+ if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
1840
+ let snapshot = this.cloneJsonValue(cached.snapshot);
1841
+ snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
1842
+ if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
1843
+ const ageMs = Math.max(0, Date.now() - cached.builtAt);
1844
+ const sourceOfTruth = snapshot.sourceOfTruth && typeof snapshot.sourceOfTruth === 'object'
1845
+ ? snapshot.sourceOfTruth
1846
+ : {};
1847
+ snapshot.sourceOfTruth = {
1848
+ ...sourceOfTruth,
1849
+ aggregateSnapshot: {
1850
+ ...(sourceOfTruth.aggregateSnapshot && typeof sourceOfTruth.aggregateSnapshot === 'object'
1851
+ ? sourceOfTruth.aggregateSnapshot
1852
+ : {}),
1853
+ owner: 'coordinator_daemon_memory',
1854
+ cached: true,
1855
+ source: 'memory',
1856
+ refreshReason: 'memory_cache_hit',
1857
+ ageMs,
1858
+ cachedAt: new Date(cached.builtAt).toISOString(),
1859
+ returnedAt: new Date().toISOString(),
1860
+ },
1861
+ };
1862
+ return snapshot;
1863
+ }
1864
+
1865
+ private rememberAggregateMeshStatus(meshId: string, snapshot: any, refreshReason: string): any {
1866
+ if (!snapshot || typeof snapshot !== 'object' || snapshot.success !== true || !Array.isArray(snapshot.nodes)) return snapshot;
1867
+ const builtAt = Date.now();
1868
+ const next = this.cloneJsonValue(snapshot);
1869
+ const sourceOfTruth = next.sourceOfTruth && typeof next.sourceOfTruth === 'object'
1870
+ ? next.sourceOfTruth
1871
+ : {};
1872
+ next.sourceOfTruth = {
1873
+ ...sourceOfTruth,
1874
+ aggregateSnapshot: {
1875
+ owner: 'coordinator_daemon_memory',
1876
+ cached: false,
1877
+ source: 'live_refresh',
1878
+ refreshReason,
1879
+ ageMs: 0,
1880
+ cachedAt: new Date(builtAt).toISOString(),
1881
+ returnedAt: new Date(builtAt).toISOString(),
1882
+ },
1883
+ };
1884
+ this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next), queueRevision: getMeshQueueRevision(meshId) });
1885
+ return next;
1886
+ }
1887
+
823
1888
  public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
824
1889
  if (inlineMesh && typeof inlineMesh === 'object') {
825
- this.inlineMeshCache.set(meshId, inlineMesh as any);
826
- return inlineMesh as any;
1890
+ return this.warmInlineMeshCache(meshId, inlineMesh);
827
1891
  }
828
1892
  return this.inlineMeshCache.get(meshId);
829
1893
  }
830
1894
 
1895
+ private warmInlineMeshCache(meshId: string, inlineMesh?: unknown): any | undefined {
1896
+ if (!inlineMesh || typeof inlineMesh !== 'object') return undefined;
1897
+ const sanitizedInlineMesh = sanitizeInlineMesh(inlineMesh as any);
1898
+ const cached = this.inlineMeshCache.get(meshId);
1899
+ if (cached) {
1900
+ const merged = reconcileInlineMeshCache(cached, sanitizedInlineMesh);
1901
+ this.inlineMeshCache.set(meshId, merged);
1902
+ return merged;
1903
+ }
1904
+ this.inlineMeshCache.set(meshId, sanitizedInlineMesh as any);
1905
+ return sanitizedInlineMesh as any;
1906
+ }
1907
+
831
1908
  private async getMeshForCommand(
832
1909
  meshId: string,
833
1910
  inlineMesh?: unknown,
834
1911
  options?: { preferInline?: boolean },
835
- ): Promise<{ mesh: any; inline: boolean } | null> {
1912
+ ): Promise<{ mesh: any; inline: boolean; source: 'inline_cache' | 'inline_bootstrap' | 'local_config' } | null> {
836
1913
  const preferInline = options?.preferInline === true;
837
1914
  if (preferInline) {
838
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
839
- if (cached) return { mesh: cached, inline: true };
1915
+ const cached = this.getCachedInlineMesh(meshId);
1916
+ if (cached) {
1917
+ if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
1918
+ const merged = reconcileInlineMeshCache(cached, inlineMesh as any);
1919
+ this.inlineMeshCache.set(meshId, sanitizeInlineMesh(merged));
1920
+ return { mesh: merged, inline: true, source: 'inline_cache' };
1921
+ }
1922
+ return { mesh: cached, inline: true, source: 'inline_cache' };
1923
+ }
1924
+ if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
1925
+ this.warmInlineMeshCache(meshId, inlineMesh);
1926
+ return { mesh: inlineMesh, inline: true, source: 'inline_bootstrap' };
1927
+ }
840
1928
  }
841
1929
  try {
842
1930
  const { getMesh } = await import('../config/mesh-config.js');
843
1931
  const mesh = getMesh(meshId);
844
- if (mesh) return { mesh, inline: false };
1932
+ if (mesh) return { mesh, inline: false, source: 'local_config' };
845
1933
  } catch { /* fall through to inline cache */ }
846
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
847
- return cached ? { mesh: cached, inline: true } : null;
1934
+ const cached = this.getCachedInlineMesh(meshId);
1935
+ if (cached) return { mesh: cached, inline: true, source: 'inline_cache' };
1936
+ const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
1937
+ return warmedInline ? { mesh: warmedInline, inline: true, source: 'inline_bootstrap' } : null;
1938
+ }
1939
+
1940
+ private invalidateAggregateMeshStatus(meshId: string): void {
1941
+ this.aggregateMeshStatusCache.delete(meshId);
1942
+ }
1943
+
1944
+
1945
+ private async requireMeshHostMutationOwner(meshId: string, inlineMesh: unknown, operation: string): Promise<CommandRouterResult | null> {
1946
+ const meshRecord = await this.getMeshForCommand(meshId, inlineMesh, { preferInline: true });
1947
+ const mesh = meshRecord?.mesh;
1948
+ if (!mesh) return { success: false, error: 'Mesh not found' };
1949
+ const meshHost = resolveMeshHostStatus(mesh);
1950
+ if (!meshHost.canOwnCoordinator || !meshHost.canOwnQueue) {
1951
+ return { ...buildMeshHostRequiredFailure(mesh, operation), success: false, meshId };
1952
+ }
1953
+ return null;
848
1954
  }
849
1955
 
850
1956
  private updateInlineMeshNode(meshId: string, mesh: any, node: any): void {
@@ -854,6 +1960,7 @@ export class DaemonCommandRouter {
854
1960
  else mesh.nodes.push(node);
855
1961
  mesh.updatedAt = new Date().toISOString();
856
1962
  this.inlineMeshCache.set(meshId, mesh);
1963
+ this.invalidateAggregateMeshStatus(meshId);
857
1964
  }
858
1965
 
859
1966
  private removeInlineMeshNode(meshId: string, mesh: any, nodeId: string): boolean {
@@ -863,6 +1970,7 @@ export class DaemonCommandRouter {
863
1970
  mesh.nodes.splice(idx, 1);
864
1971
  mesh.updatedAt = new Date().toISOString();
865
1972
  this.inlineMeshCache.set(meshId, mesh);
1973
+ this.invalidateAggregateMeshStatus(meshId);
866
1974
  return true;
867
1975
  }
868
1976
 
@@ -1141,6 +2249,7 @@ export class DaemonCommandRouter {
1141
2249
  const deletedSessionIds: string[] = [];
1142
2250
  const skippedSessionIds: string[] = [];
1143
2251
  const skippedLiveSessionIds: string[] = [];
2252
+ const skippedCoordinatorSessionIds: string[] = [];
1144
2253
  const deleteUnsupportedSessionIds: string[] = [];
1145
2254
  const recordsRemainSessionIds: string[] = [];
1146
2255
  const errors: Array<{ sessionId: string; error: string }> = [];
@@ -1175,6 +2284,12 @@ export class DaemonCommandRouter {
1175
2284
  const completed = this.isCompletedHostedSession(record);
1176
2285
  const surfaceKind = getSessionHostSurfaceKind(record);
1177
2286
  const liveRuntime = surfaceKind === 'live_runtime';
2287
+ const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
2288
+ if (!hasExplicitSessionIds && coordinatorSession) {
2289
+ skippedSessionIds.push(sessionId);
2290
+ skippedCoordinatorSessionIds.push(sessionId);
2291
+ continue;
2292
+ }
1178
2293
  if (!hasExplicitSessionIds && liveRuntime) {
1179
2294
  skippedSessionIds.push(sessionId);
1180
2295
  skippedLiveSessionIds.push(sessionId);
@@ -1244,6 +2359,7 @@ export class DaemonCommandRouter {
1244
2359
  deletedSessionIds,
1245
2360
  skippedSessionIds,
1246
2361
  skippedLiveSessionIds,
2362
+ skippedCoordinatorSessionIds,
1247
2363
  ...(deleteUnsupported ? {
1248
2364
  deleteUnsupported: true,
1249
2365
  effectiveCleanup: args.mode === 'stop_and_delete'
@@ -1365,23 +2481,458 @@ export class DaemonCommandRouter {
1365
2481
  payload: { cmd, source: logSource, success: handlerResult.success, durationMs: Date.now() - cmdStart },
1366
2482
  });
1367
2483
 
1368
- // 3. Post-chat command callback
1369
- if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
1370
- this.deps.onPostChatCommand();
2484
+ // 3. Post-chat command callback
2485
+ if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
2486
+ this.deps.onPostChatCommand();
2487
+ }
2488
+
2489
+ return handlerResult;
2490
+ } catch (e: any) {
2491
+ logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: false, error: e.message, durationMs: Date.now() - cmdStart });
2492
+ recordDebugTrace({
2493
+ interactionId,
2494
+ category: 'command',
2495
+ stage: 'failed',
2496
+ level: 'error',
2497
+ payload: { cmd, source: logSource, error: e?.message || String(e), durationMs: Date.now() - cmdStart },
2498
+ });
2499
+ throw e;
2500
+ }
2501
+ }
2502
+
2503
+
2504
+ private buildRefineJobKey(meshId: string, nodeId: string): string {
2505
+ return `${meshId}:${nodeId}`;
2506
+ }
2507
+
2508
+ private buildRefineJobHandle(args: {
2509
+ meshId: string;
2510
+ nodeId: string;
2511
+ node?: any;
2512
+ status?: MeshRefineAsyncJobStatus;
2513
+ startedAt?: string;
2514
+ completedAt?: string;
2515
+ jobId?: string;
2516
+ interactionId?: string;
2517
+ retryOfJobId?: string;
2518
+ }): MeshRefineJobHandle {
2519
+ return {
2520
+ success: true,
2521
+ async: true,
2522
+ status: args.status || 'accepted',
2523
+ jobId: args.jobId || `refine_${createInteractionId()}`,
2524
+ interactionId: args.interactionId || createInteractionId(),
2525
+ meshId: args.meshId,
2526
+ nodeId: args.nodeId,
2527
+ targetNodeId: args.nodeId,
2528
+ targetDaemonId: readStringValue(args.node?.daemonId),
2529
+ workspace: readStringValue(args.node?.workspace),
2530
+ startedAt: args.startedAt || new Date().toISOString(),
2531
+ ...(args.completedAt ? { completedAt: args.completedAt } : {}),
2532
+ ...(args.retryOfJobId ? { retryOfJobId: args.retryOfJobId } : {}),
2533
+ eventDelivery: { pendingEvents: true, ledger: true },
2534
+ evidence: {
2535
+ pendingEventsCommand: 'get_pending_mesh_events',
2536
+ ledgerCommand: 'get_mesh_ledger_slice',
2537
+ taskHistoryKind: args.status === 'completed' ? 'task_completed' : args.status === 'failed' ? 'task_failed' : 'task_dispatched',
2538
+ },
2539
+ };
2540
+ }
2541
+
2542
+ private queueRefineJobEvent(event: 'refine:accepted' | 'refine:completed' | 'refine:failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): void {
2543
+ queuePendingMeshCoordinatorEvent({
2544
+ event,
2545
+ meshId: handle.meshId,
2546
+ nodeLabel: handle.targetNodeId,
2547
+ nodeId: handle.targetNodeId,
2548
+ workspace: handle.workspace,
2549
+ metadataEvent: {
2550
+ source: 'refine_mesh_node_async_job',
2551
+ jobId: handle.jobId,
2552
+ interactionId: handle.interactionId,
2553
+ meshId: handle.meshId,
2554
+ nodeId: handle.targetNodeId,
2555
+ targetDaemonId: handle.targetDaemonId,
2556
+ workspace: handle.workspace,
2557
+ status: handle.status,
2558
+ startedAt: handle.startedAt,
2559
+ completedAt: handle.completedAt,
2560
+ retryOfJobId: handle.retryOfJobId,
2561
+ ...(result ? { result } : {}),
2562
+ },
2563
+ queuedAt: Date.now(),
2564
+ });
2565
+ }
2566
+
2567
+ private async appendRefineJobLedger(kind: 'task_dispatched' | 'task_completed' | 'task_failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): Promise<void> {
2568
+ try {
2569
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2570
+ appendLedgerEntry(handle.meshId, {
2571
+ kind,
2572
+ nodeId: handle.targetNodeId,
2573
+ payload: {
2574
+ source: 'refine_mesh_node_async_job',
2575
+ refineJob: {
2576
+ jobId: handle.jobId,
2577
+ interactionId: handle.interactionId,
2578
+ status: handle.status,
2579
+ meshId: handle.meshId,
2580
+ nodeId: handle.targetNodeId,
2581
+ targetDaemonId: handle.targetDaemonId,
2582
+ workspace: handle.workspace,
2583
+ startedAt: handle.startedAt,
2584
+ completedAt: handle.completedAt,
2585
+ retryOfJobId: handle.retryOfJobId,
2586
+ },
2587
+ async: true,
2588
+ retryOfJobId: handle.retryOfJobId,
2589
+ ...(result ? {
2590
+ success: result.success === true,
2591
+ result,
2592
+ finalBranchConvergenceState: result.finalBranchConvergenceState,
2593
+ } : {}),
2594
+ },
2595
+ });
2596
+ } catch (e: any) {
2597
+ LOG.warn('Mesh', `[Refinery] Failed to append async refine ledger entry: ${e?.message || e}`);
2598
+ }
2599
+ }
2600
+
2601
+ private async executeMeshRefineNodeSynchronously(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
2602
+ const refineStages: Array<Record<string, unknown>> = [];
2603
+ try {
2604
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2605
+ const mesh = meshRecord?.mesh;
2606
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2607
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh`, refineStages };
2608
+
2609
+ if (!node.isLocalWorktree || !node.workspace) {
2610
+ return { success: false, error: `Refinery requires a local worktree node`, refineStages };
2611
+ }
2612
+
2613
+ const sourceNode = node.clonedFromNodeId
2614
+ ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2615
+ : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2616
+ const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2617
+ if (!repoRoot) return { success: false, error: 'Source node repoRoot not found', refineStages };
2618
+
2619
+ const { execFile } = await import('node:child_process');
2620
+ const { promisify } = await import('node:util');
2621
+ const execFileAsync = promisify(execFile);
2622
+
2623
+ const resolveStarted = Date.now();
2624
+ const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2625
+ const branch = branchStdout.trim();
2626
+ if (!branch) return { success: false, error: 'Could not determine branch of the worktree node', refineStages };
2627
+
2628
+ const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2629
+ const baseBranch = baseBranchStdout.trim();
2630
+ const { stdout: baseHeadStdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
2631
+ const { stdout: branchHeadStdout } = await execFileAsync('git', ['rev-parse', branch], { cwd: node.workspace, encoding: 'utf8' });
2632
+ const baseHead = baseHeadStdout.trim();
2633
+ const branchHead = branchHeadStdout.trim();
2634
+ recordMeshRefineStage(refineStages, 'resolve_refs', 'passed', resolveStarted, { branch, baseBranch, baseHead, branchHead });
2635
+
2636
+ const validationStarted = Date.now();
2637
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2638
+ recordMeshRefineStage(
2639
+ refineStages,
2640
+ 'validation',
2641
+ validationSummary.status === 'passed' ? 'passed' : validationSummary.status === 'failed' ? 'failed' : 'skipped',
2642
+ validationStarted,
2643
+ { validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length },
2644
+ );
2645
+ if (validationSummary.status === 'failed') {
2646
+ return {
2647
+ success: false,
2648
+ code: 'validation_failed',
2649
+ convergenceStatus: 'blocked_review',
2650
+ error: 'Refinery validation gate failed; merge/refine was not attempted.',
2651
+ branch,
2652
+ into: baseBranch,
2653
+ validationSummary,
2654
+ refineStages,
2655
+ finalBranchConvergenceState: {
2656
+ branch,
2657
+ baseBranch,
2658
+ merged: false,
2659
+ removed: false,
2660
+ validation: 'failed',
2661
+ status: 'blocked_review',
2662
+ },
2663
+ };
2664
+ }
2665
+ if (validationSummary.status === 'skipped') {
2666
+ return {
2667
+ success: false,
2668
+ code: 'validation_unavailable',
2669
+ convergenceStatus: 'blocked_review',
2670
+ error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
2671
+ branch,
2672
+ into: baseBranch,
2673
+ validationSummary,
2674
+ refineStages,
2675
+ finalBranchConvergenceState: {
2676
+ branch,
2677
+ baseBranch,
2678
+ merged: false,
2679
+ removed: false,
2680
+ validation: 'unavailable',
2681
+ status: 'blocked_review',
2682
+ },
2683
+ };
2684
+ }
2685
+
2686
+ const patchEquivalenceStarted = Date.now();
2687
+ const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
2688
+ recordMeshRefineStage(refineStages, 'patch_equivalence', patchEquivalence.status, patchEquivalenceStarted, {
2689
+ equivalent: patchEquivalence.equivalent,
2690
+ expectedPatchId: patchEquivalence.expectedPatchId,
2691
+ actualPatchId: patchEquivalence.actualPatchId,
2692
+ error: patchEquivalence.error,
2693
+ });
2694
+ if (!patchEquivalence.equivalent) {
2695
+ return {
2696
+ success: false,
2697
+ code: 'patch_equivalence_failed',
2698
+ convergenceStatus: 'blocked_review',
2699
+ error: 'Refinery patch-equivalence preflight failed; merge/refine was not attempted.',
2700
+ branch,
2701
+ into: baseBranch,
2702
+ validationSummary,
2703
+ patchEquivalence,
2704
+ refineStages,
2705
+ finalBranchConvergenceState: {
2706
+ branch,
2707
+ baseBranch,
2708
+ merged: false,
2709
+ removed: false,
2710
+ validation: 'passed',
2711
+ patchEquivalence: 'failed',
2712
+ status: 'blocked_review',
2713
+ },
2714
+ };
2715
+ }
2716
+
2717
+ const submoduleReachabilityStarted = Date.now();
2718
+ const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead);
2719
+ recordMeshRefineStage(refineStages, 'submodule_reachability', submoduleReachability.status, submoduleReachabilityStarted, {
2720
+ checked: submoduleReachability.checked,
2721
+ unreachable: submoduleReachability.unreachable.map(entry => ({
2722
+ path: entry.path,
2723
+ commit: entry.commit,
2724
+ publishRequired: entry.publishRequired === true,
2725
+ remote: entry.remote,
2726
+ remoteUrl: entry.remoteUrl,
2727
+ remoteReachable: entry.remoteReachable,
2728
+ error: entry.error,
2729
+ })),
2730
+ error: submoduleReachability.error,
2731
+ });
2732
+ if (submoduleReachability.status === 'failed') {
2733
+ const nextStep = buildSubmodulePublishRequiredNextStep(submoduleReachability.unreachable);
2734
+ return {
2735
+ success: false,
2736
+ code: 'submodule_reachability_failed',
2737
+ convergenceStatus: 'blocked_review',
2738
+ publishRequired: true,
2739
+ blockedReason: 'submodule_publish_required',
2740
+ error: 'Refinery submodule reachability preflight failed because one or more submodule gitlink commits are not reachable from their configured remote; merge/refine cleanup was not attempted.',
2741
+ nextStep,
2742
+ nextSteps: [
2743
+ 'Ask the user for explicit approval before pushing or publishing any submodule commit.',
2744
+ 'Push/publish each unreachable submodule commit to the configured submodule remote shown in the evidence.',
2745
+ 'Rerun mesh_refine_node after remote reachability is confirmed.',
2746
+ 'Do not merge the root branch until every submodule gitlink commit is reachable from its configured remote.',
2747
+ ],
2748
+ unreachableSubmoduleCommits: submoduleReachability.unreachable.map(entry => ({
2749
+ path: entry.path,
2750
+ commit: entry.commit,
2751
+ remote: entry.remote,
2752
+ remoteUrl: entry.remoteUrl,
2753
+ remoteReachable: entry.remoteReachable,
2754
+ error: entry.error,
2755
+ })),
2756
+ branch,
2757
+ into: baseBranch,
2758
+ validationSummary,
2759
+ patchEquivalence,
2760
+ submoduleReachability,
2761
+ refineStages,
2762
+ finalBranchConvergenceState: {
2763
+ branch,
2764
+ baseBranch,
2765
+ merged: false,
2766
+ removed: false,
2767
+ validation: 'passed',
2768
+ patchEquivalence: 'passed',
2769
+ submoduleReachability: 'failed',
2770
+ status: 'blocked_review',
2771
+ reason: 'submodule_publish_required',
2772
+ nextStep,
2773
+ },
2774
+ };
2775
+ }
2776
+
2777
+ let mergeResult: Record<string, unknown> | undefined;
2778
+ const mergeStarted = Date.now();
2779
+ try {
2780
+ const result = await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2781
+ mergeResult = {
2782
+ stdout: truncateValidationOutput(result.stdout),
2783
+ stderr: truncateValidationOutput(result.stderr),
2784
+ durationMs: Date.now() - mergeStarted,
2785
+ };
2786
+ recordMeshRefineStage(refineStages, 'merge', 'passed', mergeStarted, mergeResult);
2787
+ } catch (e: any) {
2788
+ recordMeshRefineStage(refineStages, 'merge', 'failed', mergeStarted, {
2789
+ error: e?.message || String(e),
2790
+ stdout: truncateValidationOutput(e?.stdout),
2791
+ stderr: truncateValidationOutput(e?.stderr),
2792
+ });
2793
+ return {
2794
+ success: false,
2795
+ error: `Merge failed (conflicts?): ${e.message}`,
2796
+ validationSummary,
2797
+ patchEquivalence,
2798
+ refineStages,
2799
+ finalBranchConvergenceState: {
2800
+ branch,
2801
+ baseBranch,
2802
+ merged: false,
2803
+ removed: false,
2804
+ validation: 'passed',
2805
+ patchEquivalence: 'passed',
2806
+ status: 'not_mergeable',
2807
+ },
2808
+ };
2809
+ }
2810
+
2811
+ const cleanupStarted = Date.now();
2812
+ const removeResult = await this.execute('remove_mesh_node', {
2813
+ meshId,
2814
+ nodeId,
2815
+ sessionCleanupMode: 'preserve',
2816
+ inlineMesh: args?.inlineMesh,
2817
+ });
2818
+ recordMeshRefineStage(refineStages, 'cleanup', removeResult?.success === false ? 'failed' : 'passed', cleanupStarted, {
2819
+ removed: removeResult?.removed,
2820
+ code: removeResult?.code,
2821
+ error: removeResult?.error,
2822
+ });
2823
+
2824
+ let ledgerError: string | undefined;
2825
+ const ledgerStarted = Date.now();
2826
+ try {
2827
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2828
+ appendLedgerEntry(meshId, {
2829
+ kind: 'node_removed',
2830
+ nodeId,
2831
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence },
2832
+ });
2833
+ recordMeshRefineStage(refineStages, 'ledger', 'passed', ledgerStarted);
2834
+ } catch (e: any) {
2835
+ ledgerError = e?.message || String(e);
2836
+ recordMeshRefineStage(refineStages, 'ledger', 'failed', ledgerStarted, { error: ledgerError });
1371
2837
  }
1372
2838
 
1373
- return handlerResult;
2839
+ const finalBranchConvergenceState = {
2840
+ branch: baseBranch,
2841
+ mergedBranch: branch,
2842
+ baseBranch,
2843
+ merged: true,
2844
+ removed: removeResult?.success !== false,
2845
+ validation: 'passed',
2846
+ patchEquivalence: 'passed',
2847
+ status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
2848
+ };
2849
+
2850
+ if (removeResult?.success === false) {
2851
+ return {
2852
+ success: false,
2853
+ code: 'cleanup_failed',
2854
+ error: 'Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.',
2855
+ merged: true,
2856
+ branch,
2857
+ into: baseBranch,
2858
+ removeResult,
2859
+ validationSummary,
2860
+ patchEquivalence,
2861
+ mergeResult,
2862
+ refineStages,
2863
+ ...(ledgerError ? { ledgerError } : {}),
2864
+ finalBranchConvergenceState,
2865
+ };
2866
+ }
2867
+
2868
+ return {
2869
+ success: true,
2870
+ merged: true,
2871
+ branch,
2872
+ into: baseBranch,
2873
+ removeResult,
2874
+ validationSummary,
2875
+ patchEquivalence,
2876
+ mergeResult,
2877
+ refineStages,
2878
+ ...(ledgerError ? { ledgerError } : {}),
2879
+ finalBranchConvergenceState,
2880
+ };
1374
2881
  } 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;
2882
+ return { success: false, error: e.message, refineStages };
2883
+ }
2884
+ }
2885
+
2886
+ private async finishMeshRefineJob(handle: MeshRefineJobHandle, args: any): Promise<void> {
2887
+ const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
2888
+ let result: Record<string, unknown>;
2889
+ try {
2890
+ result = await this.executeMeshRefineNodeSynchronously(handle.meshId, handle.targetNodeId, args) as Record<string, unknown>;
2891
+ } catch (e: any) {
2892
+ result = { success: false, error: e?.message || String(e) };
1384
2893
  }
2894
+ const completedAt = new Date().toISOString();
2895
+ const terminalHandle = this.buildRefineJobHandle({
2896
+ meshId: handle.meshId,
2897
+ nodeId: handle.targetNodeId,
2898
+ status: result.success === true ? 'completed' : 'failed',
2899
+ startedAt: handle.startedAt,
2900
+ completedAt,
2901
+ jobId: handle.jobId,
2902
+ interactionId: handle.interactionId,
2903
+ retryOfJobId: handle.retryOfJobId,
2904
+ node: { daemonId: handle.targetDaemonId, workspace: handle.workspace },
2905
+ });
2906
+ const terminal: MeshRefineTerminalJob = { ...terminalHandle, result };
2907
+ this.terminalRefineJobs.set(key, terminal);
2908
+ this.runningRefineJobs.delete(key);
2909
+ this.invalidateAggregateMeshStatus(handle.meshId);
2910
+ await this.appendRefineJobLedger(result.success === true ? 'task_completed' : 'task_failed', terminalHandle, result);
2911
+ this.queueRefineJobEvent(result.success === true ? 'refine:completed' : 'refine:failed', terminalHandle, result);
2912
+ }
2913
+
2914
+ private async startMeshRefineJob(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
2915
+ const key = this.buildRefineJobKey(meshId, nodeId);
2916
+ const running = this.runningRefineJobs.get(key);
2917
+ if (running) return { ...running, duplicate: true };
2918
+ const terminal = this.terminalRefineJobs.get(key);
2919
+
2920
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2921
+ const mesh = meshRecord?.mesh;
2922
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2923
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2924
+ if (!node.isLocalWorktree || !node.workspace) return { success: false, error: `Refinery requires a local worktree node` };
2925
+
2926
+ const handle = this.buildRefineJobHandle({ meshId, nodeId, node, retryOfJobId: terminal?.jobId });
2927
+ this.runningRefineJobs.set(key, handle);
2928
+ await this.appendRefineJobLedger('task_dispatched', handle);
2929
+ this.queueRefineJobEvent('refine:accepted', handle);
2930
+
2931
+ setImmediate(() => {
2932
+ void this.finishMeshRefineJob(handle, args);
2933
+ });
2934
+
2935
+ return handle;
1385
2936
  }
1386
2937
 
1387
2938
  // ─── Daemon-level command core ───────────────────
@@ -1398,7 +2949,8 @@ export class DaemonCommandRouter {
1398
2949
  }
1399
2950
 
1400
2951
  case 'get_pending_mesh_events': {
1401
- const events = drainPendingMeshCoordinatorEvents();
2952
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2953
+ const events = drainPendingMeshCoordinatorEvents(meshId || undefined);
1402
2954
  return { success: true, events };
1403
2955
  }
1404
2956
 
@@ -2003,15 +3555,44 @@ export class DaemonCommandRouter {
2003
3555
  case 'get_mesh': {
2004
3556
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2005
3557
  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' };
3558
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3559
+ if (!meshRecord?.mesh) return { success: false, error: 'Mesh not found' };
3560
+
3561
+ const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
3562
+ const directTruth = await hydrateInlineMeshDirectTruth({
3563
+ mesh: meshRecord.mesh,
3564
+ meshSource: meshRecord.source,
3565
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
3566
+ statusInstanceId: this.deps.statusInstanceId,
3567
+ localMachineId: loadConfig().machineId || '',
3568
+ });
3569
+ const directTruthSatisfied = meshRecord.source !== 'inline_bootstrap' || directTruth.directEvidenceCount > 0;
3570
+ const sourceOfTruth = {
3571
+ membership: meshRecord.source === 'inline_cache'
3572
+ ? 'coordinator_inline_mesh_cache'
3573
+ : meshRecord.source === 'local_config'
3574
+ ? 'local_mesh_config'
3575
+ : 'inline_bootstrap_snapshot',
3576
+ coordinatorOwnsLiveTruth: directTruthSatisfied,
3577
+ directPeerTruth: {
3578
+ required: requireDirectPeerTruth,
3579
+ satisfied: directTruthSatisfied,
3580
+ directEvidenceCount: directTruth.directEvidenceCount,
3581
+ localConfirmedCount: directTruth.localConfirmedCount,
3582
+ peerAttemptedCount: directTruth.peerAttemptedCount,
3583
+ peerConfirmedCount: directTruth.peerConfirmedCount,
3584
+ unavailableNodeIds: directTruth.unavailableNodeIds,
3585
+ },
3586
+ };
3587
+ if (requireDirectPeerTruth && !directTruthSatisfied) {
3588
+ return {
3589
+ success: false,
3590
+ code: 'mesh_direct_peer_truth_unavailable',
3591
+ error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct get_mesh probes succeed.',
3592
+ sourceOfTruth,
3593
+ };
3594
+ }
3595
+ return { success: true, mesh: meshRecord.mesh, sourceOfTruth };
2015
3596
  }
2016
3597
 
2017
3598
  case 'create_mesh': {
@@ -2022,7 +3603,10 @@ export class DaemonCommandRouter {
2022
3603
  if (!name) return { success: false, error: 'name required' };
2023
3604
  try {
2024
3605
  const { createMesh } = await import('../config/mesh-config.js');
2025
- const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy });
3606
+ const meshHost = args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)
3607
+ ? args.meshHost
3608
+ : undefined;
3609
+ const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy, meshHost });
2026
3610
  return { success: true, mesh };
2027
3611
  } catch (e: any) {
2028
3612
  return { success: false, error: e.message };
@@ -2039,16 +3623,237 @@ export class DaemonCommandRouter {
2039
3623
  if (typeof args?.defaultBranch === 'string') patch.defaultBranch = args.defaultBranch;
2040
3624
  if (args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)) patch.policy = args.policy;
2041
3625
  if (args?.coordinator && typeof args.coordinator === 'object' && !Array.isArray(args.coordinator)) patch.coordinator = args.coordinator;
3626
+ if (args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)) patch.meshHost = args.meshHost;
2042
3627
  if (!Object.keys(patch).length) return { success: false, error: 'No updates provided' };
2043
3628
  const mesh = updateMesh(meshId, patch as any);
2044
3629
  if (!mesh) return { success: false, error: 'Mesh not found' };
2045
3630
  this.inlineMeshCache.set(meshId, mesh);
3631
+ this.invalidateAggregateMeshStatus(meshId);
2046
3632
  return { success: true, mesh };
2047
3633
  } catch (e: any) {
2048
3634
  return { success: false, error: e.message };
2049
3635
  }
2050
3636
  }
2051
3637
 
3638
+ case 'get_mesh_host_pairing': {
3639
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3640
+ if (!meshId) return { success: false, error: 'meshId required' };
3641
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3642
+ const mesh = meshRecord?.mesh;
3643
+ if (!mesh) return { success: false, error: 'Mesh not found' };
3644
+ const meshHost = resolveMeshHostStatus(mesh);
3645
+ const pairingStatus = meshHost.pairing?.status || 'not_configured';
3646
+ return {
3647
+ success: true,
3648
+ code: pairingStatus === 'not_configured' ? 'mesh_host_pairing_not_configured' : 'mesh_host_pairing_pending',
3649
+ meshId,
3650
+ hostAddress: meshHost.hostAddress,
3651
+ meshHost,
3652
+ manualPairing: {
3653
+ status: pairingStatus,
3654
+ joinImplemented: true,
3655
+ protocol: 'standalone_command_direct_v1',
3656
+ 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.',
3657
+ },
3658
+ };
3659
+ }
3660
+
3661
+ case 'configure_mesh_host_pairing': {
3662
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3663
+ const hostAddress = typeof args?.hostAddress === 'string' ? args.hostAddress.trim() : '';
3664
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
3665
+ if (!meshId) return { success: false, error: 'meshId required' };
3666
+ if (!hostAddress || !token) return { success: false, error: 'hostAddress and token required' };
3667
+ try {
3668
+ const { configureMeshHostPairing } = await import('../config/mesh-config.js');
3669
+ const configured = configureMeshHostPairing(meshId, { hostAddress, token });
3670
+ if (!configured) return { success: false, error: 'Mesh not found' };
3671
+ this.inlineMeshCache.set(meshId, configured.mesh);
3672
+ const meshHost = resolveMeshHostStatus(configured.mesh);
3673
+ return {
3674
+ success: true,
3675
+ code: 'mesh_host_pairing_pending',
3676
+ meshId,
3677
+ hostAddress: configured.hostAddress,
3678
+ meshHost,
3679
+ manualPairing: {
3680
+ status: meshHost.pairing?.status || 'pairing',
3681
+ joinImplemented: true,
3682
+ protocol: 'standalone_command_direct_v1',
3683
+ 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.',
3684
+ },
3685
+ };
3686
+ } catch (e: any) {
3687
+ return { success: false, code: 'mesh_host_pairing_invalid', meshId, hostAddress, error: e.message };
3688
+ }
3689
+ }
3690
+
3691
+ case 'create_mesh_host_pairing_token': {
3692
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3693
+ if (!meshId) return { success: false, error: 'meshId required' };
3694
+ try {
3695
+ const { createMeshHostPairingToken } = await import('../config/mesh-config.js');
3696
+ const created = createMeshHostPairingToken(meshId, {
3697
+ token: typeof args?.token === 'string' ? args.token : undefined,
3698
+ expiresAt: typeof args?.expiresAt === 'string' ? args.expiresAt : undefined,
3699
+ });
3700
+ if (!created) return { success: false, error: 'Mesh not found' };
3701
+ this.inlineMeshCache.set(meshId, created.mesh);
3702
+ this.invalidateAggregateMeshStatus(meshId);
3703
+ return {
3704
+ success: true,
3705
+ code: 'mesh_host_pairing_token_created',
3706
+ meshId,
3707
+ token: created.token,
3708
+ tokenId: created.tokenId,
3709
+ expiresAt: created.expiresAt,
3710
+ meshHost: resolveMeshHostStatus(created.mesh),
3711
+ warning: 'Raw token is returned once and is not persisted; share it with member daemons over a trusted channel.',
3712
+ };
3713
+ } catch (e: any) {
3714
+ return { success: false, code: 'mesh_host_pairing_token_invalid', meshId, error: e.message };
3715
+ }
3716
+ }
3717
+
3718
+ case 'apply_mesh_host_join': {
3719
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3720
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
3721
+ const memberNode = args?.memberNode && typeof args.memberNode === 'object' && !Array.isArray(args.memberNode)
3722
+ ? args.memberNode
3723
+ : null;
3724
+ if (!meshId) return { success: false, error: 'meshId required' };
3725
+ if (!token || !memberNode) return { success: false, error: 'token and memberNode required' };
3726
+ try {
3727
+ const { applyMeshHostJoinRequest } = await import('../config/mesh-config.js');
3728
+ const applied = applyMeshHostJoinRequest(meshId, {
3729
+ token,
3730
+ memberNode: memberNode as any,
3731
+ memberMeshId: typeof args?.memberMeshId === 'string' ? args.memberMeshId : undefined,
3732
+ });
3733
+ if (!applied) return { success: false, error: 'Mesh not found' };
3734
+ if (!applied.accepted) {
3735
+ return {
3736
+ success: false,
3737
+ code: 'mesh_host_join_rejected',
3738
+ meshId,
3739
+ tokenId: applied.tokenId,
3740
+ meshHost: applied.meshHost ? resolveMeshHostStatus({ meshHost: applied.meshHost }) : undefined,
3741
+ error: applied.reason,
3742
+ };
3743
+ }
3744
+ this.inlineMeshCache.set(meshId, applied.mesh);
3745
+ this.invalidateAggregateMeshStatus(meshId);
3746
+ try {
3747
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
3748
+ appendLedgerEntry(meshId, {
3749
+ kind: 'node_joined',
3750
+ nodeId: applied.node.id,
3751
+ payload: { role: 'member', tokenId: applied.tokenId, workspace: applied.node.workspace },
3752
+ });
3753
+ } catch { /* ledger append is best-effort */ }
3754
+ return {
3755
+ success: true,
3756
+ code: 'mesh_host_join_accepted',
3757
+ meshId,
3758
+ node: applied.node,
3759
+ tokenId: applied.tokenId,
3760
+ meshHost: resolveMeshHostStatus(applied.mesh),
3761
+ };
3762
+ } catch (e: any) {
3763
+ return { success: false, code: 'mesh_host_join_failed', meshId, error: e.message };
3764
+ }
3765
+ }
3766
+
3767
+ case 'join_mesh_host_pairing': {
3768
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3769
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
3770
+ if (!meshId) return { success: false, error: 'meshId required' };
3771
+ if (!token) return { success: false, error: 'token required because raw pairing tokens are not persisted' };
3772
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3773
+ const mesh = meshRecord?.mesh;
3774
+ if (!mesh) return { success: false, error: 'Mesh not found' };
3775
+ const meshHost = resolveMeshHostStatus(mesh);
3776
+ if (meshHost.role !== 'member') {
3777
+ 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.' };
3778
+ }
3779
+ try {
3780
+ const { tokenIdForManualPairing, markMeshHostPairingJoined } = await import('../config/mesh-config.js');
3781
+ const tokenId = tokenIdForManualPairing(token);
3782
+ if (meshHost.pairing?.tokenId && meshHost.pairing.tokenId !== tokenId) {
3783
+ return { success: false, code: 'mesh_host_join_rejected', meshId, tokenId, meshHost, error: 'invalid pairing token' };
3784
+ }
3785
+ const memberNode = buildMemberJoinNode(mesh, args, this.deps.statusInstanceId);
3786
+ if (!memberNode) return { success: false, error: 'member node metadata unavailable' };
3787
+ const hostMeshId = typeof args?.hostMeshId === 'string' && args.hostMeshId.trim() ? args.hostMeshId.trim() : meshId;
3788
+ const hostDaemonId = typeof args?.hostDaemonId === 'string' && args.hostDaemonId.trim()
3789
+ ? args.hostDaemonId.trim()
3790
+ : meshHost.hostDaemonId;
3791
+ let hostResult: any;
3792
+ let transport: string;
3793
+ if (hostDaemonId && this.deps.dispatchMeshCommand) {
3794
+ transport = 'mesh_command_dispatch';
3795
+ hostResult = await this.deps.dispatchMeshCommand(hostDaemonId, 'apply_mesh_host_join', {
3796
+ meshId: hostMeshId,
3797
+ token,
3798
+ memberMeshId: meshId,
3799
+ memberNode,
3800
+ });
3801
+ } else if (meshHost.hostAddress) {
3802
+ transport = 'standalone_http_command';
3803
+ const commandUrl = normalizeStandaloneHostCommandUrl(meshHost.hostAddress);
3804
+ const response = await fetch(commandUrl, {
3805
+ method: 'POST',
3806
+ headers: { 'Content-Type': 'application/json' },
3807
+ body: JSON.stringify({ type: 'apply_mesh_host_join', payload: { meshId: hostMeshId, token, memberMeshId: meshId, memberNode } }),
3808
+ });
3809
+ hostResult = await response.json().catch(() => ({ success: false, error: `Host returned HTTP ${response.status}` }));
3810
+ if (!response.ok && hostResult?.success !== false) hostResult = { success: false, error: `Host returned HTTP ${response.status}` };
3811
+ } else {
3812
+ return {
3813
+ success: false,
3814
+ code: 'mesh_host_join_transport_unavailable',
3815
+ meshId,
3816
+ meshHost,
3817
+ error: 'No hostDaemonId dispatch path or hostAddress HTTP command path is available. P2P signaling join is not implemented in this slice.',
3818
+ };
3819
+ }
3820
+ if (!hostResult?.success) {
3821
+ return { success: false, code: hostResult?.code || 'mesh_host_join_rejected', meshId, meshHost, transport, error: hostResult?.error || 'Mesh Host rejected join request', hostResult };
3822
+ }
3823
+ const joined = meshRecord.inline
3824
+ ? null
3825
+ : markMeshHostPairingJoined(meshId, {
3826
+ tokenId: hostResult.tokenId || tokenId,
3827
+ hostDaemonId: hostResult.meshHost?.hostDaemonId || hostDaemonId,
3828
+ hostNodeId: hostResult.meshHost?.hostNodeId,
3829
+ joinedAt: hostResult.meshHost?.pairing?.joinedAt,
3830
+ });
3831
+ if (joined) {
3832
+ this.inlineMeshCache.set(meshId, joined.mesh);
3833
+ this.invalidateAggregateMeshStatus(meshId);
3834
+ }
3835
+ return {
3836
+ success: true,
3837
+ code: 'mesh_host_join_applied',
3838
+ meshId,
3839
+ hostMeshId,
3840
+ transport,
3841
+ node: hostResult.node,
3842
+ tokenId: hostResult.tokenId || tokenId,
3843
+ meshHost: joined ? resolveMeshHostStatus(joined.mesh) : { ...meshHost, pairing: { ...(meshHost.pairing || {}), status: 'paired', tokenId: hostResult.tokenId || tokenId } },
3844
+ hostResult,
3845
+ manualPairing: {
3846
+ status: 'paired',
3847
+ joinImplemented: true,
3848
+ protocol: 'standalone_command_direct_v1',
3849
+ description: 'Mesh Host accepted the join and local member pairing status was marked paired. P2P runtime signaling remains outside this slice.',
3850
+ },
3851
+ };
3852
+ } catch (e: any) {
3853
+ return { success: false, code: 'mesh_host_join_failed', meshId, meshHost, error: e.message };
3854
+ }
3855
+ }
3856
+
2052
3857
  case 'delete_mesh': {
2053
3858
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2054
3859
  if (!meshId) return { success: false, error: 'meshId required' };
@@ -2142,6 +3947,8 @@ export class DaemonCommandRouter {
2142
3947
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2143
3948
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
2144
3949
  if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
3950
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue cancellation');
3951
+ if (ownerFailure) return ownerFailure;
2145
3952
  try {
2146
3953
  const { cancelTask } = await import('../mesh/mesh-work-queue.js');
2147
3954
  const reason = typeof args?.reason === 'string' ? args.reason : undefined;
@@ -2157,6 +3964,8 @@ export class DaemonCommandRouter {
2157
3964
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2158
3965
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
2159
3966
  if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
3967
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue requeue');
3968
+ if (ownerFailure) return ownerFailure;
2160
3969
  try {
2161
3970
  const { requeueTask } = await import('../mesh/mesh-work-queue.js');
2162
3971
  const task = requeueTask(meshId, taskId, {
@@ -2178,6 +3987,8 @@ export class DaemonCommandRouter {
2178
3987
  const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
2179
3988
  if (!meshId) return { success: false, error: 'meshId required' };
2180
3989
  if (!workspace) return { success: false, error: 'workspace required' };
3990
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node addition');
3991
+ if (ownerFailure) return ownerFailure;
2181
3992
  try {
2182
3993
  const { addNode } = await import('../config/mesh-config.js');
2183
3994
  const providerPriority = Array.isArray(args?.providerPriority)
@@ -2188,7 +3999,18 @@ export class DaemonCommandRouter {
2188
3999
  ...(readOnly ? { readOnly: true } : {}),
2189
4000
  ...(providerPriority.length ? { providerPriority } : {}),
2190
4001
  };
2191
- const node = addNode(meshId, { workspace, ...(policy ? { policy } : {}) });
4002
+ const role = normalizeMeshDaemonRole(args?.role);
4003
+ const daemonId = typeof args?.daemonId === 'string' && args.daemonId.trim() ? args.daemonId.trim() : undefined;
4004
+ const machineId = typeof args?.machineId === 'string' && args.machineId.trim() ? args.machineId.trim() : undefined;
4005
+ const repoRoot = typeof args?.repoRoot === 'string' && args.repoRoot.trim() ? args.repoRoot.trim() : undefined;
4006
+ const node = addNode(meshId, {
4007
+ workspace,
4008
+ ...(repoRoot ? { repoRoot } : {}),
4009
+ ...(daemonId ? { daemonId } : {}),
4010
+ ...(machineId ? { machineId } : {}),
4011
+ ...(policy ? { policy } : {}),
4012
+ ...(role ? { role } : {}),
4013
+ });
2192
4014
  if (!node) return { success: false, error: 'Mesh not found' };
2193
4015
  return { success: true, node };
2194
4016
  } catch (e: any) {
@@ -2200,6 +4022,8 @@ export class DaemonCommandRouter {
2200
4022
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2201
4023
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2202
4024
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
4025
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node update');
4026
+ if (ownerFailure) return ownerFailure;
2203
4027
  try {
2204
4028
  const { updateNode } = await import('../config/mesh-config.js');
2205
4029
  const policy = args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)
@@ -2228,6 +4052,8 @@ export class DaemonCommandRouter {
2228
4052
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2229
4053
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2230
4054
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
4055
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node removal');
4056
+ if (ownerFailure) return ownerFailure;
2231
4057
  try {
2232
4058
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2233
4059
  const mesh = meshRecord?.mesh;
@@ -2253,131 +4079,91 @@ export class DaemonCommandRouter {
2253
4079
  }
2254
4080
  }
2255
4081
 
2256
- case 'refine_mesh_node': {
4082
+ case 'get_mesh_refine_config_schema': {
4083
+ return {
4084
+ success: true,
4085
+ schema: MESH_REFINE_CONFIG_SCHEMA,
4086
+ locations: MESH_REFINE_CONFIG_LOCATIONS,
4087
+ sourceOfTruth: 'repo mesh/refine config',
4088
+ heuristicRole: 'suggestions_only_not_execution_path',
4089
+ };
4090
+ }
4091
+
4092
+ case 'validate_mesh_refine_config': {
4093
+ const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
4094
+ const mesh = args?.inlineMesh || {};
4095
+ const loaded = args?.config !== undefined
4096
+ ? { config: args.config, source: 'inline', sourceType: 'mesh_policy' as const }
4097
+ : loadMeshRefineConfig(mesh, workspace);
4098
+ const validation = loaded.config
4099
+ ? validateMeshRefineConfig(loaded.config, loaded.source)
4100
+ : { valid: false, errors: [((loaded as { error?: string }).error) || 'repo mesh/refine config unavailable'], commands: [], rejectedCommands: [] };
4101
+ return { success: validation.valid, ...loaded, ...validation };
4102
+ }
4103
+
4104
+ case 'suggest_mesh_refine_config': {
4105
+ const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
4106
+ const mesh = args?.inlineMesh || {};
4107
+ return {
4108
+ success: true,
4109
+ ...suggestMeshRefineConfig(mesh, workspace),
4110
+ note: 'Suggestions are heuristic scaffold only; Refinery will not execute them until saved into repo mesh/refine config.',
4111
+ };
4112
+ }
4113
+
4114
+ case 'plan_mesh_refine_node': {
2257
4115
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2258
4116
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2259
4117
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
2260
- try {
4118
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
4119
+ const mesh = meshRecord?.mesh;
4120
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
4121
+ if (!node?.workspace) return { success: false, error: `Node '${nodeId}' workspace not found` };
4122
+ return {
4123
+ success: true,
4124
+ dryRun: true,
4125
+ nodeId,
4126
+ workspace: node.workspace,
4127
+ validationPlan: buildMeshRefineValidationPlan(mesh, node.workspace),
4128
+ mergeWillRun: false,
4129
+ cleanupWillRun: false,
4130
+ };
4131
+ }
4132
+
4133
+ case 'fast_forward_mesh_node': {
4134
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4135
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
4136
+ let workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
4137
+ let submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths)
4138
+ ? args.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string')
4139
+ : undefined;
4140
+ if (!workspace && meshId && nodeId) {
2261
4141
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2262
4142
  const mesh = meshRecord?.mesh;
2263
4143
  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
- };
4144
+ workspace = typeof node?.workspace === 'string' ? node.workspace.trim() : '';
4145
+ if (!submoduleIgnorePaths && Array.isArray(node?.policy?.submoduleIgnorePaths)) {
4146
+ submoduleIgnorePaths = node.policy.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string');
2306
4147
  }
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
4148
  }
4149
+ const result = await (fastForwardMeshNode({
4150
+ meshId: meshId || undefined,
4151
+ nodeId: nodeId || undefined,
4152
+ workspace,
4153
+ branch: typeof args?.branch === 'string' ? args.branch : undefined,
4154
+ execute: args?.execute === true,
4155
+ dryRun: args?.dryRun === true,
4156
+ updateSubmodules: args?.updateSubmodules === true,
4157
+ submoduleIgnorePaths,
4158
+ }) as Promise<unknown>);
4159
+ return result as CommandRouterResult;
4160
+ }
4161
+
4162
+ case 'refine_mesh_node': {
4163
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4164
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
4165
+ if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
4166
+ return this.startMeshRefineJob(meshId, nodeId, args);
2381
4167
  }
2382
4168
 
2383
4169
  case 'remove_mesh_node': {
@@ -2421,6 +4207,7 @@ export class DaemonCommandRouter {
2421
4207
  } else {
2422
4208
  const { removeNode } = await import('../config/mesh-config.js');
2423
4209
  removed = removeNode(meshId, nodeId);
4210
+ if (removed) this.invalidateAggregateMeshStatus(meshId);
2424
4211
  }
2425
4212
 
2426
4213
  // Record in task ledger
@@ -2458,6 +4245,8 @@ export class DaemonCommandRouter {
2458
4245
  if (!meshId) return { success: false, error: 'meshId required' };
2459
4246
  if (!sourceNodeId) return { success: false, error: 'sourceNodeId required' };
2460
4247
  if (!branch) return { success: false, error: 'branch required' };
4248
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'worktree clone');
4249
+ if (ownerFailure) return ownerFailure;
2461
4250
 
2462
4251
  try {
2463
4252
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
@@ -2506,6 +4295,7 @@ export class DaemonCommandRouter {
2506
4295
  policy: { ...(sourceNode.policy || {}) },
2507
4296
  });
2508
4297
  if (!node) return { success: false, error: 'Failed to register worktree node' };
4298
+ this.invalidateAggregateMeshStatus(meshId);
2509
4299
  }
2510
4300
 
2511
4301
  // Initialize submodules if policy allows (default: true)
@@ -2547,6 +4337,8 @@ export class DaemonCommandRouter {
2547
4337
  case 'trigger_mesh_queue': {
2548
4338
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2549
4339
  if (!meshId) return { success: false, error: 'meshId required' };
4340
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue trigger');
4341
+ if (ownerFailure) return ownerFailure;
2550
4342
  try {
2551
4343
  const { triggerMeshQueue } = await import('../mesh/mesh-events.js');
2552
4344
  if (meshId) {
@@ -2578,6 +4370,15 @@ export class DaemonCommandRouter {
2578
4370
  mesh = getMesh(meshId);
2579
4371
  }
2580
4372
  if (!mesh) return { success: false, error: 'Mesh not found' };
4373
+ const meshHost = resolveMeshHostStatus(mesh);
4374
+ if (!meshHost.canOwnCoordinator) {
4375
+ return {
4376
+ success: false,
4377
+ ...buildMeshHostRequiredFailure(mesh, 'coordinator launch'),
4378
+ meshId,
4379
+ cliType,
4380
+ };
4381
+ }
2581
4382
  if (!Array.isArray(mesh.nodes) || mesh.nodes.length === 0) return { success: false, error: 'No nodes in mesh' };
2582
4383
 
2583
4384
  const requestedCoordinatorNodeId = typeof args?.coordinatorNodeId === 'string'
@@ -2597,7 +4398,16 @@ export class DaemonCommandRouter {
2597
4398
  cliType,
2598
4399
  };
2599
4400
  }
2600
- const workspace = typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '';
4401
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions
4402
+ ? await this.deps.sessionHostControl.listSessions().catch(() => [])
4403
+ : [];
4404
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
4405
+ const workspace = readLiveMeshNodeWorkspace({
4406
+ meshId,
4407
+ nodeId: String(coordinatorNode.id || coordinatorNode.nodeId || preferredCoordinatorNodeId || ''),
4408
+ liveSessionRecords: liveMeshSessions,
4409
+ allowCoordinatorSession: true,
4410
+ }) || (typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '');
2601
4411
  if (!workspace) return { success: false, error: 'Coordinator node workspace required', meshId, cliType };
2602
4412
  if (!cliType) {
2603
4413
  const resolved = await resolveProviderTypeFromPriority({
@@ -2942,6 +4752,27 @@ export class DaemonCommandRouter {
2942
4752
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
2943
4753
  const mesh = meshRecord?.mesh;
2944
4754
  if (!mesh) return { success: false, error: 'Mesh not found' };
4755
+ const meshHost = resolveMeshHostStatus(mesh);
4756
+
4757
+ const refreshRequested = args?.refresh === true || args?.forceRefresh === true;
4758
+ const hadAggregateCache = this.aggregateMeshStatusCache.has(meshId);
4759
+ if (!refreshRequested) {
4760
+ const cachedStatus = this.getCachedAggregateMeshStatus(meshId, mesh, { requireDirectPeerTruth: args?.requireDirectPeerTruth === true });
4761
+ if (cachedStatus) {
4762
+ logRepoMeshStatusDebug('return_cached', {
4763
+ meshId,
4764
+ command: 'mesh_status',
4765
+ refreshRequested,
4766
+ summary: summarizeRepoMeshStatusDebug(cachedStatus),
4767
+ });
4768
+ return cachedStatus;
4769
+ }
4770
+ }
4771
+ const refreshReason = refreshRequested
4772
+ ? 'explicit_refresh'
4773
+ : hadAggregateCache
4774
+ ? 'stale_pending_cache_refresh'
4775
+ : 'cold_cache_miss';
2945
4776
 
2946
4777
  const { getMeshQueueStats, getQueue } = await import('../mesh/mesh-work-queue.js');
2947
4778
  const queue = getQueue(meshId);
@@ -2955,64 +4786,343 @@ export class DaemonCommandRouter {
2955
4786
  : [];
2956
4787
  const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
2957
4788
 
4789
+ const localMachineId = loadConfig().machineId || '';
4790
+ const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
4791
+ const directTruth = requireDirectPeerTruth
4792
+ ? await hydrateInlineMeshDirectTruth({
4793
+ mesh,
4794
+ meshSource: meshRecord.source,
4795
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
4796
+ statusInstanceId: this.deps.statusInstanceId,
4797
+ localMachineId,
4798
+ })
4799
+ : {
4800
+ directEvidenceCount: 0,
4801
+ localConfirmedCount: 0,
4802
+ peerAttemptedCount: 0,
4803
+ peerConfirmedCount: 0,
4804
+ unavailableNodeIds: [] as string[],
4805
+ };
4806
+ // Default/cached loads may not attempt a remote peer probe yet; do not surface that as
4807
+ // a direct mesh truth failure until an explicit probe attempt actually fails.
4808
+ const passivePeerTruthNotAttempted = requireDirectPeerTruth
4809
+ && !refreshRequested
4810
+ && directTruth.directEvidenceCount > 0
4811
+ && directTruth.peerAttemptedCount === 0;
4812
+ const effectiveDirectTruth = passivePeerTruthNotAttempted
4813
+ ? { ...directTruth, unavailableNodeIds: [] as string[] }
4814
+ : directTruth;
4815
+ const directTruthSatisfied = !requireDirectPeerTruth
4816
+ || (effectiveDirectTruth.directEvidenceCount > 0 && effectiveDirectTruth.unavailableNodeIds.length === 0);
4817
+ if (requireDirectPeerTruth && !directTruthSatisfied) {
4818
+ const failureResult = {
4819
+ success: false,
4820
+ code: 'mesh_direct_peer_truth_unavailable',
4821
+ error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct mesh_status probes succeed.',
4822
+ sourceOfTruth: {
4823
+ membership: meshRecord.source === 'inline_cache'
4824
+ ? 'coordinator_inline_mesh_cache'
4825
+ : meshRecord.source === 'local_config'
4826
+ ? 'local_mesh_config'
4827
+ : 'inline_bootstrap_snapshot',
4828
+ coordinatorOwnsLiveTruth: false,
4829
+ currentStatus: 'direct_peer_truth_unavailable',
4830
+ directPeerTruth: {
4831
+ required: true,
4832
+ satisfied: false,
4833
+ directEvidenceCount: directTruth.directEvidenceCount,
4834
+ localConfirmedCount: directTruth.localConfirmedCount,
4835
+ peerAttemptedCount: directTruth.peerAttemptedCount,
4836
+ peerConfirmedCount: directTruth.peerConfirmedCount,
4837
+ unavailableNodeIds: directTruth.unavailableNodeIds,
4838
+ },
4839
+ },
4840
+ };
4841
+ logRepoMeshStatusDebug('direct_truth_unavailable', {
4842
+ meshId,
4843
+ command: 'mesh_status',
4844
+ refreshRequested,
4845
+ meshSource: meshRecord.source,
4846
+ directTruth,
4847
+ });
4848
+ return failureResult;
4849
+ }
4850
+ const directTruthUnavailableNodeIds = new Set(effectiveDirectTruth.unavailableNodeIds);
4851
+ const selectedCoordinatorNodeId = readStringValue(
4852
+ mesh.coordinator?.preferredNodeId,
4853
+ (mesh.nodes?.[0] as any)?.id,
4854
+ (mesh.nodes?.[0] as any)?.nodeId,
4855
+ );
4856
+ const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes)
4857
+ ? selectedCoordinatorNodeId
4858
+ : undefined;
4859
+ const refreshedAt = new Date().toISOString();
2958
4860
  const nodeStatuses = [];
2959
- for (const node of mesh.nodes || []) {
4861
+ for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
4862
+ const nodeId = String(node.id || node.nodeId || '');
4863
+ const daemonId = readStringValue(node.daemonId);
4864
+ const providerPriority = readProviderPriorityFromPolicy(node.policy);
4865
+ const isSelfNode = Boolean(
4866
+ nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId,
4867
+ ) || Boolean(
4868
+ daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId),
4869
+ ) || Boolean(meshRecord?.inline && nodeIndex === 0);
2960
4870
  const status: Record<string, unknown> = {
2961
- nodeId: node.id || node.nodeId,
4871
+ nodeId,
2962
4872
  machineLabel: node.machineLabel || node.id || node.nodeId,
2963
4873
  workspace: node.workspace,
2964
4874
  repoRoot: node.repoRoot,
2965
4875
  isLocalWorktree: node.isLocalWorktree,
2966
4876
  worktreeBranch: node.worktreeBranch,
2967
- daemonId: node.daemonId,
4877
+ role: normalizeMeshDaemonRole(node.role) || (meshHost.hostNodeId && nodeId === meshHost.hostNodeId ? 'host' : undefined),
4878
+ daemonId,
2968
4879
  machineId: node.machineId,
4880
+ machineStatus: node.machineStatus,
2969
4881
  health: 'unknown',
2970
4882
  providers: node.providers || [],
4883
+ providerPriority,
2971
4884
  activeSessions: [],
4885
+ activeSessionDetails: [],
4886
+ launchReady: false,
2972
4887
  };
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;
4888
+ if (isSelfNode) {
4889
+ status.connection = {
4890
+ perspective: 'selected_coordinator',
4891
+ source: 'mesh_peer_status',
4892
+ state: 'self',
4893
+ transport: 'local',
4894
+ reported: true,
4895
+ reason: 'Selected coordinator daemon',
4896
+ lastStateChangeAt: refreshedAt,
4897
+ };
4898
+ } else if (daemonId) {
4899
+ const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
4900
+ status.connection = connection ?? {
4901
+ perspective: 'selected_coordinator',
4902
+ source: 'not_reported',
4903
+ state: 'unknown',
4904
+ transport: 'unknown',
4905
+ reported: false,
4906
+ reason: 'No live mesh peer telemetry reported by the selected coordinator yet.',
4907
+ };
4908
+ } else {
4909
+ status.connection = {
4910
+ perspective: 'selected_coordinator',
4911
+ source: 'not_reported',
4912
+ state: 'unknown',
4913
+ transport: 'unknown',
4914
+ reported: false,
4915
+ reason: 'Node has no daemon id, so mesh transport cannot be reported from the selected coordinator.',
4916
+ };
2980
4917
  }
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;
4918
+ const matchedLiveSessionRecords = collectLiveMeshSessionRecords({
4919
+ meshId,
4920
+ node,
4921
+ nodeId,
4922
+ liveSessionRecords: liveMeshSessions,
4923
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
4924
+ });
4925
+ const workspace = readLiveMeshNodeWorkspace({
4926
+ meshId,
4927
+ nodeId,
4928
+ liveSessionRecords: matchedLiveSessionRecords,
4929
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
4930
+ }) || (typeof node.workspace === 'string' ? node.workspace : '');
4931
+ status.workspace = workspace || node.workspace;
4932
+ if (matchedLiveSessionRecords.length > 0) {
4933
+ const sessionIds = matchedLiveSessionRecords
4934
+ .map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
4935
+ .filter(Boolean);
4936
+ const providerTypes = matchedLiveSessionRecords
4937
+ .map((record: any) => readStringValue(record?.providerType))
4938
+ .filter(Boolean) as string[];
4939
+ status.activeSessions = sessionIds;
4940
+ status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
4941
+ if (providerTypes.length > 0) {
4942
+ status.providers = Array.from(new Set([...(Array.isArray(status.providers) ? status.providers as string[] : []), ...providerTypes]));
2985
4943
  }
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;
4944
+ }
4945
+ if (workspace) {
4946
+ if (!fs.existsSync(workspace)) {
4947
+ // Workspace not local — prefer direct live inline truth, then attempt a P2P git probe.
4948
+ const inlineTransitGit = buildInlineMeshTransitGitStatus(node);
4949
+ let remoteProbeApplied = false;
4950
+ if (inlineTransitGit) {
4951
+ status.git = inlineTransitGit;
4952
+ status.health = inlineTransitGit.isGitRepo
4953
+ ? deriveMeshNodeHealthFromGit(inlineTransitGit as unknown as Record<string, unknown>)
4954
+ : 'degraded';
4955
+ const connection = readObjectRecord(status.connection);
4956
+ const connectionState = readStringValue(connection.state);
4957
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
4958
+ if (!connectionReported || connectionState === 'unknown') {
4959
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
4960
+ }
4961
+ remoteProbeApplied = true;
4962
+ } else if (!isSelfNode && daemonId && this.deps.dispatchMeshCommand && !directTruthUnavailableNodeIds.has(nodeId)) {
4963
+ try {
4964
+ const remoteGit = await probeRemoteMeshGitStatus({
4965
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
4966
+ daemonId,
4967
+ workspace,
4968
+ timeoutMs: 8000,
4969
+ });
4970
+ if (remoteGit) {
4971
+ status.git = remoteGit;
4972
+ status.health = remoteGit.isGitRepo
4973
+ ? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
4974
+ : 'degraded';
4975
+ const connection = readObjectRecord(status.connection);
4976
+ const connectionState = readStringValue(connection.state);
4977
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
4978
+ if (!connectionReported || connectionState === 'unknown') {
4979
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
4980
+ }
4981
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
4982
+ remoteProbeApplied = true;
4983
+ }
4984
+ } catch {
4985
+ const refreshedConnection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
4986
+ const refreshedConnectionState = readStringValue(refreshedConnection?.state);
4987
+ if (refreshedConnection && refreshedConnectionState === 'connected') {
4988
+ status.connection = refreshedConnection;
4989
+ try {
4990
+ const remoteGit = await probeRemoteMeshGitStatus({
4991
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
4992
+ daemonId,
4993
+ workspace,
4994
+ timeoutMs: 12000,
4995
+ });
4996
+ if (remoteGit) {
4997
+ status.git = remoteGit;
4998
+ status.health = remoteGit.isGitRepo
4999
+ ? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
5000
+ : 'degraded';
5001
+ const connection = readObjectRecord(status.connection);
5002
+ const connectionState = readStringValue(connection.state);
5003
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
5004
+ if (!connectionReported || connectionState === 'unknown') {
5005
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
5006
+ }
5007
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
5008
+ remoteProbeApplied = true;
5009
+ }
5010
+ } catch {
5011
+ // Probe timed out again or P2P unavailable — fall back to cached status
5012
+ }
5013
+ }
5014
+ }
2994
5015
  }
2995
- } catch {
2996
- if (!applyCachedInlineMeshNodeStatus(status, node)) {
2997
- status.health = 'degraded';
5016
+ if (!remoteProbeApplied) {
5017
+ const connectionState = readStringValue((status.connection as any)?.state);
5018
+ const pendingPeerGitProbe = !inlineTransitGit
5019
+ && !isSelfNode
5020
+ && !!daemonId
5021
+ && (
5022
+ readStringValue(status.machineStatus) === 'online'
5023
+ || readStringValue(status.health) === 'online'
5024
+ || connectionState === 'connecting'
5025
+ || connectionState === 'connected'
5026
+ || connectionState === 'unknown'
5027
+ );
5028
+ if (pendingPeerGitProbe) {
5029
+ status.gitProbePending = true;
5030
+ status.health = 'unknown';
5031
+ }
5032
+ if (applyCachedInlineMeshNodeStatus(
5033
+ status,
5034
+ node,
5035
+ pendingPeerGitProbe ? { skipGit: true, skipError: true, skipHealth: true } : undefined,
5036
+ )) {
5037
+ applyInlineMeshBranchConvergence(mesh, node, status);
5038
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
5039
+ nodeStatuses.push(status);
5040
+ continue;
5041
+ }
5042
+ if (meshRecord?.source === 'inline_cache' && !isSelfNode) {
5043
+ applyInlineMeshBranchConvergence(mesh, node, status);
5044
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
5045
+ nodeStatuses.push(status);
5046
+ continue;
5047
+ }
5048
+ }
5049
+ } else {
5050
+ try {
5051
+ const gitStatus = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
5052
+ status.git = gitStatus;
5053
+ recordInlineMeshDirectGitTruth(node, gitStatus as unknown as Record<string, unknown>, 'selected_coordinator_local_git');
5054
+ if (gitStatus.isGitRepo) {
5055
+ status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
5056
+ } else {
5057
+ status.health = 'degraded';
5058
+ if (gitStatus.error && !status.error) status.error = gitStatus.error;
5059
+ }
5060
+ } catch {
5061
+ if (!applyCachedInlineMeshNodeStatus(status, node)) {
5062
+ status.health = 'degraded';
5063
+ }
2998
5064
  }
2999
5065
  }
3000
5066
  } else {
3001
5067
  applyCachedInlineMeshNodeStatus(status, node);
3002
5068
  }
5069
+ applyInlineMeshBranchConvergence(mesh, node, status);
5070
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
3003
5071
  nodeStatuses.push(status);
3004
5072
  }
3005
5073
 
3006
- return {
5074
+ const statusResult = {
3007
5075
  success: true,
3008
5076
  meshId: mesh.id,
3009
5077
  meshName: mesh.name,
3010
5078
  repoIdentity: mesh.repoIdentity,
3011
5079
  defaultBranch: mesh.defaultBranch,
5080
+ refreshedAt,
5081
+ meshHost,
5082
+ sourceOfTruth: {
5083
+ membership: meshRecord?.source === 'inline_cache'
5084
+ ? 'coordinator_inline_mesh_cache'
5085
+ : meshRecord?.source === 'local_config'
5086
+ ? 'local_mesh_config'
5087
+ : 'inline_bootstrap_snapshot',
5088
+ coordinatorOwnsLiveTruth: directTruthSatisfied,
5089
+ meshHost: {
5090
+ owner: 'mesh_host_daemon',
5091
+ localRole: meshHost.role,
5092
+ hostDaemonId: meshHost.hostDaemonId,
5093
+ hostNodeId: meshHost.hostNodeId,
5094
+ hostAddress: meshHost.hostAddress,
5095
+ },
5096
+ ...(requireDirectPeerTruth ? {
5097
+ currentStatus: directTruthSatisfied ? 'live_git_and_session_probes' : 'direct_peer_truth_unavailable',
5098
+ directPeerTruth: {
5099
+ required: true,
5100
+ satisfied: directTruthSatisfied,
5101
+ directEvidenceCount: effectiveDirectTruth.directEvidenceCount,
5102
+ localConfirmedCount: effectiveDirectTruth.localConfirmedCount,
5103
+ peerAttemptedCount: effectiveDirectTruth.peerAttemptedCount,
5104
+ peerConfirmedCount: effectiveDirectTruth.peerConfirmedCount,
5105
+ unavailableNodeIds: effectiveDirectTruth.unavailableNodeIds,
5106
+ },
5107
+ } : {}),
5108
+ historicalEvidenceOnly: ['recoveryHints', 'ledger.summary', 'queue.summary'],
5109
+ },
5110
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodeStatuses),
3012
5111
  nodes: nodeStatuses,
3013
5112
  queue: { tasks: queue, summary: queueSummary },
3014
5113
  ledger: { entries: ledgerEntries, summary: ledgerSummary },
3015
5114
  };
5115
+ const rememberedStatus = this.rememberAggregateMeshStatus(meshId, statusResult, refreshReason);
5116
+ logRepoMeshStatusDebug('return_live', {
5117
+ meshId,
5118
+ command: 'mesh_status',
5119
+ refreshRequested,
5120
+ refreshReason,
5121
+ meshSource: meshRecord.source,
5122
+ directTruth,
5123
+ summary: summarizeRepoMeshStatusDebug(rememberedStatus),
5124
+ });
5125
+ return rememberedStatus;
3016
5126
  } catch (e: any) {
3017
5127
  return { success: false, error: e.message };
3018
5128
  }
@@ -3070,7 +5180,7 @@ export class DaemonCommandRouter {
3070
5180
 
3071
5181
  // 3. Kill OS process if requested
3072
5182
  if (killProcess) {
3073
- const running = isIdeRunning(ideType);
5183
+ const running = await isIdeRunning(ideType);
3074
5184
  if (running) {
3075
5185
  LOG.info('StopIDE', `Killing IDE process: ${ideType}`);
3076
5186
  const killed = await killIdeProcess(ideType);