@adhdev/daemon-core 0.9.82-rc.1 → 0.9.82-rc.100

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 (75) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +10 -0
  3. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  4. package/dist/cli-adapters/provider-cli-shared.d.ts +10 -0
  5. package/dist/commands/router.d.ts +24 -0
  6. package/dist/config/mesh-config.d.ts +66 -1
  7. package/dist/git/git-commands.d.ts +1 -0
  8. package/dist/git/git-status.d.ts +5 -0
  9. package/dist/git/git-types.d.ts +10 -0
  10. package/dist/index.d.ts +13 -6
  11. package/dist/index.js +6068 -1214
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +6033 -1201
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/installer.d.ts +1 -4
  16. package/dist/launch.d.ts +1 -1
  17. package/dist/logging/async-batch-writer.d.ts +10 -0
  18. package/dist/mesh/beads-db.d.ts +18 -0
  19. package/dist/mesh/mesh-active-work.d.ts +60 -0
  20. package/dist/mesh/mesh-events.d.ts +29 -5
  21. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  22. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  23. package/dist/mesh/mesh-ledger.d.ts +38 -1
  24. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  25. package/dist/mesh/refine-config.d.ts +176 -0
  26. package/dist/providers/chat-message-normalization.d.ts +1 -0
  27. package/dist/providers/cli-provider-instance.d.ts +2 -1
  28. package/dist/repo-mesh-types.d.ts +167 -0
  29. package/dist/status/reporter.d.ts +2 -0
  30. package/package.json +3 -1
  31. package/src/boot/daemon-lifecycle.ts +4 -0
  32. package/src/cli-adapters/provider-cli-adapter.ts +255 -14
  33. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  34. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  35. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  36. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  37. package/src/cli-adapters/provider-cli-shared.ts +28 -10
  38. package/src/commands/chat-commands.ts +570 -20
  39. package/src/commands/cli-manager.ts +129 -1
  40. package/src/commands/handler.ts +8 -1
  41. package/src/commands/mesh-coordinator.ts +13 -143
  42. package/src/commands/router.ts +3095 -406
  43. package/src/config/chat-history.ts +9 -7
  44. package/src/config/mesh-config.ts +245 -1
  45. package/src/daemon/dev-cli-debug.ts +10 -1
  46. package/src/detection/ide-detector.ts +26 -16
  47. package/src/git/git-commands.ts +3 -3
  48. package/src/git/git-status.ts +97 -6
  49. package/src/git/git-summary.ts +3 -0
  50. package/src/git/git-types.ts +11 -0
  51. package/src/index.ts +39 -5
  52. package/src/installer.d.ts +1 -1
  53. package/src/installer.ts +8 -6
  54. package/src/launch.d.ts +1 -1
  55. package/src/launch.ts +37 -28
  56. package/src/logging/async-batch-writer.ts +55 -0
  57. package/src/logging/logger.ts +2 -1
  58. package/src/mesh/beads-db.ts +176 -0
  59. package/src/mesh/coordinator-prompt.ts +31 -8
  60. package/src/mesh/mesh-active-work.ts +255 -0
  61. package/src/mesh/mesh-events.ts +400 -47
  62. package/src/mesh/mesh-fast-forward.ts +430 -0
  63. package/src/mesh/mesh-host-ownership.ts +73 -0
  64. package/src/mesh/mesh-ledger.ts +138 -1
  65. package/src/mesh/mesh-work-queue.ts +199 -137
  66. package/src/mesh/refine-config.ts +356 -0
  67. package/src/providers/chat-message-normalization.ts +7 -12
  68. package/src/providers/cli-provider-instance.ts +93 -14
  69. package/src/providers/ide-provider-instance.ts +17 -3
  70. package/src/providers/provider-loader.ts +10 -4
  71. package/src/providers/read-chat-contract.ts +1 -1
  72. package/src/providers/version-archive.ts +38 -20
  73. package/src/repo-mesh-types.ts +182 -0
  74. package/src/status/reporter.ts +15 -0
  75. package/src/system/host-memory.ts +29 -12
@@ -26,6 +26,8 @@ import { getSavedProviderSessions } from '../config/saved-sessions.js';
26
26
  import { listProviderHistorySessions } from '../config/chat-history.js';
27
27
  import { detectIDEs } from '../detection/ide-detector.js';
28
28
  import { detectCLI } from '../detection/cli-detector.js';
29
+ import { getGitRepoStatus } from '../git/git-status.js';
30
+ import type { GitSubmoduleStatus } from '../git/git-types.js';
29
31
  import { SessionRegistry } from '../sessions/registry.js';
30
32
  import { LOG } from '../logging/logger.js';
31
33
  import { logCommand } from '../logging/command-log.js';
@@ -36,13 +38,25 @@ import { createInteractionId, getRecentDebugTrace, recordDebugTrace } from '../l
36
38
  import { getSessionHostSurfaceKind, partitionSessionHostRecords } from '../session-host/runtime-surface.js';
37
39
  import { createHermesManualMeshCoordinatorSetup, resolveMeshCoordinatorSetup } from './mesh-coordinator.js';
38
40
  import { buildSessionEntries } from '../status/builders.js';
39
- 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';
40
53
  import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
41
54
  import { getSessionCompletionMarker } from '../status/snapshot.js';
42
55
  import { execNpmCommandSync, resolveCurrentGlobalInstallSurface, spawnDetachedDaemonUpgradeHelper } from './upgrade-helper.js';
56
+ import { getMeshQueueRevision } from '../mesh/mesh-work-queue.js';
43
57
  import type { RepoMeshSessionCleanupMode } from '../repo-mesh-types.js';
44
- import { homedir } from 'os';
45
- 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';
46
60
  import * as fs from 'fs';
47
61
 
48
62
  type ReleaseChannel = 'stable' | 'preview';
@@ -85,6 +99,1062 @@ function readProviderPriorityFromPolicy(policy: unknown): string[] {
85
99
  });
86
100
  }
87
101
 
