@adhdev/daemon-core 0.9.82-rc.11 → 0.9.82-rc.110

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 (68) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +18 -0
  2. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
  4. package/dist/commands/router.d.ts +22 -0
  5. package/dist/config/chat-history.d.ts +4 -0
  6. package/dist/config/mesh-config.d.ts +66 -1
  7. package/dist/index.d.ts +12 -5
  8. package/dist/index.js +5936 -1225
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +5902 -1212
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/installer.d.ts +1 -4
  13. package/dist/launch.d.ts +1 -1
  14. package/dist/logging/async-batch-writer.d.ts +10 -0
  15. package/dist/mesh/beads-db.d.ts +18 -0
  16. package/dist/mesh/mesh-active-work.d.ts +60 -0
  17. package/dist/mesh/mesh-events.d.ts +26 -5
  18. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  19. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  20. package/dist/mesh/mesh-ledger.d.ts +38 -1
  21. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  22. package/dist/mesh/refine-config.d.ts +176 -0
  23. package/dist/providers/chat-message-normalization.d.ts +1 -0
  24. package/dist/providers/cli-provider-instance.d.ts +2 -1
  25. package/dist/repo-mesh-types.d.ts +45 -0
  26. package/dist/status/reporter.d.ts +2 -0
  27. package/package.json +3 -1
  28. package/src/boot/daemon-lifecycle.ts +1 -0
  29. package/src/cli-adapters/provider-cli-adapter.ts +379 -17
  30. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  31. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  32. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  33. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  34. package/src/cli-adapters/provider-cli-shared.ts +32 -10
  35. package/src/commands/chat-commands.ts +626 -20
  36. package/src/commands/cli-manager.ts +129 -1
  37. package/src/commands/handler.ts +8 -1
  38. package/src/commands/mesh-coordinator.ts +13 -143
  39. package/src/commands/router.ts +2820 -437
  40. package/src/config/chat-history.ts +37 -9
  41. package/src/config/mesh-config.ts +245 -1
  42. package/src/daemon/dev-cli-debug.ts +10 -1
  43. package/src/detection/ide-detector.ts +26 -16
  44. package/src/index.ts +30 -4
  45. package/src/installer.d.ts +1 -1
  46. package/src/installer.ts +8 -6
  47. package/src/launch.d.ts +1 -1
  48. package/src/launch.ts +37 -28
  49. package/src/logging/async-batch-writer.ts +55 -0
  50. package/src/logging/logger.ts +2 -1
  51. package/src/mesh/beads-db.ts +176 -0
  52. package/src/mesh/coordinator-prompt.ts +31 -8
  53. package/src/mesh/mesh-active-work.ts +255 -0
  54. package/src/mesh/mesh-events.ts +389 -47
  55. package/src/mesh/mesh-fast-forward.ts +430 -0
  56. package/src/mesh/mesh-host-ownership.ts +73 -0
  57. package/src/mesh/mesh-ledger.ts +138 -1
  58. package/src/mesh/mesh-work-queue.ts +199 -137
  59. package/src/mesh/refine-config.ts +356 -0
  60. package/src/providers/chat-message-normalization.ts +7 -12
  61. package/src/providers/cli-provider-instance.ts +143 -18
  62. package/src/providers/ide-provider-instance.ts +17 -3
  63. package/src/providers/provider-loader.ts +10 -4
  64. package/src/providers/read-chat-contract.ts +1 -1
  65. package/src/providers/version-archive.ts +38 -20
  66. package/src/repo-mesh-types.ts +50 -0
  67. package/src/status/reporter.ts +15 -0
  68. package/src/system/host-memory.ts +29 -12
@@ -38,13 +38,25 @@ 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, getPendingMeshCoordinatorEvents, 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';
47
- import { join as pathJoin, resolve as pathResolve } from 'path';
58
+ import { homedir, hostname as osHostname } from 'os';
59
+ import { basename as pathBasename, join as pathJoin, resolve as pathResolve } from 'path';
48
60
  import * as fs from 'fs';
49
61
 
50
62
  type ReleaseChannel = 'stable' | 'preview';
@@ -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,146 @@ 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
- }
233
+ function buildMeshNodeDisplayLabel(node: Record<string, unknown>, nodeId: string, providerPriority: string[]): string {
234
+ const explicit = readStringValue(node.machineLabel, node.machine_label, node.machineNickname, node.machine_nickname, node.alias);
235
+ if (explicit) return explicit;
236
+ const workspace = readStringValue(node.workspace, node.repoRoot, node.repo_root);
237
+ const workspaceName = workspace ? pathBasename(workspace) : undefined;
238
+ const host = readStringValue(node.machineName, node.machine_name, node.hostname, node.host, node.daemonId, node.daemon_id, node.machineId, node.machine_id);
239
+ const provider = providerPriority[0] || (Array.isArray(node.providers) ? readStringValue(...node.providers) : undefined);
240
+ const parts = [workspaceName, host, provider].filter(Boolean);
241
+ if (parts.length > 0) return parts.join(' · ');
242
+ return nodeId || 'unidentified mesh node';
243
+ }
244
+
245
+ function normalizeMeshHostname(value: unknown): string | undefined {
246
+ const hostname = readStringValue(value);
247
+ if (!hostname) return undefined;
248
+ return hostname.toLowerCase().replace(/\.$/, '');
249
+ }
250
+
251
+ function readMeshNodeMachineId(node: Record<string, unknown>): string | undefined {
252
+ return readStringValue(
253
+ node.machineId,
254
+ node.machine_id,
255
+ readObjectRecord(node.machine)?.id,
256
+ readObjectRecord(node.machine)?.machineId,
257
+ readObjectRecord(node.lastProbe)?.machineId,
258
+ readObjectRecord(node.last_probe)?.machine_id,
259
+ readObjectRecord(readObjectRecord(node.lastProbe)?.machine)?.id,
260
+ readObjectRecord(readObjectRecord(node.lastProbe)?.machine)?.machineId,
261
+ readObjectRecord(readObjectRecord(node.last_probe)?.machine)?.id,
262
+ readObjectRecord(readObjectRecord(node.last_probe)?.machine)?.machine_id,
263
+ );
264
+ }
265
+
266
+ function readMeshNodeDaemonId(node: Record<string, unknown>): string | undefined {
267
+ return readStringValue(
268
+ node.daemonId,
269
+ node.daemon_id,
270
+ readObjectRecord(node.machine)?.daemonId,
271
+ readObjectRecord(node.machine)?.daemon_id,
272
+ readObjectRecord(node.lastProbe)?.daemonId,
273
+ readObjectRecord(node.last_probe)?.daemon_id,
274
+ readObjectRecord(readObjectRecord(node.lastProbe)?.machine)?.daemonId,
275
+ readObjectRecord(readObjectRecord(node.lastProbe)?.machine)?.daemon_id,
276
+ readObjectRecord(readObjectRecord(node.last_probe)?.machine)?.daemonId,
277
+ readObjectRecord(readObjectRecord(node.last_probe)?.machine)?.daemon_id,
278
+ );
279
+ }
280
+
281
+ function readMeshNodeHostname(node: Record<string, unknown>): string | undefined {
282
+ return readStringValue(
283
+ node.hostname,
284
+ node.host,
285
+ node.machineHostname,
286
+ node.machine_hostname,
287
+ readObjectRecord(node.machine)?.hostname,
288
+ readObjectRecord(node.machine)?.host,
289
+ readObjectRecord(node.lastProbe)?.hostname,
290
+ readObjectRecord(node.last_probe)?.hostname,
291
+ readObjectRecord(readObjectRecord(node.lastProbe)?.machine)?.hostname,
292
+ readObjectRecord(readObjectRecord(node.last_probe)?.machine)?.hostname,
293
+ );
294
+ }
295
+
296
+ function readMeshNodeDisplayMachineName(node: Record<string, unknown>): string | undefined {
297
+ return readStringValue(
298
+ node.machineName,
299
+ node.machine_name,
300
+ node.machineLabel,
301
+ node.machine_label,
302
+ node.machineNickname,
303
+ node.machine_nickname,
304
+ node.alias,
305
+ readObjectRecord(node.machine)?.name,
306
+ readObjectRecord(node.machine)?.displayName,
307
+ readObjectRecord(node.machine)?.display_name,
308
+ readObjectRecord(node.lastProbe)?.machineName,
309
+ readObjectRecord(node.last_probe)?.machine_name,
310
+ readObjectRecord(readObjectRecord(node.lastProbe)?.machine)?.name,
311
+ readObjectRecord(readObjectRecord(node.last_probe)?.machine)?.name,
312
+ readMeshNodeHostname(node),
313
+ );
314
+ }
315
+
316
+ function compactMeshIdentityEvidence(value: string | undefined): string | undefined {
317
+ if (!value) return undefined;
318
+ return value.length > 24 ? `${value.slice(0, 12)}…${value.slice(-8)}` : value;
319
+ }
320
+
321
+ function buildMeshNodeMachineIdentity(node: Record<string, unknown>, opts: {
322
+ localMachineId?: string;
323
+ localDaemonId?: string;
324
+ coordinatorHostname?: string;
325
+ isSelfNode?: boolean;
326
+ }): Record<string, unknown> {
327
+ const machineId = readMeshNodeMachineId(node);
328
+ const daemonId = readMeshNodeDaemonId(node);
329
+ const hostname = readMeshNodeHostname(node);
330
+ const machineName = readMeshNodeDisplayMachineName(node);
331
+ const coordinatorHostname = readStringValue(opts.coordinatorHostname);
332
+ const machineIdMatches = Boolean(opts.localMachineId && machineId && opts.localMachineId === machineId);
333
+ const daemonIdMatches = Boolean(opts.localDaemonId && daemonId && opts.localDaemonId === daemonId);
334
+ const hostnameMatches = Boolean(
335
+ normalizeMeshHostname(hostname)
336
+ && normalizeMeshHostname(coordinatorHostname)
337
+ && normalizeMeshHostname(hostname) === normalizeMeshHostname(coordinatorHostname),
338
+ );
339
+ const sameMachine = opts.isSelfNode === true || machineIdMatches || daemonIdMatches || hostnameMatches;
340
+ const evidence: string[] = [];
341
+ for (const [label, value] of [['machineName', machineName], ['hostname', hostname], ['machineId', machineId], ['daemonId', daemonId]] as const) {
342
+ const compact = compactMeshIdentityEvidence(value);
343
+ if (compact) evidence.push(`${label}:${compact}`);
174
344
  }
345
+ const locality = sameMachine ? 'same_machine' : (evidence.length > 0 ? 'remote_known' : 'remote_or_unknown');
346
+ const localityReason = sameMachine
347
+ ? (machineIdMatches ? 'matched coordinator machine id'
348
+ : daemonIdMatches ? 'matched coordinator daemon id'
349
+ : hostnameMatches ? 'matched coordinator hostname'
350
+ : 'selected coordinator node')
351
+ : evidence.length > 0
352
+ ? `known remote/other machine identity; no local coordinator match (${evidence.join(', ')})`
353
+ : 'no useful machine identity evidence available';
354
+ return {
355
+ daemonId,
356
+ machineId,
357
+ hostname,
358
+ machineName,
359
+ displayName: machineName || hostname || daemonId || machineId,
360
+ coordinatorHostname,
361
+ sameMachine,
362
+ locality,
363
+ localityReason,
364
+ identityEvidence: evidence,
365
+ };
366
+ }
175
367
 
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
- : {};
368
+ function normalizeInlineMeshGitStatus(
369
+ status: Record<string, unknown>,
370
+ node: any,
371
+ options?: { lastCheckedAt?: number },
372
+ ): Record<string, unknown> | undefined {
194
373
  const isGitRepo = readBooleanValue(status.isGitRepo);
195
374
  if (!Object.keys(status).length || isGitRepo === undefined) return undefined;
196
375
  const conflictFiles = Array.isArray(status.conflictFiles)
@@ -198,15 +377,20 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
198
377
  : [];
199
378
  const conflictCount = readNumberValue(status.conflicts) ?? conflictFiles.length;
200
379
  const hasConflicts = readBooleanValue(status.hasConflicts) ?? conflictCount > 0;
201
- const submodules = readGitSubmodules(status.submodules);
380
+ const repoRoot = readStringValue(status.repoRoot, status.repo_root, node?.repoRoot, node?.repo_root, status.workspace, node?.workspace) || undefined;
381
+ const submodules = readGitSubmodules(status.submodules, repoRoot);
202
382
  return {
203
383
  workspace: readStringValue(status.workspace, node?.workspace) || '',
204
- repoRoot: readStringValue(status.repoRoot, node?.repoRoot, node?.workspace) || null,
384
+ repoRoot: repoRoot ?? null,
205
385
  isGitRepo,
206
386
  branch: readStringValue(status.branch) ?? null,
207
387
  headCommit: readStringValue(status.headCommit) ?? null,
208
388
  headMessage: readStringValue(status.headMessage) ?? null,
209
389
  upstream: readStringValue(status.upstream) ?? null,
390
+ upstreamStatus: readStringValue(status.upstreamStatus, status.upstream_status)
391
+ ?? (readStringValue(status.upstream) ? 'unchecked' : 'no_upstream'),
392
+ upstreamFetchedAt: readNumberValue(status.upstreamFetchedAt, status.upstream_fetched_at),
393
+ upstreamFetchError: readStringValue(status.upstreamFetchError, status.upstream_fetch_error),
210
394
  ahead: readNumberValue(status.ahead) ?? 0,
211
395
  behind: readNumberValue(status.behind) ?? 0,
212
396
  staged: readNumberValue(status.staged) ?? 0,
@@ -217,14 +401,247 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
217
401
  hasConflicts,
218
402
  conflictFiles,
219
403
  stashCount: readNumberValue(status.stashCount) ?? 0,
220
- lastCheckedAt: Date.now(),
404
+ lastCheckedAt: options?.lastCheckedAt ?? readNumberValue(status.lastCheckedAt) ?? Date.now(),
221
405
  ...(submodules ? { submodules } : {}),
222
406
  };
223
407
  }
224
408
 
409
+ function scoreInlineMeshGitStatus(git: Record<string, unknown> | undefined): number {
410
+ if (!git) return Number.NEGATIVE_INFINITY;
411
+ let score = 0;
412
+ if (readBooleanValue(git.isGitRepo) === true) score += 50;
413
+ if (readBooleanValue(git.isGitRepo) === false) score -= 10;
414
+ if (readStringValue(git.branch)) score += 20;
415
+ if (readStringValue(git.headCommit)) score += 20;
416
+ if (readStringValue(git.upstream)) score += 10;
417
+ if (readStringValue(git.upstreamStatus)) score += 5;
418
+ if (readNumberValue(git.ahead) !== undefined) score += 2;
419
+ if (readNumberValue(git.behind) !== undefined) score += 2;
420
+ if (Array.isArray(git.submodules) && git.submodules.length > 0) score += 4 + git.submodules.length;
421
+ if (readStringValue(git.error)) score -= 20;
422
+ return score;
423
+ }
424
+
425
+ function buildInlineMeshTransitGitStatus(node: any): Record<string, unknown> | undefined {
426
+ const rawGit = readObjectRecord(node?.lastGit ?? node?.last_git);
427
+ const gitResult = readObjectRecord(rawGit.result);
428
+ const directStatus = readObjectRecord(rawGit.status);
429
+ const nestedStatus = readObjectRecord(gitResult.status);
430
+ const rawProbe = readObjectRecord(node?.lastProbe ?? node?.last_probe);
431
+ const probeGit = readObjectRecord(rawProbe.git);
432
+ const probeGitResult = readObjectRecord(probeGit.result);
433
+ const probeDirectStatus = readObjectRecord(probeGit.status);
434
+ const probeNestedStatus = readObjectRecord(probeGitResult.status);
435
+ const candidates = [directStatus, nestedStatus, probeDirectStatus, probeNestedStatus];
436
+ let best: { git: Record<string, unknown>; score: number } | null = null;
437
+ for (const status of candidates) {
438
+ const normalized = normalizeInlineMeshGitStatus(status, node, { lastCheckedAt: Date.now() });
439
+ if (!normalized) continue;
440
+ const score = scoreInlineMeshGitStatus(normalized);
441
+ if (!best || score > best.score) best = { git: normalized, score };
442
+ }
443
+ return best?.git;
444
+ }
445
+
446
+ function shouldRefreshStalePendingAggregate(snapshot: any, options?: { requireDirectPeerTruth?: boolean }): boolean {
447
+ if (options?.requireDirectPeerTruth !== true || !Array.isArray(snapshot?.nodes)) return false;
448
+ return snapshot.nodes.some((node: any) => {
449
+ if (node?.gitProbePending !== true) return false;
450
+ const git = readObjectRecord(node?.git);
451
+ return !readBooleanValue(git.isGitRepo) && !readStringValue(git.branch, git.headCommit, git.upstream);
452
+ });
453
+ }
454
+
455
+ function buildLivePeerGitConnection(connection: Record<string, unknown>, timestamp = new Date().toISOString()): Record<string, unknown> {
456
+ const source = readStringValue(connection.source);
457
+ const transport = readStringValue(connection.transport);
458
+ return {
459
+ ...connection,
460
+ perspective: readStringValue(connection.perspective) ?? 'selected_coordinator',
461
+ source: source && source !== 'not_reported' ? source : 'mesh_peer_status',
462
+ state: 'connected',
463
+ transport: transport && transport !== 'unknown' ? transport : 'direct',
464
+ reported: true,
465
+ reason: 'Live peer git snapshot reported by the selected coordinator.',
466
+ lastStateChangeAt: readStringValue(connection.lastStateChangeAt) ?? timestamp,
467
+ };
468
+ }
469
+
470
+ function recordInlineMeshDirectGitTruth(
471
+ node: any,
472
+ git: Record<string, unknown>,
473
+ source: 'selected_coordinator_local_git' | 'selected_coordinator_mesh_p2p_git',
474
+ ): void {
475
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return;
476
+ const checkedAt = readNumberValue(git.lastCheckedAt) ?? Date.now();
477
+ const updatedAt = new Date(checkedAt).toISOString();
478
+ const nextGit: Record<string, unknown> = {
479
+ ...git,
480
+ lastCheckedAt: checkedAt,
481
+ };
482
+ node.lastGit = {
483
+ source,
484
+ checkedAt,
485
+ status: nextGit,
486
+ };
487
+ node.last_git = node.lastGit;
488
+ node.machineStatus = 'online';
489
+ node.updatedAt = updatedAt;
490
+ node.lastSeenAt = updatedAt;
491
+ const repoRoot = readStringValue(nextGit.repoRoot);
492
+ if (repoRoot && !readStringValue(node.repoRoot)) node.repoRoot = repoRoot;
493
+ }
494
+
495
+ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | undefined {
496
+ const liveGit = buildInlineMeshTransitGitStatus(node);
497
+ if (liveGit) return liveGit;
498
+
499
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
500
+ const cachedGit = readObjectRecord(cachedStatus.git);
501
+ if (!Object.keys(cachedGit).length) return undefined;
502
+ return normalizeInlineMeshGitStatus(cachedGit, node);
503
+ }
504
+
505
+ function shouldDiscardCachedInlineMeshStatus(node: any): boolean {
506
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
507
+ if (!Object.keys(cachedStatus).length) return false;
508
+ const cachedGit = readObjectRecord(cachedStatus.git);
509
+ const workspaceError = readStringValue(cachedStatus.error, node?.error);
510
+ if (workspaceError && /workspace must be an existing directory/i.test(workspaceError)) return true;
511
+ const isGitRepo = readBooleanValue(cachedGit.isGitRepo);
512
+ const branch = readStringValue(cachedGit.branch);
513
+ const headCommit = readStringValue(cachedGit.headCommit);
514
+ return isGitRepo === false && !branch && !headCommit;
515
+ }
516
+
517
+ function stripInlineMeshTransientNodeState(node: any): any {
518
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return node;
519
+ const {
520
+ cachedStatus,
521
+ lastGit: _lastGit,
522
+ last_git: _lastGitLegacy,
523
+ lastProbe: _lastProbe,
524
+ last_probe: _lastProbeLegacy,
525
+ error: _error,
526
+ health: _health,
527
+ machineStatus: _machineStatus,
528
+ lastSeenAt: _lastSeenAt,
529
+ last_seen_at: _lastSeenAtLegacy,
530
+ updatedAt: _updatedAt,
531
+ updated_at: _updatedAtLegacy,
532
+ activeSession: _activeSession,
533
+ active_session: _activeSessionLegacy,
534
+ activeSessionId: _activeSessionId,
535
+ active_session_id: _activeSessionIdLegacy,
536
+ sessionId: _sessionId,
537
+ session_id: _sessionIdLegacy,
538
+ providerType: _providerType,
539
+ provider_type: _providerTypeLegacy,
540
+ ...rest
541
+ } = node as Record<string, unknown>;
542
+ if (cachedStatus && !shouldDiscardCachedInlineMeshStatus(node)) {
543
+ return { ...rest, cachedStatus };
544
+ }
545
+ return rest;
546
+ }
547
+
548
+ function hasInlineMeshTransientNodeState(node: any): boolean {
549
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return false;
550
+ return 'cachedStatus' in node
551
+ || 'lastGit' in node
552
+ || 'last_git' in node
553
+ || 'lastProbe' in node
554
+ || 'last_probe' in node
555
+ || 'error' in node
556
+ || 'health' in node
557
+ || 'machineStatus' in node
558
+ || 'lastSeenAt' in node
559
+ || 'last_seen_at' in node
560
+ || 'updatedAt' in node
561
+ || 'updated_at' in node
562
+ || 'activeSession' in node
563
+ || 'active_session' in node
564
+ || 'activeSessionId' in node
565
+ || 'active_session_id' in node
566
+ || 'sessionId' in node
567
+ || 'session_id' in node
568
+ || 'providerType' in node
569
+ || 'provider_type' in node;
570
+ }
571
+
572
+ function inlineMeshCarriesTransientNodeTruth(inlineMesh: any): boolean {
573
+ if (!inlineMesh || typeof inlineMesh !== 'object' || Array.isArray(inlineMesh)) return false;
574
+ if (!Array.isArray(inlineMesh.nodes) || inlineMesh.nodes.length === 0) return false;
575
+ return inlineMesh.nodes.some((node: any) => hasInlineMeshTransientNodeState(node));
576
+ }
577
+
578
+ function readInlineMeshNodeId(node: any): string {
579
+ return readStringValue(node?.id, node?.nodeId) || '';
580
+ }
581
+
582
+ function sanitizeInlineMesh(inlineMesh: any): any {
583
+ if (!inlineMesh || typeof inlineMesh !== 'object' || Array.isArray(inlineMesh)) return inlineMesh;
584
+ if (!Array.isArray(inlineMesh.nodes)) return inlineMesh;
585
+ let changed = false;
586
+ const nodes = inlineMesh.nodes.map((node: any) => {
587
+ if (!hasInlineMeshTransientNodeState(node)) return node;
588
+ changed = true;
589
+ return stripInlineMeshTransientNodeState(node);
590
+ });
591
+ if (!changed) return inlineMesh;
592
+ return {
593
+ ...inlineMesh,
594
+ nodes,
595
+ };
596
+ }
597
+
598
+ function reconcileInlineMeshCache(cached: any, incoming: any): any {
599
+ if (!cached || typeof cached !== 'object' || Array.isArray(cached)) return incoming;
600
+ if (!incoming || typeof incoming !== 'object' || Array.isArray(incoming)) return cached;
601
+ const cachedNodes = Array.isArray(cached.nodes) ? cached.nodes : [];
602
+ const incomingNodes = Array.isArray(incoming.nodes) ? incoming.nodes : [];
603
+ if (!cachedNodes.length || !incomingNodes.length) return { ...cached, ...incoming };
604
+
605
+ const cachedUpdatedAt = Date.parse(readStringValue(cached.updatedAt, cached.updated_at) || '');
606
+ const incomingUpdatedAt = Date.parse(readStringValue(incoming.updatedAt, incoming.updated_at) || '');
607
+ const preserveCachedMembership = Number.isFinite(cachedUpdatedAt)
608
+ && (!Number.isFinite(incomingUpdatedAt) || cachedUpdatedAt > incomingUpdatedAt);
609
+
610
+ const cachedById = new Map<string, any>();
611
+ for (const node of cachedNodes) {
612
+ const nodeId = readInlineMeshNodeId(node);
613
+ if (nodeId) cachedById.set(nodeId, node);
614
+ }
615
+
616
+ const nodes = incomingNodes.map((incomingNode: any) => {
617
+ const nodeId = readInlineMeshNodeId(incomingNode);
618
+ const cachedNode = nodeId ? cachedById.get(nodeId) : undefined;
619
+ if (!cachedNode && preserveCachedMembership) return null;
620
+ if (!cachedNode) return incomingNode;
621
+ if (hasInlineMeshTransientNodeState(incomingNode)) {
622
+ return { ...cachedNode, ...incomingNode };
623
+ }
624
+ return { ...stripInlineMeshTransientNodeState(cachedNode), ...incomingNode };
625
+ }).filter(Boolean);
626
+
627
+ return {
628
+ ...cached,
629
+ ...incoming,
630
+ nodes,
631
+ };
632
+ }
633
+
225
634
  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;
