@adhdev/daemon-core 0.9.82-rc.2 → 0.9.82-rc.21

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.
@@ -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';
@@ -112,6 +114,29 @@ function readBooleanValue(...values: unknown[]): boolean | undefined {
112
114
  return undefined;
113
115
  }
114
116
 
117
+ function readGitSubmodules(value: unknown): GitSubmoduleStatus[] | undefined {
118
+ if (!Array.isArray(value)) return undefined;
119
+ const submodules = value
120
+ .map(entry => {
121
+ const submodule = readObjectRecord(entry);
122
+ const path = readStringValue(submodule.path);
123
+ const commit = readStringValue(submodule.commit);
124
+ const repoPath = readStringValue(submodule.repoPath, submodule.repo_root);
125
+ if (!path || !commit || !repoPath) return null;
126
+ return {
127
+ path,
128
+ commit,
129
+ repoPath,
130
+ dirty: readBooleanValue(submodule.dirty) ?? false,
131
+ outOfSync: readBooleanValue(submodule.outOfSync, submodule.out_of_sync) ?? false,
132
+ lastCheckedAt: readNumberValue(submodule.lastCheckedAt, submodule.last_checked_at) ?? Date.now(),
133
+ ...(readStringValue(submodule.error) ? { error: readStringValue(submodule.error) } : {}),
134
+ };
135
+ })
136
+ .filter((entry): entry is GitSubmoduleStatus => entry !== null);
137
+ return submodules.length > 0 ? submodules : undefined;
138
+ }
139
+
115
140
  function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | undefined {
116
141
  const cachedStatus = readObjectRecord(node?.cachedStatus);
117
142
  const cachedGit = readObjectRecord(cachedStatus.git);
@@ -123,6 +148,7 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
123
148
  const hasConflicts = readBooleanValue(cachedGit.hasConflicts) ?? conflictCount > 0;
124
149
  const isGitRepo = readBooleanValue(cachedGit.isGitRepo);
125
150
  if (isGitRepo !== undefined) {
151
+ const submodules = readGitSubmodules(cachedGit.submodules);
126
152
  return {
127
153
  workspace: readStringValue(cachedGit.workspace, node?.workspace) || '',
128
154
  repoRoot: readStringValue(cachedGit.repoRoot, node?.repoRoot, node?.workspace) || null,
@@ -142,6 +168,7 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
142
168
  conflictFiles,
143
169
  stashCount: readNumberValue(cachedGit.stashCount) ?? 0,
144
170
  lastCheckedAt: readNumberValue(cachedGit.lastCheckedAt) ?? Date.now(),
171
+ ...(submodules ? { submodules } : {}),
145
172
  };
146
173
  }
147
174
  }
@@ -171,6 +198,7 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
171
198
  : [];
172
199
  const conflictCount = readNumberValue(status.conflicts) ?? conflictFiles.length;
173
200
  const hasConflicts = readBooleanValue(status.hasConflicts) ?? conflictCount > 0;
201
+ const submodules = readGitSubmodules(status.submodules);
174
202
  return {
175
203
  workspace: readStringValue(status.workspace, node?.workspace) || '',
176
204
  repoRoot: readStringValue(status.repoRoot, node?.repoRoot, node?.workspace) || null,
@@ -190,29 +218,196 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
190
218
  conflictFiles,
191
219
  stashCount: readNumberValue(status.stashCount) ?? 0,
192
220
  lastCheckedAt: Date.now(),
221
+ ...(submodules ? { submodules } : {}),
193
222
  };
194
223
  }
195
224
 