102
+ function readObjectRecord(value: unknown): Record<string, any> {
103
+ return value && typeof value === 'object' && !Array.isArray(value)
104
+ ? value as Record<string, any>
105
+ : {};
106
+ }
107
+
108
+ function readStringValue(...values: unknown[]): string | undefined {
109
+ for (const value of values) {
110
+ if (typeof value === 'string' && value.trim()) return value.trim();
111
+ }
112
+ return undefined;
113
+ }
114
+
115
+ function readNumberValue(...values: unknown[]): number | undefined {
116
+ for (const value of values) {
117
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
118
+ }
119
+ return undefined;
120
+ }
121
+
122
+ function readBooleanValue(...values: unknown[]): boolean | undefined {
123
+ for (const value of values) {
124
+ if (typeof value === 'boolean') return value;
125
+ }
126
+ return undefined;
127
+ }
128
+
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 {
210
+ if (!Array.isArray(value)) return undefined;
211
+ const submodules = value
212
+ .map(entry => {
213
+ const submodule = readObjectRecord(entry);
214
+ const path = readStringValue(submodule.path);
215
+ const commit = readStringValue(submodule.commit);
216
+ const repoPath = readStringValue(submodule.repoPath, submodule.repo_root)
217
+ ?? joinRepoPath(parentRepoRoot, path);
218
+ if (!path || !commit || !repoPath) return null;
219
+ return {
220
+ path,
221
+ commit,
222
+ repoPath,
223
+ dirty: readBooleanValue(submodule.dirty) ?? false,
224
+ outOfSync: readBooleanValue(submodule.outOfSync, submodule.out_of_sync) ?? false,
225
+ lastCheckedAt: readNumberValue(submodule.lastCheckedAt, submodule.last_checked_at) ?? Date.now(),
226
+ ...(readStringValue(submodule.error) ? { error: readStringValue(submodule.error) } : {}),
227
+ };
228
+ })
229
+ .filter((entry): entry is GitSubmoduleStatus => entry !== null);
230
+ return submodules.length > 0 ? submodules : undefined;
231
+ }
232
+
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}`);
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
+ }
367
+
368
+ function normalizeInlineMeshGitStatus(
369
+ status: Record<string, unknown>,
370
+ node: any,
371
+ options?: { lastCheckedAt?: number },
372
+ ): Record<string, unknown> | undefined {
373
+ const isGitRepo = readBooleanValue(status.isGitRepo);
374
+ if (!Object.keys(status).length || isGitRepo === undefined) return undefined;
375
+ const conflictFiles = Array.isArray(status.conflictFiles)
376
+ ? status.conflictFiles.filter((value: unknown): value is string => typeof value === 'string')
377
+ : [];
378
+ const conflictCount = readNumberValue(status.conflicts) ?? conflictFiles.length;
379
+ const hasConflicts = readBooleanValue(status.hasConflicts) ?? conflictCount > 0;
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);
382
+ return {
383
+ workspace: readStringValue(status.workspace, node?.workspace) || '',
384
+ repoRoot: repoRoot ?? null,
385
+ isGitRepo,
386
+ branch: readStringValue(status.branch) ?? null,
387
+ headCommit: readStringValue(status.headCommit) ?? null,
388
+ headMessage: readStringValue(status.headMessage) ?? null,
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),
394
+ ahead: readNumberValue(status.ahead) ?? 0,
395
+ behind: readNumberValue(status.behind) ?? 0,
396
+ staged: readNumberValue(status.staged) ?? 0,
397
+ modified: readNumberValue(status.modified) ?? 0,
398
+ untracked: readNumberValue(status.untracked) ?? 0,
399
+ deleted: readNumberValue(status.deleted) ?? 0,
400
+ renamed: readNumberValue(status.renamed) ?? 0,
401
+ hasConflicts,
402
+ conflictFiles,
403
+ stashCount: readNumberValue(status.stashCount) ?? 0,
404
+ lastCheckedAt: options?.lastCheckedAt ?? readNumberValue(status.lastCheckedAt) ?? Date.now(),
405
+ ...(submodules ? { submodules } : {}),
406
+ };
407
+ }
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
+
634
+ function hasGitWorktreeChanges(git: Record<string, unknown> | null | undefined): boolean {
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);
645
+ }
646
+
647
+ function getGitSubmoduleDriftState(git: Record<string, unknown> | null | undefined): { dirty: boolean; outOfSync: boolean } {
648
+ const submodules = Array.isArray(git?.submodules) ? git.submodules : [];
649
+ let dirty = false;
650
+ let outOfSync = false;
651
+ for (const entry of submodules) {
652
+ const submodule = readObjectRecord(entry);
653
+ if (readBooleanValue(submodule.dirty) === true) dirty = true;
654
+ if (readBooleanValue(submodule.outOfSync) === true || !!readStringValue(submodule.error)) outOfSync = true;
655
+ }
656
+ return { dirty, outOfSync };
657
+ }
658
+
659
+ function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undefined): 'online' | 'dirty' | 'degraded' {
660
+ if (!git || readBooleanValue(git.isGitRepo) === false) return 'degraded';
661
+ const branch = readStringValue(git.branch);
662
+ if (!branch) return 'degraded';
663
+ const submoduleDrift = getGitSubmoduleDriftState(git);
664
+ if (submoduleDrift.outOfSync) return 'degraded';
665
+ if (submoduleDrift.dirty || hasGitWorktreeChanges(git)) return 'dirty';
666
+ return 'online';
667
+ }
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
+
830
+ function readCachedInlineMeshActiveSessions(node: any): string[] {
831
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
832
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
833
+ const fallbackSession = Object.keys(activeSession).length
834
+ ? activeSession
835
+ : readObjectRecord(node?.activeSession ?? node?.active_session);
836
+ const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
837
+ return sessionId ? [sessionId] : [];
838
+ }
839
+
840
+ function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<string, unknown>> {
841
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
842
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
843
+ const fallbackSession = Object.keys(activeSession).length
844
+ ? activeSession
845
+ : readObjectRecord(node?.activeSession ?? node?.active_session);
846
+ const sessionId = readStringValue(
847
+ fallbackSession.id,
848
+ fallbackSession.sessionId,
849
+ fallbackSession.session_id,
850
+ node?.activeSessionId,
851
+ node?.active_session_id,
852
+ node?.sessionId,
853
+ node?.session_id,
854
+ );
855
+ if (!sessionId) return [];
856
+ return [{
857
+ sessionId,
858
+ providerType: readStringValue(
859
+ fallbackSession.providerType,
860
+ fallbackSession.provider_type,
861
+ fallbackSession.cliType,
862
+ fallbackSession.cli_type,
863
+ fallbackSession.provider,
864
+ node?.providerType,
865
+ node?.provider_type,
866
+ ),
867
+ state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
868
+ lifecycle: readStringValue(fallbackSession.lifecycle),
869
+ title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
870
+ workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
871
+ lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
872
+ recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
873
+ isCached: true,
874
+ }];
875
+ }
876
+
877
+ function readLiveMeshSessionState(record: any): string | undefined {
878
+ return readStringValue(
879
+ record?.meta?.sessionStatus,
880
+ record?.meta?.status,
881
+ record?.meta?.providerStatus,
882
+ record?.status,
883
+ record?.state,
884
+ record?.lifecycle,
885
+ );
886
+ }
887
+
888
+ function toIsoTimestamp(value: unknown): string | null {
889
+ if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString();
890
+ const stringValue = readStringValue(value);
891
+ return stringValue || null;
892
+ }
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
+
1040
+ function summarizeMeshSessionRecord(record: any): Record<string, unknown> {
1041
+ return {
1042
+ sessionId: readStringValue(record?.sessionId) || 'unknown',
1043
+ providerType: readStringValue(record?.providerType),
1044
+ state: readLiveMeshSessionState(record),
1045
+ lifecycle: readStringValue(record?.lifecycle),
1046
+ surfaceKind: getSessionHostSurfaceKind(record as any),
1047
+ recoveryState: readStringValue(record?.meta?.runtimeRecoveryState) ?? null,
1048
+ workspace: readStringValue(record?.workspace) ?? null,
1049
+ title: readStringValue(record?.displayName, record?.workspaceLabel) ?? null,
1050
+ lastActivityAt: toIsoTimestamp(record?.updatedAt ?? record?.lastActivityAt ?? record?.last_activity_at),
1051
+ isCached: false,
1052
+ };
1053
+ }
1054
+
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 {
1129
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
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));
1134
+ const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
1135
+ const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
1136
+ const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
1137
+ const activeSessions = readCachedInlineMeshActiveSessions(node);
1138
+ const activeSessionDetails = readCachedInlineMeshActiveSessionDetails(node);
1139
+ if (!git && !error && !health && !machineStatus && !lastSeenAt && !updatedAt && activeSessions.length === 0) return false;
1140
+ if (git) status.git = git;
1141
+ if (error) status.error = error;
1142
+ if (machineStatus) status.machineStatus = machineStatus;
1143
+ if (lastSeenAt) status.lastSeenAt = lastSeenAt;
1144
+ if (updatedAt) status.updatedAt = updatedAt;
1145
+ if (activeSessions.length > 0) status.activeSessions = activeSessions;
1146
+ if (activeSessionDetails.length > 0) status.activeSessionDetails = activeSessionDetails;
1147
+ if (health) {
1148
+ status.health = health;
1149
+ return true;
1150
+ }
1151
+ if (git) {
1152
+ status.health = deriveMeshNodeHealthFromGit(git);
1153
+ return true;
1154
+ }
1155
+ return activeSessions.length > 0 || !!machineStatus || !!lastSeenAt || !!updatedAt;
1156
+ }
1157
+
88
1158
  async function resolveProviderTypeFromPriority(args: {
89
1159
  nodeId: string;
90
1160
  providerPriority: string[];
@@ -117,29 +1187,114 @@ async function resolveProviderTypeFromPriority(args: {
117
1187
  }
118
1188
  type MeshCoordinatorConfigFormat = 'claude_mcp_json' | 'hermes_config_yaml';
119
1189
  type MeshRefineValidationStatus = 'passed' | 'failed' | 'skipped';
120
- type MeshRefineValidationCommand = {
121
- command: string;
122
- args: string[];
123
- displayCommand: string;
124
- category: string;
125
- source: string;
126
- };
1190
+ type MeshRefineValidationCommand = MeshRefineValidationCommandPlan;
127
1191
 
128
1192
  type MeshRefineValidationSummary = {
129
1193
  status: MeshRefineValidationStatus;
130
1194
  required: true;
131
1195
  commandsRun: Array<Record<string, unknown>>;
1196
+ bootstrapCommandsRun: Array<Record<string, unknown>>;
132
1197
  rejectedCommands: Array<Record<string, unknown>>;
133
1198
  skippedReason?: string;
1199
+ failureKind?: string;
1200
+ failureCode?: string;
134
1201
  timeoutMs: number;
135
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
+ };
136
1288
  };
137
1289
 
1290
+ type MeshRefineTerminalJob = MeshRefineJobHandle & { result?: Record<string, unknown> };
1291
+
138
1292
  const REFINE_VALIDATION_CATEGORIES = ['typecheck', 'test', 'lint', 'build'] as const;
139
1293
  const REFINE_VALIDATION_TIMEOUT_MS = 120_000;
140
1294
  const REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
141
1295
  const REFINE_VALIDATION_SUMMARY_CHARS = 2_000;
142
1296
  const REFINE_VALIDATION_MAX_COMMANDS = 4;
1297
+ const REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES = 4 * 1024 * 1024;
143
1298
 
144
1299
  function truncateValidationOutput(value: unknown): string {
145
1300
  const text = typeof value === 'string' ? value : value == null ? '' : String(value);
@@ -147,171 +1302,336 @@ function truncateValidationOutput(value: unknown): string {
147
1302
  return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}\n[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
148
1303
  }
149
1304
 
150
- function readPackageScripts(workspace: string): Record<string, string> {
151
- try {
152
- const packageJsonPath = pathJoin(workspace, 'package.json');
153
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
154
- return parsed?.scripts && typeof parsed.scripts === 'object' && !Array.isArray(parsed.scripts)
155
- ? parsed.scripts as Record<string, string>
156
- : {};
157
- } catch {
158
- return {};
159
- }
160
- }
161
-
162
- function tokenizeValidationCommand(command: string): string[] | null {
163
- const trimmed = command.trim();
164
- if (!trimmed) return null;
165
- // Fail closed: the gate never hands shell syntax to a shell. Package-manager
166
- // scripts are invoked via execFile(binary, args), and metacharacters/quotes are
167
- // rejected before tokenization so `npm run test && rm -rf` cannot be smuggled in.
168
- if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
169
- const tokens = trimmed.split(/\s+/).filter(Boolean);
170
- if (!tokens.length) return null;
171
- if (tokens.some(token => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
172
- return tokens;
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
+ });
173
1318
  }
174
1319
 
175
- function scriptMatchesValidationCategory(scriptName: string, category: string): boolean {
176
- return scriptName === category || scriptName.startsWith(`${category}:`);
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.`;
177
1325
  }