635
+ return countGitWorktreeChanges(git) > 0;
636
+ }
637
+
638
+ function countGitWorktreeChanges(git: Record<string, unknown> | null | undefined): number {
639
+ if (!git) return 0;
640
+ return Number(git.staged || 0)
641
+ + Number(git.modified || 0)
642
+ + Number(git.untracked || 0)
643
+ + Number(git.deleted || 0)
644
+ + Number(git.renamed || 0);
228
645
  }
229
646
 
230
647
  function getGitSubmoduleDriftState(git: Record<string, unknown> | null | undefined): { dirty: boolean; outOfSync: boolean } {
@@ -249,6 +666,167 @@ function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undef
249
666
  return 'online';
250
667
  }
251
668
 
669
+ function readMeshNodeLabel(status: Record<string, unknown>, node: any): string {
670
+ return readStringValue(status.nodeId, node?.id, node?.nodeId) ?? 'unknown';
671
+ }
672
+
673
+ function buildInlineMeshBranchConvergence(args: {
674
+ mesh: any;
675
+ node: any;
676
+ status: Record<string, unknown>;
677
+ }): Record<string, unknown> {
678
+ const git = readObjectRecord(args.status.git);
679
+ const nodeLabel = readMeshNodeLabel(args.status, args.node);
680
+ const defaultBranch = readStringValue(args.mesh?.defaultBranch) ?? 'main';
681
+ const branch = readStringValue(git.branch, args.node?.worktreeBranch) ?? null;
682
+ const upstream = readStringValue(git.upstream) ?? null;
683
+ const upstreamStatus = readStringValue(git.upstreamStatus, git.upstream_status)
684
+ ?? (upstream ? 'unchecked' : 'no_upstream');
685
+ const ahead = readNumberValue(git.ahead) ?? 0;
686
+ const behind = readNumberValue(git.behind) ?? 0;
687
+ const uncommittedChanges = countGitWorktreeChanges(git);
688
+ const hasConflicts = readBooleanValue(git.hasConflicts)
689
+ ?? (Array.isArray(git.conflictFiles) && git.conflictFiles.length > 0);
690
+ const base = {
691
+ defaultBranch,
692
+ branch,
693
+ upstream,
694
+ upstreamStatus,
695
+ ahead,
696
+ behind,
697
+ isWorktree: args.node?.isLocalWorktree === true || args.status.isLocalWorktree === true,
698
+ isDefaultBranch: branch === defaultBranch,
699
+ };
700
+
701
+ if (readBooleanValue(git.isGitRepo) !== true) {
702
+ return {
703
+ ...base,
704
+ status: 'blocked_review',
705
+ needsConvergence: true,
706
+ reason: 'git_status_unavailable',
707
+ nextStep: `Resolve git status for node '${nodeLabel}' before marking the task complete.`,
708
+ };
709
+ }
710
+
711
+ if (!branch) {
712
+ return {
713
+ ...base,
714
+ status: 'blocked_review',
715
+ needsConvergence: true,
716
+ reason: 'branch_unknown',
717
+ nextStep: `Inspect node '${nodeLabel}' git branch before deciding whether it is merged to ${defaultBranch}.`,
718
+ };
719
+ }
720
+
721
+ if (hasConflicts || uncommittedChanges > 0) {
722
+ return {
723
+ ...base,
724
+ status: 'not_mergeable',
725
+ needsConvergence: true,
726
+ reason: hasConflicts ? 'conflicts_present' : 'dirty_workspace',
727
+ nextStep: `Commit, checkpoint, or resolve node '${nodeLabel}' before any main convergence step.`,
728
+ };
729
+ }
730
+
731
+ if (branch === defaultBranch) {
732
+ if (upstream && upstreamStatus !== 'fresh') {
733
+ return {
734
+ ...base,
735
+ status: 'blocked_review',
736
+ needsConvergence: true,
737
+ reason: 'default_branch_upstream_unverified',
738
+ nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${nodeLabel}'.`,
739
+ };
740
+ }
741
+ if (ahead > 0 || behind > 0) {
742
+ return {
743
+ ...base,
744
+ status: 'blocked_review',
745
+ needsConvergence: true,
746
+ reason: 'default_branch_not_even_with_upstream',
747
+ nextStep: `Bring ${defaultBranch} even with its upstream before declaring convergence complete.`,
748
+ };
749
+ }
750
+ return {
751
+ ...base,
752
+ status: 'merged_to_main',
753
+ needsConvergence: false,
754
+ reason: 'clean_default_branch',
755
+ nextStep: null,
756
+ };
757
+ }
758
+
759
+ if (args.node?.isLocalWorktree === true || args.status.isLocalWorktree === true) {
760
+ return {
761
+ ...base,
762
+ status: 'cleanup_candidate',
763
+ needsConvergence: true,
764
+ reason: 'clean_non_default_worktree_branch',
765
+ nextStep: `Run mesh_refine_node(node_id: "${nodeLabel}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`,
766
+ };
767
+ }
768
+
769
+ if (upstream && upstreamStatus !== 'fresh') {
770
+ return {
771
+ ...base,
772
+ status: 'blocked_review',
773
+ needsConvergence: true,
774
+ reason: 'feature_branch_upstream_unverified',
775
+ nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`,
776
+ };
777
+ }
778
+
779
+ if (!upstream || ahead > 0 || behind > 0) {
780
+ return {
781
+ ...base,
782
+ status: 'blocked_review',
783
+ needsConvergence: true,
784
+ reason: !upstream ? 'feature_branch_missing_upstream' : 'feature_branch_not_even_with_upstream',
785
+ nextStep: `Push or reconcile branch '${branch}', then merge it into ${defaultBranch} or mark it not_mergeable with a reason.`,
786
+ };
787
+ }
788
+
789
+ return {
790
+ ...base,
791
+ status: 'pushed_feature_branch_needs_merge',
792
+ needsConvergence: true,
793
+ reason: 'clean_non_default_branch',
794
+ nextStep: `Review and merge branch '${branch}' into ${defaultBranch}; do not report the task as fully complete while it remains off main.`,
795
+ };
796
+ }
797
+
798
+ function applyInlineMeshBranchConvergence(mesh: any, node: any, status: Record<string, unknown>): void {
799
+ const git = readObjectRecord(status.git);
800
+ if (Object.keys(git).length === 0 && !status.gitProbePending) return;
801
+ const uncommittedChanges = countGitWorktreeChanges(git);
802
+ status.isDirty = uncommittedChanges > 0;
803
+ status.uncommittedChanges = uncommittedChanges;
804
+ status.branchConvergence = buildInlineMeshBranchConvergence({ mesh, node, status });
805
+ }
806
+
807
+ function summarizeInlineMeshBranchConvergence(nodes: Array<Record<string, unknown>>): Record<string, unknown> {
808
+ const followUps = nodes
809
+ .filter(node => readObjectRecord(node.branchConvergence).needsConvergence === true)
810
+ .map(node => {
811
+ const convergence = readObjectRecord(node.branchConvergence);
812
+ return {
813
+ nodeId: node.nodeId,
814
+ workspace: node.workspace,
815
+ branch: convergence.branch,
816
+ status: convergence.status,
817
+ reason: convergence.reason,
818
+ nextStep: convergence.nextStep,
819
+ };
820
+ });
821
+
822
+ return {
823
+ needsFollowUp: followUps.length > 0,
824
+ unresolvedCount: followUps.length,
825
+ requiredFinalStates: ['merged_to_main', 'pushed_feature_branch_needs_merge', 'blocked_review', 'cleanup_candidate', 'not_mergeable'],
826
+ followUps,
827
+ };
828
+ }
829
+
252
830
  function readCachedInlineMeshActiveSessions(node: any): string[] {
253
831
  const cachedStatus = readObjectRecord(node?.cachedStatus);
254
832
  const activeSession = readObjectRecord(cachedStatus.activeSession);
@@ -313,6 +891,152 @@ function toIsoTimestamp(value: unknown): string | null {
313
891
  return stringValue || null;
314
892
  }
315
893
 
894
+ function synthesizeMeshNodeFreshnessFromConnection(status: Record<string, unknown>): void {
895
+ const connection = readObjectRecord(status.connection);
896
+ const connectionFreshAt = toIsoTimestamp(connection.lastCommandAt ?? connection.lastConnectedAt ?? connection.lastStateChangeAt);
897
+ const git = readObjectRecord(status.git);
898
+ const gitCheckedAt = toIsoTimestamp(git.lastCheckedAt);
899
+ if (!status.lastSeenAt && connectionFreshAt) status.lastSeenAt = connectionFreshAt;
900
+ if (!status.updatedAt && (gitCheckedAt || connectionFreshAt)) {
901
+ status.updatedAt = gitCheckedAt ?? connectionFreshAt;
902
+ }
903
+ }
904
+
905
+ function finalizeMeshNodeStatus(args: {
906
+ status: Record<string, unknown>;
907
+ node: any;
908
+ daemonId?: string;
909
+ isSelfNode: boolean;
910
+ }): void {
911
+ const { status, node, daemonId, isSelfNode } = args;
912
+ if (!readStringValue(status.machineStatus)) {
913
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
914
+ const machineStatus = readStringValue(cachedStatus.machineStatus, cachedStatus.machine_status, node?.machineStatus);
915
+ if (machineStatus) status.machineStatus = machineStatus;
916
+ }
917
+ synthesizeMeshNodeFreshnessFromConnection(status);
918
+ const connectionState = readStringValue(readObjectRecord(status.connection).state);
919
+ status.launchReady = !!daemonId && (
920
+ readStringValue(status.machineStatus) === 'online'
921
+ || connectionState === 'connected'
922
+ || isSelfNode
923
+ );
924
+ }
925
+
926
+ async function probeRemoteMeshGitStatus(args: {
927
+ dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
928
+ daemonId: string;
929
+ workspace: string;
930
+ timeoutMs: number;
931
+ }): Promise<Record<string, unknown> | null> {
932
+ if (!args.dispatchMeshCommand) return null;
933
+ const remoteResult = await Promise.race([
934
+ args.dispatchMeshCommand(args.daemonId, 'git_status', { workspace: args.workspace, refreshUpstream: true }),
935
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), args.timeoutMs)),
936
+ ]) as any;
937
+ const remoteGit = remoteResult?.status ?? remoteResult?.git ?? remoteResult;
938
+ return remoteGit && typeof remoteGit === 'object' && typeof remoteGit.isGitRepo === 'boolean'
939
+ ? remoteGit as Record<string, unknown>
940
+ : null;
941
+ }
942
+
943
+ async function hydrateInlineMeshDirectTruth(args: {
944
+ mesh: any;
945
+ meshSource: 'inline_cache' | 'inline_bootstrap' | 'local_config';
946
+ dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
947
+ statusInstanceId?: string;
948
+ localMachineId?: string;
949
+ }): Promise<{
950
+ directEvidenceCount: number;
951
+ localConfirmedCount: number;
952
+ peerAttemptedCount: number;
953
+ peerConfirmedCount: number;
954
+ unavailableNodeIds: string[];
955
+ }> {
956
+ const nodes = Array.isArray(args.mesh?.nodes) ? args.mesh.nodes : [];
957
+ if (!nodes.length) {
958
+ return {
959
+ directEvidenceCount: 0,
960
+ localConfirmedCount: 0,
961
+ peerAttemptedCount: 0,
962
+ peerConfirmedCount: 0,
963
+ unavailableNodeIds: [],
964
+ };
965
+ }
966
+
967
+ const selectedCoordinatorNodeId = readStringValue(
968
+ args.mesh?.coordinator?.preferredNodeId,
969
+ nodes[0]?.id,
970
+ nodes[0]?.nodeId,
971
+ );
972
+
973
+ let localConfirmedCount = 0;
974
+ let peerAttemptedCount = 0;
975
+ let peerConfirmedCount = 0;
976
+ const unavailableNodeIds: string[] = [];
977
+
978
+ for (const [nodeIndex, node] of nodes.entries()) {
979
+ const nodeId = readStringValue(node?.id, node?.nodeId) || `node_${nodeIndex}`;
980
+ const workspace = readStringValue(node?.workspace);
981
+ const daemonId = readStringValue(node?.daemonId);
982
+ const isSelfNode = Boolean(
983
+ nodeId && selectedCoordinatorNodeId && nodeId === selectedCoordinatorNodeId,
984
+ ) || Boolean(
985
+ daemonId && (daemonId === args.localMachineId || daemonId === args.statusInstanceId),
986
+ ) || Boolean(args.meshSource !== 'local_config' && nodeIndex === 0);
987
+
988
+ if (!workspace) {
989
+ if (!isSelfNode && daemonId) unavailableNodeIds.push(nodeId);
990
+ continue;
991
+ }
992
+
993
+ if (fs.existsSync(workspace)) {
994
+ try {
995
+ const localGit = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
996
+ if (localGit?.isGitRepo) {
997
+ recordInlineMeshDirectGitTruth(node, localGit as unknown as Record<string, unknown>, 'selected_coordinator_local_git');
998
+ localConfirmedCount += 1;
999
+ continue;
1000
+ }
1001
+ } catch {
1002
+ // Fall through to remote classification.
1003
+ }
1004
+ }
1005
+
1006
+ if (!daemonId || !args.dispatchMeshCommand) {
1007
+ if (!isSelfNode) unavailableNodeIds.push(nodeId);
1008
+ continue;
1009
+ }
1010
+
1011
+ peerAttemptedCount += 1;
1012
+ try {
1013
+ const remoteGit = await probeRemoteMeshGitStatus({
1014
+ dispatchMeshCommand: args.dispatchMeshCommand,
1015
+ daemonId,
1016
+ workspace,
1017
+ timeoutMs: 8_000,
1018
+ });
1019
+ if (remoteGit) {
1020
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
1021
+ peerConfirmedCount += 1;
1022
+ continue;
1023
+ }
1024
+ } catch {
1025
+ // Strict direct-only path: do not fall back to persisted cloud truth here.
1026
+ }
1027
+
1028
+ unavailableNodeIds.push(nodeId);
1029
+ }
1030
+
1031
+ return {
1032
+ directEvidenceCount: localConfirmedCount + peerConfirmedCount,
1033
+ localConfirmedCount,
1034
+ peerAttemptedCount,
1035
+ peerConfirmedCount,
1036
+ unavailableNodeIds,
1037
+ };
1038
+ }
1039
+
316
1040
  function summarizeMeshSessionRecord(record: any): Record<string, unknown> {
317
1041
  return {
318
1042
  sessionId: readStringValue(record?.sessionId) || 'unknown',
@@ -328,11 +1052,85 @@ function summarizeMeshSessionRecord(record: any): Record<string, unknown> {
328
1052
  };
329
1053
  }
330
1054
 
331
- function applyCachedInlineMeshNodeStatus(status: Record<string, unknown>, node: any): boolean {
1055
+ function liveSessionRecordMatchesMeshNode(record: any, meshId: string, nodeId: string): boolean {
1056
+ const recordNodeId = readStringValue(record?.meta?.meshNodeId);
1057
+ if (!recordNodeId || recordNodeId !== nodeId) return false;
1058
+ const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
1059
+ return !recordMeshId || recordMeshId === meshId;
1060
+ }
1061
+
1062
+ function liveSessionRecordMatchesMeshWorkspace(record: any, meshId: string, workspace: string): boolean {
1063
+ const recordWorkspace = readStringValue(record?.workspace);
1064
+ if (!recordWorkspace || !workspace || recordWorkspace !== workspace) return false;
1065
+
1066
+ const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
1067
+ if (recordMeshId) return recordMeshId === meshId;
1068
+
1069
+ return record?.meta?.launchedByCoordinator === true || !!readStringValue(record?.meta?.meshNodeId);
1070
+ }
1071
+
1072
+ function readLiveMeshNodeWorkspace(args: {
1073
+ meshId: string;
1074
+ nodeId: string;
1075
+ liveSessionRecords: any[];
1076
+ allowCoordinatorSession?: boolean;
1077
+ }): string {
1078
+ const directNodeWorkspace = args.liveSessionRecords.find((record) => (
1079
+ liveSessionRecordMatchesMeshNode(record, args.meshId, args.nodeId)
1080
+ && readStringValue(record?.workspace)
1081
+ ));
1082
+ if (directNodeWorkspace) {
1083
+ return readStringValue(directNodeWorkspace.workspace) || '';
1084
+ }
1085
+
1086
+ if (args.allowCoordinatorSession) {
1087
+ const coordinatorWorkspace = args.liveSessionRecords.find((record) => (
1088
+ readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId
1089
+ && readStringValue(record?.workspace)
1090
+ ));
1091
+ if (coordinatorWorkspace) {
1092
+ return readStringValue(coordinatorWorkspace.workspace) || '';
1093
+ }
1094
+ }
1095
+
1096
+ return '';
1097
+ }
1098
+
1099
+ function collectLiveMeshSessionRecords(args: {
1100
+ meshId: string;
1101
+ node: any;
1102
+ nodeId: string;
1103
+ liveSessionRecords: any[];
1104
+ allowCoordinatorSession?: boolean;
1105
+ }): any[] {
1106
+ const matches = args.liveSessionRecords.filter((record) => {
1107
+ const nodeWorkspace = readStringValue(args.node?.workspace);
1108
+ if (liveSessionRecordMatchesMeshNode(record, args.meshId, args.nodeId)) return true;
1109
+ return !!nodeWorkspace && liveSessionRecordMatchesMeshWorkspace(record, args.meshId, nodeWorkspace);
1110
+ });
1111
+
1112
+ if (args.allowCoordinatorSession) {
1113
+ for (const record of args.liveSessionRecords) {
1114
+ if (readStringValue(record?.meta?.meshCoordinatorFor) !== args.meshId) continue;
1115
+ const sessionId = readStringValue(record?.sessionId);
1116
+ if (sessionId && matches.some((entry) => readStringValue(entry?.sessionId) === sessionId)) continue;
1117
+ matches.push(record);
1118
+ }
1119
+ }
1120
+
1121
+ return matches;
1122
+ }
1123
+
1124
+ function applyCachedInlineMeshNodeStatus(
1125
+ status: Record<string, unknown>,
1126
+ node: any,
1127
+ options?: { skipGit?: boolean; skipError?: boolean; skipHealth?: boolean },
1128
+ ): boolean {
332
1129
  const cachedStatus = readObjectRecord(node?.cachedStatus);
333
- const git = buildCachedInlineMeshGitStatus(node);
334
- const error = readStringValue(cachedStatus.error, node?.error);
335
- const health = readStringValue(cachedStatus.health, node?.health);
1130
+ const liveGit = buildInlineMeshTransitGitStatus(node);
1131
+ const git = options?.skipGit ? undefined : (liveGit ?? buildCachedInlineMeshGitStatus(node));
1132
+ const error = options?.skipError ? undefined : (liveGit ? undefined : readStringValue(cachedStatus.error, node?.error));
1133
+ const health = options?.skipHealth ? undefined : (liveGit ? undefined : readStringValue(cachedStatus.health, node?.health));
336
1134
  const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
337
1135
  const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
338
1136
  const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
@@ -389,29 +1187,114 @@ async function resolveProviderTypeFromPriority(args: {
389
1187
  }
390
1188
  type MeshCoordinatorConfigFormat = 'claude_mcp_json' | 'hermes_config_yaml';
391
1189
  type MeshRefineValidationStatus = 'passed' | 'failed' | 'skipped';
392
- type MeshRefineValidationCommand = {
393
- command: string;
394
- args: string[];
395
- displayCommand: string;
396
- category: string;
397
- source: string;
398
- };
1190
+ type MeshRefineValidationCommand = MeshRefineValidationCommandPlan;
399
1191
 
400
1192
  type MeshRefineValidationSummary = {
401
1193
  status: MeshRefineValidationStatus;
402
1194
  required: true;
403
1195
  commandsRun: Array<Record<string, unknown>>;
1196
+ bootstrapCommandsRun: Array<Record<string, unknown>>;
404
1197
  rejectedCommands: Array<Record<string, unknown>>;
405
1198
  skippedReason?: string;
1199
+ failureKind?: string;
1200
+ failureCode?: string;
406
1201
  timeoutMs: number;
407
1202
  outputLimitBytes: number;
1203
+ configSource?: string;
1204
+ configSourceType?: string;
1205
+ suggestions?: unknown[];
1206
+ suggestedConfig?: unknown;
1207
+ };
1208
+
1209
+ type MeshRefineStageStatus = 'passed' | 'failed' | 'skipped';
1210
+
1211
+ type MeshRefinePatchEquivalenceSummary = {
1212
+ status: MeshRefineStageStatus;
1213
+ equivalent: boolean;
1214
+ baseHead: string;
1215
+ branchHead: string;
1216
+ mergeBase?: string;
1217
+ mergedTree?: string;
1218
+ expectedPatchId?: string;
1219
+ actualPatchId?: string;
1220
+ durationMs: number;
1221
+ error?: string;
1222
+ stdout?: string;
1223
+ stderr?: string;
1224
+ };
1225
+
1226
+ type MeshRefineSubmoduleReachabilityEntry = {
1227
+ path: string;
1228
+ commit: string;
1229
+ reachable: boolean;
1230
+ publishRequired?: boolean;
1231
+ autoPublishAllowed?: boolean;
1232
+ autoPublishAttempted?: boolean;
1233
+ autoPublishSucceeded?: boolean;
1234
+ autoPublishVerified?: boolean;
1235
+ autoPublishRefspec?: string;
1236
+ autoPublishSkippedReason?: string;
1237
+ importedFromWorktree?: boolean;
1238
+ checkedLocal?: boolean;
1239
+ localReachable?: boolean;
1240
+ remote?: string;
1241
+ remoteUrl?: string;
1242
+ remoteReachable?: boolean;
1243
+ remoteMainBranch?: string;
1244
+ remoteMainReachable?: boolean;
1245
+ fetchedFromOrigin?: boolean;
1246
+ error?: string;
1247
+ publishStdout?: string;
1248
+ publishStderr?: string;
1249
+ };
1250
+
1251
+ type MeshRefineSubmoduleReachabilitySummary = {
1252
+ status: MeshRefineStageStatus;
1253
+ checked: number;
1254
+ unreachable: MeshRefineSubmoduleReachabilityEntry[];
1255
+ entries: MeshRefineSubmoduleReachabilityEntry[];
1256
+ durationMs: number;
1257
+ autoPublishAllowed?: boolean;
1258
+ autoPublishPolicySource?: string;
1259
+ error?: string;
1260
+ };
1261
+
1262
+ type MeshRefineAsyncJobStatus = 'accepted' | 'completed' | 'failed';
1263
+
1264
+ type MeshRefineJobHandle = {
1265
+ success: true;
1266
+ async: true;
1267
+ status: MeshRefineAsyncJobStatus;
1268
+ jobId: string;
1269
+ interactionId: string;
1270
+ meshId: string;
1271
+ nodeId: string;
1272
+ targetNodeId: string;
1273
+ targetDaemonId?: string;
1274
+ workspace?: string;
1275
+ startedAt: string;
1276
+ completedAt?: string;
1277
+ duplicate?: boolean;
1278
+ retryOfJobId?: string;
1279
+ eventDelivery: {
1280
+ pendingEvents: true;
1281
+ ledger: true;
1282
+ };
1283
+ evidence: {
1284
+ pendingEventsCommand: 'get_pending_mesh_events';
1285
+ ledgerCommand: 'get_mesh_ledger_slice';
1286
+ taskHistoryKind: 'task_dispatched' | 'task_completed' | 'task_failed';
1287
+ };
408
1288
  };
409
1289
 
1290
+ type MeshRefineTerminalJob = MeshRefineJobHandle & { result?: Record<string, unknown> };
1291
+
410
1292
  const REFINE_VALIDATION_CATEGORIES = ['typecheck', 'test', 'lint', 'build'] as const;
411
1293
  const REFINE_VALIDATION_TIMEOUT_MS = 120_000;
412
1294
  const REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
413
1295
  const REFINE_VALIDATION_SUMMARY_CHARS = 2_000;
414
1296
  const REFINE_VALIDATION_MAX_COMMANDS = 4;
1297
+ const REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES = 4 * 1024 * 1024;
415
1298
 
416
1299
  function truncateValidationOutput(value: unknown): string {
417
1300
  const text = typeof value === 'string' ? value : value == null ? '' : String(value);
@@ -419,171 +1302,336 @@ function truncateValidationOutput(value: unknown): string {
419
1302
  return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}\n[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
420
1303
  }
421
1304
 
422
- function readPackageScripts(workspace: string): Record<string, string> {
423
- try {
424
- const packageJsonPath = pathJoin(workspace, 'package.json');
425
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
426
- return parsed?.scripts && typeof parsed.scripts === 'object' && !Array.isArray(parsed.scripts)
427
- ? parsed.scripts as Record<string, string>
428
- : {};
429
- } catch {
430
- return {};
431
- }
432
- }
433
-
434
- function tokenizeValidationCommand(command: string): string[] | null {
435
- const trimmed = command.trim();
436
- if (!trimmed) return null;
437
- // Fail closed: the gate never hands shell syntax to a shell. Package-manager
438
- // scripts are invoked via execFile(binary, args), and metacharacters/quotes are
439
- // rejected before tokenization so `npm run test && rm -rf` cannot be smuggled in.
440
- if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
441
- const tokens = trimmed.split(/\s+/).filter(Boolean);
442
- if (!tokens.length) return null;
443
- if (tokens.some(token => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
444
- return tokens;
445
- }
446
-
447
- function scriptMatchesValidationCategory(scriptName: string, category: string): boolean {
448
- return scriptName === category || scriptName.startsWith(`${category}:`);
1305
+ function recordMeshRefineStage(
1306
+ stages: Array<Record<string, unknown>>,
1307
+ stage: string,
1308
+ status: MeshRefineStageStatus,
1309
+ startedAt: number,
1310
+ details?: Record<string, unknown>,
1311
+ ): void {
1312
+ stages.push({
1313
+ stage,
1314
+ status,
1315
+ durationMs: Date.now() - startedAt,
1316
+ ...(details || {}),
1317
+ });
449
1318
  }
450
1319
 
451
- function parsePackageManagerValidationCommand(
452
- rawCommand: string,
453
- category: string,
454
- scripts: Record<string, string>,
455
- source: string,
456
- ): { command?: MeshRefineValidationCommand; rejected?: Record<string, unknown> } {
457
- const tokens = tokenizeValidationCommand(rawCommand);
458
- if (!tokens) {
459
- return { rejected: { command: rawCommand, category, source, reason: 'unsafe command string is not allowlisted' } };
460
- }
461
-
462
- const [binary, second, third, ...rest] = tokens;
463
- let scriptName = '';
464
- let command = binary;
465
- let args: string[] = [];
466
-
467
- if ((binary === 'npm' || binary === 'pnpm' || binary === 'bun') && second === 'run' && third) {
468
- scriptName = third;
469
- args = ['run', scriptName, ...rest];
470
- } else if (binary === 'npm' && second === 'test' && !third) {
471
- scriptName = 'test';
472
- args = ['test'];
473
- } else if (binary === 'yarn' && second === 'run' && third) {
474
- scriptName = third;
475
- args = ['run', scriptName, ...rest];
476
- } else if (binary === 'yarn' && second && !third) {
477
- scriptName = second;
478
- args = [scriptName];
479
- } else {
480
- return { rejected: { command: rawCommand, category, source, reason: 'command is not a supported package-manager script invocation' } };
481
- }
1320
+ function buildSubmodulePublishRequiredNextStep(entries: MeshRefineSubmoduleReachabilityEntry[]): string {
1321
+ const refs = entries
1322
+ .map(entry => `${entry.path}@${entry.commit}`)
1323
+ .join(', ');
1324
+ return `Ask the user for explicit approval to push/publish the unreachable submodule commit(s) (${refs}) to the configured submodule remote main branch, then rerun mesh_refine_node. Do not merge the root branch until every submodule gitlink commit is reachable from submodule origin/main.`;
1325
+ }
482
1326
 
483
- if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
484
- return { rejected: { command: rawCommand, category, source, script: scriptName, reason: 'script is not declared in package.json' } };
1327
+ function resolveRefineryAutoPublishSubmoduleMainCommits(mesh: any, workspace: string): { enabled: boolean; source?: string } {
1328
+ if (mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true) {
1329
+ return { enabled: true, source: 'mesh.policy.allowAutoPublishSubmoduleMainCommits' };
485
1330
  }
486
- if (!scriptMatchesValidationCategory(scriptName, category)) {
487
- return { rejected: { command: rawCommand, category, source, script: scriptName, reason: 'script name is outside the validation category allowlist' } };
1331
+ const loaded = loadMeshRefineConfig(mesh, workspace);
1332
+ if (loaded.config?.allowAutoPublishSubmoduleMainCommits === true) {
1333
+ return { enabled: true, source: loaded.path || loaded.source };
488
1334
  }
1335
+ return { enabled: false };
1336
+ }
489
1337
 
490
- return {
491
- command: {
492
- command,
493
- args,
494
- displayCommand: [command, ...args].join(' '),
495
- category,
496
- source,
497
- },
498
- };
1338
+ async function computeGitPatchId(cwd: string, fromRef: string, toRef: string): Promise<string> {
1339
+ const { execFileSync } = await import('node:child_process');
1340
+ const diff = execFileSync('git', ['diff', '--patch', '--full-index', fromRef, toRef], {
1341
+ cwd,
1342
+ encoding: 'utf8',
1343
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1344
+ });
1345
+ if (!diff.trim()) return '';
1346
+ const patchId = execFileSync('git', ['patch-id', '--stable'], {
1347
+ cwd,
1348
+ input: diff,
1349
+ encoding: 'utf8',
1350
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1351
+ }).trim();
1352
+ return patchId.split(/\s+/)[0] || '';
499
1353
  }
500
1354
 
501
- function collectProjectContextValidationCandidates(mesh: any): Array<{ command: string; category: string; source: string; confidence?: string }> {
502
- const commands = mesh?.projectContext?.commands;
503
- if (!commands || typeof commands !== 'object' || Array.isArray(commands)) return [];
504
- const candidates: Array<{ command: string; category: string; source: string; confidence?: string }> = [];
505
- for (const category of REFINE_VALIDATION_CATEGORIES) {
506
- const entries = Array.isArray(commands[category]) ? commands[category] : [];
507
- for (const entry of entries) {
508
- if (typeof entry?.command !== 'string') continue;
509
- candidates.push({
510
- command: entry.command,
511
- category,
512
- source: typeof entry.sourcePath === 'string' ? entry.sourcePath : 'projectContext.commands',
513
- confidence: typeof entry.confidence === 'string' ? entry.confidence : undefined,
514
- });
1355
+ async function runMeshRefinePatchEquivalenceGate(
1356
+ repoRoot: string,
1357
+ baseHead: string,
1358
+ branchHead: string,
1359
+ ): Promise<MeshRefinePatchEquivalenceSummary> {
1360
+ const startedAt = Date.now();
1361
+ try {
1362
+ const { execFileSync } = await import('node:child_process');
1363
+ const git = (args: string[]) => execFileSync('git', args, {
1364
+ cwd: repoRoot,
1365
+ encoding: 'utf8',
1366
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1367
+ });
1368
+ const mergeBase = git(['merge-base', baseHead, branchHead]).trim();
1369
+ const mergeTreeStdout = git(['merge-tree', '--write-tree', baseHead, branchHead]);
1370
+ const mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || '';
1371
+ if (!mergeBase || !mergedTree) {
1372
+ return {
1373
+ status: 'failed',
1374
+ equivalent: false,
1375
+ baseHead,
1376
+ branchHead,
1377
+ mergeBase: mergeBase || undefined,
1378
+ mergedTree: mergedTree || undefined,
1379
+ durationMs: Date.now() - startedAt,
1380
+ error: 'patch equivalence preflight could not resolve merge-base or synthetic merge tree',
1381
+ stdout: truncateValidationOutput(mergeTreeStdout),
1382
+ };
515
1383
  }
1384
+ const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
1385
+ const actualPatchId = await computeGitPatchId(repoRoot, baseHead, mergedTree);
1386
+ const equivalent = expectedPatchId === actualPatchId;
1387
+ return {
1388
+ status: equivalent ? 'passed' : 'failed',
1389
+ equivalent,
1390
+ baseHead,
1391
+ branchHead,
1392
+ mergeBase,
1393
+ mergedTree,
1394
+ expectedPatchId,
1395
+ actualPatchId,
1396
+ durationMs: Date.now() - startedAt,
1397
+ };
1398
+ } catch (e: any) {
1399
+ return {
1400
+ status: 'failed',
1401
+ equivalent: false,
1402
+ baseHead,
1403
+ branchHead,
1404
+ durationMs: Date.now() - startedAt,
1405
+ error: e?.message || String(e),
1406
+ stdout: truncateValidationOutput(e?.stdout),
1407
+ stderr: truncateValidationOutput(e?.stderr),
1408
+ };
516
1409
  }
517
- return candidates.sort((a, b) => {
518
- const rank = (value?: string) => value === 'high' ? 0 : value === 'medium' ? 1 : 2;
519
- return rank(a.confidence) - rank(b.confidence);
520
- });
521
1410
  }
522
1411
 
523
- function collectPolicyValidationCandidates(mesh: any): Array<{ command: string; category: string; source: string }> {
524
- const policy = mesh?.policy && typeof mesh.policy === 'object' && !Array.isArray(mesh.policy) ? mesh.policy : {};
525
- const configured = Array.isArray(policy.validationCommands)
526
- ? policy.validationCommands
527
- : Array.isArray(policy.validationGate?.commands)
528
- ? policy.validationGate.commands
529
- : [];
530
- return configured
531
- .map((entry: any) => typeof entry === 'string' ? { command: entry, category: '', source: 'mesh.policy.validationCommands' } : entry)
532
- .filter((entry: any) => entry && typeof entry.command === 'string')
533
- .map((entry: any) => {
534
- const commandText = entry.command.trim();
535
- const category = REFINE_VALIDATION_CATEGORIES.find(cat => commandText.includes(` ${cat}`)) ?? '';
536
- return { command: commandText, category, source: 'mesh.policy.validationCommands' };
537
- })
538
- .filter((entry: any) => !!entry.category);
539
- }
1412
+ async function runMeshRefineSubmoduleReachabilityGate(
1413
+ repoRoot: string,
1414
+ mergedTree: string,
1415
+ options: { allowAutoPublishSubmoduleMainCommits?: boolean; autoPublishPolicySource?: string; worktreeRoot?: string } = {},
1416
+ ): Promise<MeshRefineSubmoduleReachabilitySummary> {
1417
+ const startedAt = Date.now();
1418
+ const entries: MeshRefineSubmoduleReachabilityEntry[] = [];
1419
+ try {
1420
+ const { execFile } = await import('node:child_process');
1421
+ const { promisify } = await import('node:util');
1422
+ const execFileAsync = promisify(execFile);
1423
+ const runGit = async (cwd: string, args: string[]): Promise<string> => {
1424
+ const { stdout } = await execFileAsync('git', args, {
1425
+ cwd,
1426
+ encoding: 'utf8',
1427
+ timeout: 30_000,
1428
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1429
+ windowsHide: true,
1430
+ });
1431
+ return String(stdout || '');
1432
+ };
1433
+ const verifyRemoteMainContainsCommit = async (submodulePath: string, commit: string, branch = 'main'): Promise<void> => {
1434
+ await runGit(submodulePath, ['-c', 'protocol.file.allow=always', 'fetch', 'origin', `refs/heads/${branch}:refs/remotes/origin/${branch}`]);
1435
+ await runGit(submodulePath, ['merge-base', '--is-ancestor', commit, `refs/remotes/origin/${branch}`]);
1436
+ };
1437
+ const publishCommitToRemoteMain = async (submodulePath: string, commit: string, branch = 'main'): Promise<{ stdout: string; stderr: string; refspec: string }> => {
1438
+ const refspec = `${commit}:refs/heads/${branch}`;
1439
+ const { stdout, stderr } = await execFileAsync('git', ['push', 'origin', refspec], {
1440
+ cwd: submodulePath,
1441
+ encoding: 'utf8',
1442
+ timeout: 30_000,
1443
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1444
+ windowsHide: true,
1445
+ });
1446
+ return { stdout: String(stdout || ''), stderr: String(stderr || ''), refspec };
1447
+ };
1448
+ const importCommitFromWorktreeSubmodule = async (submodulePath: string, worktreeSubmodulePath: string, commit: string): Promise<boolean> => {
1449
+ if (!fs.existsSync(worktreeSubmodulePath)) return false;
1450
+ try {
1451
+ await runGit(worktreeSubmodulePath, ['cat-file', '-e', `${commit}^{commit}`]);
1452
+ } catch {
1453
+ return false;
1454
+ }
1455
+ await runGit(submodulePath, ['-c', 'protocol.file.allow=always', 'fetch', worktreeSubmodulePath, commit]);
1456
+ await runGit(submodulePath, ['cat-file', '-e', `${commit}^{commit}`]);
1457
+ return true;
1458
+ };
540
1459
 
541
- function selectMeshRefineValidationCommands(mesh: any, workspace: string): { commands: MeshRefineValidationCommand[]; rejectedCommands: Array<Record<string, unknown>>; source: string } {
542
- const scripts = readPackageScripts(workspace);
543
- const rejectedCommands: Array<Record<string, unknown>> = [];
544
- const selected: MeshRefineValidationCommand[] = [];
545
- const seen = new Set<string>();
546
- const candidates = [
547
- ...collectPolicyValidationCandidates(mesh),
548
- ...collectProjectContextValidationCandidates(mesh),
549
- ];
550
-
551
- for (const candidate of candidates) {
552
- const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
553
- if (parsed.rejected) {
554
- rejectedCommands.push(parsed.rejected);
555
- continue;
556
- }
557
- if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
558
- selected.push(parsed.command);
559
- seen.add(parsed.command.displayCommand);
560
- if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
561
- }
562
-
563
- if (!selected.length && candidates.length === 0) {
564
- for (const category of REFINE_VALIDATION_CATEGORIES) {
565
- if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
566
- const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, 'package.json:scripts');
567
- if (fallback.command && !seen.has(fallback.command.displayCommand)) {
568
- selected.push(fallback.command);
569
- seen.add(fallback.command.displayCommand);
570
- } else if (fallback.rejected) {
571
- rejectedCommands.push(fallback.rejected);
572
- }
573
- if (selected.length >= 2) break;
1460
+ const treeOutput = await runGit(repoRoot, ['ls-tree', '-r', '-z', mergedTree]);
1461
+ const gitlinks = treeOutput
1462
+ .split('\0')
1463
+ .filter(Boolean)
1464
+ .map(record => {
1465
+ const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record);
1466
+ return match ? { commit: match[1], path: match[2] } : null;
1467
+ })
1468
+ .filter((entry): entry is { commit: string; path: string } => !!entry);
1469
+
1470
+ for (const gitlink of gitlinks) {
1471
+ const submodulePath = pathResolve(repoRoot, gitlink.path);
1472
+ const entry: MeshRefineSubmoduleReachabilityEntry = {
1473
+ path: gitlink.path,
1474
+ commit: gitlink.commit,
1475
+ reachable: false,
1476
+ };
1477
+ try {
1478
+ if (!fs.existsSync(submodulePath)) {
1479
+ entry.error = `Submodule checkout missing at ${gitlink.path}`;
1480
+ entry.publishRequired = true;
1481
+ if (options.allowAutoPublishSubmoduleMainCommits === true) {
1482
+ entry.autoPublishAllowed = true;
1483
+ entry.autoPublishAttempted = false;
1484
+ entry.autoPublishSkippedReason = `submodule checkout missing at ${gitlink.path}; cannot perform non-force push to origin/main`;
1485
+ }
1486
+ entries.push(entry);
1487
+ continue;
1488
+ }
1489
+
1490
+ entry.checkedLocal = true;
1491
+ try {
1492
+ await runGit(submodulePath, ['cat-file', '-e', `${gitlink.commit}^{commit}`]);
1493
+ entry.localReachable = true;
1494
+ } catch {
1495
+ entry.localReachable = false;
1496
+ if (options.allowAutoPublishSubmoduleMainCommits === true && options.worktreeRoot) {
1497
+ try {
1498
+ const imported = await importCommitFromWorktreeSubmodule(
1499
+ submodulePath,
1500
+ pathResolve(options.worktreeRoot, gitlink.path),
1501
+ gitlink.commit,
1502
+ );
1503
+ if (imported) {
1504
+ entry.localReachable = true;
1505
+ entry.importedFromWorktree = true;
1506
+ }
1507
+ } catch (importError: any) {
1508
+ entry.autoPublishSkippedReason = `candidate commit was not present in the source checkout and could not be imported from worktree submodule: ${truncateValidationOutput(importError?.stderr || importError?.message || String(importError))}`;
1509
+ }
1510
+ }
1511
+ // Probe the submodule remote before allowing cleanup/completion.
1512
+ }
1513
+
1514
+ try {
1515
+ entry.remote = 'origin';
1516
+ let remoteUrl = '';
1517
+ try {
1518
+ remoteUrl = (await runGit(submodulePath, ['remote', 'get-url', 'origin'])).trim();
1519
+ if (!remoteUrl) throw new Error('origin remote has no URL');
1520
+ entry.remoteUrl = remoteUrl;
1521
+ } catch {
1522
+ entry.error = 'Submodule remote reachability check failed: no configured origin remote';
1523
+ entry.publishRequired = true;
1524
+ if (options.allowAutoPublishSubmoduleMainCommits === true) {
1525
+ entry.autoPublishAllowed = true;
1526
+ entry.autoPublishAttempted = false;
1527
+ entry.autoPublishSkippedReason = 'submodule origin remote is not configured; cannot perform non-force push to origin/main';
1528
+ }
1529
+ entries.push(entry);
1530
+ continue;
1531
+ }
1532
+ entry.remoteMainBranch = 'main';
1533
+ try {
1534
+ await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, 'main');
1535
+ entry.fetchedFromOrigin = true;
1536
+ entry.remoteReachable = true;
1537
+ entry.remoteMainReachable = true;
1538
+ entry.reachable = true;
1539
+ } catch (e: any) {
1540
+ entry.remoteReachable = false;
1541
+ entry.remoteMainReachable = false;
1542
+ entry.publishRequired = true;
1543
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
1544
+ entry.error = `Submodule remote main reachability check failed for origin/main: ${details}`;
1545
+ if (options.allowAutoPublishSubmoduleMainCommits === true && entry.localReachable === true) {
1546
+ entry.autoPublishAllowed = true;
1547
+ entry.autoPublishAttempted = true;
1548
+ try {
1549
+ const publish = await publishCommitToRemoteMain(submodulePath, gitlink.commit, 'main');
1550
+ entry.autoPublishRefspec = publish.refspec;
1551
+ entry.publishStdout = truncateValidationOutput(publish.stdout);
1552
+ entry.publishStderr = truncateValidationOutput(publish.stderr);
1553
+ entry.autoPublishSucceeded = true;
1554
+ await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, 'main');
1555
+ entry.fetchedFromOrigin = true;
1556
+ entry.remoteReachable = true;
1557
+ entry.remoteMainReachable = true;
1558
+ entry.autoPublishVerified = true;
1559
+ entry.publishRequired = false;
1560
+ entry.reachable = true;
1561
+ entry.error = undefined;
1562
+ } catch (publishError: any) {
1563
+ entry.autoPublishSucceeded = false;
1564
+ entry.autoPublishVerified = false;
1565
+ const publishDetails = truncateValidationOutput(publishError?.stderr || publishError?.message || String(publishError));
1566
+ entry.error = `Submodule auto-publish to origin/main failed or could not be verified: ${publishDetails}`;
1567
+ }
1568
+ } else if (options.allowAutoPublishSubmoduleMainCommits === true) {
1569
+ entry.autoPublishAllowed = true;
1570
+ entry.autoPublishAttempted = false;
1571
+ entry.autoPublishSkippedReason = entry.autoPublishSkippedReason
1572
+ || 'candidate commit is not reachable in the source checkout or worktree submodule, so Refinery cannot push it to origin/main';
1573
+ }
1574
+ }
1575
+ } catch (e: any) {
1576
+ entry.remoteReachable = false;
1577
+ entry.remoteMainReachable = false;
1578
+ entry.publishRequired = true;
1579
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
1580
+ entry.error = `Submodule remote main reachability check failed for origin/main: ${details}`;
1581
+ }
1582
+ } catch (e: any) {
1583
+ entry.error = truncateValidationOutput(e?.message || String(e));
1584
+ entry.publishRequired = true;
1585
+ }
1586
+ entries.push(entry);
574
1587
  }
1588
+
1589
+ const unreachable = entries.filter(entry => !entry.reachable);
1590
+ return {
1591
+ status: unreachable.length ? 'failed' : 'passed',
1592
+ checked: entries.length,
1593
+ unreachable: unreachable.map(entry => ({ ...entry, publishRequired: entry.publishRequired !== false })),
1594
+ entries: entries.map(entry => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
1595
+ durationMs: Date.now() - startedAt,
1596
+ autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
1597
+ autoPublishPolicySource: options.autoPublishPolicySource,
1598
+ };
1599
+ } catch (e: any) {
1600
+ const unreachable = entries.filter(entry => !entry.reachable).map(entry => ({ ...entry, publishRequired: true }));
1601
+ return {
1602
+ status: 'failed',
1603
+ checked: entries.length,
1604
+ unreachable,
1605
+ entries: entries.map(entry => entry.reachable ? entry : { ...entry, publishRequired: true }),
1606
+ durationMs: Date.now() - startedAt,
1607
+ autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
1608
+ autoPublishPolicySource: options.autoPublishPolicySource,
1609
+ error: truncateValidationOutput(e?.message || String(e)),
1610
+ };
575
1611
  }
1612
+ }
576
1613
 
1614
+ function buildMeshRefineValidationPlan(mesh: any, workspace: string): Record<string, unknown> {
1615
+ const plan = resolveMeshRefineValidationPlan(mesh, workspace);
1616
+ const mapCommand = (command: MeshRefineValidationCommandPlan) => ({
1617
+ displayCommand: command.displayCommand,
1618
+ category: command.category,
1619
+ source: command.source,
1620
+ cwd: command.cwd,
1621
+ timeoutMs: command.timeoutMs,
1622
+ });
577
1623
  return {
578
- commands: selected,
579
- rejectedCommands,
580
- source: selected.some(command => command.source === 'mesh.policy.validationCommands')
581
- ? 'mesh_policy'
582
- : selected.some(command => command.source !== 'package.json:scripts')
583
- ? 'project_context'
584
- : selected.length
585
- ? 'package_json_scripts'
586
- : 'unavailable',
1624
+ source: plan.source,
1625
+ sourceType: plan.sourceType,
1626
+ bootstrapCommands: plan.bootstrapCommands.map(mapCommand),
1627
+ commands: plan.commands.map(mapCommand),
1628
+ unavailableReason: plan.unavailableReason,
1629
+ rejectedCommands: plan.rejectedCommands,
1630
+ suggestions: plan.suggestions,
1631
+ suggestedConfig: plan.suggestedConfig,
1632
+ note: plan.sourceType === 'unavailable'
1633
+ ? 'No validation command will be executed until a repo mesh/refine config is provided. Heuristics are suggestions only.'
1634
+ : 'Validation commands are resolved from repo mesh/refine config; heuristics are suggestions only.',
587
1635
  };
588
1636
  }
589
1637
 
@@ -591,60 +1639,119 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
591
1639
  const { execFile } = await import('node:child_process');
592
1640
  const { promisify } = await import('node:util');
593
1641
  const execFileAsync = promisify(execFile);
594
- const selection = selectMeshRefineValidationCommands(mesh, workspace);
1642
+ const selection = resolveMeshRefineValidationPlan(mesh, workspace);
595
1643
  const summary: MeshRefineValidationSummary = {
596
1644
  status: 'skipped',
597
1645
  required: true,
598
1646
  commandsRun: [],
1647
+ bootstrapCommandsRun: [],
599
1648
  rejectedCommands: selection.rejectedCommands,
600
1649
  skippedReason: undefined,
601
1650
  timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
602
1651
  outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
1652
+ configSource: selection.source,
1653
+ configSourceType: selection.sourceType,
1654
+ suggestions: selection.suggestions,
1655
+ suggestedConfig: selection.suggestedConfig,
603
1656
  };
604
1657
 
605
1658
  if (!selection.commands.length) {
606
- summary.skippedReason = 'validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available';
1659
+ summary.skippedReason = selection.unavailableReason || 'validation_unavailable: repo mesh/refine config did not provide executable validation.commands';
607
1660
  return summary;
608
1661
  }
609
1662
 
610
- for (const candidate of selection.commands) {
1663
+ const commandRecord = (candidate: MeshRefineValidationCommand, cwd: string, startedAt: number, result: any, passed: boolean, extras: Record<string, unknown> = {}) => ({
1664
+ command: candidate.command,
1665
+ args: candidate.args,
1666
+ displayCommand: candidate.displayCommand,
1667
+ category: candidate.category,
1668
+ source: candidate.source,
1669
+ cwd,
1670
+ passed,
1671
+ durationMs: Date.now() - startedAt,
1672
+ stdout: truncateValidationOutput(result?.stdout),
1673
+ stderr: truncateValidationOutput(result?.stderr || result?.message),
1674
+ ...extras,
1675
+ });
1676
+ const isPackageManagerValidation = (candidate: MeshRefineValidationCommand): boolean => {
1677
+ const command = pathBasename(candidate.command).replace(/\.(?:cmd|exe)$/i, '');
1678
+ return ['npm', 'pnpm', 'yarn', 'bun'].includes(command)
1679
+ && candidate.args.some(arg => arg === 'run' || arg === 'test' || arg === 'exec');
1680
+ };
1681
+ const dependenciesLikelyMissing = (cwd: string): boolean => {
1682
+ if (!fs.existsSync(pathJoin(cwd, 'package.json'))) return false;
1683
+ if (fs.existsSync(pathJoin(cwd, 'node_modules'))) return false;
1684
+ return ['package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lockb', 'bun.lock']
1685
+ .some(lock => fs.existsSync(pathJoin(cwd, lock)));
1686
+ };
1687
+
1688
+ for (const candidate of selection.bootstrapCommands) {
611
1689
  const startedAt = Date.now();
1690
+ const cwd = candidate.cwd ? pathResolve(workspace, candidate.cwd) : workspace;
1691
+ const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
612
1692
  try {
613
1693
  const result = await execFileAsync(candidate.command, candidate.args, {
614
- cwd: workspace,
1694
+ cwd,
615
1695
  encoding: 'utf8',
616
- timeout: REFINE_VALIDATION_TIMEOUT_MS,
1696
+ timeout,
617
1697
  maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
618
- env: { ...process.env, CI: process.env.CI || '1' },
619
- });
620
- summary.commandsRun.push({
621
- command: candidate.command,
622
- args: candidate.args,
623
- displayCommand: candidate.displayCommand,
624
- category: candidate.category,
625
- source: candidate.source,
626
- passed: true,
627
- exitCode: 0,
628
- durationMs: Date.now() - startedAt,
629
- stdout: truncateValidationOutput(result.stdout),
630
- stderr: truncateValidationOutput(result.stderr),
1698
+ env: { ...process.env, CI: process.env.CI || '1', ...(candidate.env || {}) },
631
1699
  });
1700
+ summary.bootstrapCommandsRun.push(commandRecord(candidate, cwd, startedAt, result, true, { exitCode: 0 }));
632
1701
  } catch (error: any) {
633
- summary.commandsRun.push({
634
- command: candidate.command,
635
- args: candidate.args,
636
- displayCommand: candidate.displayCommand,
637
- category: candidate.category,
638
- source: candidate.source,
639
- passed: false,
1702
+ summary.bootstrapCommandsRun.push(commandRecord(candidate, cwd, startedAt, error, false, {
640
1703
  exitCode: typeof error?.code === 'number' ? error.code : null,
641
1704
  signal: typeof error?.signal === 'string' ? error.signal : null,
642
1705
  timedOut: error?.killed === true || /timed out/i.test(String(error?.message || '')),
643
- durationMs: Date.now() - startedAt,
644
- stdout: truncateValidationOutput(error?.stdout),
645
- stderr: truncateValidationOutput(error?.stderr || error?.message),
1706
+ failureKind: 'dependency_bootstrap_failed',
1707
+ }));
1708
+ summary.status = 'failed';
1709
+ summary.failureKind = 'dependency_bootstrap_failed';
1710
+ summary.failureCode = 'dependency_bootstrap_failed';
1711
+ return summary;
1712
+ }
1713
+ }
1714
+
1715
+ for (const candidate of selection.commands) {
1716
+ const startedAt = Date.now();
1717
+ const cwd = candidate.cwd ? pathResolve(workspace, candidate.cwd) : workspace;
1718
+ const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
1719
+ if (selection.bootstrapCommands.length === 0 && isPackageManagerValidation(candidate) && dependenciesLikelyMissing(cwd)) {
1720
+ summary.commandsRun.push(commandRecord(candidate, cwd, startedAt, {
1721
+ stderr: 'Dependencies appear to be missing: package.json and a lockfile are present, but node_modules is absent. Configure validation.bootstrapCommands in repo mesh/refine config if Refinery should install/bootstrap before validation.',
1722
+ }, false, {
1723
+ exitCode: null,
1724
+ skipped: true,
1725
+ failureKind: 'missing_dependencies',
1726
+ }));
1727
+ summary.status = 'failed';
1728
+ summary.failureKind = 'missing_dependencies';
1729
+ summary.failureCode = 'missing_dependencies';
1730
+ return summary;
1731
+ }
1732
+ try {
1733
+ const result = await execFileAsync(candidate.command, candidate.args, {
1734
+ cwd,
1735
+ encoding: 'utf8',
1736
+ timeout,
1737
+ maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
1738
+ env: { ...process.env, CI: process.env.CI || '1', ...(candidate.env || {}) },
646
1739
  });
1740
+ summary.commandsRun.push(commandRecord(candidate, cwd, startedAt, result, true, { exitCode: 0 }));
1741
+ } catch (error: any) {
1742
+ const stderr = truncateValidationOutput(error?.stderr || error?.message);
1743
+ const missingDependencyFailure = /Cannot find module|MODULE_NOT_FOUND|node_modules|command not found|not found/i.test(stderr);
1744
+ summary.commandsRun.push(commandRecord(candidate, cwd, startedAt, error, false, {
1745
+ exitCode: typeof error?.code === 'number' ? error.code : null,
1746
+ signal: typeof error?.signal === 'string' ? error.signal : null,
1747
+ timedOut: error?.killed === true || /timed out/i.test(String(error?.message || '')),
1748
+ ...(missingDependencyFailure ? { failureKind: 'missing_dependencies' } : {}),
1749
+ }));
647
1750
  summary.status = 'failed';
1751
+ if (missingDependencyFailure) {
1752
+ summary.failureKind = 'missing_dependencies';
1753
+ summary.failureCode = 'missing_dependencies';
1754
+ }
648
1755
  return summary;
649
1756
  }
650
1757
  }
@@ -776,6 +1883,8 @@ export interface CommandRouterDeps {
776
1883
  sessionHostControl?: SessionHostControlPlane | null;
777
1884
  /** Selected-coordinator mesh peer telemetry surface for target daemons, when supported by the runtime. */
778
1885
  getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
1886
+ /** Dispatch a command to a remote mesh node via P2P/relay. Injected by cloud runtime; absent in standalone. */
1887
+ dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
779
1888
  }
780
1889
 
781
1890
  export interface CommandRouterResult {
@@ -887,42 +1996,266 @@ function summarizeSessionHostPruneResult(result: unknown): Record<string, unknow
887
1996
  };
888
1997
  }
889
1998
 
1999
+ function normalizeStandaloneHostCommandUrl(hostAddress: string): string {
2000
+ const raw = hostAddress.trim();
2001
+ if (!raw) throw new Error('hostAddress required');
2002
+ const url = new URL(raw.replace(/^ws:/, 'http:').replace(/^wss:/, 'https:'));
2003
+ url.pathname = '/api/v1/command';
2004
+ url.search = '';
2005
+ url.hash = '';
2006
+ return url.toString();
2007
+ }
2008
+
2009
+ function buildMemberJoinNode(mesh: any, args: any, fallbackDaemonId?: string): Record<string, unknown> | null {
2010
+ const requestedNodeId = typeof args?.memberNodeId === 'string' ? args.memberNodeId.trim() : '';
2011
+ const explicit = args?.memberNode && typeof args.memberNode === 'object' && !Array.isArray(args.memberNode)
2012
+ ? args.memberNode as Record<string, any>
2013
+ : null;
2014
+ const configured = Array.isArray(mesh?.nodes)
2015
+ ? (requestedNodeId
2016
+ ? mesh.nodes.find((node: any) => node?.id === requestedNodeId || node?.nodeId === requestedNodeId)
2017
+ : mesh.nodes[0])
2018
+ : null;
2019
+ const source = explicit || configured;
2020
+ const workspace = typeof source?.workspace === 'string' && source.workspace.trim()
2021
+ ? source.workspace.trim()
2022
+ : typeof args?.workspace === 'string' && args.workspace.trim()
2023
+ ? args.workspace.trim()
2024
+ : process.cwd();
2025
+ if (!workspace) return null;
2026
+ const nodeId = typeof source?.id === 'string' && source.id.trim()
2027
+ ? source.id.trim()
2028
+ : typeof source?.nodeId === 'string' && source.nodeId.trim()
2029
+ ? source.nodeId.trim()
2030
+ : undefined;
2031
+ return {
2032
+ ...(nodeId ? { id: nodeId } : {}),
2033
+ workspace,
2034
+ ...(typeof source?.repoRoot === 'string' && source.repoRoot.trim() ? { repoRoot: source.repoRoot.trim() } : {}),
2035
+ ...(typeof source?.daemonId === 'string' && source.daemonId.trim() ? { daemonId: source.daemonId.trim() } : fallbackDaemonId ? { daemonId: fallbackDaemonId } : {}),
2036
+ ...(typeof source?.machineId === 'string' && source.machineId.trim() ? { machineId: source.machineId.trim() } : {}),
2037
+ userOverrides: source?.userOverrides && typeof source.userOverrides === 'object' && !Array.isArray(source.userOverrides) ? source.userOverrides : {},
2038
+ policy: source?.policy && typeof source.policy === 'object' && !Array.isArray(source.policy) ? source.policy : {},
2039
+ role: 'member',
2040
+ };
2041
+ }
2042
+
890
2043
  export class DaemonCommandRouter {
891
2044
  private deps: CommandRouterDeps;
892
2045
  /** In-memory cache for cloud-originating meshes passed via inlineMesh.
893
2046
  * Allows the MCP server to query mesh data via get_mesh even when
894
2047
  * the mesh doesn't exist in the local meshes.json file. */
895
2048
  private inlineMeshCache = new Map<string, any>();
2049
+ /** Coordinator-owned whole-mesh aggregate status snapshots. Browser callers read this by default. */
2050
+ private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any; queueRevision: string }>();
2051
+ /** In-memory async Refinery jobs keyed by meshId:nodeId to reject/return duplicate in-flight requests. */
2052
+ private runningRefineJobs = new Map<string, MeshRefineJobHandle>();
2053
+ /** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
2054
+ private terminalRefineJobs = new Map<string, MeshRefineTerminalJob>();
896
2055
 
897
2056
  constructor(deps: CommandRouterDeps) {
898
2057
  this.deps = deps;
899
2058
  }
900
2059
 
2060
+ private cloneJsonValue<T>(value: T): T {
2061
+ if (typeof structuredClone === 'function') return structuredClone(value);
2062
+ return JSON.parse(JSON.stringify(value)) as T;
2063
+ }
2064
+
2065
+ private hydrateCachedAggregateMeshStatusFromInline(snapshot: any, mesh: any, options?: { requireDirectPeerTruth?: boolean }): any {
2066
+ if (!mesh || typeof mesh !== 'object' || !Array.isArray(mesh.nodes) || !Array.isArray(snapshot?.nodes)) return snapshot;
2067
+ const inlineNodesById = new Map<string, any>();
2068
+ for (const node of mesh.nodes) {
2069
+ const nodeId = readInlineMeshNodeId(node);
2070
+ if (nodeId) inlineNodesById.set(nodeId, node);
2071
+ }
2072
+ if (!inlineNodesById.size) return snapshot;
2073
+
2074
+ let changed = false;
2075
+ const unavailableNodeIds = new Set<string>();
2076
+ const sourceOfTruth = readObjectRecord(snapshot.sourceOfTruth);
2077
+ const directPeerTruth = readObjectRecord(sourceOfTruth.directPeerTruth);
2078
+ for (const entry of Array.isArray(directPeerTruth.unavailableNodeIds) ? directPeerTruth.unavailableNodeIds : []) {
2079
+ const nodeId = readStringValue(entry);
2080
+ if (nodeId) unavailableNodeIds.add(nodeId);
2081
+ }
2082
+
2083
+ const nodes = snapshot.nodes.map((statusNode: any) => {
2084
+ const nodeId = readStringValue(statusNode?.nodeId, statusNode?.id);
2085
+ const inlineNode = nodeId ? inlineNodesById.get(nodeId) : undefined;
2086
+ if (!inlineNode) return statusNode;
2087
+ const liveGit = buildInlineMeshTransitGitStatus(inlineNode);
2088
+ if (!liveGit) return statusNode;
2089
+ const nextStatus = { ...statusNode };
2090
+ nextStatus.git = liveGit;
2091
+ nextStatus.health = deriveMeshNodeHealthFromGit(liveGit);
2092
+ applyInlineMeshBranchConvergence(mesh, inlineNode, nextStatus);
2093
+ nextStatus.launchReady = readBooleanValue(nextStatus.launchReady) ?? true;
2094
+ const connection = readObjectRecord(nextStatus.connection);
2095
+ const connectionState = readStringValue(connection.state);
2096
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
2097
+ if (!connectionReported || connectionState === 'unknown') {
2098
+ nextStatus.connection = buildLivePeerGitConnection(connection);
2099
+ }
2100
+ delete nextStatus.gitProbePending;
2101
+ const error = readStringValue(nextStatus.error);
2102
+ if (error && /pending_git|git probe|live peer git snapshot|no peer git snapshot/i.test(error)) delete nextStatus.error;
2103
+ if (!readStringValue(nextStatus.machineStatus)) nextStatus.machineStatus = 'online';
2104
+ if (nodeId) unavailableNodeIds.delete(nodeId);
2105
+ changed = true;
2106
+ return nextStatus;
2107
+ });
2108
+
2109
+ const aggregateDirectTruthSatisfied = sourceOfTruth.coordinatorOwnsLiveTruth === true
2110
+ || directPeerTruth.satisfied === true;
2111
+ if (!changed && !(options?.requireDirectPeerTruth && unavailableNodeIds.size > 0 && !aggregateDirectTruthSatisfied)) return snapshot;
2112
+ const nextSourceOfTruth = {
2113
+ ...sourceOfTruth,
2114
+ ...(Object.keys(directPeerTruth).length ? {
2115
+ directPeerTruth: {
2116
+ ...directPeerTruth,
2117
+ satisfied: options?.requireDirectPeerTruth === true
2118
+ ? aggregateDirectTruthSatisfied || unavailableNodeIds.size === 0
2119
+ : directPeerTruth.satisfied,
2120
+ unavailableNodeIds: [...unavailableNodeIds],
2121
+ },
2122
+ ...(options?.requireDirectPeerTruth === true ? {
2123
+ coordinatorOwnsLiveTruth: aggregateDirectTruthSatisfied || unavailableNodeIds.size === 0,
2124
+ currentStatus: aggregateDirectTruthSatisfied || unavailableNodeIds.size === 0 ? 'live_git_and_session_probes' : 'direct_peer_truth_unavailable',
2125
+ } : {}),
2126
+ } : {}),
2127
+ };
2128
+ return {
2129
+ ...snapshot,
2130
+ ...(options?.requireDirectPeerTruth === true && unavailableNodeIds.size > 0 && !aggregateDirectTruthSatisfied ? {
2131
+ success: false,
2132
+ code: 'mesh_direct_peer_truth_unavailable',
2133
+ error: 'Selected coordinator could not confirm direct mesh truth for every remote node yet.',
2134
+ } : {}),
2135
+ sourceOfTruth: nextSourceOfTruth,
2136
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodes),
2137
+ nodes,
2138
+ };
2139
+ }
2140
+
2141
+ private getCachedAggregateMeshStatus(meshId: string, mesh?: any, options?: { requireDirectPeerTruth?: boolean }): any | null {
2142
+ const cached = this.aggregateMeshStatusCache.get(meshId);
2143
+ if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
2144
+ if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
2145
+ let snapshot = this.cloneJsonValue(cached.snapshot);
2146
+ snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
2147
+ if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
2148
+ const ageMs = Math.max(0, Date.now() - cached.builtAt);
2149
+ const sourceOfTruth = snapshot.sourceOfTruth && typeof snapshot.sourceOfTruth === 'object'
2150
+ ? snapshot.sourceOfTruth
2151
+ : {};
2152
+ snapshot.sourceOfTruth = {
2153
+ ...sourceOfTruth,
2154
+ aggregateSnapshot: {
2155
+ ...(sourceOfTruth.aggregateSnapshot && typeof sourceOfTruth.aggregateSnapshot === 'object'
2156
+ ? sourceOfTruth.aggregateSnapshot
2157
+ : {}),
2158
+ owner: 'coordinator_daemon_memory',
2159
+ cached: true,
2160
+ source: 'memory',
2161
+ refreshReason: 'memory_cache_hit',
2162
+ ageMs,
2163
+ cachedAt: new Date(cached.builtAt).toISOString(),
2164
+ returnedAt: new Date().toISOString(),
2165
+ },
2166
+ };
2167
+ return snapshot;
2168
+ }
2169
+
2170
+ private rememberAggregateMeshStatus(meshId: string, snapshot: any, refreshReason: string): any {
2171
+ if (!snapshot || typeof snapshot !== 'object' || snapshot.success !== true || !Array.isArray(snapshot.nodes)) return snapshot;
2172
+ const builtAt = Date.now();
2173
+ const next = this.cloneJsonValue(snapshot);
2174
+ const sourceOfTruth = next.sourceOfTruth && typeof next.sourceOfTruth === 'object'
2175
+ ? next.sourceOfTruth
2176
+ : {};
2177
+ next.sourceOfTruth = {
2178
+ ...sourceOfTruth,
2179
+ aggregateSnapshot: {
2180
+ owner: 'coordinator_daemon_memory',
2181
+ cached: false,
2182
+ source: 'live_refresh',
2183
+ refreshReason,
2184
+ ageMs: 0,
2185
+ cachedAt: new Date(builtAt).toISOString(),
2186
+ returnedAt: new Date(builtAt).toISOString(),
2187
+ },
2188
+ };
2189
+ this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next), queueRevision: getMeshQueueRevision(meshId) });
2190
+ return next;
2191
+ }
2192
+
901
2193
  public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
902
2194
  if (inlineMesh && typeof inlineMesh === 'object') {
903
- this.inlineMeshCache.set(meshId, inlineMesh as any);
904
- return inlineMesh as any;
2195
+ return this.warmInlineMeshCache(meshId, inlineMesh);
905
2196
  }
906
2197
  return this.inlineMeshCache.get(meshId);
907
2198
  }
908
2199
 
2200
+ private warmInlineMeshCache(meshId: string, inlineMesh?: unknown): any | undefined {
2201
+ if (!inlineMesh || typeof inlineMesh !== 'object') return undefined;
2202
+ const sanitizedInlineMesh = sanitizeInlineMesh(inlineMesh as any);
2203
+ const cached = this.inlineMeshCache.get(meshId);
2204
+ if (cached) {
2205
+ const merged = reconcileInlineMeshCache(cached, sanitizedInlineMesh);
2206
+ this.inlineMeshCache.set(meshId, merged);
2207
+ return merged;
2208
+ }
2209
+ this.inlineMeshCache.set(meshId, sanitizedInlineMesh as any);
2210
+ return sanitizedInlineMesh as any;
2211
+ }
2212
+
909
2213
  private async getMeshForCommand(
910
2214
  meshId: string,
911
2215
  inlineMesh?: unknown,
912
2216
  options?: { preferInline?: boolean },
913
- ): Promise<{ mesh: any; inline: boolean } | null> {
2217
+ ): Promise<{ mesh: any; inline: boolean; source: 'inline_cache' | 'inline_bootstrap' | 'local_config' } | null> {
914
2218
  const preferInline = options?.preferInline === true;
915
2219
  if (preferInline) {
916
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
917
- if (cached) return { mesh: cached, inline: true };
2220
+ const cached = this.getCachedInlineMesh(meshId);
2221
+ if (cached) {
2222
+ if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
2223
+ const merged = reconcileInlineMeshCache(cached, inlineMesh as any);
2224
+ this.inlineMeshCache.set(meshId, sanitizeInlineMesh(merged));
2225
+ return { mesh: merged, inline: true, source: 'inline_cache' };
2226
+ }
2227
+ return { mesh: cached, inline: true, source: 'inline_cache' };
2228
+ }
2229
+ if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
2230
+ this.warmInlineMeshCache(meshId, inlineMesh);
2231
+ return { mesh: inlineMesh, inline: true, source: 'inline_bootstrap' };
2232
+ }
918
2233
  }
919
2234
  try {
920
2235
  const { getMesh } = await import('../config/mesh-config.js');
921
2236
  const mesh = getMesh(meshId);
922
- if (mesh) return { mesh, inline: false };
2237
+ if (mesh) return { mesh, inline: false, source: 'local_config' };
923
2238
  } catch { /* fall through to inline cache */ }
924
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
925
- return cached ? { mesh: cached, inline: true } : null;
2239
+ const cached = this.getCachedInlineMesh(meshId);
2240
+ if (cached) return { mesh: cached, inline: true, source: 'inline_cache' };
2241
+ const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
2242
+ return warmedInline ? { mesh: warmedInline, inline: true, source: 'inline_bootstrap' } : null;
2243
+ }
2244
+
2245
+ private invalidateAggregateMeshStatus(meshId: string): void {
2246
+ this.aggregateMeshStatusCache.delete(meshId);
2247
+ }
2248
+
2249
+
2250
+ private async requireMeshHostMutationOwner(meshId: string, inlineMesh: unknown, operation: string): Promise<CommandRouterResult | null> {
2251
+ const meshRecord = await this.getMeshForCommand(meshId, inlineMesh, { preferInline: true });
2252
+ const mesh = meshRecord?.mesh;
2253
+ if (!mesh) return { success: false, error: 'Mesh not found' };
2254
+ const meshHost = resolveMeshHostStatus(mesh);
2255
+ if (!meshHost.canOwnCoordinator || !meshHost.canOwnQueue) {
2256
+ return { ...buildMeshHostRequiredFailure(mesh, operation), success: false, meshId };
2257
+ }
2258
+ return null;
926
2259
  }
927
2260
 
928
2261
  private updateInlineMeshNode(meshId: string, mesh: any, node: any): void {
@@ -932,6 +2265,7 @@ export class DaemonCommandRouter {
932
2265
  else mesh.nodes.push(node);
933
2266
  mesh.updatedAt = new Date().toISOString();
934
2267
  this.inlineMeshCache.set(meshId, mesh);
2268
+ this.invalidateAggregateMeshStatus(meshId);
935
2269
  }
936
2270
 
937
2271
  private removeInlineMeshNode(meshId: string, mesh: any, nodeId: string): boolean {
@@ -941,6 +2275,7 @@ export class DaemonCommandRouter {
941
2275
  mesh.nodes.splice(idx, 1);
942
2276
  mesh.updatedAt = new Date().toISOString();
943
2277
  this.inlineMeshCache.set(meshId, mesh);
2278
+ this.invalidateAggregateMeshStatus(meshId);
944
2279
  return true;
945
2280
  }
946
2281
 
@@ -1219,6 +2554,7 @@ export class DaemonCommandRouter {
1219
2554
  const deletedSessionIds: string[] = [];
1220
2555
  const skippedSessionIds: string[] = [];
1221
2556
  const skippedLiveSessionIds: string[] = [];
2557
+ const skippedCoordinatorSessionIds: string[] = [];
1222
2558
  const deleteUnsupportedSessionIds: string[] = [];
1223
2559
  const recordsRemainSessionIds: string[] = [];
1224
2560
  const errors: Array<{ sessionId: string; error: string }> = [];
@@ -1253,6 +2589,12 @@ export class DaemonCommandRouter {
1253
2589
  const completed = this.isCompletedHostedSession(record);
1254
2590
  const surfaceKind = getSessionHostSurfaceKind(record);
1255
2591
  const liveRuntime = surfaceKind === 'live_runtime';
2592
+ const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
2593
+ if (!hasExplicitSessionIds && coordinatorSession) {
2594
+ skippedSessionIds.push(sessionId);
2595
+ skippedCoordinatorSessionIds.push(sessionId);
2596
+ continue;
2597
+ }
1256
2598
  if (!hasExplicitSessionIds && liveRuntime) {
1257
2599
  skippedSessionIds.push(sessionId);
1258
2600
  skippedLiveSessionIds.push(sessionId);
@@ -1322,6 +2664,7 @@ export class DaemonCommandRouter {
1322
2664
  deletedSessionIds,
1323
2665
  skippedSessionIds,
1324
2666
  skippedLiveSessionIds,
2667
+ skippedCoordinatorSessionIds,
1325
2668
  ...(deleteUnsupported ? {
1326
2669
  deleteUnsupported: true,
1327
2670
  effectiveCleanup: args.mode === 'stop_and_delete'
@@ -1429,37 +2772,548 @@ export class DaemonCommandRouter {
1429
2772
  level: daemonResult.success ? 'info' : 'warn',
1430
2773
  payload: { cmd, source: logSource, success: daemonResult.success, durationMs: Date.now() - cmdStart },
1431
2774
  });
1432
- return daemonResult;
2775
+ return daemonResult;
2776
+ }
2777
+
2778
+ // 2. Delegate to DaemonCommandHandler
2779
+ const handlerResult = await this.deps.commandHandler.handle(cmd, normalizedArgs);
2780
+ logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: handlerResult.success, durationMs: Date.now() - cmdStart });
2781
+ recordDebugTrace({
2782
+ interactionId,
2783
+ category: 'command',
2784
+ stage: 'completed',
2785
+ level: handlerResult.success ? 'info' : 'warn',
2786
+ payload: { cmd, source: logSource, success: handlerResult.success, durationMs: Date.now() - cmdStart },
2787
+ });
2788
+
2789
+ // 3. Post-chat command callback
2790
+ if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
2791
+ this.deps.onPostChatCommand();
2792
+ }
2793
+
2794
+ return handlerResult;
2795
+ } catch (e: any) {
2796
+ logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: false, error: e.message, durationMs: Date.now() - cmdStart });
2797
+ recordDebugTrace({
2798
+ interactionId,
2799
+ category: 'command',
2800
+ stage: 'failed',
2801
+ level: 'error',
2802
+ payload: { cmd, source: logSource, error: e?.message || String(e), durationMs: Date.now() - cmdStart },
2803
+ });
2804
+ throw e;
2805
+ }
2806
+ }
2807
+
2808
+
2809
+ private buildRefineJobKey(meshId: string, nodeId: string): string {
2810
+ return `${meshId}:${nodeId}`;
2811
+ }
2812
+
2813
+ private buildRefineJobHandle(args: {
2814
+ meshId: string;
2815
+ nodeId: string;
2816
+ node?: any;
2817
+ status?: MeshRefineAsyncJobStatus;
2818
+ startedAt?: string;
2819
+ completedAt?: string;
2820
+ jobId?: string;
2821
+ interactionId?: string;
2822
+ retryOfJobId?: string;
2823
+ }): MeshRefineJobHandle {
2824
+ return {
2825
+ success: true,
2826
+ async: true,
2827
+ status: args.status || 'accepted',
2828
+ jobId: args.jobId || `refine_${createInteractionId()}`,
2829
+ interactionId: args.interactionId || createInteractionId(),
2830
+ meshId: args.meshId,
2831
+ nodeId: args.nodeId,
2832
+ targetNodeId: args.nodeId,
2833
+ targetDaemonId: readStringValue(args.node?.daemonId),
2834
+ workspace: readStringValue(args.node?.workspace),
2835
+ startedAt: args.startedAt || new Date().toISOString(),
2836
+ ...(args.completedAt ? { completedAt: args.completedAt } : {}),
2837
+ ...(args.retryOfJobId ? { retryOfJobId: args.retryOfJobId } : {}),
2838
+ eventDelivery: { pendingEvents: true, ledger: true },
2839
+ evidence: {
2840
+ pendingEventsCommand: 'get_pending_mesh_events',
2841
+ ledgerCommand: 'get_mesh_ledger_slice',
2842
+ taskHistoryKind: args.status === 'completed' ? 'task_completed' : args.status === 'failed' ? 'task_failed' : 'task_dispatched',
2843
+ },
2844
+ };
2845
+ }
2846
+
2847
+ private queueRefineJobEvent(event: 'refine:accepted' | 'refine:completed' | 'refine:failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): void {
2848
+ const metadataEvent = {
2849
+ source: 'refine_mesh_node_async_job',
2850
+ jobId: handle.jobId,
2851
+ interactionId: handle.interactionId,
2852
+ meshId: handle.meshId,
2853
+ nodeId: handle.targetNodeId,
2854
+ targetDaemonId: handle.targetDaemonId,
2855
+ workspace: handle.workspace,
2856
+ status: handle.status,
2857
+ startedAt: handle.startedAt,
2858
+ completedAt: handle.completedAt,
2859
+ retryOfJobId: handle.retryOfJobId,
2860
+ ...(result ? { result } : {}),
2861
+ };
2862
+ const eventPayload = {
2863
+ event,
2864
+ meshId: handle.meshId,
2865
+ nodeLabel: handle.targetNodeId,
2866
+ nodeId: handle.targetNodeId,
2867
+ workspace: handle.workspace,
2868
+ metadataEvent,
2869
+ queuedAt: Date.now(),
2870
+ };
2871
+ if (typeof this.deps.instanceManager?.getByCategory === 'function') {
2872
+ const forwarded = handleMeshForwardEvent(
2873
+ { instanceManager: this.deps.instanceManager } as any,
2874
+ {
2875
+ event,
2876
+ meshId: handle.meshId,
2877
+ nodeId: handle.targetNodeId,
2878
+ workspace: handle.workspace,
2879
+ jobId: handle.jobId,
2880
+ interactionId: handle.interactionId,
2881
+ status: handle.status,
2882
+ targetDaemonId: handle.targetDaemonId,
2883
+ startedAt: handle.startedAt,
2884
+ completedAt: handle.completedAt,
2885
+ retryOfJobId: handle.retryOfJobId,
2886
+ ...(result ? { result } : {}),
2887
+ },
2888
+ );
2889
+ if (forwarded?.success === true) return;
2890
+ LOG.warn('Mesh', `[Refinery] Failed to forward async refine event ${event}: ${forwarded?.error || 'unknown error'}`);
2891
+ }
2892
+ queuePendingMeshCoordinatorEvent(eventPayload);
2893
+ }
2894
+
2895
+ private async appendRefineJobLedger(kind: 'task_dispatched' | 'task_completed' | 'task_failed', handle: MeshRefineJobHandle, result?: Record<string, unknown>): Promise<void> {
2896
+ try {
2897
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2898
+ appendLedgerEntry(handle.meshId, {
2899
+ kind,
2900
+ nodeId: handle.targetNodeId,
2901
+ payload: {
2902
+ source: 'refine_mesh_node_async_job',
2903
+ refineJob: {
2904
+ jobId: handle.jobId,
2905
+ interactionId: handle.interactionId,
2906
+ status: handle.status,
2907
+ meshId: handle.meshId,
2908
+ nodeId: handle.targetNodeId,
2909
+ targetDaemonId: handle.targetDaemonId,
2910
+ workspace: handle.workspace,
2911
+ startedAt: handle.startedAt,
2912
+ completedAt: handle.completedAt,
2913
+ retryOfJobId: handle.retryOfJobId,
2914
+ },
2915
+ async: true,
2916
+ retryOfJobId: handle.retryOfJobId,
2917
+ ...(result ? {
2918
+ success: result.success === true,
2919
+ result,
2920
+ finalBranchConvergenceState: result.finalBranchConvergenceState,
2921
+ } : {}),
2922
+ },
2923
+ });
2924
+ } catch (e: any) {
2925
+ LOG.warn('Mesh', `[Refinery] Failed to append async refine ledger entry: ${e?.message || e}`);
2926
+ }
2927
+ }
2928
+
2929
+ private async executeMeshRefineNodeSynchronously(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
2930
+ const refineStages: Array<Record<string, unknown>> = [];
2931
+ try {
2932
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2933
+ const mesh = meshRecord?.mesh;
2934
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2935
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh`, refineStages };
2936
+
2937
+ if (!node.isLocalWorktree || !node.workspace) {
2938
+ return { success: false, error: `Refinery requires a local worktree node`, refineStages };
2939
+ }
2940
+
2941
+ const sourceNode = node.clonedFromNodeId
2942
+ ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2943
+ : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2944
+ const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2945
+ if (!repoRoot) return { success: false, error: 'Source node repoRoot not found', refineStages };
2946
+
2947
+ const { execFile } = await import('node:child_process');
2948
+ const { promisify } = await import('node:util');
2949
+ const execFileAsync = promisify(execFile);
2950
+
2951
+ const resolveStarted = Date.now();
2952
+ const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2953
+ const branch = branchStdout.trim();
2954
+ if (!branch) return { success: false, error: 'Could not determine branch of the worktree node', refineStages };
2955
+
2956
+ const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2957
+ const baseBranch = baseBranchStdout.trim();
2958
+ const { stdout: baseHeadStdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
2959
+ const { stdout: branchHeadStdout } = await execFileAsync('git', ['rev-parse', branch], { cwd: node.workspace, encoding: 'utf8' });
2960
+ const baseHead = baseHeadStdout.trim();
2961
+ const branchHead = branchHeadStdout.trim();
2962
+ recordMeshRefineStage(refineStages, 'resolve_refs', 'passed', resolveStarted, { branch, baseBranch, baseHead, branchHead });
2963
+
2964
+ const validationStarted = Date.now();
2965
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2966
+ recordMeshRefineStage(
2967
+ refineStages,
2968
+ 'validation',
2969
+ validationSummary.status === 'passed' ? 'passed' : validationSummary.status === 'failed' ? 'failed' : 'skipped',
2970
+ validationStarted,
2971
+ { validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length },
2972
+ );
2973
+ if (validationSummary.status === 'failed') {
2974
+ return {
2975
+ success: false,
2976
+ code: validationSummary.failureCode || 'validation_failed',
2977
+ convergenceStatus: 'blocked_review',
2978
+ error: validationSummary.failureCode === 'missing_dependencies'
2979
+ ? 'Refinery validation dependencies are missing; merge/refine was not attempted. Configure validation.bootstrapCommands if Refinery should bootstrap dependencies before validation.'
2980
+ : validationSummary.failureCode === 'dependency_bootstrap_failed'
2981
+ ? 'Refinery dependency/bootstrap command failed; merge/refine was not attempted.'
2982
+ : 'Refinery validation gate failed; merge/refine was not attempted.',
2983
+ branch,
2984
+ into: baseBranch,
2985
+ validationSummary,
2986
+ refineStages,
2987
+ finalBranchConvergenceState: {
2988
+ branch,
2989
+ baseBranch,
2990
+ merged: false,
2991
+ removed: false,
2992
+ validation: 'failed',
2993
+ status: 'blocked_review',
2994
+ },
2995
+ };
2996
+ }
2997
+ if (validationSummary.status === 'skipped') {
2998
+ return {
2999
+ success: false,
3000
+ code: 'validation_unavailable',
3001
+ convergenceStatus: 'blocked_review',
3002
+ error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
3003
+ branch,
3004
+ into: baseBranch,
3005
+ validationSummary,
3006
+ refineStages,
3007
+ finalBranchConvergenceState: {
3008
+ branch,
3009
+ baseBranch,
3010
+ merged: false,
3011
+ removed: false,
3012
+ validation: 'unavailable',
3013
+ status: 'blocked_review',
3014
+ },
3015
+ };
3016
+ }
3017
+
3018
+ const patchEquivalenceStarted = Date.now();
3019
+ const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
3020
+ recordMeshRefineStage(refineStages, 'patch_equivalence', patchEquivalence.status, patchEquivalenceStarted, {
3021
+ equivalent: patchEquivalence.equivalent,
3022
+ expectedPatchId: patchEquivalence.expectedPatchId,
3023
+ actualPatchId: patchEquivalence.actualPatchId,
3024
+ error: patchEquivalence.error,
3025
+ });
3026
+ if (!patchEquivalence.equivalent) {
3027
+ return {
3028
+ success: false,
3029
+ code: 'patch_equivalence_failed',
3030
+ convergenceStatus: 'blocked_review',
3031
+ error: 'Refinery patch-equivalence preflight failed; merge/refine was not attempted.',
3032
+ branch,
3033
+ into: baseBranch,
3034
+ validationSummary,
3035
+ patchEquivalence,
3036
+ refineStages,
3037
+ finalBranchConvergenceState: {
3038
+ branch,
3039
+ baseBranch,
3040
+ merged: false,
3041
+ removed: false,
3042
+ validation: 'passed',
3043
+ patchEquivalence: 'failed',
3044
+ status: 'blocked_review',
3045
+ },
3046
+ };
3047
+ }
3048
+
3049
+ const submoduleReachabilityStarted = Date.now();
3050
+ const autoPublishSubmoduleMainCommits = resolveRefineryAutoPublishSubmoduleMainCommits(mesh, node.workspace);
3051
+ const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead, {
3052
+ allowAutoPublishSubmoduleMainCommits: autoPublishSubmoduleMainCommits.enabled,
3053
+ autoPublishPolicySource: autoPublishSubmoduleMainCommits.source,
3054
+ worktreeRoot: node.workspace,
3055
+ });
3056
+ recordMeshRefineStage(refineStages, 'submodule_reachability', submoduleReachability.status, submoduleReachabilityStarted, {
3057
+ checked: submoduleReachability.checked,
3058
+ autoPublishAllowed: submoduleReachability.autoPublishAllowed,
3059
+ autoPublishPolicySource: submoduleReachability.autoPublishPolicySource,
3060
+ autoPublished: submoduleReachability.entries
3061
+ .filter(entry => entry.autoPublishAttempted)
3062
+ .map(entry => ({
3063
+ path: entry.path,
3064
+ commit: entry.commit,
3065
+ remote: entry.remote,
3066
+ remoteUrl: entry.remoteUrl,
3067
+ remoteMainBranch: entry.remoteMainBranch,
3068
+ refspec: entry.autoPublishRefspec,
3069
+ succeeded: entry.autoPublishSucceeded,
3070
+ verified: entry.autoPublishVerified,
3071
+ remoteMainReachable: entry.remoteMainReachable,
3072
+ error: entry.error,
3073
+ })),
3074
+ autoPublishSkipped: submoduleReachability.entries
3075
+ .filter(entry => entry.autoPublishAllowed === true && entry.autoPublishAttempted !== true)
3076
+ .map(entry => ({
3077
+ path: entry.path,
3078
+ commit: entry.commit,
3079
+ remote: entry.remote,
3080
+ remoteUrl: entry.remoteUrl,
3081
+ remoteMainBranch: entry.remoteMainBranch,
3082
+ reason: entry.autoPublishSkippedReason || entry.error || 'auto-publish was allowed but no publish attempt was possible',
3083
+ })),
3084
+ unreachable: submoduleReachability.unreachable.map(entry => ({
3085
+ path: entry.path,
3086
+ commit: entry.commit,
3087
+ publishRequired: entry.publishRequired === true,
3088
+ autoPublishAllowed: entry.autoPublishAllowed,
3089
+ autoPublishAttempted: entry.autoPublishAttempted,
3090
+ autoPublishSucceeded: entry.autoPublishSucceeded,
3091
+ autoPublishVerified: entry.autoPublishVerified,
3092
+ autoPublishRefspec: entry.autoPublishRefspec,
3093
+ autoPublishSkippedReason: entry.autoPublishSkippedReason,
3094
+ remote: entry.remote,
3095
+ remoteUrl: entry.remoteUrl,
3096
+ remoteReachable: entry.remoteReachable,
3097
+ remoteMainBranch: entry.remoteMainBranch,
3098
+ remoteMainReachable: entry.remoteMainReachable,
3099
+ error: entry.error,
3100
+ })),
3101
+ error: submoduleReachability.error,
3102
+ });
3103
+ if (submoduleReachability.status === 'failed') {
3104
+ const nextStep = buildSubmodulePublishRequiredNextStep(submoduleReachability.unreachable);
3105
+ return {
3106
+ success: false,
3107
+ code: 'submodule_reachability_failed',
3108
+ convergenceStatus: 'blocked_review',
3109
+ publishRequired: true,
3110
+ blockedReason: 'submodule_publish_required',
3111
+ error: 'Refinery submodule reachability preflight failed because one or more submodule gitlink commits are not reachable from their configured remote main branch; merge/refine cleanup was not attempted.',
3112
+ nextStep,
3113
+ nextSteps: [
3114
+ 'Ask the user for explicit approval before pushing or publishing any submodule commit.',
3115
+ 'Push/publish each unreachable submodule commit to the configured submodule remote main branch shown in the evidence.',
3116
+ 'Rerun mesh_refine_node after remote reachability is confirmed.',
3117
+ 'Do not merge the root branch until every submodule gitlink commit is reachable from submodule origin/main.',
3118
+ ],
3119
+ unreachableSubmoduleCommits: submoduleReachability.unreachable.map(entry => ({
3120
+ path: entry.path,
3121
+ commit: entry.commit,
3122
+ remote: entry.remote,
3123
+ remoteUrl: entry.remoteUrl,
3124
+ remoteReachable: entry.remoteReachable,
3125
+ remoteMainBranch: entry.remoteMainBranch,
3126
+ remoteMainReachable: entry.remoteMainReachable,
3127
+ autoPublishAllowed: entry.autoPublishAllowed,
3128
+ autoPublishAttempted: entry.autoPublishAttempted,
3129
+ autoPublishSucceeded: entry.autoPublishSucceeded,
3130
+ autoPublishVerified: entry.autoPublishVerified,
3131
+ autoPublishRefspec: entry.autoPublishRefspec,
3132
+ autoPublishSkippedReason: entry.autoPublishSkippedReason,
3133
+ error: entry.error,
3134
+ })),
3135
+ branch,
3136
+ into: baseBranch,
3137
+ validationSummary,
3138
+ patchEquivalence,
3139
+ submoduleReachability,
3140
+ refineStages,
3141
+ finalBranchConvergenceState: {
3142
+ branch,
3143
+ baseBranch,
3144
+ merged: false,
3145
+ removed: false,
3146
+ validation: 'passed',
3147
+ patchEquivalence: 'passed',
3148
+ submoduleReachability: 'failed',
3149
+ status: 'blocked_review',
3150
+ reason: 'submodule_publish_required',
3151
+ nextStep,
3152
+ },
3153
+ };
3154
+ }
3155
+
3156
+ let mergeResult: Record<string, unknown> | undefined;
3157
+ const mergeStarted = Date.now();
3158
+ try {
3159
+ const result = await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
3160
+ mergeResult = {
3161
+ stdout: truncateValidationOutput(result.stdout),
3162
+ stderr: truncateValidationOutput(result.stderr),
3163
+ durationMs: Date.now() - mergeStarted,
3164
+ };
3165
+ recordMeshRefineStage(refineStages, 'merge', 'passed', mergeStarted, mergeResult);
3166
+ } catch (e: any) {
3167
+ recordMeshRefineStage(refineStages, 'merge', 'failed', mergeStarted, {
3168
+ error: e?.message || String(e),
3169
+ stdout: truncateValidationOutput(e?.stdout),
3170
+ stderr: truncateValidationOutput(e?.stderr),
3171
+ });
3172
+ return {
3173
+ success: false,
3174
+ error: `Merge failed (conflicts?): ${e.message}`,
3175
+ validationSummary,
3176
+ patchEquivalence,
3177
+ refineStages,
3178
+ finalBranchConvergenceState: {
3179
+ branch,
3180
+ baseBranch,
3181
+ merged: false,
3182
+ removed: false,
3183
+ validation: 'passed',
3184
+ patchEquivalence: 'passed',
3185
+ status: 'not_mergeable',
3186
+ },
3187
+ };
3188
+ }
3189
+
3190
+ const cleanupStarted = Date.now();
3191
+ const removeResult = await this.execute('remove_mesh_node', {
3192
+ meshId,
3193
+ nodeId,
3194
+ sessionCleanupMode: 'preserve',
3195
+ inlineMesh: args?.inlineMesh,
3196
+ });
3197
+ recordMeshRefineStage(refineStages, 'cleanup', removeResult?.success === false ? 'failed' : 'passed', cleanupStarted, {
3198
+ removed: removeResult?.removed,
3199
+ code: removeResult?.code,
3200
+ error: removeResult?.error,
3201
+ });
3202
+
3203
+ let ledgerError: string | undefined;
3204
+ const ledgerStarted = Date.now();
3205
+ try {
3206
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
3207
+ appendLedgerEntry(meshId, {
3208
+ kind: 'node_removed',
3209
+ nodeId,
3210
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence, submoduleReachability },
3211
+ });
3212
+ recordMeshRefineStage(refineStages, 'ledger', 'passed', ledgerStarted);
3213
+ } catch (e: any) {
3214
+ ledgerError = e?.message || String(e);
3215
+ recordMeshRefineStage(refineStages, 'ledger', 'failed', ledgerStarted, { error: ledgerError });
1433
3216
  }
1434
3217
 
1435
- // 2. Delegate to DaemonCommandHandler
1436
- const handlerResult = await this.deps.commandHandler.handle(cmd, normalizedArgs);
1437
- logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: handlerResult.success, durationMs: Date.now() - cmdStart });
1438
- recordDebugTrace({
1439
- interactionId,
1440
- category: 'command',
1441
- stage: 'completed',
1442
- level: handlerResult.success ? 'info' : 'warn',
1443
- payload: { cmd, source: logSource, success: handlerResult.success, durationMs: Date.now() - cmdStart },
1444
- });
3218
+ const finalBranchConvergenceState = {
3219
+ branch: baseBranch,
3220
+ mergedBranch: branch,
3221
+ baseBranch,
3222
+ merged: true,
3223
+ removed: removeResult?.success !== false,
3224
+ validation: 'passed',
3225
+ patchEquivalence: 'passed',
3226
+ status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
3227
+ };
1445
3228
 