225
+ function hasGitWorktreeChanges(git: Record<string, unknown> | null | undefined): boolean {
226
+ if (!git) return false;
227
+ return Number(git.staged || 0) + Number(git.modified || 0) + Number(git.untracked || 0) + Number(git.deleted || 0) + Number(git.renamed || 0) > 0;
228
+ }
229
+
230
+ function getGitSubmoduleDriftState(git: Record<string, unknown> | null | undefined): { dirty: boolean; outOfSync: boolean } {
231
+ const submodules = Array.isArray(git?.submodules) ? git.submodules : [];
232
+ let dirty = false;
233
+ let outOfSync = false;
234
+ for (const entry of submodules) {
235
+ const submodule = readObjectRecord(entry);
236
+ if (readBooleanValue(submodule.dirty) === true) dirty = true;
237
+ if (readBooleanValue(submodule.outOfSync) === true || !!readStringValue(submodule.error)) outOfSync = true;
238
+ }
239
+ return { dirty, outOfSync };
240
+ }
241
+
242
+ function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undefined): 'online' | 'dirty' | 'degraded' {
243
+ if (!git || readBooleanValue(git.isGitRepo) === false) return 'degraded';
244
+ const branch = readStringValue(git.branch);
245
+ if (!branch) return 'degraded';
246
+ const submoduleDrift = getGitSubmoduleDriftState(git);
247
+ if (submoduleDrift.outOfSync) return 'degraded';
248
+ if (submoduleDrift.dirty || hasGitWorktreeChanges(git)) return 'dirty';
249
+ return 'online';
250
+ }
251
+
252
+ function readCachedInlineMeshActiveSessions(node: any): string[] {
253
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
254
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
255
+ const fallbackSession = Object.keys(activeSession).length
256
+ ? activeSession
257
+ : readObjectRecord(node?.activeSession ?? node?.active_session);
258
+ const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
259
+ return sessionId ? [sessionId] : [];
260
+ }
261
+
262
+ function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<string, unknown>> {
263
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
264
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
265
+ const fallbackSession = Object.keys(activeSession).length
266
+ ? activeSession
267
+ : readObjectRecord(node?.activeSession ?? node?.active_session);
268
+ const sessionId = readStringValue(
269
+ fallbackSession.id,
270
+ fallbackSession.sessionId,
271
+ fallbackSession.session_id,
272
+ node?.activeSessionId,
273
+ node?.active_session_id,
274
+ node?.sessionId,
275
+ node?.session_id,
276
+ );
277
+ if (!sessionId) return [];
278
+ return [{
279
+ sessionId,
280
+ providerType: readStringValue(
281
+ fallbackSession.providerType,
282
+ fallbackSession.provider_type,
283
+ fallbackSession.cliType,
284
+ fallbackSession.cli_type,
285
+ fallbackSession.provider,
286
+ node?.providerType,
287
+ node?.provider_type,
288
+ ),
289
+ state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
290
+ lifecycle: readStringValue(fallbackSession.lifecycle),
291
+ title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
292
+ workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
293
+ lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
294
+ recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
295
+ isCached: true,
296
+ }];
297
+ }
298
+
299
+ function readLiveMeshSessionState(record: any): string | undefined {
300
+ return readStringValue(
301
+ record?.meta?.sessionStatus,
302
+ record?.meta?.status,
303
+ record?.meta?.providerStatus,
304
+ record?.status,
305
+ record?.state,
306
+ record?.lifecycle,
307
+ );
308
+ }
309
+
310
+ function toIsoTimestamp(value: unknown): string | null {
311
+ if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString();
312
+ const stringValue = readStringValue(value);
313
+ return stringValue || null;
314
+ }
315
+
316
+ function summarizeMeshSessionRecord(record: any): Record<string, unknown> {
317
+ return {
318
+ sessionId: readStringValue(record?.sessionId) || 'unknown',
319
+ providerType: readStringValue(record?.providerType),
320
+ state: readLiveMeshSessionState(record),
321
+ lifecycle: readStringValue(record?.lifecycle),
322
+ surfaceKind: getSessionHostSurfaceKind(record as any),
323
+ recoveryState: readStringValue(record?.meta?.runtimeRecoveryState) ?? null,
324
+ workspace: readStringValue(record?.workspace) ?? null,
325
+ title: readStringValue(record?.displayName, record?.workspaceLabel) ?? null,
326
+ lastActivityAt: toIsoTimestamp(record?.updatedAt ?? record?.lastActivityAt ?? record?.last_activity_at),
327
+ isCached: false,
328
+ };
329
+ }
330
+
331
+ function readLiveMeshNodeWorkspace(args: {
332
+ meshId: string;
333
+ nodeId: string;
334
+ liveSessionRecords: any[];
335
+ allowCoordinatorSession?: boolean;
336
+ }): string {
337
+ const directNodeWorkspace = args.liveSessionRecords.find((record) => (
338
+ readStringValue(record?.meta?.meshNodeId) === args.nodeId
339
+ && readStringValue(record?.workspace)
340
+ ));
341
+ if (directNodeWorkspace) {
342
+ return readStringValue(directNodeWorkspace.workspace) || '';
343
+ }
344
+
345
+ if (args.allowCoordinatorSession) {
346
+ const coordinatorWorkspace = args.liveSessionRecords.find((record) => (
347
+ readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId
348
+ && readStringValue(record?.workspace)
349
+ ));
350
+ if (coordinatorWorkspace) {
351
+ return readStringValue(coordinatorWorkspace.workspace) || '';
352
+ }
353
+ }
354
+
355
+ return '';
356
+ }
357
+
358
+ function collectLiveMeshSessionRecords(args: {
359
+ meshId: string;
360
+ node: any;
361
+ nodeId: string;
362
+ liveSessionRecords: any[];
363
+ allowCoordinatorSession?: boolean;
364
+ }): any[] {
365
+ const matches = args.liveSessionRecords.filter((record) => {
366
+ if (readStringValue(record?.meta?.meshNodeId) === args.nodeId) return true;
367
+ const recordWorkspace = readStringValue(record?.workspace);
368
+ const nodeWorkspace = readStringValue(args.node?.workspace);
369
+ return !!recordWorkspace && !!nodeWorkspace && recordWorkspace === nodeWorkspace;
370
+ });
371
+
372
+ if (args.allowCoordinatorSession) {
373
+ for (const record of args.liveSessionRecords) {
374
+ if (readStringValue(record?.meta?.meshCoordinatorFor) !== args.meshId) continue;
375
+ const sessionId = readStringValue(record?.sessionId);
376
+ if (sessionId && matches.some((entry) => readStringValue(entry?.sessionId) === sessionId)) continue;
377
+ matches.push(record);
378
+ }
379
+ }
380
+
381
+ return matches;
382
+ }
383
+
196
384
  function applyCachedInlineMeshNodeStatus(status: Record<string, unknown>, node: any): boolean {
197
385
  const cachedStatus = readObjectRecord(node?.cachedStatus);
198
386
  const git = buildCachedInlineMeshGitStatus(node);
199
387
  const error = readStringValue(cachedStatus.error, node?.error);
200
388
  const health = readStringValue(cachedStatus.health, node?.health);
201
389
  const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
202
- if (!git && !error && !health) return false;
203
- if (!machineStatus && !git && !error) return false;
390
+ const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
391
+ const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
392
+ const activeSessions = readCachedInlineMeshActiveSessions(node);
393
+ const activeSessionDetails = readCachedInlineMeshActiveSessionDetails(node);
394
+ if (!git && !error && !health && !machineStatus && !lastSeenAt && !updatedAt && activeSessions.length === 0) return false;
204
395
  if (git) status.git = git;
205
396
  if (error) status.error = error;
397
+ if (machineStatus) status.machineStatus = machineStatus;
398
+ if (lastSeenAt) status.lastSeenAt = lastSeenAt;
399
+ if (updatedAt) status.updatedAt = updatedAt;
400
+ if (activeSessions.length > 0) status.activeSessions = activeSessions;
401
+ if (activeSessionDetails.length > 0) status.activeSessionDetails = activeSessionDetails;
206
402
  if (health) {
207
403
  status.health = health;
208
404
  return true;
209
405
  }
210
406
  if (git) {
211
- const dirty = Number(git.staged || 0) + Number(git.modified || 0) + Number(git.untracked || 0) + Number(git.deleted || 0) + Number(git.renamed || 0) > 0;
212
- status.health = git.isGitRepo === false ? 'degraded' : dirty ? 'dirty' : 'online';
407
+ status.health = deriveMeshNodeHealthFromGit(git);
213
408
  return true;
214
409
  }
215
- return false;
410
+ return activeSessions.length > 0 || !!machineStatus || !!lastSeenAt || !!updatedAt;
216
411
  }