178
1326
 
179
- function parsePackageManagerValidationCommand(
180
- rawCommand: string,
181
- category: string,
182
- scripts: Record<string, string>,
183
- source: string,
184
- ): { command?: MeshRefineValidationCommand; rejected?: Record<string, unknown> } {
185
- const tokens = tokenizeValidationCommand(rawCommand);
186
- if (!tokens) {
187
- return { rejected: { command: rawCommand, category, source, reason: 'unsafe command string is not allowlisted' } };
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' };
188
1330
  }
189
-
190
- const [binary, second, third, ...rest] = tokens;
191
- let scriptName = '';
192
- let command = binary;
193
- let args: string[] = [];
194
-
195
- if ((binary === 'npm' || binary === 'pnpm' || binary === 'bun') && second === 'run' && third) {
196
- scriptName = third;
197
- args = ['run', scriptName, ...rest];
198
- } else if (binary === 'npm' && second === 'test' && !third) {
199
- scriptName = 'test';
200
- args = ['test'];
201
- } else if (binary === 'yarn' && second === 'run' && third) {
202
- scriptName = third;
203
- args = ['run', scriptName, ...rest];
204
- } else if (binary === 'yarn' && second && !third) {
205
- scriptName = second;
206
- args = [scriptName];
207
- } else {
208
- return { rejected: { command: rawCommand, category, source, reason: 'command is not a supported package-manager script invocation' } };
209
- }
210
-
211
- if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
212
- return { rejected: { command: rawCommand, category, source, script: scriptName, reason: 'script is not declared in package.json' } };
213
- }
214
- if (!scriptMatchesValidationCategory(scriptName, category)) {
215
- 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 };
216
1334
  }