1446
- // 3. Post-chat command callback
1447
- if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
1448
- this.deps.onPostChatCommand();
3229
+ if (removeResult?.success === false) {
3230
+ return {
3231
+ success: false,
3232
+ code: 'cleanup_failed',
3233
+ error: 'Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.',
3234
+ merged: true,
3235
+ branch,
3236
+ into: baseBranch,
3237
+ removeResult,
3238
+ validationSummary,
3239
+ patchEquivalence,
3240
+ submoduleReachability,
3241
+ mergeResult,
3242
+ refineStages,
3243
+ ...(ledgerError ? { ledgerError } : {}),
3244
+ finalBranchConvergenceState,
3245
+ };
1449
3246
  }
1450
3247
 
1451
- return handlerResult;
3248
+ return {
3249
+ success: true,
3250
+ merged: true,
3251
+ branch,
3252
+ into: baseBranch,
3253
+ removeResult,
3254
+ validationSummary,
3255
+ patchEquivalence,
3256
+ submoduleReachability,
3257
+ mergeResult,
3258
+ refineStages,
3259
+ ...(ledgerError ? { ledgerError } : {}),
3260
+ finalBranchConvergenceState,
3261
+ };
1452
3262
  } catch (e: any) {
1453
- logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: false, error: e.message, durationMs: Date.now() - cmdStart });
1454
- recordDebugTrace({
1455
- interactionId,
1456
- category: 'command',
1457
- stage: 'failed',
1458
- level: 'error',
1459
- payload: { cmd, source: logSource, error: e?.message || String(e), durationMs: Date.now() - cmdStart },
1460
- });
1461
- throw e;
3263
+ return { success: false, error: e.message, refineStages };
3264
+ }
3265
+ }
3266
+
3267
+ private async finishMeshRefineJob(handle: MeshRefineJobHandle, args: any): Promise<void> {
3268
+ const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
3269
+ let result: Record<string, unknown>;
3270
+ try {
3271
+ result = await this.executeMeshRefineNodeSynchronously(handle.meshId, handle.targetNodeId, args) as Record<string, unknown>;
3272
+ } catch (e: any) {
3273
+ result = { success: false, error: e?.message || String(e) };
1462
3274
  }
3275
+ const completedAt = new Date().toISOString();
3276
+ const terminalHandle = this.buildRefineJobHandle({
3277
+ meshId: handle.meshId,
3278
+ nodeId: handle.targetNodeId,
3279
+ status: result.success === true ? 'completed' : 'failed',
3280
+ startedAt: handle.startedAt,
3281
+ completedAt,
3282
+ jobId: handle.jobId,
3283
+ interactionId: handle.interactionId,
3284
+ retryOfJobId: handle.retryOfJobId,
3285
+ node: { daemonId: handle.targetDaemonId, workspace: handle.workspace },
3286
+ });
3287
+ const terminal: MeshRefineTerminalJob = { ...terminalHandle, result };
3288
+ this.terminalRefineJobs.set(key, terminal);
3289
+ this.runningRefineJobs.delete(key);
3290
+ this.invalidateAggregateMeshStatus(handle.meshId);
3291
+ await this.appendRefineJobLedger(result.success === true ? 'task_completed' : 'task_failed', terminalHandle, result);
3292
+ this.queueRefineJobEvent(result.success === true ? 'refine:completed' : 'refine:failed', terminalHandle, result);
3293
+ }
3294
+
3295
+ private async startMeshRefineJob(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
3296
+ const key = this.buildRefineJobKey(meshId, nodeId);
3297
+ const running = this.runningRefineJobs.get(key);
3298
+ if (running) return { ...running, duplicate: true };
3299
+ const terminal = this.terminalRefineJobs.get(key);
3300
+
3301
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
3302
+ const mesh = meshRecord?.mesh;
3303
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
3304
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
3305
+ if (!node.isLocalWorktree || !node.workspace) return { success: false, error: `Refinery requires a local worktree node` };
3306
+
3307
+ const handle = this.buildRefineJobHandle({ meshId, nodeId, node, retryOfJobId: terminal?.jobId });
3308
+ this.runningRefineJobs.set(key, handle);
3309
+ await this.appendRefineJobLedger('task_dispatched', handle);
3310
+ this.queueRefineJobEvent('refine:accepted', handle);
3311
+
3312
+ setImmediate(() => {
3313
+ void this.finishMeshRefineJob(handle, args);
3314
+ });
3315
+
3316
+ return handle;
1463
3317
  }