217
412
 
218
413
  async function resolveProviderTypeFromPriority(args: {
@@ -632,6 +827,8 @@ export interface CommandRouterDeps {
632
827
  statusVersion?: string;
633
828
  /** Session host control plane */
634
829
  sessionHostControl?: SessionHostControlPlane | null;
830
+ /** Selected-coordinator mesh peer telemetry surface for target daemons, when supported by the runtime. */
831
+ getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
635
832
  }
636
833
 
637
834
  export interface CommandRouterResult {
@@ -756,29 +953,40 @@ export class DaemonCommandRouter {
756
953
 
757
954
  public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
758
955
  if (inlineMesh && typeof inlineMesh === 'object') {
759
- this.inlineMeshCache.set(meshId, inlineMesh as any);
760
- return inlineMesh as any;
956
+ return this.warmInlineMeshCache(meshId, inlineMesh);
761
957
  }
762
958
  return this.inlineMeshCache.get(meshId);
763
959
  }
764
960
 
961
+ private warmInlineMeshCache(meshId: string, inlineMesh?: unknown): any | undefined {
962
+ if (!inlineMesh || typeof inlineMesh !== 'object') return undefined;
963
+ const cached = this.inlineMeshCache.get(meshId);
964
+ if (cached) return cached;
965
+ this.inlineMeshCache.set(meshId, inlineMesh as any);
966
+ return inlineMesh as any;
967
+ }
968
+
765
969
  private async getMeshForCommand(
766
970
  meshId: string,
767
971
  inlineMesh?: unknown,
768
972
  options?: { preferInline?: boolean },
769
- ): Promise<{ mesh: any; inline: boolean } | null> {
973
+ ): Promise<{ mesh: any; inline: boolean; source: 'inline_cache' | 'inline_bootstrap' | 'local_config' } | null> {
770
974
  const preferInline = options?.preferInline === true;
771
975
  if (preferInline) {
772
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
773
- if (cached) return { mesh: cached, inline: true };
976
+ const cached = this.getCachedInlineMesh(meshId);
977
+ if (cached) return { mesh: cached, inline: true, source: 'inline_cache' };
978
+ const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
979
+ if (warmedInline) return { mesh: warmedInline, inline: true, source: 'inline_bootstrap' };
774
980
  }
775
981
  try {
776
982
  const { getMesh } = await import('../config/mesh-config.js');
777
983
  const mesh = getMesh(meshId);
778
- if (mesh) return { mesh, inline: false };
984
+ if (mesh) return { mesh, inline: false, source: 'local_config' };
779
985
  } catch { /* fall through to inline cache */ }
780
- const cached = this.getCachedInlineMesh(meshId, inlineMesh);
781
- return cached ? { mesh: cached, inline: true } : null;
986
+ const cached = this.getCachedInlineMesh(meshId);
987
+ if (cached) return { mesh: cached, inline: true, source: 'inline_cache' };
988
+ const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
989
+ return warmedInline ? { mesh: warmedInline, inline: true, source: 'inline_bootstrap' } : null;
782
990
  }
783
991
 
784
992
  private updateInlineMeshNode(meshId: string, mesh: any, node: any): void {
@@ -1075,6 +1283,7 @@ export class DaemonCommandRouter {
1075
1283
  const deletedSessionIds: string[] = [];
1076
1284
  const skippedSessionIds: string[] = [];
1077
1285
  const skippedLiveSessionIds: string[] = [];
1286
+ const skippedCoordinatorSessionIds: string[] = [];
1078
1287
  const deleteUnsupportedSessionIds: string[] = [];
1079
1288
  const recordsRemainSessionIds: string[] = [];
1080
1289
  const errors: Array<{ sessionId: string; error: string }> = [];
@@ -1109,6 +1318,12 @@ export class DaemonCommandRouter {
1109
1318
  const completed = this.isCompletedHostedSession(record);
1110
1319
  const surfaceKind = getSessionHostSurfaceKind(record);
1111
1320
  const liveRuntime = surfaceKind === 'live_runtime';
1321
+ const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
1322
+ if (!hasExplicitSessionIds && coordinatorSession) {
1323
+ skippedSessionIds.push(sessionId);
1324
+ skippedCoordinatorSessionIds.push(sessionId);
1325
+ continue;
1326
+ }
1112
1327
  if (!hasExplicitSessionIds && liveRuntime) {
1113
1328
  skippedSessionIds.push(sessionId);
1114
1329
  skippedLiveSessionIds.push(sessionId);
@@ -1178,6 +1393,7 @@ export class DaemonCommandRouter {
1178
1393
  deletedSessionIds,
1179
1394
  skippedSessionIds,
1180
1395
  skippedLiveSessionIds,
1396
+ skippedCoordinatorSessionIds,
1181
1397
  ...(deleteUnsupported ? {
1182
1398
  deleteUnsupported: true,
1183
1399
  effectiveCleanup: args.mode === 'stop_and_delete'
@@ -1937,14 +2153,8 @@ export class DaemonCommandRouter {
1937
2153
  case 'get_mesh': {
1938
2154
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1939
2155
  if (!meshId) return { success: false, error: 'meshId required' };
1940
- try {
1941
- const { getMesh } = await import('../config/mesh-config.js');
1942
- const mesh = getMesh(meshId);
1943
- if (mesh) return { success: true, mesh };
1944
- } catch { /* fall through to inline cache */ }
1945
- // Fallback: check in-memory cache for cloud-originating meshes
1946
- const cached = this.inlineMeshCache.get(meshId);
1947
- if (cached) return { success: true, mesh: cached };
2156
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
2157
+ if (meshRecord?.mesh) return { success: true, mesh: meshRecord.mesh };
1948
2158
  return { success: false, error: 'Mesh not found' };
1949
2159
  }
1950
2160
 
@@ -2531,7 +2741,16 @@ export class DaemonCommandRouter {
2531
2741
  cliType,
2532
2742
  };
2533
2743
  }
2534
- const workspace = typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '';
2744
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions
2745
+ ? await this.deps.sessionHostControl.listSessions().catch(() => [])
2746
+ : [];
2747
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
2748
+ const workspace = readLiveMeshNodeWorkspace({
2749
+ meshId,
2750
+ nodeId: String(coordinatorNode.id || coordinatorNode.nodeId || preferredCoordinatorNodeId || ''),
2751
+ liveSessionRecords: liveMeshSessions,
2752
+ allowCoordinatorSession: true,
2753
+ }) || (typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '');
2535
2754
  if (!workspace) return { success: false, error: 'Coordinator node workspace required', meshId, cliType };
2536
2755
  if (!cliType) {
2537
2756
  const resolved = await resolveProviderTypeFromPriority({
@@ -2884,90 +3103,120 @@ export class DaemonCommandRouter {
2884
3103
  const { readLedgerEntries, getLedgerSummary } = await import('../mesh/mesh-ledger.js');
2885
3104
  const ledgerEntries = readLedgerEntries(meshId, { tail: 20 });
2886
3105
  const ledgerSummary = getLedgerSummary(meshId);
3106
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions
3107
+ ? await this.deps.sessionHostControl.listSessions().catch(() => [])
3108
+ : [];
3109
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
2887
3110
 
3111
+ const localMachineId = loadConfig().machineId || '';
3112
+ const selectedCoordinatorNodeId = readStringValue(
3113
+ mesh.coordinator?.preferredNodeId,
3114
+ (mesh.nodes?.[0] as any)?.id,
3115
+ (mesh.nodes?.[0] as any)?.nodeId,
3116
+ );
3117
+ const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes)
3118
+ ? selectedCoordinatorNodeId
3119
+ : undefined;
3120
+ const refreshedAt = new Date().toISOString();
2888
3121
  const nodeStatuses = [];
2889
- for (const node of mesh.nodes || []) {
3122
+ for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
3123
+ const nodeId = String(node.id || node.nodeId || '');
3124
+ const daemonId = readStringValue(node.daemonId);
3125
+ const providerPriority = readProviderPriorityFromPolicy(node.policy);
3126
+ const isSelfNode = Boolean(
3127
+ nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId,
3128
+ ) || Boolean(
3129
+ daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId),
3130
+ ) || Boolean(meshRecord?.inline && nodeIndex === 0);
2890
3131
  const status: Record<string, unknown> = {
2891
- nodeId: node.id || node.nodeId,
3132
+ nodeId,
2892
3133
  machineLabel: node.machineLabel || node.id || node.nodeId,
2893
3134
  workspace: node.workspace,
2894
3135
  repoRoot: node.repoRoot,
2895
3136
  isLocalWorktree: node.isLocalWorktree,
2896
3137
  worktreeBranch: node.worktreeBranch,
2897
- daemonId: node.daemonId,
3138
+ daemonId,
2898
3139
  machineId: node.machineId,
3140
+ machineStatus: node.machineStatus,
2899
3141
  health: 'unknown',
2900
3142
  providers: node.providers || [],
3143
+ providerPriority,
2901
3144
  activeSessions: [],
3145
+ activeSessionDetails: [],
3146
+ launchReady: false,
2902
3147
  };
2903
- if (node.workspace && typeof node.workspace === 'string') {
2904
- if (!fs.existsSync(node.workspace as string) && applyCachedInlineMeshNodeStatus(status, node)) {
3148
+ if (isSelfNode) {
3149
+ status.connection = {
3150
+ perspective: 'selected_coordinator',
3151
+ source: 'mesh_peer_status',
3152
+ state: 'self',
3153
+ transport: 'local',
3154
+ reported: true,
3155
+ reason: 'Selected coordinator daemon',
3156
+ lastStateChangeAt: refreshedAt,
3157
+ };
3158
+ } else if (daemonId) {
3159
+ const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
3160
+ status.connection = connection ?? {
3161
+ perspective: 'selected_coordinator',
3162
+ source: 'not_reported',
3163
+ state: 'unknown',
3164
+ transport: 'unknown',
3165
+ reported: false,
3166
+ reason: 'No live mesh peer telemetry reported by the selected coordinator yet.',
3167
+ };
3168
+ } else {
3169
+ status.connection = {
3170
+ perspective: 'selected_coordinator',
3171
+ source: 'not_reported',
3172
+ state: 'unknown',
3173
+ transport: 'unknown',
3174
+ reported: false,
3175
+ reason: 'Node has no daemon id, so mesh transport cannot be reported from the selected coordinator.',
3176
+ };
3177
+ }
3178
+ const matchedLiveSessionRecords = collectLiveMeshSessionRecords({
3179
+ meshId,
3180
+ node,
3181
+ nodeId,
3182
+ liveSessionRecords: liveMeshSessions,
3183
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
3184
+ });
3185
+ const workspace = readLiveMeshNodeWorkspace({
3186
+ meshId,
3187
+ nodeId,
3188
+ liveSessionRecords: matchedLiveSessionRecords,
3189
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
3190
+ }) || (typeof node.workspace === 'string' ? node.workspace : '');
3191
+ status.workspace = workspace || node.workspace;
3192
+ if (matchedLiveSessionRecords.length > 0) {
3193
+ const sessionIds = matchedLiveSessionRecords
3194
+ .map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
3195
+ .filter(Boolean);
3196
+ const providerTypes = matchedLiveSessionRecords
3197
+ .map((record: any) => readStringValue(record?.providerType))
3198
+ .filter(Boolean) as string[];
3199
+ status.activeSessions = sessionIds;
3200
+ status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
3201
+ if (providerTypes.length > 0) {
3202
+ status.providers = Array.from(new Set([...(Array.isArray(status.providers) ? status.providers as string[] : []), ...providerTypes]));
3203
+ }
3204
+ }
3205
+ if (workspace) {
3206
+ if (!fs.existsSync(workspace) && applyCachedInlineMeshNodeStatus(status, node)) {
3207
+ status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === 'online' || isSelfNode);
2905
3208
  nodeStatuses.push(status);
2906
3209
  continue;
2907
3210
  }
2908
3211
  try {
2909
- const { execFile } = await import('node:child_process');
2910
- const { promisify } = await import('node:util');
2911
- const execFileAsync = promisify(execFile);
2912
-
2913
- const runGit = async (args: string[]): Promise<string> => {
2914
- const result = await execFileAsync('git', ['-C', node.workspace as string, ...args], {
2915
- encoding: 'utf8',
2916
- timeout: 10_000,
2917
- });
2918
- return result.stdout.trim();
2919
- };
2920
-
2921
- const branch = await runGit(['branch', '--show-current']).catch(() => '');
2922
- const porc = await runGit(['status', '--porcelain']).catch(() => '');
2923
- const headCommit = await runGit(['rev-parse', '--short', 'HEAD']).catch(() => null);
2924
- const headMessage = await runGit(['log', '-1', '--format=%s']).catch(() => null);
2925
- const upstream = await runGit(['rev-parse', '--abbrev-ref', '@{upstream}']).catch(() => null);
2926
- const aheadBehind = await runGit(['rev-list', '--left-right', '--count', '@{upstream}...HEAD']).catch(() => '');
2927
- const stashCount = await runGit(['stash', 'list']).catch(() => '');
2928
-
2929
- let ahead = 0, behind = 0;
2930
- if (aheadBehind) {
2931
- const parts = aheadBehind.split(/\s+/);
2932
- if (parts.length >= 2) {
2933
- behind = parseInt(parts[0], 10) || 0;
2934
- ahead = parseInt(parts[1], 10) || 0;
2935
- }
2936
- }
2937
-
2938
- const dirty = porc.length > 0;
2939
- const lines = porc ? porc.split('\n').filter(Boolean) : [];
2940
- let staged = 0, modified = 0, untracked = 0, deleted = 0, renamed = 0;
2941
- for (const line of lines) {
2942
- const xy = line.slice(0, 2);
2943
- if (xy[0] !== ' ' && xy[0] !== '?') staged++;
2944
- if (xy[1] === 'M') modified++;
2945
- if (xy[1] === 'D') deleted++;
2946
- if (xy[0] === 'R' || xy[1] === 'R') renamed++;
2947
- if (xy === '??') untracked++;
3212
+ const gitStatus = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
3213
+ status.git = gitStatus;
3214
+ if (gitStatus.isGitRepo) {
3215
+ status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
3216
+ } else {
3217
+ status.health = 'degraded';
3218
+ if (gitStatus.error && !status.error) status.error = gitStatus.error;
2948
3219
  }
2949
-
2950
- status.git = {
2951
- workspace: node.workspace,
2952
- repoRoot: node.workspace,
2953
- isGitRepo: true,
2954
- branch: branch || null,
2955
- headCommit,
2956
- headMessage,
2957
- upstream,
2958
- ahead,
2959
- behind,
2960
- staged,
2961
- modified,
2962
- untracked,
2963
- deleted,
2964
- renamed,
2965
- hasConflicts: false,
2966
- conflictFiles: [],
2967
- stashCount: stashCount ? stashCount.split('\n').filter(Boolean).length : 0,
2968
- lastCheckedAt: Date.now(),
2969
- };
2970
- status.health = branch ? (dirty ? 'dirty' : 'online') : 'degraded';
2971
3220
  } catch {
2972
3221
  if (!applyCachedInlineMeshNodeStatus(status, node)) {
2973
3222
  status.health = 'degraded';
@@ -2976,6 +3225,7 @@ export class DaemonCommandRouter {
2976
3225
  } else {
2977
3226
  applyCachedInlineMeshNodeStatus(status, node);
2978
3227
  }
3228
+ status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === 'online' || isSelfNode);
2979
3229
  nodeStatuses.push(status);
2980
3230
  }
2981
3231
 
@@ -2985,6 +3235,16 @@ export class DaemonCommandRouter {
2985
3235
  meshName: mesh.name,
2986
3236
  repoIdentity: mesh.repoIdentity,
2987
3237
  defaultBranch: mesh.defaultBranch,
3238
+ refreshedAt: new Date().toISOString(),
3239
+ sourceOfTruth: {
3240
+ membership: meshRecord?.source === 'inline_cache'
3241
+ ? 'coordinator_inline_mesh_cache'
3242
+ : meshRecord?.source === 'local_config'
3243
+ ? 'local_mesh_config'
3244
+ : 'inline_bootstrap_snapshot',
3245
+ coordinatorOwnsLiveTruth: meshRecord?.source !== 'inline_bootstrap',
3246
+ historicalEvidenceOnly: ['recoveryHints', 'ledger.summary', 'queue.summary'],
3247
+ },
2988
3248
  nodes: nodeStatuses,
2989
3249
  queue: { tasks: queue, summary: queueSummary },
2990
3250
  ledger: { entries: ledgerEntries, summary: ledgerSummary },
@@ -62,7 +62,7 @@ export interface GitPushResult extends GitRepoIdentity {
62
62
  }
63
63
 
64
64
  export interface GitCommandServices {
65
- getStatus?: (params: { workspace: string }) => Promise<GitRepoStatus> | GitRepoStatus;
65
+ getStatus?: (params: { workspace: string; refreshUpstream?: boolean }) => Promise<GitRepoStatus> | GitRepoStatus;
66
66
  getDiffSummary?: (params: { workspace: string; staged?: boolean }) => Promise<GitDiffSummary> | GitDiffSummary;
67
67
  getDiffFile?: (params: { workspace: string; path: string; staged?: boolean }) => Promise<GitFileDiff> | GitFileDiff;
68
68
  createSnapshot?: (params: {
@@ -171,7 +171,7 @@ const defaultSnapshotStore = createGitSnapshotStore({
171
171
 
172
172
  export function createDefaultGitCommandServices(): GitCommandServices {
173
173
  return {
174
- getStatus: ({ workspace }) => getGitRepoStatus(workspace),
174
+ getStatus: ({ workspace, refreshUpstream }) => getGitRepoStatus(workspace, { refreshUpstream }),
175
175
  getDiffSummary: ({ workspace }) => getGitDiffSummary(workspace),
176
176
  getDiffFile: ({ workspace, path: filePath }) => getGitFileDiff(workspace, filePath),
177
177
  createSnapshot: ({ workspace, reason, sessionId, turnId }) => defaultSnapshotStore.create({
@@ -290,7 +290,7 @@ export async function handleGitCommand(
290
290
  switch (command) {
291
291
  case 'git_status': {
292
292
  if (!services.getStatus) return serviceNotImplemented(command);
293
- const status = await runService(() => services.getStatus!({ workspace }));
293
+ const status = await runService(() => services.getStatus!({ workspace, refreshUpstream: optionalBoolean(args?.refreshUpstream) }));
294
294
  return 'success' in status ? status : { success: true, status };
295
295
  }
296
296