1335
+ return { enabled: false };
1336
+ }
217
1337
 
218
- return {
219
- command: {
220
- command,
221
- args,
222
- displayCommand: [command, ...args].join(' '),
223
- category,
224
- source,
225
- },
226
- };
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] || '';
227
1353
  }
228
1354
 
229
- function collectProjectContextValidationCandidates(mesh: any): Array<{ command: string; category: string; source: string; confidence?: string }> {
230
- const commands = mesh?.projectContext?.commands;
231
- if (!commands || typeof commands !== 'object' || Array.isArray(commands)) return [];
232
- const candidates: Array<{ command: string; category: string; source: string; confidence?: string }> = [];
233
- for (const category of REFINE_VALIDATION_CATEGORIES) {
234
- const entries = Array.isArray(commands[category]) ? commands[category] : [];
235
- for (const entry of entries) {
236
- if (typeof entry?.command !== 'string') continue;
237
- candidates.push({
238
- command: entry.command,
239
- category,
240
- source: typeof entry.sourcePath === 'string' ? entry.sourcePath : 'projectContext.commands',
241
- confidence: typeof entry.confidence === 'string' ? entry.confidence : undefined,
242
- });
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
+ };
243
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
+ };
244
1409
  }
245
- return candidates.sort((a, b) => {
246
- const rank = (value?: string) => value === 'high' ? 0 : value === 'medium' ? 1 : 2;
247
- return rank(a.confidence) - rank(b.confidence);
248
- });
249
1410
  }
250
1411
 
251
- function collectPolicyValidationCandidates(mesh: any): Array<{ command: string; category: string; source: string }> {
252
- const policy = mesh?.policy && typeof mesh.policy === 'object' && !Array.isArray(mesh.policy) ? mesh.policy : {};
253
- const configured = Array.isArray(policy.validationCommands)
254
- ? policy.validationCommands
255
- : Array.isArray(policy.validationGate?.commands)
256
- ? policy.validationGate.commands
257
- : [];
258
- return configured
259
- .map((entry: any) => typeof entry === 'string' ? { command: entry, category: '', source: 'mesh.policy.validationCommands' } : entry)
260
- .filter((entry: any) => entry && typeof entry.command === 'string')
261
- .map((entry: any) => {
262
- const commandText = entry.command.trim();
263
- const category = REFINE_VALIDATION_CATEGORIES.find(cat => commandText.includes(` ${cat}`)) ?? '';
264
- return { command: commandText, category, source: 'mesh.policy.validationCommands' };
265
- })
266
- .filter((entry: any) => !!entry.category);
267
- }
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
+ };
268
1459
 
269
- function selectMeshRefineValidationCommands(mesh: any, workspace: string): { commands: MeshRefineValidationCommand[]; rejectedCommands: Array<Record<string, unknown>>; source: string } {
270
- const scripts = readPackageScripts(workspace);
271
- const rejectedCommands: Array<Record<string, unknown>> = [];
272
- const selected: MeshRefineValidationCommand[] = [];
273
- const seen = new Set<string>();
274
- const candidates = [
275
- ...collectPolicyValidationCandidates(mesh),
276
- ...collectProjectContextValidationCandidates(mesh),
277
- ];
278
-
279
- for (const candidate of candidates) {
280
- const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
281
- if (parsed.rejected) {
282
- rejectedCommands.push(parsed.rejected);
283
- continue;
284
- }
285
- if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
286
- selected.push(parsed.command);
287
- seen.add(parsed.command.displayCommand);
288
- if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
289
- }
290
-
291
- if (!selected.length && candidates.length === 0) {
292
- for (const category of REFINE_VALIDATION_CATEGORIES) {
293
- if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
294
- const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, 'package.json:scripts');
295
- if (fallback.command && !seen.has(fallback.command.displayCommand)) {
296
- selected.push(fallback.command);
297
- seen.add(fallback.command.displayCommand);
298
- } else if (fallback.rejected) {
299
- rejectedCommands.push(fallback.rejected);
300
- }
301
- 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);
302
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
+ };
303
1611
  }
1612
+ }
304
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
+ });
305
1623
  return {
306
- commands: selected,
307
- rejectedCommands,
308
- source: selected.some(command => command.source === 'mesh.policy.validationCommands')
309
- ? 'mesh_policy'
310
- : selected.some(command => command.source !== 'package.json:scripts')
311
- ? 'project_context'
312
- : selected.length
313
- ? 'package_json_scripts'
314
- : '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.',
315
1635
  };
316
1636
  }
317
1637
 
@@ -319,60 +1639,119 @@ async function runMeshRefineValidationGate(mesh: any, workspace: string): Promis
319
1639
  const { execFile } = await import('node:child_process');
320
1640
  const { promisify } = await import('node:util');
321
1641
  const execFileAsync = promisify(execFile);
322
- const selection = selectMeshRefineValidationCommands(mesh, workspace);
1642
+ const selection = resolveMeshRefineValidationPlan(mesh, workspace);
323
1643
  const summary: MeshRefineValidationSummary = {
324
1644
  status: 'skipped',
325
1645
  required: true,
326
1646
  commandsRun: [],
1647
+ bootstrapCommandsRun: [],
327
1648
  rejectedCommands: selection.rejectedCommands,
328
1649
  skippedReason: undefined,
329
1650
  timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
330
1651
  outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
1652
+ configSource: selection.source,
1653
+ configSourceType: selection.sourceType,
1654
+ suggestions: selection.suggestions,
1655
+ suggestedConfig: selection.suggestedConfig,
331
1656
  };
332
1657
 
333
1658
  if (!selection.commands.length) {
334
- 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';
335
1660
  return summary;
336
1661
  }
337
1662
 