1464
3318
 
1465
3319
  // ─── Daemon-level command core ───────────────────
@@ -1476,7 +3330,8 @@ export class DaemonCommandRouter {
1476
3330
  }
1477
3331
 
1478
3332
  case 'get_pending_mesh_events': {
1479
- const events = drainPendingMeshCoordinatorEvents();
3333
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3334
+ const events = drainPendingMeshCoordinatorEvents(meshId || undefined);
1480
3335
  return { success: true, events };
1481
3336
  }
1482
3337
 
@@ -2081,15 +3936,44 @@ export class DaemonCommandRouter {
2081
3936
  case 'get_mesh': {
2082
3937
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2083
3938
  if (!meshId) return { success: false, error: 'meshId required' };
2084
- try {
2085
- const { getMesh } = await import('../config/mesh-config.js');
2086
- const mesh = getMesh(meshId);
2087
- if (mesh) return { success: true, mesh };
2088
- } catch { /* fall through to inline cache */ }
2089
- // Fallback: check in-memory cache for cloud-originating meshes
2090
- const cached = this.inlineMeshCache.get(meshId);
2091
- if (cached) return { success: true, mesh: cached };
2092
- return { success: false, error: 'Mesh not found' };
3939
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3940
+ if (!meshRecord?.mesh) return { success: false, error: 'Mesh not found' };
3941
+
3942
+ const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
3943
+ const directTruth = await hydrateInlineMeshDirectTruth({
3944
+ mesh: meshRecord.mesh,
3945
+ meshSource: meshRecord.source,
3946
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
3947
+ statusInstanceId: this.deps.statusInstanceId,
3948
+ localMachineId: loadConfig().machineId || '',
3949
+ });
3950
+ const directTruthSatisfied = meshRecord.source !== 'inline_bootstrap' || directTruth.directEvidenceCount > 0;
3951
+ const sourceOfTruth = {
3952
+ membership: meshRecord.source === 'inline_cache'
3953
+ ? 'coordinator_inline_mesh_cache'
3954
+ : meshRecord.source === 'local_config'
3955
+ ? 'local_mesh_config'
3956
+ : 'inline_bootstrap_snapshot',
3957
+ coordinatorOwnsLiveTruth: directTruthSatisfied,
3958
+ directPeerTruth: {
3959
+ required: requireDirectPeerTruth,
3960
+ satisfied: directTruthSatisfied,
3961
+ directEvidenceCount: directTruth.directEvidenceCount,
3962
+ localConfirmedCount: directTruth.localConfirmedCount,
3963
+ peerAttemptedCount: directTruth.peerAttemptedCount,
3964
+ peerConfirmedCount: directTruth.peerConfirmedCount,
3965
+ unavailableNodeIds: directTruth.unavailableNodeIds,
3966
+ },
3967
+ };
3968
+ if (requireDirectPeerTruth && !directTruthSatisfied) {
3969
+ return {
3970
+ success: false,
3971
+ code: 'mesh_direct_peer_truth_unavailable',
3972
+ error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct get_mesh probes succeed.',
3973
+ sourceOfTruth,
3974
+ };
3975
+ }
3976
+ return { success: true, mesh: meshRecord.mesh, sourceOfTruth };
2093
3977
  }
2094
3978
 
2095
3979
  case 'create_mesh': {
@@ -2100,7 +3984,10 @@ export class DaemonCommandRouter {
2100
3984
  if (!name) return { success: false, error: 'name required' };
2101
3985
  try {
2102
3986
  const { createMesh } = await import('../config/mesh-config.js');
2103
- const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy });
3987
+ const meshHost = args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)
3988
+ ? args.meshHost
3989
+ : undefined;
3990
+ const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy, meshHost });
2104
3991
  return { success: true, mesh };