338
- 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) {
339
1689
  const startedAt = Date.now();
1690
+ const cwd = candidate.cwd ? pathResolve(workspace, candidate.cwd) : workspace;
1691
+ const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
340
1692
  try {
341
1693
  const result = await execFileAsync(candidate.command, candidate.args, {
342
- cwd: workspace,
1694
+ cwd,
343
1695
  encoding: 'utf8',
344
- timeout: REFINE_VALIDATION_TIMEOUT_MS,
1696
+ timeout,
345
1697
  maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
346
- env: { ...process.env, CI: process.env.CI || '1' },
347
- });
348
- summary.commandsRun.push({
349
- command: candidate.command,
350
- args: candidate.args,
351
- displayCommand: candidate.displayCommand,
352
- category: candidate.category,
353
- source: candidate.source,
354
- passed: true,
355
- exitCode: 0,
356
- durationMs: Date.now() - startedAt,
357
- stdout: truncateValidationOutput(result.stdout),
358
- stderr: truncateValidationOutput(result.stderr),
1698
+ env: { ...process.env, CI: process.env.CI || '1', ...(candidate.env || {}) },
359
1699
  });
1700
+ summary.bootstrapCommandsRun.push(commandRecord(candidate, cwd, startedAt, result, true, { exitCode: 0 }));
360
1701
  } catch (error: any) {
361
- summary.commandsRun.push({
362
- command: candidate.command,
363
- args: candidate.args,
364
- displayCommand: candidate.displayCommand,
365
- category: candidate.category,
366
- source: candidate.source,
367
- passed: false,
1702
+ summary.bootstrapCommandsRun.push(commandRecord(candidate, cwd, startedAt, error, false, {
368
1703
  exitCode: typeof error?.code === 'number' ? error.code : null,
369
1704
  signal: typeof error?.signal === 'string' ? error.signal : null,
370
1705
  timedOut: error?.killed === true || /timed out/i.test(String(error?.message || '')),
371
- durationMs: Date.now() - startedAt,
372
- stdout: truncateValidationOutput(error?.stdout),
373
- 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 || {}) },
374
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
+ }));
375
1750
  summary.status = 'failed';
1751
+ if (missingDependencyFailure) {
1752
+ summary.failureKind = 'missing_dependencies';
1753
+ summary.failureCode = 'missing_dependencies';
1754
+ }
376
1755
  return summary;
377
1756
  }
378
1757
  }
@@ -502,6 +1881,10 @@ export interface CommandRouterDeps {
502
1881
  statusVersion?: string;
503
1882
  /** Session host control plane */
504
1883
  sessionHostControl?: SessionHostControlPlane | null;
1884
+ /** Selected-coordinator mesh peer telemetry surface for target daemons, when supported by the runtime. */
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>;
505
1888
  }
506
1889
 
507
1890
  export interface CommandRouterResult {
@@ -613,33 +1996,266 @@ function summarizeSessionHostPruneResult(result: unknown): Record<string, unknow
613
1996
  };
614
1997
  }
615
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
+
616
2043
  export class DaemonCommandRouter {
617
2044
  private deps: CommandRouterDeps;
618
2045
  /** In-memory cache for cloud-originating meshes passed via inlineMesh.
619
2046
  * Allows the MCP server to query mesh data via get_mesh even when
620
2047
  * the mesh doesn't exist in the local meshes.json file. */
621
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>();
622
2055
 
623
2056
  constructor(deps: CommandRouterDeps) {
624
2057
  this.deps = deps;
625
2058
  }
626
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
+
627
2193
  public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
628
2194
  if (inlineMesh && typeof inlineMesh === 'object') {
629
- this.inlineMeshCache.set(meshId, inlineMesh as any);
630
- return inlineMesh as any;
2195
+ return this.warmInlineMeshCache(meshId, inlineMesh);
631
2196
  }
632
2197
  return this.inlineMeshCache.get(meshId);
633
2198
  }
634
2199
 
635
- private async getMeshForCommand(meshId: string, inlineMesh?: unknown): Promise<{ mesh: any; inline: boolean } | null> {
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
+
2213
+ private async getMeshForCommand(
2214
+ meshId: string,
2215
+ inlineMesh?: unknown,
2216
+ options?: { preferInline?: boolean },
2217
+ ): Promise<{ mesh: any; inline: boolean; source: 'inline_cache' | 'inline_bootstrap' | 'local_config' } | null> {
2218
+ const preferInline = options?.preferInline === true;
2219
+ if (preferInline) {
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
+ }
2233
+ }
636
2234
  try {
637
2235
  const { getMesh } = await import('../config/mesh-config.js');
638
2236
  const mesh = getMesh(meshId);
639
- if (mesh) return { mesh, inline: false };
2237
+ if (mesh) return { mesh, inline: false, source: 'local_config' };
640
2238
  } catch { /* fall through to inline cache */ }
641
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
642
- 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;
643
2259
  }
644
2260
 
645
2261
  private updateInlineMeshNode(meshId: string, mesh: any, node: any): void {
@@ -649,6 +2265,7 @@ export class DaemonCommandRouter {
649
2265
  else mesh.nodes.push(node);
650
2266
  mesh.updatedAt = new Date().toISOString();
651
2267
  this.inlineMeshCache.set(meshId, mesh);
2268
+ this.invalidateAggregateMeshStatus(meshId);
652
2269
  }
653
2270
 
654
2271
  private removeInlineMeshNode(meshId: string, mesh: any, nodeId: string): boolean {
@@ -658,6 +2275,7 @@ export class DaemonCommandRouter {
658
2275
  mesh.nodes.splice(idx, 1);
659
2276
  mesh.updatedAt = new Date().toISOString();
660
2277
  this.inlineMeshCache.set(meshId, mesh);
2278
+ this.invalidateAggregateMeshStatus(meshId);
661
2279
  return true;
662
2280
  }
663
2281
 
@@ -936,6 +2554,7 @@ export class DaemonCommandRouter {
936
2554
  const deletedSessionIds: string[] = [];
937
2555
  const skippedSessionIds: string[] = [];
938
2556
  const skippedLiveSessionIds: string[] = [];
2557
+ const skippedCoordinatorSessionIds: string[] = [];
939
2558
  const deleteUnsupportedSessionIds: string[] = [];
940
2559
  const recordsRemainSessionIds: string[] = [];
941
2560
  const errors: Array<{ sessionId: string; error: string }> = [];
@@ -970,6 +2589,12 @@ export class DaemonCommandRouter {
970
2589
  const completed = this.isCompletedHostedSession(record);
971
2590
  const surfaceKind = getSessionHostSurfaceKind(record);
972
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
+ }
973
2598
  if (!hasExplicitSessionIds && liveRuntime) {
974
2599
  skippedSessionIds.push(sessionId);
975
2600
  skippedLiveSessionIds.push(sessionId);
@@ -1039,6 +2664,7 @@ export class DaemonCommandRouter {
1039
2664
  deletedSessionIds,
1040
2665
  skippedSessionIds,
1041
2666
  skippedLiveSessionIds,
2667
+ skippedCoordinatorSessionIds,
1042
2668
  ...(deleteUnsupported ? {
1043
2669
  deleteUnsupported: true,
1044
2670
  effectiveCleanup: args.mode === 'stop_and_delete'
@@ -1160,23 +2786,534 @@ export class DaemonCommandRouter {
1160
2786
  payload: { cmd, source: logSource, success: handlerResult.success, durationMs: Date.now() - cmdStart },
1161
2787
  });
1162
2788
 
1163
- // 3. Post-chat command callback
1164
- if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
1165
- this.deps.onPostChatCommand();
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 });
3216
+ }
3217
+
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
+ };
3228
+
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
+ };
1166
3246
  }
1167
3247
 
1168
- 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
+ };
1169
3262
  } catch (e: any) {
1170
- logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: false, error: e.message, durationMs: Date.now() - cmdStart });
1171
- recordDebugTrace({
1172
- interactionId,
1173
- category: 'command',
1174
- stage: 'failed',
1175
- level: 'error',
1176
- payload: { cmd, source: logSource, error: e?.message || String(e), durationMs: Date.now() - cmdStart },
1177
- });
1178
- 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) };
1179
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;
1180
3317
  }
1181
3318
 
1182
3319
  // ─── Daemon-level command core ───────────────────
@@ -1193,7 +3330,8 @@ export class DaemonCommandRouter {
1193
3330
  }
1194
3331
 
1195
3332
  case 'get_pending_mesh_events': {
1196
- const events = drainPendingMeshCoordinatorEvents();
3333
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
3334
+ const events = drainPendingMeshCoordinatorEvents(meshId || undefined);
1197
3335
  return { success: true, events };
1198
3336
  }
1199
3337
 
@@ -1798,15 +3936,44 @@ export class DaemonCommandRouter {
1798
3936
  case 'get_mesh': {
1799
3937
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1800
3938
  if (!meshId) return { success: false, error: 'meshId required' };
1801
- try {
1802
- const { getMesh } = await import('../config/mesh-config.js');
1803
- const mesh = getMesh(meshId);
1804
- if (mesh) return { success: true, mesh };
1805
- } catch { /* fall through to inline cache */ }
1806
- // Fallback: check in-memory cache for cloud-originating meshes
1807
- const cached = this.inlineMeshCache.get(meshId);
1808
- if (cached) return { success: true, mesh: cached };
1809
- 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 };
1810
3977
  }
1811
3978
 
1812
3979
  case 'create_mesh': {
@@ -1817,7 +3984,10 @@ export class DaemonCommandRouter {
1817
3984
  if (!name) return { success: false, error: 'name required' };
1818
3985
  try {
1819
3986
  const { createMesh } = await import('../config/mesh-config.js');
1820
- 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 });
1821
3991
  return { success: true, mesh };
1822
3992
  } catch (e: any) {
1823
3993
  return { success: false, error: e.message };
@@ -1834,16 +4004,237 @@ export class DaemonCommandRouter {
1834
4004
  if (typeof args?.defaultBranch === 'string') patch.defaultBranch = args.defaultBranch;
1835
4005
  if (args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)) patch.policy = args.policy;
1836
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;
1837
4008
  if (!Object.keys(patch).length) return { success: false, error: 'No updates provided' };
1838
4009
  const mesh = updateMesh(meshId, patch as any);
1839
4010
  if (!mesh) return { success: false, error: 'Mesh not found' };
1840
4011
  this.inlineMeshCache.set(meshId, mesh);
4012
+ this.invalidateAggregateMeshStatus(meshId);
1841
4013
  return { success: true, mesh };
1842
4014
  } catch (e: any) {
1843
4015
  return { success: false, error: e.message };
1844
4016
  }
1845
4017
  }