2105
3992
  } catch (e: any) {
2106
3993
  return { success: false, error: e.message };
@@ -2117,16 +4004,237 @@ export class DaemonCommandRouter {
2117
4004
  if (typeof args?.defaultBranch === 'string') patch.defaultBranch = args.defaultBranch;
2118
4005
  if (args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)) patch.policy = args.policy;
2119
4006
  if (args?.coordinator && typeof args.coordinator === 'object' && !Array.isArray(args.coordinator)) patch.coordinator = args.coordinator;
4007
+ if (args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)) patch.meshHost = args.meshHost;
2120
4008
  if (!Object.keys(patch).length) return { success: false, error: 'No updates provided' };
2121
4009
  const mesh = updateMesh(meshId, patch as any);
2122
4010
  if (!mesh) return { success: false, error: 'Mesh not found' };
2123
4011
  this.inlineMeshCache.set(meshId, mesh);
4012
+ this.invalidateAggregateMeshStatus(meshId);
2124
4013
  return { success: true, mesh };
2125
4014
  } catch (e: any) {
2126
4015
  return { success: false, error: e.message };
2127
4016
  }
2128
4017
  }
2129
4018
 
4019
+ case 'get_mesh_host_pairing': {
4020
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4021
+ if (!meshId) return { success: false, error: 'meshId required' };
4022
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
4023
+ const mesh = meshRecord?.mesh;
4024
+ if (!mesh) return { success: false, error: 'Mesh not found' };
4025
+ const meshHost = resolveMeshHostStatus(mesh);
4026
+ const pairingStatus = meshHost.pairing?.status || 'not_configured';
4027
+ return {
4028
+ success: true,
4029
+ code: pairingStatus === 'not_configured' ? 'mesh_host_pairing_not_configured' : 'mesh_host_pairing_pending',
4030
+ meshId,
4031
+ hostAddress: meshHost.hostAddress,
4032
+ meshHost,
4033
+ manualPairing: {
4034
+ status: pairingStatus,
4035
+ joinImplemented: true,
4036
+ protocol: 'standalone_command_direct_v1',
4037
+ 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.',
4038
+ },
4039
+ };
4040
+ }
4041
+
4042
+ case 'configure_mesh_host_pairing': {
4043
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4044
+ const hostAddress = typeof args?.hostAddress === 'string' ? args.hostAddress.trim() : '';
4045
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
4046
+ if (!meshId) return { success: false, error: 'meshId required' };
4047
+ if (!hostAddress || !token) return { success: false, error: 'hostAddress and token required' };
4048
+ try {
4049
+ const { configureMeshHostPairing } = await import('../config/mesh-config.js');
4050
+ const configured = configureMeshHostPairing(meshId, { hostAddress, token });
4051
+ if (!configured) return { success: false, error: 'Mesh not found' };
4052
+ this.inlineMeshCache.set(meshId, configured.mesh);
4053
+ const meshHost = resolveMeshHostStatus(configured.mesh);
4054
+ return {
4055
+ success: true,
4056
+ code: 'mesh_host_pairing_pending',
4057
+ meshId,
4058
+ hostAddress: configured.hostAddress,
4059
+ meshHost,
4060
+ manualPairing: {
4061
+ status: meshHost.pairing?.status || 'pairing',
4062
+ joinImplemented: true,
4063
+ protocol: 'standalone_command_direct_v1',
4064
+ 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.',
4065
+ },
4066
+ };
4067
+ } catch (e: any) {
4068
+ return { success: false, code: 'mesh_host_pairing_invalid', meshId, hostAddress, error: e.message };
4069
+ }
4070
+ }
4071
+
4072
+ case 'create_mesh_host_pairing_token': {
4073
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4074
+ if (!meshId) return { success: false, error: 'meshId required' };
4075
+ try {
4076
+ const { createMeshHostPairingToken } = await import('../config/mesh-config.js');
4077
+ const created = createMeshHostPairingToken(meshId, {
4078
+ token: typeof args?.token === 'string' ? args.token : undefined,
4079
+ expiresAt: typeof args?.expiresAt === 'string' ? args.expiresAt : undefined,
4080
+ });
4081
+ if (!created) return { success: false, error: 'Mesh not found' };
4082
+ this.inlineMeshCache.set(meshId, created.mesh);
4083
+ this.invalidateAggregateMeshStatus(meshId);
4084
+ return {
4085
+ success: true,
4086
+ code: 'mesh_host_pairing_token_created',
4087
+ meshId,
4088
+ token: created.token,
4089
+ tokenId: created.tokenId,
4090
+ expiresAt: created.expiresAt,
4091
+ meshHost: resolveMeshHostStatus(created.mesh),
4092
+ warning: 'Raw token is returned once and is not persisted; share it with member daemons over a trusted channel.',
4093
+ };
4094
+ } catch (e: any) {
4095
+ return { success: false, code: 'mesh_host_pairing_token_invalid', meshId, error: e.message };
4096
+ }
4097
+ }
4098
+
4099
+ case 'apply_mesh_host_join': {
4100
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4101
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
4102
+ const memberNode = args?.memberNode && typeof args.memberNode === 'object' && !Array.isArray(args.memberNode)
4103
+ ? args.memberNode
4104
+ : null;
4105
+ if (!meshId) return { success: false, error: 'meshId required' };
4106
+ if (!token || !memberNode) return { success: false, error: 'token and memberNode required' };
4107
+ try {
4108
+ const { applyMeshHostJoinRequest } = await import('../config/mesh-config.js');
4109
+ const applied = applyMeshHostJoinRequest(meshId, {
4110
+ token,
4111
+ memberNode: memberNode as any,
4112
+ memberMeshId: typeof args?.memberMeshId === 'string' ? args.memberMeshId : undefined,
4113
+ });
4114
+ if (!applied) return { success: false, error: 'Mesh not found' };
4115
+ if (!applied.accepted) {
4116
+ return {
4117
+ success: false,
4118
+ code: 'mesh_host_join_rejected',
4119
+ meshId,
4120
+ tokenId: applied.tokenId,
4121
+ meshHost: applied.meshHost ? resolveMeshHostStatus({ meshHost: applied.meshHost }) : undefined,
4122
+ error: applied.reason,
4123
+ };
4124
+ }
4125
+ this.inlineMeshCache.set(meshId, applied.mesh);
4126
+ this.invalidateAggregateMeshStatus(meshId);
4127
+ try {
4128
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
4129
+ appendLedgerEntry(meshId, {
4130
+ kind: 'node_joined',
4131
+ nodeId: applied.node.id,
4132
+ payload: { role: 'member', tokenId: applied.tokenId, workspace: applied.node.workspace },
4133
+ });
4134
+ } catch { /* ledger append is best-effort */ }
4135
+ return {
4136
+ success: true,
4137
+ code: 'mesh_host_join_accepted',
4138
+ meshId,
4139
+ node: applied.node,
4140
+ tokenId: applied.tokenId,
4141
+ meshHost: resolveMeshHostStatus(applied.mesh),
4142
+ };
4143
+ } catch (e: any) {
4144
+ return { success: false, code: 'mesh_host_join_failed', meshId, error: e.message };
4145
+ }
4146
+ }
4147
+
4148
+ case 'join_mesh_host_pairing': {
4149
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4150
+ const token = typeof args?.token === 'string' ? args.token.trim() : '';
4151
+ if (!meshId) return { success: false, error: 'meshId required' };
4152
+ if (!token) return { success: false, error: 'token required because raw pairing tokens are not persisted' };
4153
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
4154
+ const mesh = meshRecord?.mesh;
4155
+ if (!mesh) return { success: false, error: 'Mesh not found' };
4156
+ const meshHost = resolveMeshHostStatus(mesh);
4157
+ if (meshHost.role !== 'member') {
4158
+ 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.' };
4159
+ }
4160
+ try {
4161
+ const { tokenIdForManualPairing, markMeshHostPairingJoined } = await import('../config/mesh-config.js');
4162
+ const tokenId = tokenIdForManualPairing(token);
4163
+ if (meshHost.pairing?.tokenId && meshHost.pairing.tokenId !== tokenId) {
4164
+ return { success: false, code: 'mesh_host_join_rejected', meshId, tokenId, meshHost, error: 'invalid pairing token' };
4165
+ }
4166
+ const memberNode = buildMemberJoinNode(mesh, args, this.deps.statusInstanceId);
4167
+ if (!memberNode) return { success: false, error: 'member node metadata unavailable' };
4168
+ const hostMeshId = typeof args?.hostMeshId === 'string' && args.hostMeshId.trim() ? args.hostMeshId.trim() : meshId;
4169
+ const hostDaemonId = typeof args?.hostDaemonId === 'string' && args.hostDaemonId.trim()
4170
+ ? args.hostDaemonId.trim()
4171
+ : meshHost.hostDaemonId;
4172
+ let hostResult: any;
4173
+ let transport: string;
4174
+ if (hostDaemonId && this.deps.dispatchMeshCommand) {
4175
+ transport = 'mesh_command_dispatch';
4176
+ hostResult = await this.deps.dispatchMeshCommand(hostDaemonId, 'apply_mesh_host_join', {
4177
+ meshId: hostMeshId,
4178
+ token,
4179
+ memberMeshId: meshId,
4180
+ memberNode,
4181
+ });
4182
+ } else if (meshHost.hostAddress) {
4183
+ transport = 'standalone_http_command';
4184
+ const commandUrl = normalizeStandaloneHostCommandUrl(meshHost.hostAddress);
4185
+ const response = await fetch(commandUrl, {
4186
+ method: 'POST',
4187
+ headers: { 'Content-Type': 'application/json' },
4188
+ body: JSON.stringify({ type: 'apply_mesh_host_join', payload: { meshId: hostMeshId, token, memberMeshId: meshId, memberNode } }),
4189
+ });
4190
+ hostResult = await response.json().catch(() => ({ success: false, error: `Host returned HTTP ${response.status}` }));
4191
+ if (!response.ok && hostResult?.success !== false) hostResult = { success: false, error: `Host returned HTTP ${response.status}` };
4192
+ } else {
4193
+ return {
4194
+ success: false,
4195
+ code: 'mesh_host_join_transport_unavailable',
4196
+ meshId,
4197
+ meshHost,
4198
+ error: 'No hostDaemonId dispatch path or hostAddress HTTP command path is available. P2P signaling join is not implemented in this slice.',
4199
+ };
4200
+ }
4201
+ if (!hostResult?.success) {
4202
+ return { success: false, code: hostResult?.code || 'mesh_host_join_rejected', meshId, meshHost, transport, error: hostResult?.error || 'Mesh Host rejected join request', hostResult };
4203
+ }
4204
+ const joined = meshRecord.inline
4205
+ ? null
4206
+ : markMeshHostPairingJoined(meshId, {
4207
+ tokenId: hostResult.tokenId || tokenId,
4208
+ hostDaemonId: hostResult.meshHost?.hostDaemonId || hostDaemonId,
4209
+ hostNodeId: hostResult.meshHost?.hostNodeId,
4210
+ joinedAt: hostResult.meshHost?.pairing?.joinedAt,
4211
+ });
4212
+ if (joined) {
4213
+ this.inlineMeshCache.set(meshId, joined.mesh);
4214
+ this.invalidateAggregateMeshStatus(meshId);
4215
+ }
4216
+ return {
4217
+ success: true,
4218
+ code: 'mesh_host_join_applied',
4219
+ meshId,
4220
+ hostMeshId,
4221
+ transport,
4222
+ node: hostResult.node,
4223
+ tokenId: hostResult.tokenId || tokenId,
4224
+ meshHost: joined ? resolveMeshHostStatus(joined.mesh) : { ...meshHost, pairing: { ...(meshHost.pairing || {}), status: 'paired', tokenId: hostResult.tokenId || tokenId } },
4225
+ hostResult,
4226
+ manualPairing: {
4227
+ status: 'paired',
4228
+ joinImplemented: true,
4229
+ protocol: 'standalone_command_direct_v1',
4230
+ description: 'Mesh Host accepted the join and local member pairing status was marked paired. P2P runtime signaling remains outside this slice.',
4231
+ },
4232
+ };
4233
+ } catch (e: any) {
4234
+ return { success: false, code: 'mesh_host_join_failed', meshId, meshHost, error: e.message };
4235
+ }
4236
+ }
4237
+
2130
4238
  case 'delete_mesh': {
2131
4239
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2132
4240
  if (!meshId) return { success: false, error: 'meshId required' };
@@ -2220,6 +4328,8 @@ export class DaemonCommandRouter {
2220
4328
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2221
4329
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
2222
4330
  if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
4331
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue cancellation');
4332
+ if (ownerFailure) return ownerFailure;
2223
4333
  try {
2224
4334
  const { cancelTask } = await import('../mesh/mesh-work-queue.js');
2225
4335
  const reason = typeof args?.reason === 'string' ? args.reason : undefined;
@@ -2235,6 +4345,8 @@ export class DaemonCommandRouter {
2235
4345
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2236
4346
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
2237
4347
  if (!meshId || !taskId) return { success: false, error: 'meshId and taskId required' };
4348
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue requeue');
4349
+ if (ownerFailure) return ownerFailure;
2238
4350
  try {
2239
4351
  const { requeueTask } = await import('../mesh/mesh-work-queue.js');
2240
4352
  const task = requeueTask(meshId, taskId, {
@@ -2256,6 +4368,8 @@ export class DaemonCommandRouter {
2256
4368
  const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
2257
4369
  if (!meshId) return { success: false, error: 'meshId required' };
2258
4370
  if (!workspace) return { success: false, error: 'workspace required' };
4371
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node addition');
4372
+ if (ownerFailure) return ownerFailure;
2259
4373
  try {
2260
4374
  const { addNode } = await import('../config/mesh-config.js');
2261
4375
  const providerPriority = Array.isArray(args?.providerPriority)
@@ -2266,7 +4380,18 @@ export class DaemonCommandRouter {
2266
4380
  ...(readOnly ? { readOnly: true } : {}),
2267
4381
  ...(providerPriority.length ? { providerPriority } : {}),
2268
4382
  };
2269
- const node = addNode(meshId, { workspace, ...(policy ? { policy } : {}) });
4383
+ const role = normalizeMeshDaemonRole(args?.role);
4384
+ const daemonId = typeof args?.daemonId === 'string' && args.daemonId.trim() ? args.daemonId.trim() : undefined;
4385
+ const machineId = typeof args?.machineId === 'string' && args.machineId.trim() ? args.machineId.trim() : undefined;
4386
+ const repoRoot = typeof args?.repoRoot === 'string' && args.repoRoot.trim() ? args.repoRoot.trim() : undefined;
4387
+ const node = addNode(meshId, {
4388
+ workspace,
4389
+ ...(repoRoot ? { repoRoot } : {}),
4390
+ ...(daemonId ? { daemonId } : {}),
4391
+ ...(machineId ? { machineId } : {}),
4392
+ ...(policy ? { policy } : {}),
4393
+ ...(role ? { role } : {}),
4394
+ });
2270
4395
  if (!node) return { success: false, error: 'Mesh not found' };
2271
4396
  return { success: true, node };
2272
4397
  } catch (e: any) {
@@ -2278,6 +4403,8 @@ export class DaemonCommandRouter {
2278
4403
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2279
4404
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2280
4405
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
4406
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node update');
4407
+ if (ownerFailure) return ownerFailure;
2281
4408
  try {
2282
4409
  const { updateNode } = await import('../config/mesh-config.js');
2283
4410
  const policy = args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)
@@ -2306,6 +4433,8 @@ export class DaemonCommandRouter {
2306
4433
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2307
4434
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2308
4435
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
4436
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node removal');
4437
+ if (ownerFailure) return ownerFailure;
2309
4438
  try {
2310
4439
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2311
4440
  const mesh = meshRecord?.mesh;
@@ -2331,131 +4460,91 @@ export class DaemonCommandRouter {
2331
4460
  }
2332
4461
  }
2333
4462
 
2334
- case 'refine_mesh_node': {
4463
+ case 'get_mesh_refine_config_schema': {
4464
+ return {
4465
+ success: true,
4466
+ schema: MESH_REFINE_CONFIG_SCHEMA,
4467
+ locations: MESH_REFINE_CONFIG_LOCATIONS,
4468
+ sourceOfTruth: 'repo mesh/refine config',
4469
+ heuristicRole: 'suggestions_only_not_execution_path',
4470
+ };
4471
+ }
4472
+
4473
+ case 'validate_mesh_refine_config': {
4474
+ const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
4475
+ const mesh = args?.inlineMesh || {};
4476
+ const loaded = args?.config !== undefined
4477
+ ? { config: args.config, source: 'inline', sourceType: 'mesh_policy' as const }
4478
+ : loadMeshRefineConfig(mesh, workspace);
4479
+ const validation = loaded.config
4480
+ ? validateMeshRefineConfig(loaded.config, loaded.source)
4481
+ : { valid: false, errors: [((loaded as { error?: string }).error) || 'repo mesh/refine config unavailable'], commands: [], rejectedCommands: [] };
4482
+ return { success: validation.valid, ...loaded, ...validation };
4483
+ }
4484
+
4485
+ case 'suggest_mesh_refine_config': {
4486
+ const workspace = typeof args?.workspace === 'string' ? args.workspace : process.cwd();
4487
+ const mesh = args?.inlineMesh || {};
4488
+ return {
4489
+ success: true,
4490
+ ...suggestMeshRefineConfig(mesh, workspace),
4491
+ note: 'Suggestions are heuristic scaffold only; Refinery will not execute them until saved into repo mesh/refine config.',
4492
+ };
4493
+ }
4494
+
4495
+ case 'plan_mesh_refine_node': {
2335
4496
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2336
4497
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2337
4498
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
2338
- try {
4499
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
4500
+ const mesh = meshRecord?.mesh;
4501
+ const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
4502
+ if (!node?.workspace) return { success: false, error: `Node '${nodeId}' workspace not found` };
4503
+ return {
4504
+ success: true,
4505
+ dryRun: true,
4506
+ nodeId,
4507
+ workspace: node.workspace,
4508
+ validationPlan: buildMeshRefineValidationPlan(mesh, node.workspace),
4509
+ mergeWillRun: false,
4510
+ cleanupWillRun: false,
4511
+ };
4512
+ }
4513
+
4514
+ case 'fast_forward_mesh_node': {
4515
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4516
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
4517
+ let workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
4518
+ let submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths)
4519
+ ? args.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string')
4520
+ : undefined;
4521
+ if (!workspace && meshId && nodeId) {
2339
4522
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2340
4523
  const mesh = meshRecord?.mesh;
2341
4524
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2342
- if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2343
-
2344
- if (!node.isLocalWorktree || !node.workspace) {
2345
- return { success: false, error: `Refinery requires a local worktree node` };
2346
- }
2347
-
2348
- const sourceNode = node.clonedFromNodeId
2349
- ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2350
- : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2351
- const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2352
- if (!repoRoot) return { success: false, error: 'Source node repoRoot not found' };
2353
-
2354
- const { execFile } = await import('node:child_process');
2355
- const { promisify } = await import('node:util');
2356
- const execFileAsync = promisify(execFile);
2357
-
2358
- const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2359
- const branch = branchStdout.trim();
2360
- if (!branch) return { success: false, error: 'Could not determine branch of the worktree node' };
2361
-
2362
- const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2363
- const baseBranch = baseBranchStdout.trim();
2364
-
2365
- const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2366
- if (validationSummary.status === 'failed') {
2367
- return {
2368
- success: false,
2369
- code: 'validation_failed',
2370
- convergenceStatus: 'blocked_review',
2371
- error: 'Refinery validation gate failed; merge/refine was not attempted.',
2372
- branch,
2373
- into: baseBranch,
2374
- validationSummary,
2375
- finalBranchConvergenceState: {
2376
- branch,
2377
- baseBranch,
2378
- merged: false,
2379
- removed: false,
2380
- validation: 'failed',
2381
- status: 'blocked_review',
2382
- },
2383
- };
2384
- }
2385
- if (validationSummary.status === 'skipped') {
2386
- return {
2387
- success: false,
2388
- code: 'validation_unavailable',
2389
- convergenceStatus: 'blocked_review',
2390
- error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
2391
- branch,
2392
- into: baseBranch,
2393
- validationSummary,
2394
- finalBranchConvergenceState: {
2395
- branch,
2396
- baseBranch,
2397
- merged: false,
2398
- removed: false,
2399
- validation: 'unavailable',
2400
- status: 'blocked_review',
2401
- },
2402
- };
2403
- }
2404
-
2405
- try {
2406
- await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2407
- } catch (e: any) {
2408
- return {
2409
- success: false,
2410
- error: `Merge failed (conflicts?): ${e.message}`,
2411
- validationSummary,
2412
- finalBranchConvergenceState: {
2413
- branch,
2414
- baseBranch,
2415
- merged: false,
2416
- removed: false,
2417
- validation: 'passed',
2418
- status: 'not_mergeable',
2419
- },
2420
- };
4525
+ workspace = typeof node?.workspace === 'string' ? node.workspace.trim() : '';
4526
+ if (!submoduleIgnorePaths && Array.isArray(node?.policy?.submoduleIgnorePaths)) {
4527
+ submoduleIgnorePaths = node.policy.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string');
2421
4528
  }
2422
-
2423
- const removeResult = await this.execute('remove_mesh_node', {
2424
- meshId,
2425
- nodeId,
2426
- sessionCleanupMode: 'kill',
2427
- inlineMesh: args?.inlineMesh,
2428
- });
2429
-
2430
- try {
2431
- const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2432
- appendLedgerEntry(meshId, {
2433
- kind: 'node_removed',
2434
- nodeId,
2435
- payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary },
2436
- });
2437
- } catch {}
2438
-
2439
- return {
2440
- success: true,
2441
- merged: true,
2442
- branch,
2443
- into: baseBranch,
2444
- removeResult,
2445
- validationSummary,
2446
- finalBranchConvergenceState: {
2447
- branch: baseBranch,
2448
- mergedBranch: branch,
2449
- baseBranch,
2450
- merged: true,
2451
- removed: removeResult?.success !== false,
2452
- validation: 'passed',
2453
- status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
2454
- },
2455
- };
2456
- } catch (e: any) {
2457
- return { success: false, error: e.message };
2458
4529
  }
4530
+ const result = await (fastForwardMeshNode({
4531
+ meshId: meshId || undefined,
4532
+ nodeId: nodeId || undefined,
4533
+ workspace,
4534
+ branch: typeof args?.branch === 'string' ? args.branch : undefined,
4535
+ execute: args?.execute === true,
4536
+ dryRun: args?.dryRun === true,
4537
+ updateSubmodules: args?.updateSubmodules === true,
4538
+ submoduleIgnorePaths,
4539
+ }) as Promise<unknown>);
4540
+ return result as CommandRouterResult;
4541
+ }
4542
+
4543
+ case 'refine_mesh_node': {
4544
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
4545
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
4546
+ if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
4547
+ return this.startMeshRefineJob(meshId, nodeId, args);
2459
4548
  }
2460
4549
 
2461
4550
  case 'remove_mesh_node': {
@@ -2499,6 +4588,7 @@ export class DaemonCommandRouter {
2499
4588
  } else {
2500
4589
  const { removeNode } = await import('../config/mesh-config.js');
2501
4590
  removed = removeNode(meshId, nodeId);
4591
+ if (removed) this.invalidateAggregateMeshStatus(meshId);
2502
4592
  }
2503
4593
 
2504
4594
  // Record in task ledger
@@ -2536,6 +4626,8 @@ export class DaemonCommandRouter {
2536
4626
  if (!meshId) return { success: false, error: 'meshId required' };
2537
4627
  if (!sourceNodeId) return { success: false, error: 'sourceNodeId required' };
2538
4628
  if (!branch) return { success: false, error: 'branch required' };
4629
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'worktree clone');
4630
+ if (ownerFailure) return ownerFailure;
2539
4631
 
2540
4632
  try {
2541
4633
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
@@ -2584,6 +4676,7 @@ export class DaemonCommandRouter {
2584
4676
  policy: { ...(sourceNode.policy || {}) },
2585
4677
  });
2586
4678
  if (!node) return { success: false, error: 'Failed to register worktree node' };
4679
+ this.invalidateAggregateMeshStatus(meshId);
2587
4680
  }
2588
4681
 
2589
4682
  // Initialize submodules if policy allows (default: true)
@@ -2625,6 +4718,8 @@ export class DaemonCommandRouter {
2625
4718
  case 'trigger_mesh_queue': {
2626
4719
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2627
4720
  if (!meshId) return { success: false, error: 'meshId required' };
4721
+ const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue trigger');
4722
+ if (ownerFailure) return ownerFailure;
2628
4723
  try {
2629
4724
  const { triggerMeshQueue } = await import('../mesh/mesh-events.js');
2630
4725
  if (meshId) {
@@ -2656,6 +4751,15 @@ export class DaemonCommandRouter {
2656
4751
  mesh = getMesh(meshId);
2657
4752
  }
2658
4753
  if (!mesh) return { success: false, error: 'Mesh not found' };
4754
+ const meshHost = resolveMeshHostStatus(mesh);
4755
+ if (!meshHost.canOwnCoordinator) {
4756
+ return {
4757
+ success: false,
4758
+ ...buildMeshHostRequiredFailure(mesh, 'coordinator launch'),
4759
+ meshId,
4760
+ cliType,
4761
+ };
4762
+ }
2659
4763
  if (!Array.isArray(mesh.nodes) || mesh.nodes.length === 0) return { success: false, error: 'No nodes in mesh' };
2660
4764
 
2661
4765
  const requestedCoordinatorNodeId = typeof args?.coordinatorNodeId === 'string'
@@ -2675,7 +4779,16 @@ export class DaemonCommandRouter {
2675
4779
  cliType,
2676
4780
  };
2677
4781
  }
2678
- const workspace = typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '';
4782
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions
4783
+ ? await this.deps.sessionHostControl.listSessions().catch(() => [])
4784
+ : [];
4785
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
4786
+ const workspace = readLiveMeshNodeWorkspace({
4787
+ meshId,
4788
+ nodeId: String(coordinatorNode.id || coordinatorNode.nodeId || preferredCoordinatorNodeId || ''),
4789
+ liveSessionRecords: liveMeshSessions,
4790
+ allowCoordinatorSession: true,
4791
+ }) || (typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '');
2679
4792
  if (!workspace) return { success: false, error: 'Coordinator node workspace required', meshId, cliType };
2680
4793
  if (!cliType) {
2681
4794
  const resolved = await resolveProviderTypeFromPriority({
@@ -3020,6 +5133,30 @@ export class DaemonCommandRouter {
3020
5133
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3021
5134
  const mesh = meshRecord?.mesh;
3022
5135
  if (!mesh) return { success: false, error: 'Mesh not found' };
5136
+ const meshHost = resolveMeshHostStatus(mesh);
5137
+
5138
+ const refreshRequested = args?.refresh === true || args?.forceRefresh === true;
5139
+ const pendingCoordinatorEventCount = getPendingMeshCoordinatorEvents(meshId).length;
5140
+ const hadAggregateCache = this.aggregateMeshStatusCache.has(meshId);
5141
+ if (!refreshRequested && pendingCoordinatorEventCount === 0) {
5142
+ const cachedStatus = this.getCachedAggregateMeshStatus(meshId, mesh, { requireDirectPeerTruth: args?.requireDirectPeerTruth === true });
5143
+ if (cachedStatus) {
5144
+ logRepoMeshStatusDebug('return_cached', {
5145
+ meshId,
5146
+ command: 'mesh_status',
5147
+ refreshRequested,
5148
+ summary: summarizeRepoMeshStatusDebug(cachedStatus),
5149
+ });
5150
+ return cachedStatus;
5151
+ }
5152
+ }
5153
+ const refreshReason = refreshRequested
5154
+ ? 'explicit_refresh'
5155
+ : pendingCoordinatorEventCount > 0
5156
+ ? 'pending_coordinator_events'
5157
+ : hadAggregateCache
5158
+ ? 'stale_pending_cache_refresh'
5159
+ : 'cold_cache_miss';
3023
5160
 
3024
5161
  const { getMeshQueueStats, getQueue } = await import('../mesh/mesh-work-queue.js');
3025
5162
  const queue = getQueue(meshId);
@@ -3034,8 +5171,81 @@ export class DaemonCommandRouter {
3034
5171
  const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
3035
5172
 
3036
5173
  const localMachineId = loadConfig().machineId || '';
5174
+ const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
5175
+ const directTruth = requireDirectPeerTruth
5176
+ ? await hydrateInlineMeshDirectTruth({
5177
+ mesh,
5178
+ meshSource: meshRecord.source,
5179
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
5180
+ statusInstanceId: this.deps.statusInstanceId,
5181
+ localMachineId,
5182
+ })
5183
+ : {
5184
+ directEvidenceCount: 0,
5185
+ localConfirmedCount: 0,
5186
+ peerAttemptedCount: 0,
5187
+ peerConfirmedCount: 0,
5188
+ unavailableNodeIds: [] as string[],
5189
+ };
5190
+ // Default/cached loads may not attempt a remote peer probe yet; do not surface that as
5191
+ // a direct mesh truth failure until an explicit probe attempt actually fails.
5192
+ const passivePeerTruthNotAttempted = requireDirectPeerTruth
5193
+ && !refreshRequested
5194
+ && directTruth.directEvidenceCount > 0
5195
+ && directTruth.peerAttemptedCount === 0;
5196
+ const effectiveDirectTruth = passivePeerTruthNotAttempted
5197
+ ? { ...directTruth, unavailableNodeIds: [] as string[] }
5198
+ : directTruth;
5199
+ const unavailableDirectTruthNodeIds = new Set(effectiveDirectTruth.unavailableNodeIds);
5200
+ const unavailableNodesAreOnlyRemovedWorktrees = unavailableDirectTruthNodeIds.size > 0
5201
+ && Array.isArray(mesh.nodes)
5202
+ && mesh.nodes
5203
+ .filter((node: any) => unavailableDirectTruthNodeIds.has(String(node.id || node.nodeId || '')))
5204
+ .every((node: any) => node?.isLocalWorktree === true);
5205
+ const directTruthSatisfied = !requireDirectPeerTruth
5206
+ || (effectiveDirectTruth.directEvidenceCount > 0 && (effectiveDirectTruth.unavailableNodeIds.length === 0 || unavailableNodesAreOnlyRemovedWorktrees));
5207
+ if (requireDirectPeerTruth && !directTruthSatisfied) {
5208
+ const failureResult = {
5209
+ success: false,
5210
+ code: 'mesh_direct_peer_truth_unavailable',
5211
+ error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct mesh_status probes succeed.',
5212
+ sourceOfTruth: {
5213
+ membership: meshRecord.source === 'inline_cache'
5214
+ ? 'coordinator_inline_mesh_cache'
5215
+ : meshRecord.source === 'local_config'
5216
+ ? 'local_mesh_config'
5217
+ : 'inline_bootstrap_snapshot',
5218
+ coordinatorOwnsLiveTruth: false,
5219
+ currentStatus: 'direct_peer_truth_unavailable',
5220
+ directPeerTruth: {
5221
+ required: true,
5222
+ satisfied: false,
5223
+ directEvidenceCount: directTruth.directEvidenceCount,
5224
+ localConfirmedCount: directTruth.localConfirmedCount,
5225
+ peerAttemptedCount: directTruth.peerAttemptedCount,
5226
+ peerConfirmedCount: directTruth.peerConfirmedCount,
5227
+ unavailableNodeIds: directTruth.unavailableNodeIds,
5228
+ },
5229
+ },
5230
+ };
5231
+ logRepoMeshStatusDebug('direct_truth_unavailable', {
5232
+ meshId,
5233
+ command: 'mesh_status',
5234
+ refreshRequested,
5235
+ meshSource: meshRecord.source,
5236
+ directTruth,
5237
+ });
5238
+ return failureResult;
5239
+ }
5240
+ const directTruthUnavailableNodeIds = new Set(effectiveDirectTruth.unavailableNodeIds);
5241
+ const coordinatorHostname = osHostname();
5242
+ const selectedCoordinatorNodeId = readStringValue(
5243
+ mesh.coordinator?.preferredNodeId,
5244
+ (mesh.nodes?.[0] as any)?.id,
5245
+ (mesh.nodes?.[0] as any)?.nodeId,
5246
+ );
3037
5247
  const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes)
3038
- ? readStringValue((mesh.nodes[0] as any)?.id, (mesh.nodes[0] as any)?.nodeId)
5248
+ ? selectedCoordinatorNodeId
3039
5249
  : undefined;
3040
5250
  const refreshedAt = new Date().toISOString();
3041
5251
  const nodeStatuses = [];
@@ -3048,15 +5258,26 @@ export class DaemonCommandRouter {
3048
5258
  ) || Boolean(
3049
5259
  daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId),
3050
5260
  ) || Boolean(meshRecord?.inline && nodeIndex === 0);
5261
+ const machineIdentity = buildMeshNodeMachineIdentity(node as Record<string, unknown>, {
5262
+ localMachineId,
5263
+ localDaemonId: this.deps.statusInstanceId,
5264
+ coordinatorHostname,
5265
+ isSelfNode,
5266
+ });
3051
5267
  const status: Record<string, unknown> = {
3052
5268
  nodeId,
3053
- machineLabel: node.machineLabel || node.id || node.nodeId,
5269
+ machineLabel: buildMeshNodeDisplayLabel(node as Record<string, unknown>, nodeId, providerPriority),
5270
+ labelSource: readStringValue(node.machineLabel, node.machine_label, node.machineNickname, node.machine_nickname, node.alias)
5271
+ ? 'explicit_metadata'
5272
+ : 'workspace_host_provider_context',
3054
5273
  workspace: node.workspace,
3055
5274
  repoRoot: node.repoRoot,
3056
5275
  isLocalWorktree: node.isLocalWorktree,
3057
5276
  worktreeBranch: node.worktreeBranch,
5277
+ role: normalizeMeshDaemonRole(node.role) || (meshHost.hostNodeId && nodeId === meshHost.hostNodeId ? 'host' : undefined),
3058
5278
  daemonId,
3059
- machineId: node.machineId,
5279
+ machineId: readMeshNodeMachineId(node as Record<string, unknown>) || node.machineId,
5280
+ machine: machineIdentity,
3060
5281
  machineStatus: node.machineStatus,
3061
5282
  health: 'unknown',
3062
5283
  providers: node.providers || [],
@@ -3095,8 +5316,20 @@ export class DaemonCommandRouter {
3095
5316
  reason: 'Node has no daemon id, so mesh transport cannot be reported from the selected coordinator.',
3096
5317
  };
3097
5318
  }
3098
- const matchedLiveSessionRecords = liveMeshSessions
3099
- .filter((record) => this.sessionMatchesMeshNode(record, node, nodeId));
5319
+ const matchedLiveSessionRecords = collectLiveMeshSessionRecords({
5320
+ meshId,
5321
+ node,
5322
+ nodeId,
5323
+ liveSessionRecords: liveMeshSessions,
5324
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
5325
+ });
5326
+ const workspace = readLiveMeshNodeWorkspace({
5327
+ meshId,
5328
+ nodeId,
5329
+ liveSessionRecords: matchedLiveSessionRecords,
5330
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
5331
+ }) || (typeof node.workspace === 'string' ? node.workspace : '');
5332
+ status.workspace = workspace || node.workspace;
3100
5333
  if (matchedLiveSessionRecords.length > 0) {
3101
5334
  const sessionIds = matchedLiveSessionRecords
3102
5335
  .map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
@@ -3110,44 +5343,194 @@ export class DaemonCommandRouter {
3110
5343
  status.providers = Array.from(new Set([...(Array.isArray(status.providers) ? status.providers as string[] : []), ...providerTypes]));
3111
5344
  }
3112
5345
  }
3113
- if (node.workspace && typeof node.workspace === 'string') {
3114
- if (!fs.existsSync(node.workspace as string) && applyCachedInlineMeshNodeStatus(status, node)) {
3115
- status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === 'online' || isSelfNode);
3116
- nodeStatuses.push(status);
3117
- continue;
3118
- }
3119
- try {
3120
- const gitStatus = await getGitRepoStatus(node.workspace as string, { timeoutMs: 10_000, refreshUpstream: true });
3121
- status.git = gitStatus;
3122
- if (gitStatus.isGitRepo) {
3123
- status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
3124
- } else {
3125
- status.health = 'degraded';
3126
- if (gitStatus.error && !status.error) status.error = gitStatus.error;
5346
+ if (workspace) {
5347
+ if (!fs.existsSync(workspace)) {
5348
+ // Workspace not local prefer direct live inline truth, then attempt a P2P git probe.
5349
+ const inlineTransitGit = buildInlineMeshTransitGitStatus(node);
5350
+ let remoteProbeApplied = false;
5351
+ if (inlineTransitGit) {
5352
+ status.git = inlineTransitGit;
5353
+ status.health = inlineTransitGit.isGitRepo
5354
+ ? deriveMeshNodeHealthFromGit(inlineTransitGit as unknown as Record<string, unknown>)
5355
+ : 'degraded';
5356
+ const connection = readObjectRecord(status.connection);
5357
+ const connectionState = readStringValue(connection.state);
5358
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
5359
+ if (!connectionReported || connectionState === 'unknown') {
5360
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
5361
+ }
5362
+ remoteProbeApplied = true;
5363
+ } else if (!isSelfNode && daemonId && this.deps.dispatchMeshCommand && !directTruthUnavailableNodeIds.has(nodeId)) {
5364
+ try {
5365
+ const remoteGit = await probeRemoteMeshGitStatus({
5366
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
5367
+ daemonId,
5368
+ workspace,
5369
+ timeoutMs: 8000,
5370
+ });
5371
+ if (remoteGit) {
5372
+ status.git = remoteGit;
5373
+ status.health = remoteGit.isGitRepo
5374
+ ? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
5375
+ : 'degraded';
5376
+ const connection = readObjectRecord(status.connection);
5377
+ const connectionState = readStringValue(connection.state);
5378
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
5379
+ if (!connectionReported || connectionState === 'unknown') {
5380
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
5381
+ }
5382
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
5383
+ remoteProbeApplied = true;
5384
+ }
5385
+ } catch {
5386
+ const refreshedConnection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
5387
+ const refreshedConnectionState = readStringValue(refreshedConnection?.state);
5388
+ if (refreshedConnection && refreshedConnectionState === 'connected') {
5389
+ status.connection = refreshedConnection;
5390
+ try {
5391
+ const remoteGit = await probeRemoteMeshGitStatus({
5392
+ dispatchMeshCommand: this.deps.dispatchMeshCommand,
5393
+ daemonId,
5394
+ workspace,
5395
+ timeoutMs: 12000,
5396
+ });
5397
+ if (remoteGit) {
5398
+ status.git = remoteGit;
5399
+ status.health = remoteGit.isGitRepo
5400
+ ? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
5401
+ : 'degraded';
5402
+ const connection = readObjectRecord(status.connection);
5403
+ const connectionState = readStringValue(connection.state);
5404
+ const connectionReported = readBooleanValue(connection.reported) ?? false;
5405
+ if (!connectionReported || connectionState === 'unknown') {
5406
+ status.connection = buildLivePeerGitConnection(connection, refreshedAt);
5407
+ }
5408
+ recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
5409
+ remoteProbeApplied = true;
5410
+ }
5411
+ } catch {
5412
+ // Probe timed out again or P2P unavailable — fall back to cached status
5413
+ }
5414
+ }
5415
+ }
3127
5416
  }
3128
- } catch {
3129
- if (!applyCachedInlineMeshNodeStatus(status, node)) {
3130
- status.health = 'degraded';
5417
+ if (!remoteProbeApplied) {
5418
+ const connectionState = readStringValue((status.connection as any)?.state);
5419
+ const pendingPeerGitProbe = !inlineTransitGit
5420
+ && !isSelfNode
5421
+ && !!daemonId
5422
+ && (
5423
+ readStringValue(status.machineStatus) === 'online'
5424
+ || readStringValue(status.health) === 'online'
5425
+ || connectionState === 'connecting'
5426
+ || connectionState === 'connected'
5427
+ || connectionState === 'unknown'
5428
+ );
5429
+ if (pendingPeerGitProbe) {
5430
+ status.gitProbePending = true;
5431
+ status.health = 'unknown';
5432
+ }
5433
+ if (applyCachedInlineMeshNodeStatus(
5434
+ status,
5435
+ node,
5436
+ pendingPeerGitProbe ? { skipGit: true, skipError: true, skipHealth: true } : undefined,
5437
+ )) {
5438
+ applyInlineMeshBranchConvergence(mesh, node, status);
5439
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
5440
+ nodeStatuses.push(status);
5441
+ continue;
5442
+ }
5443
+ if (meshRecord?.source === 'inline_cache' && !isSelfNode) {
5444
+ applyInlineMeshBranchConvergence(mesh, node, status);
5445
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
5446
+ nodeStatuses.push(status);
5447
+ continue;
5448
+ }
5449
+ }
5450
+ } else {
5451
+ try {
5452
+ const gitStatus = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
5453
+ status.git = gitStatus;
5454
+ recordInlineMeshDirectGitTruth(node, gitStatus as unknown as Record<string, unknown>, 'selected_coordinator_local_git');
5455
+ if (gitStatus.isGitRepo) {
5456
+ status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
5457
+ } else {
5458
+ status.health = 'degraded';
5459
+ if (gitStatus.error && !status.error) status.error = gitStatus.error;
5460
+ }
5461
+ } catch {
5462
+ if (!applyCachedInlineMeshNodeStatus(status, node)) {
5463
+ status.health = 'degraded';
5464
+ }
3131
5465
  }
3132
5466
  }
3133
5467
  } else {
3134
5468
  applyCachedInlineMeshNodeStatus(status, node);
3135
5469
  }
3136
- status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === 'online' || isSelfNode);
5470
+ applyInlineMeshBranchConvergence(mesh, node, status);
5471
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
3137
5472
  nodeStatuses.push(status);
3138
5473
  }
3139
5474
 
3140
- return {
5475
+ const pendingCoordinatorEvents = drainPendingMeshCoordinatorEvents(meshId);
5476
+ const statusResult = {
3141
5477
  success: true,
3142
5478
  meshId: mesh.id,
3143
5479
  meshName: mesh.name,
3144
5480
  repoIdentity: mesh.repoIdentity,
3145
5481
  defaultBranch: mesh.defaultBranch,
3146
- refreshedAt: new Date().toISOString(),
5482
+ refreshedAt,
5483
+ meshHost,
5484
+ sourceOfTruth: {
5485
+ membership: meshRecord?.source === 'inline_cache'
5486
+ ? 'coordinator_inline_mesh_cache'
5487
+ : meshRecord?.source === 'local_config'
5488
+ ? 'local_mesh_config'
5489
+ : 'inline_bootstrap_snapshot',
5490
+ coordinatorOwnsLiveTruth: directTruthSatisfied,
5491
+ meshHost: {
5492
+ owner: 'mesh_host_daemon',
5493
+ localRole: meshHost.role,
5494
+ hostDaemonId: meshHost.hostDaemonId,
5495
+ hostNodeId: meshHost.hostNodeId,
5496
+ hostAddress: meshHost.hostAddress,
5497
+ },
5498
+ ...(requireDirectPeerTruth ? {
5499
+ currentStatus: directTruthSatisfied ? 'live_git_and_session_probes' : 'direct_peer_truth_unavailable',
5500
+ directPeerTruth: {
5501
+ required: true,
5502
+ satisfied: directTruthSatisfied,
5503
+ directEvidenceCount: effectiveDirectTruth.directEvidenceCount,
5504
+ localConfirmedCount: effectiveDirectTruth.localConfirmedCount,
5505
+ peerAttemptedCount: effectiveDirectTruth.peerAttemptedCount,
5506
+ peerConfirmedCount: effectiveDirectTruth.peerConfirmedCount,
5507
+ unavailableNodeIds: effectiveDirectTruth.unavailableNodeIds,
5508
+ partialNodeFailures: effectiveDirectTruth.unavailableNodeIds,
5509
+ },
5510
+ } : {}),
5511
+ historicalEvidenceOnly: ['recoveryHints', 'ledger.summary', 'queue.summary'],
5512
+ },
5513
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodeStatuses),
3147
5514
  nodes: nodeStatuses,
3148
5515
  queue: { tasks: queue, summary: queueSummary },
3149
5516
  ledger: { entries: ledgerEntries, summary: ledgerSummary },
5517
+ ...(pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {}),
3150
5518
  };
5519
+ const { pendingCoordinatorEvents: _pendingCoordinatorEvents, ...cacheableStatusResult } = statusResult as any;
5520
+ const rememberedStatus = this.rememberAggregateMeshStatus(meshId, cacheableStatusResult, refreshReason);
5521
+ const returnedStatus = pendingCoordinatorEvents.length > 0
5522
+ ? { ...rememberedStatus, pendingCoordinatorEvents }
5523
+ : rememberedStatus;
5524
+ logRepoMeshStatusDebug('return_live', {
5525
+ meshId,
5526
+ command: 'mesh_status',
5527
+ refreshRequested,
5528
+ refreshReason,
5529
+ meshSource: meshRecord.source,
5530
+ directTruth,
5531
+ summary: summarizeRepoMeshStatusDebug(returnedStatus),
5532
+ });
5533
+ return returnedStatus;
3151
5534
  } catch (e: any) {
3152
5535
  return { success: false, error: e.message };
3153
5536
  }
@@ -3205,7 +5588,7 @@ export class DaemonCommandRouter {
3205
5588
 
3206
5589
  // 3. Kill OS process if requested
3207
5590
  if (killProcess) {
3208
- const running = isIdeRunning(ideType);
5591
+ const running = await isIdeRunning(ideType);
3209
5592
  if (running) {
3210
5593
  LOG.info('StopIDE', `Killing IDE process: ${ideType}`);
3211
5594
  const killed = await killIdeProcess(ideType);