1846
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
+
1847
4238
  case 'delete_mesh': {
1848
4239
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1849
4240
  if (!meshId) return { success: false, error: 'meshId required' };
@@ -1937,6 +4328,8 @@ export class DaemonCommandRouter {
1937
4328
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1938
4329
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
1939
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;
1940
4333
  try {
1941
4334
  const { cancelTask } = await import('../mesh/mesh-work-queue.js');
1942
4335
  const reason = typeof args?.reason === 'string' ? args.reason : undefined;
@@ -1952,6 +4345,8 @@ export class DaemonCommandRouter {
1952
4345
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1953
4346
  const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
1954
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;
1955
4350
  try {
1956
4351
  const { requeueTask } = await import('../mesh/mesh-work-queue.js');
1957
4352
  const task = requeueTask(meshId, taskId, {
@@ -1973,6 +4368,8 @@ export class DaemonCommandRouter {
1973
4368
  const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
1974
4369
  if (!meshId) return { success: false, error: 'meshId required' };
1975
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;
1976
4373
  try {
1977
4374
  const { addNode } = await import('../config/mesh-config.js');
1978
4375
  const providerPriority = Array.isArray(args?.providerPriority)
@@ -1983,7 +4380,18 @@ export class DaemonCommandRouter {
1983
4380
  ...(readOnly ? { readOnly: true } : {}),
1984
4381
  ...(providerPriority.length ? { providerPriority } : {}),
1985
4382
  };
1986
- 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
+ });
1987
4395
  if (!node) return { success: false, error: 'Mesh not found' };
1988
4396
  return { success: true, node };
1989
4397
  } catch (e: any) {
@@ -1995,6 +4403,8 @@ export class DaemonCommandRouter {
1995
4403
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1996
4404
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
1997
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;
1998
4408
  try {
1999
4409
  const { updateNode } = await import('../config/mesh-config.js');
2000
4410
  const policy = args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)
@@ -2023,6 +4433,8 @@ export class DaemonCommandRouter {
2023
4433
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2024
4434
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2025
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;
2026
4438
  try {
2027
4439
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2028
4440
  const mesh = meshRecord?.mesh;
@@ -2048,131 +4460,91 @@ export class DaemonCommandRouter {
2048
4460
  }
2049
4461
  }
2050
4462
 
2051
- 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': {
2052
4496
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2053
4497
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2054
4498
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
2055
- 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) {
2056
4522
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2057
4523
  const mesh = meshRecord?.mesh;
2058
4524
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2059
- if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2060
-
2061
- if (!node.isLocalWorktree || !node.workspace) {
2062
- return { success: false, error: `Refinery requires a local worktree node` };
2063
- }
2064
-
2065
- const sourceNode = node.clonedFromNodeId
2066
- ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2067
- : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2068
- const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2069
- if (!repoRoot) return { success: false, error: 'Source node repoRoot not found' };
2070
-
2071
- const { execFile } = await import('node:child_process');
2072
- const { promisify } = await import('node:util');
2073
- const execFileAsync = promisify(execFile);
2074
-
2075
- const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2076
- const branch = branchStdout.trim();
2077
- if (!branch) return { success: false, error: 'Could not determine branch of the worktree node' };
2078
-
2079
- const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2080
- const baseBranch = baseBranchStdout.trim();
2081
-
2082
- const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2083
- if (validationSummary.status === 'failed') {
2084
- return {
2085
- success: false,
2086
- code: 'validation_failed',
2087
- convergenceStatus: 'blocked_review',
2088
- error: 'Refinery validation gate failed; merge/refine was not attempted.',
2089
- branch,
2090
- into: baseBranch,
2091
- validationSummary,
2092
- finalBranchConvergenceState: {
2093
- branch,
2094
- baseBranch,
2095
- merged: false,
2096
- removed: false,
2097
- validation: 'failed',
2098
- status: 'blocked_review',
2099
- },
2100
- };
2101
- }
2102
- if (validationSummary.status === 'skipped') {
2103
- return {
2104
- success: false,
2105
- code: 'validation_unavailable',
2106
- convergenceStatus: 'blocked_review',
2107
- error: 'Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.',
2108
- branch,
2109
- into: baseBranch,
2110
- validationSummary,
2111
- finalBranchConvergenceState: {
2112
- branch,
2113
- baseBranch,
2114
- merged: false,
2115
- removed: false,
2116
- validation: 'unavailable',
2117
- status: 'blocked_review',
2118
- },
2119
- };
2120
- }
2121
-
2122
- try {
2123
- await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2124
- } catch (e: any) {
2125
- return {
2126
- success: false,
2127
- error: `Merge failed (conflicts?): ${e.message}`,
2128
- validationSummary,
2129
- finalBranchConvergenceState: {
2130
- branch,
2131
- baseBranch,
2132
- merged: false,
2133
- removed: false,
2134
- validation: 'passed',
2135
- status: 'not_mergeable',
2136
- },
2137
- };
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');
2138
4528
  }
2139
-
2140
- const removeResult = await this.execute('remove_mesh_node', {
2141
- meshId,
2142
- nodeId,
2143
- sessionCleanupMode: 'kill',
2144
- inlineMesh: args?.inlineMesh,
2145
- });
2146
-
2147
- try {
2148
- const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2149
- appendLedgerEntry(meshId, {
2150
- kind: 'node_removed',
2151
- nodeId,
2152
- payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary },
2153
- });
2154
- } catch {}
2155
-
2156
- return {
2157
- success: true,
2158
- merged: true,
2159
- branch,
2160
- into: baseBranch,
2161
- removeResult,
2162
- validationSummary,
2163
- finalBranchConvergenceState: {
2164
- branch: baseBranch,
2165
- mergedBranch: branch,
2166
- baseBranch,
2167
- merged: true,
2168
- removed: removeResult?.success !== false,
2169
- validation: 'passed',
2170
- status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
2171
- },
2172
- };
2173
- } catch (e: any) {
2174
- return { success: false, error: e.message };
2175
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);
2176
4548
  }
2177
4549
 
2178
4550
  case 'remove_mesh_node': {
@@ -2216,6 +4588,7 @@ export class DaemonCommandRouter {
2216
4588
  } else {
2217
4589
  const { removeNode } = await import('../config/mesh-config.js');
2218
4590
  removed = removeNode(meshId, nodeId);
4591
+ if (removed) this.invalidateAggregateMeshStatus(meshId);
2219
4592
  }
2220
4593
 
2221
4594
  // Record in task ledger
@@ -2253,6 +4626,8 @@ export class DaemonCommandRouter {
2253
4626
  if (!meshId) return { success: false, error: 'meshId required' };
2254
4627
  if (!sourceNodeId) return { success: false, error: 'sourceNodeId required' };
2255
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;
2256
4631
 
2257
4632
  try {
2258
4633
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
@@ -2301,6 +4676,7 @@ export class DaemonCommandRouter {
2301
4676
  policy: { ...(sourceNode.policy || {}) },
2302
4677
  });
2303
4678
  if (!node) return { success: false, error: 'Failed to register worktree node' };
4679
+ this.invalidateAggregateMeshStatus(meshId);
2304
4680
  }
2305
4681
 
2306
4682
  // Initialize submodules if policy allows (default: true)
@@ -2342,6 +4718,8 @@ export class DaemonCommandRouter {
2342
4718
  case 'trigger_mesh_queue': {
2343
4719
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2344
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;
2345
4723
  try {
2346
4724
  const { triggerMeshQueue } = await import('../mesh/mesh-events.js');
2347
4725
  if (meshId) {
@@ -2373,6 +4751,15 @@ export class DaemonCommandRouter {
2373
4751
  mesh = getMesh(meshId);
2374
4752
  }
2375
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
+ }
2376
4763
  if (!Array.isArray(mesh.nodes) || mesh.nodes.length === 0) return { success: false, error: 'No nodes in mesh' };
2377
4764
 
2378
4765
  const requestedCoordinatorNodeId = typeof args?.coordinatorNodeId === 'string'
@@ -2392,7 +4779,16 @@ export class DaemonCommandRouter {
2392
4779
  cliType,
2393
4780
  };
2394
4781
  }
2395
- 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() : '');
2396
4792
  if (!workspace) return { success: false, error: 'Coordinator node workspace required', meshId, cliType };
2397
4793
  if (!cliType) {
2398
4794
  const resolved = await resolveProviderTypeFromPriority({
@@ -2734,9 +5130,33 @@ export class DaemonCommandRouter {
2734
5130
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2735
5131
  if (!meshId) return { success: false, error: 'meshId required' };
2736
5132
  try {
2737
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
5133
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
2738
5134
  const mesh = meshRecord?.mesh;
2739
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';
2740
5160
 
2741
5161
  const { getMeshQueueStats, getQueue } = await import('../mesh/mesh-work-queue.js');
2742
5162
  const queue = getQueue(meshId);
@@ -2745,103 +5165,372 @@ export class DaemonCommandRouter {
2745
5165
  const { readLedgerEntries, getLedgerSummary } = await import('../mesh/mesh-ledger.js');
2746
5166
  const ledgerEntries = readLedgerEntries(meshId, { tail: 20 });
2747
5167
  const ledgerSummary = getLedgerSummary(meshId);
2748
-
5168
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions
5169
+ ? await this.deps.sessionHostControl.listSessions().catch(() => [])
5170
+ : [];
5171
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
5172
+
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
+ );
5247
+ const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes)
5248
+ ? selectedCoordinatorNodeId
5249
+ : undefined;
5250
+ const refreshedAt = new Date().toISOString();
2749
5251
  const nodeStatuses = [];
2750
- for (const node of mesh.nodes || []) {
5252
+ for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
5253
+ const nodeId = String(node.id || node.nodeId || '');
5254
+ const daemonId = readStringValue(node.daemonId);
5255
+ const providerPriority = readProviderPriorityFromPolicy(node.policy);
5256
+ const isSelfNode = Boolean(
5257
+ nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId,
5258
+ ) || Boolean(
5259
+ daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId),
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
+ });
2751
5267
  const status: Record<string, unknown> = {
2752
- nodeId: node.id || node.nodeId,
2753
- machineLabel: node.machineLabel || node.id || node.nodeId,
5268
+ 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',
2754
5273
  workspace: node.workspace,
2755
5274
  repoRoot: node.repoRoot,
2756
5275
  isLocalWorktree: node.isLocalWorktree,
2757
5276
  worktreeBranch: node.worktreeBranch,
2758
- daemonId: node.daemonId,
2759
- machineId: node.machineId,
5277
+ role: normalizeMeshDaemonRole(node.role) || (meshHost.hostNodeId && nodeId === meshHost.hostNodeId ? 'host' : undefined),
5278
+ daemonId,
5279
+ machineId: readMeshNodeMachineId(node as Record<string, unknown>) || node.machineId,
5280
+ machine: machineIdentity,
5281
+ machineStatus: node.machineStatus,
2760
5282
  health: 'unknown',
2761
5283
  providers: node.providers || [],
5284
+ providerPriority,
2762
5285
  activeSessions: [],
5286
+ activeSessionDetails: [],
5287
+ launchReady: false,
2763
5288
  };
2764
- if (node.workspace && typeof node.workspace === 'string') {
2765
- try {
2766
- const { execFile } = await import('node:child_process');
2767
- const { promisify } = await import('node:util');
2768
- const execFileAsync = promisify(execFile);
2769
-
2770
- const runGit = async (args: string[]): Promise<string> => {
2771
- const result = await execFileAsync('git', ['-C', node.workspace as string, ...args], {
2772
- encoding: 'utf8',
2773
- timeout: 10_000,
2774
- });
2775
- return result.stdout.trim();
2776
- };
2777
-
2778
- const branch = await runGit(['branch', '--show-current']).catch(() => '');
2779
- const porc = await runGit(['status', '--porcelain']).catch(() => '');
2780
- const headCommit = await runGit(['rev-parse', '--short', 'HEAD']).catch(() => null);
2781
- const headMessage = await runGit(['log', '-1', '--format=%s']).catch(() => null);
2782
- const upstream = await runGit(['rev-parse', '--abbrev-ref', '@{upstream}']).catch(() => null);
2783
- const aheadBehind = await runGit(['rev-list', '--left-right', '--count', '@{upstream}...HEAD']).catch(() => '');
2784
- const stashCount = await runGit(['stash', 'list']).catch(() => '');
2785
-
2786
- let ahead = 0, behind = 0;
2787
- if (aheadBehind) {
2788
- const parts = aheadBehind.split(/\s+/);
2789
- if (parts.length >= 2) {
2790
- behind = parseInt(parts[0], 10) || 0;
2791
- ahead = parseInt(parts[1], 10) || 0;
5289
+ if (isSelfNode) {
5290
+ status.connection = {
5291
+ perspective: 'selected_coordinator',
5292
+ source: 'mesh_peer_status',
5293
+ state: 'self',
5294
+ transport: 'local',
5295
+ reported: true,
5296
+ reason: 'Selected coordinator daemon',
5297
+ lastStateChangeAt: refreshedAt,
5298
+ };
5299
+ } else if (daemonId) {
5300
+ const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
5301
+ status.connection = connection ?? {
5302
+ perspective: 'selected_coordinator',
5303
+ source: 'not_reported',
5304
+ state: 'unknown',
5305
+ transport: 'unknown',
5306
+ reported: false,
5307
+ reason: 'No live mesh peer telemetry reported by the selected coordinator yet.',
5308
+ };
5309
+ } else {
5310
+ status.connection = {
5311
+ perspective: 'selected_coordinator',
5312
+ source: 'not_reported',
5313
+ state: 'unknown',
5314
+ transport: 'unknown',
5315
+ reported: false,
5316
+ reason: 'Node has no daemon id, so mesh transport cannot be reported from the selected coordinator.',
5317
+ };
5318
+ }
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;
5333
+ if (matchedLiveSessionRecords.length > 0) {
5334
+ const sessionIds = matchedLiveSessionRecords
5335
+ .map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
5336
+ .filter(Boolean);
5337
+ const providerTypes = matchedLiveSessionRecords
5338
+ .map((record: any) => readStringValue(record?.providerType))
5339
+ .filter(Boolean) as string[];
5340
+ status.activeSessions = sessionIds;
5341
+ status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
5342
+ if (providerTypes.length > 0) {
5343
+ status.providers = Array.from(new Set([...(Array.isArray(status.providers) ? status.providers as string[] : []), ...providerTypes]));
5344
+ }
5345
+ }
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
+ }
2792
5415
  }
2793
5416
  }
2794
-
2795
- const dirty = porc.length > 0;
2796
- const lines = porc ? porc.split('\n').filter(Boolean) : [];
2797
- let staged = 0, modified = 0, untracked = 0, deleted = 0, renamed = 0;
2798
- for (const line of lines) {
2799
- const xy = line.slice(0, 2);
2800
- if (xy[0] !== ' ' && xy[0] !== '?') staged++;
2801
- if (xy[1] === 'M') modified++;
2802
- if (xy[1] === 'D') deleted++;
2803
- if (xy[0] === 'R' || xy[1] === 'R') renamed++;
2804
- if (xy === '??') untracked++;
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
+ }
2805
5465
  }
2806
-
2807
- status.git = {
2808
- workspace: node.workspace,
2809
- repoRoot: node.workspace,
2810
- isGitRepo: true,
2811
- branch: branch || null,
2812
- headCommit,
2813
- headMessage,
2814
- upstream,
2815
- ahead,
2816
- behind,
2817
- staged,
2818
- modified,
2819
- untracked,
2820
- deleted,
2821
- renamed,
2822
- hasConflicts: false,
2823
- conflictFiles: [],
2824
- stashCount: stashCount ? stashCount.split('\n').filter(Boolean).length : 0,
2825
- lastCheckedAt: Date.now(),
2826
- };
2827
- status.health = branch ? (dirty ? 'dirty' : 'online') : 'degraded';
2828
- } catch {
2829
- status.health = 'degraded';
2830
5466
  }
5467
+ } else {
5468
+ applyCachedInlineMeshNodeStatus(status, node);
2831
5469
  }
5470
+ applyInlineMeshBranchConvergence(mesh, node, status);
5471
+ finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
2832
5472
  nodeStatuses.push(status);
2833
5473
  }
2834
5474
 
2835
- return {
5475
+ const pendingCoordinatorEvents = drainPendingMeshCoordinatorEvents(meshId);
5476
+ const statusResult = {
2836
5477
  success: true,
2837
5478
  meshId: mesh.id,
2838
5479
  meshName: mesh.name,
2839
5480
  repoIdentity: mesh.repoIdentity,
2840
5481
  defaultBranch: mesh.defaultBranch,
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),
2841
5514
  nodes: nodeStatuses,
2842
5515
  queue: { tasks: queue, summary: queueSummary },
2843
5516
  ledger: { entries: ledgerEntries, summary: ledgerSummary },
5517
+ ...(pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {}),
2844
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;
2845
5534
  } catch (e: any) {
2846
5535
  return { success: false, error: e.message };
2847
5536
  }
@@ -2899,7 +5588,7 @@ export class DaemonCommandRouter {
2899
5588
 
2900
5589
  // 3. Kill OS process if requested
2901
5590
  if (killProcess) {
2902
- const running = isIdeRunning(ideType);
5591
+ const running = await isIdeRunning(ideType);
2903
5592
  if (running) {
2904
5593
  LOG.info('StopIDE', `Killing IDE process: ${ideType}`);
2905
5594
  const killed = await killIdeProcess(ideType);