@adhdev/daemon-core 0.9.82-rc.3 → 0.9.82-rc.31
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.
- package/dist/boot/daemon-lifecycle.d.ts +2 -0
- package/dist/commands/router.d.ts +5 -0
- package/dist/git/git-commands.d.ts +1 -0
- package/dist/git/git-status.d.ts +5 -0
- package/dist/git/git-types.d.ts +10 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1052 -292
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1051 -292
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +17 -5
- package/dist/mesh/mesh-work-queue.d.ts +3 -1
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/repo-mesh-types.d.ts +128 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +3 -0
- package/src/commands/router.ts +836 -149
- package/src/git/git-commands.ts +3 -3
- package/src/git/git-status.ts +97 -6
- package/src/git/git-summary.ts +3 -0
- package/src/git/git-types.ts +11 -0
- package/src/index.ts +9 -1
- package/src/mesh/mesh-events.ts +168 -30
- package/src/mesh/mesh-work-queue.ts +135 -119
- package/src/providers/chat-message-normalization.ts +3 -1
- package/src/repo-mesh-types.ts +138 -0
package/src/commands/router.ts
CHANGED
|
@@ -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,58 +114,44 @@ function readBooleanValue(...values: unknown[]): boolean | undefined {
|
|
|
112
114
|
return undefined;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
function
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
function joinRepoPath(root: string | undefined, relativePath: string | undefined): string | undefined {
|
|
118
|
+
const normalizedRoot = typeof root === 'string' ? root.trim().replace(/[\\/]+$/, '') : '';
|
|
119
|
+
const normalizedPath = typeof relativePath === 'string' ? relativePath.trim() : '';
|
|
120
|
+
if (!normalizedPath) return undefined;
|
|
121
|
+
if (/^(?:[A-Za-z]:[\\/]|\/)/.test(normalizedPath)) return normalizedPath;
|
|
122
|
+
if (!normalizedRoot) return undefined;
|
|
123
|
+
return `${normalizedRoot}/${normalizedPath.replace(/^[\\/]+/, '')}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readGitSubmodules(value: unknown, parentRepoRoot?: string): GitSubmoduleStatus[] | undefined {
|
|
127
|
+
if (!Array.isArray(value)) return undefined;
|
|
128
|
+
const submodules = value
|
|
129
|
+
.map(entry => {
|
|
130
|
+
const submodule = readObjectRecord(entry);
|
|
131
|
+
const path = readStringValue(submodule.path);
|
|
132
|
+
const commit = readStringValue(submodule.commit);
|
|
133
|
+
const repoPath = readStringValue(submodule.repoPath, submodule.repo_root)
|
|
134
|
+
?? joinRepoPath(parentRepoRoot, path);
|
|
135
|
+
if (!path || !commit || !repoPath) return null;
|
|
126
136
|
return {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
ahead: readNumberValue(cachedGit.ahead) ?? 0,
|
|
135
|
-
behind: readNumberValue(cachedGit.behind) ?? 0,
|
|
136
|
-
staged: readNumberValue(cachedGit.staged) ?? 0,
|
|
137
|
-
modified: readNumberValue(cachedGit.modified) ?? 0,
|
|
138
|
-
untracked: readNumberValue(cachedGit.untracked) ?? 0,
|
|
139
|
-
deleted: readNumberValue(cachedGit.deleted) ?? 0,
|
|
140
|
-
renamed: readNumberValue(cachedGit.renamed) ?? 0,
|
|
141
|
-
hasConflicts,
|
|
142
|
-
conflictFiles,
|
|
143
|
-
stashCount: readNumberValue(cachedGit.stashCount) ?? 0,
|
|
144
|
-
lastCheckedAt: readNumberValue(cachedGit.lastCheckedAt) ?? Date.now(),
|
|
137
|
+
path,
|
|
138
|
+
commit,
|
|
139
|
+
repoPath,
|
|
140
|
+
dirty: readBooleanValue(submodule.dirty) ?? false,
|
|
141
|
+
outOfSync: readBooleanValue(submodule.outOfSync, submodule.out_of_sync) ?? false,
|
|
142
|
+
lastCheckedAt: readNumberValue(submodule.lastCheckedAt, submodule.last_checked_at) ?? Date.now(),
|
|
143
|
+
...(readStringValue(submodule.error) ? { error: readStringValue(submodule.error) } : {}),
|
|
145
144
|
};
|
|
146
|
-
}
|
|
147
|
-
|
|
145
|
+
})
|
|
146
|
+
.filter((entry): entry is GitSubmoduleStatus => entry !== null);
|
|
147
|
+
return submodules.length > 0 ? submodules : undefined;
|
|
148
|
+
}
|
|
148
149
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const probeGit = readObjectRecord(rawProbe.git);
|
|
155
|
-
const probeGitResult = readObjectRecord(probeGit.result);
|
|
156
|
-
const probeDirectStatus = readObjectRecord(probeGit.status);
|
|
157
|
-
const probeNestedStatus = readObjectRecord(probeGitResult.status);
|
|
158
|
-
const status = Object.keys(directStatus).length
|
|
159
|
-
? directStatus
|
|
160
|
-
: Object.keys(nestedStatus).length
|
|
161
|
-
? nestedStatus
|
|
162
|
-
: Object.keys(probeDirectStatus).length
|
|
163
|
-
? probeDirectStatus
|
|
164
|
-
: Object.keys(probeNestedStatus).length
|
|
165
|
-
? probeNestedStatus
|
|
166
|
-
: {};
|
|
150
|
+
function normalizeInlineMeshGitStatus(
|
|
151
|
+
status: Record<string, unknown>,
|
|
152
|
+
node: any,
|
|
153
|
+
options?: { lastCheckedAt?: number },
|
|
154
|
+
): Record<string, unknown> | undefined {
|
|
167
155
|
const isGitRepo = readBooleanValue(status.isGitRepo);
|
|
168
156
|
if (!Object.keys(status).length || isGitRepo === undefined) return undefined;
|
|
169
157
|
const conflictFiles = Array.isArray(status.conflictFiles)
|
|
@@ -171,9 +159,11 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
|
|
|
171
159
|
: [];
|
|
172
160
|
const conflictCount = readNumberValue(status.conflicts) ?? conflictFiles.length;
|
|
173
161
|
const hasConflicts = readBooleanValue(status.hasConflicts) ?? conflictCount > 0;
|
|
162
|
+
const repoRoot = readStringValue(status.repoRoot, status.repo_root, node?.repoRoot, node?.repo_root, status.workspace, node?.workspace) || undefined;
|
|
163
|
+
const submodules = readGitSubmodules(status.submodules, repoRoot);
|
|
174
164
|
return {
|
|
175
165
|
workspace: readStringValue(status.workspace, node?.workspace) || '',
|
|
176
|
-
repoRoot:
|
|
166
|
+
repoRoot: repoRoot ?? null,
|
|
177
167
|
isGitRepo,
|
|
178
168
|
branch: readStringValue(status.branch) ?? null,
|
|
179
169
|
headCommit: readStringValue(status.headCommit) ?? null,
|
|
@@ -189,30 +179,538 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
|
|
|
189
179
|
hasConflicts,
|
|
190
180
|
conflictFiles,
|
|
191
181
|
stashCount: readNumberValue(status.stashCount) ?? 0,
|
|
192
|
-
lastCheckedAt: Date.now(),
|
|
182
|
+
lastCheckedAt: options?.lastCheckedAt ?? readNumberValue(status.lastCheckedAt) ?? Date.now(),
|
|
183
|
+
...(submodules ? { submodules } : {}),
|
|
193
184
|
};
|
|
194
185
|
}
|
|
195
186
|
|
|
196
|
-
function
|
|
187
|
+
function buildInlineMeshTransitGitStatus(node: any): Record<string, unknown> | undefined {
|
|
188
|
+
const rawGit = readObjectRecord(node?.lastGit ?? node?.last_git);
|
|
189
|
+
const gitResult = readObjectRecord(rawGit.result);
|
|
190
|
+
const directStatus = readObjectRecord(rawGit.status);
|
|
191
|
+
const nestedStatus = readObjectRecord(gitResult.status);
|
|
192
|
+
const rawProbe = readObjectRecord(node?.lastProbe ?? node?.last_probe);
|
|
193
|
+
const probeGit = readObjectRecord(rawProbe.git);
|
|
194
|
+
const probeGitResult = readObjectRecord(probeGit.result);
|
|
195
|
+
const probeDirectStatus = readObjectRecord(probeGit.status);
|
|
196
|
+
const probeNestedStatus = readObjectRecord(probeGitResult.status);
|
|
197
|
+
const status = Object.keys(directStatus).length
|
|
198
|
+
? directStatus
|
|
199
|
+
: Object.keys(nestedStatus).length
|
|
200
|
+
? nestedStatus
|
|
201
|
+
: Object.keys(probeDirectStatus).length
|
|
202
|
+
? probeDirectStatus
|
|
203
|
+
: Object.keys(probeNestedStatus).length
|
|
204
|
+
? probeNestedStatus
|
|
205
|
+
: {};
|
|
206
|
+
return normalizeInlineMeshGitStatus(status, node, { lastCheckedAt: Date.now() });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function recordInlineMeshDirectGitTruth(
|
|
210
|
+
node: any,
|
|
211
|
+
git: Record<string, unknown>,
|
|
212
|
+
source: 'selected_coordinator_local_git' | 'selected_coordinator_mesh_p2p_git',
|
|
213
|
+
): void {
|
|
214
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) return;
|
|
215
|
+
const checkedAt = readNumberValue(git.lastCheckedAt) ?? Date.now();
|
|
216
|
+
const updatedAt = new Date(checkedAt).toISOString();
|
|
217
|
+
const nextGit: Record<string, unknown> = {
|
|
218
|
+
...git,
|
|
219
|
+
lastCheckedAt: checkedAt,
|
|
220
|
+
};
|
|
221
|
+
node.lastGit = {
|
|
222
|
+
source,
|
|
223
|
+
checkedAt,
|
|
224
|
+
status: nextGit,
|
|
225
|
+
};
|
|
226
|
+
node.last_git = node.lastGit;
|
|
227
|
+
node.machineStatus = 'online';
|
|
228
|
+
node.updatedAt = updatedAt;
|
|
229
|
+
node.lastSeenAt = updatedAt;
|
|
230
|
+
const repoRoot = readStringValue(nextGit.repoRoot);
|
|
231
|
+
if (repoRoot && !readStringValue(node.repoRoot)) node.repoRoot = repoRoot;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | undefined {
|
|
235
|
+
const liveGit = buildInlineMeshTransitGitStatus(node);
|
|
236
|
+
if (liveGit) return liveGit;
|
|
237
|
+
|
|
197
238
|
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
239
|
+
const cachedGit = readObjectRecord(cachedStatus.git);
|
|
240
|
+
if (!Object.keys(cachedGit).length) return undefined;
|
|
241
|
+
return normalizeInlineMeshGitStatus(cachedGit, node);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function shouldDiscardCachedInlineMeshStatus(node: any): boolean {
|
|
245
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
246
|
+
if (!Object.keys(cachedStatus).length) return false;
|
|
247
|
+
const cachedGit = readObjectRecord(cachedStatus.git);
|
|
248
|
+
const workspaceError = readStringValue(cachedStatus.error, node?.error);
|
|
249
|
+
if (workspaceError && /workspace must be an existing directory/i.test(workspaceError)) return true;
|
|
250
|
+
const isGitRepo = readBooleanValue(cachedGit.isGitRepo);
|
|
251
|
+
const branch = readStringValue(cachedGit.branch);
|
|
252
|
+
const headCommit = readStringValue(cachedGit.headCommit);
|
|
253
|
+
return isGitRepo === false && !branch && !headCommit;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function stripInlineMeshTransientNodeState(node: any): any {
|
|
257
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) return node;
|
|
258
|
+
const {
|
|
259
|
+
cachedStatus,
|
|
260
|
+
lastGit: _lastGit,
|
|
261
|
+
last_git: _lastGitLegacy,
|
|
262
|
+
lastProbe: _lastProbe,
|
|
263
|
+
last_probe: _lastProbeLegacy,
|
|
264
|
+
error: _error,
|
|
265
|
+
health: _health,
|
|
266
|
+
machineStatus: _machineStatus,
|
|
267
|
+
lastSeenAt: _lastSeenAt,
|
|
268
|
+
last_seen_at: _lastSeenAtLegacy,
|
|
269
|
+
updatedAt: _updatedAt,
|
|
270
|
+
updated_at: _updatedAtLegacy,
|
|
271
|
+
activeSession: _activeSession,
|
|
272
|
+
active_session: _activeSessionLegacy,
|
|
273
|
+
activeSessionId: _activeSessionId,
|
|
274
|
+
active_session_id: _activeSessionIdLegacy,
|
|
275
|
+
sessionId: _sessionId,
|
|
276
|
+
session_id: _sessionIdLegacy,
|
|
277
|
+
providerType: _providerType,
|
|
278
|
+
provider_type: _providerTypeLegacy,
|
|
279
|
+
...rest
|
|
280
|
+
} = node as Record<string, unknown>;
|
|
281
|
+
if (cachedStatus && !shouldDiscardCachedInlineMeshStatus(node)) {
|
|
282
|
+
return { ...rest, cachedStatus };
|
|
283
|
+
}
|
|
284
|
+
return rest;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hasInlineMeshTransientNodeState(node: any): boolean {
|
|
288
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) return false;
|
|
289
|
+
return 'cachedStatus' in node
|
|
290
|
+
|| 'lastGit' in node
|
|
291
|
+
|| 'last_git' in node
|
|
292
|
+
|| 'lastProbe' in node
|
|
293
|
+
|| 'last_probe' in node
|
|
294
|
+
|| 'error' in node
|
|
295
|
+
|| 'health' in node
|
|
296
|
+
|| 'machineStatus' in node
|
|
297
|
+
|| 'lastSeenAt' in node
|
|
298
|
+
|| 'last_seen_at' in node
|
|
299
|
+
|| 'updatedAt' in node
|
|
300
|
+
|| 'updated_at' in node
|
|
301
|
+
|| 'activeSession' in node
|
|
302
|
+
|| 'active_session' in node
|
|
303
|
+
|| 'activeSessionId' in node
|
|
304
|
+
|| 'active_session_id' in node
|
|
305
|
+
|| 'sessionId' in node
|
|
306
|
+
|| 'session_id' in node
|
|
307
|
+
|| 'providerType' in node
|
|
308
|
+
|| 'provider_type' in node;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function readInlineMeshNodeId(node: any): string {
|
|
312
|
+
return readStringValue(node?.id, node?.nodeId) || '';
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function sanitizeInlineMesh(inlineMesh: any): any {
|
|
316
|
+
if (!inlineMesh || typeof inlineMesh !== 'object' || Array.isArray(inlineMesh)) return inlineMesh;
|
|
317
|
+
if (!Array.isArray(inlineMesh.nodes)) return inlineMesh;
|
|
318
|
+
let changed = false;
|
|
319
|
+
const nodes = inlineMesh.nodes.map((node: any) => {
|
|
320
|
+
if (!hasInlineMeshTransientNodeState(node)) return node;
|
|
321
|
+
changed = true;
|
|
322
|
+
return stripInlineMeshTransientNodeState(node);
|
|
323
|
+
});
|
|
324
|
+
if (!changed) return inlineMesh;
|
|
325
|
+
return {
|
|
326
|
+
...inlineMesh,
|
|
327
|
+
nodes,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function reconcileInlineMeshCache(cached: any, incoming: any): any {
|
|
332
|
+
if (!cached || typeof cached !== 'object' || Array.isArray(cached)) return incoming;
|
|
333
|
+
if (!incoming || typeof incoming !== 'object' || Array.isArray(incoming)) return cached;
|
|
334
|
+
const cachedNodes = Array.isArray(cached.nodes) ? cached.nodes : [];
|
|
335
|
+
const incomingNodes = Array.isArray(incoming.nodes) ? incoming.nodes : [];
|
|
336
|
+
if (!cachedNodes.length || !incomingNodes.length) return { ...cached, ...incoming };
|
|
337
|
+
|
|
338
|
+
const incomingById = new Map<string, any>();
|
|
339
|
+
for (const node of incomingNodes) {
|
|
340
|
+
const nodeId = readInlineMeshNodeId(node);
|
|
341
|
+
if (nodeId) incomingById.set(nodeId, node);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const nodes = cachedNodes.map((cachedNode: any) => {
|
|
345
|
+
const nodeId = readInlineMeshNodeId(cachedNode);
|
|
346
|
+
const incomingNode = nodeId ? incomingById.get(nodeId) : undefined;
|
|
347
|
+
if (!incomingNode) return cachedNode;
|
|
348
|
+
if (hasInlineMeshTransientNodeState(incomingNode)) {
|
|
349
|
+
return { ...cachedNode, ...incomingNode };
|
|
350
|
+
}
|
|
351
|
+
return { ...stripInlineMeshTransientNodeState(cachedNode), ...incomingNode };
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
...cached,
|
|
356
|
+
...incoming,
|
|
357
|
+
nodes,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function hasGitWorktreeChanges(git: Record<string, unknown> | null | undefined): boolean {
|
|
362
|
+
if (!git) return false;
|
|
363
|
+
return Number(git.staged || 0) + Number(git.modified || 0) + Number(git.untracked || 0) + Number(git.deleted || 0) + Number(git.renamed || 0) > 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function getGitSubmoduleDriftState(git: Record<string, unknown> | null | undefined): { dirty: boolean; outOfSync: boolean } {
|
|
367
|
+
const submodules = Array.isArray(git?.submodules) ? git.submodules : [];
|
|
368
|
+
let dirty = false;
|
|
369
|
+
let outOfSync = false;
|
|
370
|
+
for (const entry of submodules) {
|
|
371
|
+
const submodule = readObjectRecord(entry);
|
|
372
|
+
if (readBooleanValue(submodule.dirty) === true) dirty = true;
|
|
373
|
+
if (readBooleanValue(submodule.outOfSync) === true || !!readStringValue(submodule.error)) outOfSync = true;
|
|
374
|
+
}
|
|
375
|
+
return { dirty, outOfSync };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undefined): 'online' | 'dirty' | 'degraded' {
|
|
379
|
+
if (!git || readBooleanValue(git.isGitRepo) === false) return 'degraded';
|
|
380
|
+
const branch = readStringValue(git.branch);
|
|
381
|
+
if (!branch) return 'degraded';
|
|
382
|
+
const submoduleDrift = getGitSubmoduleDriftState(git);
|
|
383
|
+
if (submoduleDrift.outOfSync) return 'degraded';
|
|
384
|
+
if (submoduleDrift.dirty || hasGitWorktreeChanges(git)) return 'dirty';
|
|
385
|
+
return 'online';
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function readCachedInlineMeshActiveSessions(node: any): string[] {
|
|
389
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
390
|
+
const activeSession = readObjectRecord(cachedStatus.activeSession);
|
|
391
|
+
const fallbackSession = Object.keys(activeSession).length
|
|
392
|
+
? activeSession
|
|
393
|
+
: readObjectRecord(node?.activeSession ?? node?.active_session);
|
|
394
|
+
const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
|
|
395
|
+
return sessionId ? [sessionId] : [];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<string, unknown>> {
|
|
399
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
400
|
+
const activeSession = readObjectRecord(cachedStatus.activeSession);
|
|
401
|
+
const fallbackSession = Object.keys(activeSession).length
|
|
402
|
+
? activeSession
|
|
403
|
+
: readObjectRecord(node?.activeSession ?? node?.active_session);
|
|
404
|
+
const sessionId = readStringValue(
|
|
405
|
+
fallbackSession.id,
|
|
406
|
+
fallbackSession.sessionId,
|
|
407
|
+
fallbackSession.session_id,
|
|
408
|
+
node?.activeSessionId,
|
|
409
|
+
node?.active_session_id,
|
|
410
|
+
node?.sessionId,
|
|
411
|
+
node?.session_id,
|
|
412
|
+
);
|
|
413
|
+
if (!sessionId) return [];
|
|
414
|
+
return [{
|
|
415
|
+
sessionId,
|
|
416
|
+
providerType: readStringValue(
|
|
417
|
+
fallbackSession.providerType,
|
|
418
|
+
fallbackSession.provider_type,
|
|
419
|
+
fallbackSession.cliType,
|
|
420
|
+
fallbackSession.cli_type,
|
|
421
|
+
fallbackSession.provider,
|
|
422
|
+
node?.providerType,
|
|
423
|
+
node?.provider_type,
|
|
424
|
+
),
|
|
425
|
+
state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
|
|
426
|
+
lifecycle: readStringValue(fallbackSession.lifecycle),
|
|
427
|
+
title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
|
|
428
|
+
workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
|
|
429
|
+
lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
|
|
430
|
+
recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
|
|
431
|
+
isCached: true,
|
|
432
|
+
}];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function readLiveMeshSessionState(record: any): string | undefined {
|
|
436
|
+
return readStringValue(
|
|
437
|
+
record?.meta?.sessionStatus,
|
|
438
|
+
record?.meta?.status,
|
|
439
|
+
record?.meta?.providerStatus,
|
|
440
|
+
record?.status,
|
|
441
|
+
record?.state,
|
|
442
|
+
record?.lifecycle,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function toIsoTimestamp(value: unknown): string | null {
|
|
447
|
+
if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString();
|
|
448
|
+
const stringValue = readStringValue(value);
|
|
449
|
+
return stringValue || null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function synthesizeMeshNodeFreshnessFromConnection(status: Record<string, unknown>): void {
|
|
453
|
+
const connection = readObjectRecord(status.connection);
|
|
454
|
+
const connectionFreshAt = toIsoTimestamp(connection.lastCommandAt ?? connection.lastConnectedAt ?? connection.lastStateChangeAt);
|
|
455
|
+
const git = readObjectRecord(status.git);
|
|
456
|
+
const gitCheckedAt = toIsoTimestamp(git.lastCheckedAt);
|
|
457
|
+
if (!status.lastSeenAt && connectionFreshAt) status.lastSeenAt = connectionFreshAt;
|
|
458
|
+
if (!status.updatedAt && (gitCheckedAt || connectionFreshAt)) {
|
|
459
|
+
status.updatedAt = gitCheckedAt ?? connectionFreshAt;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function finalizeMeshNodeStatus(args: {
|
|
464
|
+
status: Record<string, unknown>;
|
|
465
|
+
node: any;
|
|
466
|
+
daemonId?: string;
|
|
467
|
+
isSelfNode: boolean;
|
|
468
|
+
}): void {
|
|
469
|
+
const { status, node, daemonId, isSelfNode } = args;
|
|
470
|
+
if (!readStringValue(status.machineStatus)) {
|
|
471
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
472
|
+
const machineStatus = readStringValue(cachedStatus.machineStatus, cachedStatus.machine_status, node?.machineStatus);
|
|
473
|
+
if (machineStatus) status.machineStatus = machineStatus;
|
|
474
|
+
}
|
|
475
|
+
synthesizeMeshNodeFreshnessFromConnection(status);
|
|
476
|
+
const connectionState = readStringValue(readObjectRecord(status.connection).state);
|
|
477
|
+
status.launchReady = !!daemonId && (
|
|
478
|
+
readStringValue(status.machineStatus) === 'online'
|
|
479
|
+
|| connectionState === 'connected'
|
|
480
|
+
|| isSelfNode
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async function probeRemoteMeshGitStatus(args: {
|
|
485
|
+
dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
486
|
+
daemonId: string;
|
|
487
|
+
workspace: string;
|
|
488
|
+
timeoutMs: number;
|
|
489
|
+
}): Promise<Record<string, unknown> | null> {
|
|
490
|
+
if (!args.dispatchMeshCommand) return null;
|
|
491
|
+
const remoteResult = await Promise.race([
|
|
492
|
+
args.dispatchMeshCommand(args.daemonId, 'git_status', { workspace: args.workspace }),
|
|
493
|
+
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), args.timeoutMs)),
|
|
494
|
+
]) as any;
|
|
495
|
+
const remoteGit = remoteResult?.status ?? remoteResult?.git ?? remoteResult;
|
|
496
|
+
return remoteGit && typeof remoteGit === 'object' && typeof remoteGit.isGitRepo === 'boolean'
|
|
497
|
+
? remoteGit as Record<string, unknown>
|
|
498
|
+
: null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function hydrateInlineMeshDirectTruth(args: {
|
|
502
|
+
mesh: any;
|
|
503
|
+
meshSource: 'inline_cache' | 'inline_bootstrap' | 'local_config';
|
|
504
|
+
dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
505
|
+
statusInstanceId?: string;
|
|
506
|
+
localMachineId?: string;
|
|
507
|
+
}): Promise<{
|
|
508
|
+
directEvidenceCount: number;
|
|
509
|
+
localConfirmedCount: number;
|
|
510
|
+
peerAttemptedCount: number;
|
|
511
|
+
peerConfirmedCount: number;
|
|
512
|
+
unavailableNodeIds: string[];
|
|
513
|
+
}> {
|
|
514
|
+
const nodes = Array.isArray(args.mesh?.nodes) ? args.mesh.nodes : [];
|
|
515
|
+
if (!nodes.length) {
|
|
516
|
+
return {
|
|
517
|
+
directEvidenceCount: 0,
|
|
518
|
+
localConfirmedCount: 0,
|
|
519
|
+
peerAttemptedCount: 0,
|
|
520
|
+
peerConfirmedCount: 0,
|
|
521
|
+
unavailableNodeIds: [],
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const selectedCoordinatorNodeId = readStringValue(
|
|
526
|
+
args.mesh?.coordinator?.preferredNodeId,
|
|
527
|
+
nodes[0]?.id,
|
|
528
|
+
nodes[0]?.nodeId,
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
let localConfirmedCount = 0;
|
|
532
|
+
let peerAttemptedCount = 0;
|
|
533
|
+
let peerConfirmedCount = 0;
|
|
534
|
+
const unavailableNodeIds: string[] = [];
|
|
535
|
+
|
|
536
|
+
for (const [nodeIndex, node] of nodes.entries()) {
|
|
537
|
+
const nodeId = readStringValue(node?.id, node?.nodeId) || `node_${nodeIndex}`;
|
|
538
|
+
const workspace = readStringValue(node?.workspace);
|
|
539
|
+
const daemonId = readStringValue(node?.daemonId);
|
|
540
|
+
const isSelfNode = Boolean(
|
|
541
|
+
nodeId && selectedCoordinatorNodeId && nodeId === selectedCoordinatorNodeId,
|
|
542
|
+
) || Boolean(
|
|
543
|
+
daemonId && (daemonId === args.localMachineId || daemonId === args.statusInstanceId),
|
|
544
|
+
) || Boolean(args.meshSource !== 'local_config' && nodeIndex === 0);
|
|
545
|
+
|
|
546
|
+
if (!workspace) {
|
|
547
|
+
if (!isSelfNode && daemonId) unavailableNodeIds.push(nodeId);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (isSelfNode && fs.existsSync(workspace)) {
|
|
552
|
+
try {
|
|
553
|
+
const localGit = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
|
|
554
|
+
if (localGit?.isGitRepo) {
|
|
555
|
+
recordInlineMeshDirectGitTruth(node, localGit as unknown as Record<string, unknown>, 'selected_coordinator_local_git');
|
|
556
|
+
localConfirmedCount += 1;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
// Fall through to remote classification.
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (!daemonId || !args.dispatchMeshCommand) {
|
|
565
|
+
if (!isSelfNode) unavailableNodeIds.push(nodeId);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
peerAttemptedCount += 1;
|
|
570
|
+
try {
|
|
571
|
+
const remoteGit = await probeRemoteMeshGitStatus({
|
|
572
|
+
dispatchMeshCommand: args.dispatchMeshCommand,
|
|
573
|
+
daemonId,
|
|
574
|
+
workspace,
|
|
575
|
+
timeoutMs: 8_000,
|
|
576
|
+
});
|
|
577
|
+
if (remoteGit) {
|
|
578
|
+
recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
|
|
579
|
+
peerConfirmedCount += 1;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
// Strict direct-only path: do not fall back to persisted cloud truth here.
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
unavailableNodeIds.push(nodeId);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
directEvidenceCount: localConfirmedCount + peerConfirmedCount,
|
|
591
|
+
localConfirmedCount,
|
|
592
|
+
peerAttemptedCount,
|
|
593
|
+
peerConfirmedCount,
|
|
594
|
+
unavailableNodeIds,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function summarizeMeshSessionRecord(record: any): Record<string, unknown> {
|
|
599
|
+
return {
|
|
600
|
+
sessionId: readStringValue(record?.sessionId) || 'unknown',
|
|
601
|
+
providerType: readStringValue(record?.providerType),
|
|
602
|
+
state: readLiveMeshSessionState(record),
|
|
603
|
+
lifecycle: readStringValue(record?.lifecycle),
|
|
604
|
+
surfaceKind: getSessionHostSurfaceKind(record as any),
|
|
605
|
+
recoveryState: readStringValue(record?.meta?.runtimeRecoveryState) ?? null,
|
|
606
|
+
workspace: readStringValue(record?.workspace) ?? null,
|
|
607
|
+
title: readStringValue(record?.displayName, record?.workspaceLabel) ?? null,
|
|
608
|
+
lastActivityAt: toIsoTimestamp(record?.updatedAt ?? record?.lastActivityAt ?? record?.last_activity_at),
|
|
609
|
+
isCached: false,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function liveSessionRecordMatchesMeshNode(record: any, meshId: string, nodeId: string): boolean {
|
|
614
|
+
const recordNodeId = readStringValue(record?.meta?.meshNodeId);
|
|
615
|
+
if (!recordNodeId || recordNodeId !== nodeId) return false;
|
|
616
|
+
const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
|
|
617
|
+
return !recordMeshId || recordMeshId === meshId;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function liveSessionRecordMatchesMeshWorkspace(record: any, meshId: string, workspace: string): boolean {
|
|
621
|
+
const recordWorkspace = readStringValue(record?.workspace);
|
|
622
|
+
if (!recordWorkspace || !workspace || recordWorkspace !== workspace) return false;
|
|
623
|
+
|
|
624
|
+
const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
|
|
625
|
+
if (recordMeshId) return recordMeshId === meshId;
|
|
626
|
+
|
|
627
|
+
return record?.meta?.launchedByCoordinator === true || !!readStringValue(record?.meta?.meshNodeId);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function readLiveMeshNodeWorkspace(args: {
|
|
631
|
+
meshId: string;
|
|
632
|
+
nodeId: string;
|
|
633
|
+
liveSessionRecords: any[];
|
|
634
|
+
allowCoordinatorSession?: boolean;
|
|
635
|
+
}): string {
|
|
636
|
+
const directNodeWorkspace = args.liveSessionRecords.find((record) => (
|
|
637
|
+
liveSessionRecordMatchesMeshNode(record, args.meshId, args.nodeId)
|
|
638
|
+
&& readStringValue(record?.workspace)
|
|
639
|
+
));
|
|
640
|
+
if (directNodeWorkspace) {
|
|
641
|
+
return readStringValue(directNodeWorkspace.workspace) || '';
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (args.allowCoordinatorSession) {
|
|
645
|
+
const coordinatorWorkspace = args.liveSessionRecords.find((record) => (
|
|
646
|
+
readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId
|
|
647
|
+
&& readStringValue(record?.workspace)
|
|
648
|
+
));
|
|
649
|
+
if (coordinatorWorkspace) {
|
|
650
|
+
return readStringValue(coordinatorWorkspace.workspace) || '';
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return '';
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function collectLiveMeshSessionRecords(args: {
|
|
658
|
+
meshId: string;
|
|
659
|
+
node: any;
|
|
660
|
+
nodeId: string;
|
|
661
|
+
liveSessionRecords: any[];
|
|
662
|
+
allowCoordinatorSession?: boolean;
|
|
663
|
+
}): any[] {
|
|
664
|
+
const matches = args.liveSessionRecords.filter((record) => {
|
|
665
|
+
const nodeWorkspace = readStringValue(args.node?.workspace);
|
|
666
|
+
if (liveSessionRecordMatchesMeshNode(record, args.meshId, args.nodeId)) return true;
|
|
667
|
+
return !!nodeWorkspace && liveSessionRecordMatchesMeshWorkspace(record, args.meshId, nodeWorkspace);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
if (args.allowCoordinatorSession) {
|
|
671
|
+
for (const record of args.liveSessionRecords) {
|
|
672
|
+
if (readStringValue(record?.meta?.meshCoordinatorFor) !== args.meshId) continue;
|
|
673
|
+
const sessionId = readStringValue(record?.sessionId);
|
|
674
|
+
if (sessionId && matches.some((entry) => readStringValue(entry?.sessionId) === sessionId)) continue;
|
|
675
|
+
matches.push(record);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return matches;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function applyCachedInlineMeshNodeStatus(
|
|
683
|
+
status: Record<string, unknown>,
|
|
684
|
+
node: any,
|
|
685
|
+
options?: { skipGit?: boolean; skipError?: boolean; skipHealth?: boolean },
|
|
686
|
+
): boolean {
|
|
687
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
688
|
+
const liveGit = buildInlineMeshTransitGitStatus(node);
|
|
689
|
+
const git = options?.skipGit ? undefined : (liveGit ?? buildCachedInlineMeshGitStatus(node));
|
|
690
|
+
const error = options?.skipError ? undefined : (liveGit ? undefined : readStringValue(cachedStatus.error, node?.error));
|
|
691
|
+
const health = options?.skipHealth ? undefined : (liveGit ? undefined : readStringValue(cachedStatus.health, node?.health));
|
|
201
692
|
const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
|
|
202
|
-
|
|
203
|
-
|
|
693
|
+
const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
|
|
694
|
+
const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
|
|
695
|
+
const activeSessions = readCachedInlineMeshActiveSessions(node);
|
|
696
|
+
const activeSessionDetails = readCachedInlineMeshActiveSessionDetails(node);
|
|
697
|
+
if (!git && !error && !health && !machineStatus && !lastSeenAt && !updatedAt && activeSessions.length === 0) return false;
|
|
204
698
|
if (git) status.git = git;
|
|
205
699
|
if (error) status.error = error;
|
|
700
|
+
if (machineStatus) status.machineStatus = machineStatus;
|
|
701
|
+
if (lastSeenAt) status.lastSeenAt = lastSeenAt;
|
|
702
|
+
if (updatedAt) status.updatedAt = updatedAt;
|
|
703
|
+
if (activeSessions.length > 0) status.activeSessions = activeSessions;
|
|
704
|
+
if (activeSessionDetails.length > 0) status.activeSessionDetails = activeSessionDetails;
|
|
206
705
|
if (health) {
|
|
207
706
|
status.health = health;
|
|
208
707
|
return true;
|
|
209
708
|
}
|
|
210
709
|
if (git) {
|
|
211
|
-
|
|
212
|
-
status.health = git.isGitRepo === false ? 'degraded' : dirty ? 'dirty' : 'online';
|
|
710
|
+
status.health = deriveMeshNodeHealthFromGit(git);
|
|
213
711
|
return true;
|
|
214
712
|
}
|
|
215
|
-
return
|
|
713
|
+
return activeSessions.length > 0 || !!machineStatus || !!lastSeenAt || !!updatedAt;
|
|
216
714
|
}
|
|
217
715
|
|
|
218
716
|
async function resolveProviderTypeFromPriority(args: {
|
|
@@ -632,6 +1130,10 @@ export interface CommandRouterDeps {
|
|
|
632
1130
|
statusVersion?: string;
|
|
633
1131
|
/** Session host control plane */
|
|
634
1132
|
sessionHostControl?: SessionHostControlPlane | null;
|
|
1133
|
+
/** Selected-coordinator mesh peer telemetry surface for target daemons, when supported by the runtime. */
|
|
1134
|
+
getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
|
|
1135
|
+
/** Dispatch a command to a remote mesh node via P2P/relay. Injected by cloud runtime; absent in standalone. */
|
|
1136
|
+
dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
635
1137
|
}
|
|
636
1138
|
|
|
637
1139
|
export interface CommandRouterResult {
|
|
@@ -756,29 +1258,45 @@ export class DaemonCommandRouter {
|
|
|
756
1258
|
|
|
757
1259
|
public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
|
|
758
1260
|
if (inlineMesh && typeof inlineMesh === 'object') {
|
|
759
|
-
this.
|
|
760
|
-
return inlineMesh as any;
|
|
1261
|
+
return this.warmInlineMeshCache(meshId, inlineMesh);
|
|
761
1262
|
}
|
|
762
1263
|
return this.inlineMeshCache.get(meshId);
|
|
763
1264
|
}
|
|
764
1265
|
|
|
1266
|
+
private warmInlineMeshCache(meshId: string, inlineMesh?: unknown): any | undefined {
|
|
1267
|
+
if (!inlineMesh || typeof inlineMesh !== 'object') return undefined;
|
|
1268
|
+
const sanitizedInlineMesh = sanitizeInlineMesh(inlineMesh as any);
|
|
1269
|
+
const cached = this.inlineMeshCache.get(meshId);
|
|
1270
|
+
if (cached) {
|
|
1271
|
+
const merged = reconcileInlineMeshCache(cached, sanitizedInlineMesh);
|
|
1272
|
+
this.inlineMeshCache.set(meshId, merged);
|
|
1273
|
+
return merged;
|
|
1274
|
+
}
|
|
1275
|
+
this.inlineMeshCache.set(meshId, sanitizedInlineMesh as any);
|
|
1276
|
+
return sanitizedInlineMesh as any;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
765
1279
|
private async getMeshForCommand(
|
|
766
1280
|
meshId: string,
|
|
767
1281
|
inlineMesh?: unknown,
|
|
768
1282
|
options?: { preferInline?: boolean },
|
|
769
|
-
): Promise<{ mesh: any; inline: boolean } | null> {
|
|
1283
|
+
): Promise<{ mesh: any; inline: boolean; source: 'inline_cache' | 'inline_bootstrap' | 'local_config' } | null> {
|
|
770
1284
|
const preferInline = options?.preferInline === true;
|
|
771
1285
|
if (preferInline) {
|
|
772
|
-
const cached = this.getCachedInlineMesh(meshId
|
|
773
|
-
if (cached) return { mesh: cached, inline: true };
|
|
1286
|
+
const cached = this.getCachedInlineMesh(meshId);
|
|
1287
|
+
if (cached) return { mesh: cached, inline: true, source: 'inline_cache' };
|
|
1288
|
+
const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
|
|
1289
|
+
if (warmedInline) return { mesh: warmedInline, inline: true, source: 'inline_bootstrap' };
|
|
774
1290
|
}
|
|
775
1291
|
try {
|
|
776
1292
|
const { getMesh } = await import('../config/mesh-config.js');
|
|
777
1293
|
const mesh = getMesh(meshId);
|
|
778
|
-
if (mesh) return { mesh, inline: false };
|
|
1294
|
+
if (mesh) return { mesh, inline: false, source: 'local_config' };
|
|
779
1295
|
} catch { /* fall through to inline cache */ }
|
|
780
|
-
const cached = this.getCachedInlineMesh(meshId
|
|
781
|
-
|
|
1296
|
+
const cached = this.getCachedInlineMesh(meshId);
|
|
1297
|
+
if (cached) return { mesh: cached, inline: true, source: 'inline_cache' };
|
|
1298
|
+
const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
|
|
1299
|
+
return warmedInline ? { mesh: warmedInline, inline: true, source: 'inline_bootstrap' } : null;
|
|
782
1300
|
}
|
|
783
1301
|
|
|
784
1302
|
private updateInlineMeshNode(meshId: string, mesh: any, node: any): void {
|
|
@@ -1075,6 +1593,7 @@ export class DaemonCommandRouter {
|
|
|
1075
1593
|
const deletedSessionIds: string[] = [];
|
|
1076
1594
|
const skippedSessionIds: string[] = [];
|
|
1077
1595
|
const skippedLiveSessionIds: string[] = [];
|
|
1596
|
+
const skippedCoordinatorSessionIds: string[] = [];
|
|
1078
1597
|
const deleteUnsupportedSessionIds: string[] = [];
|
|
1079
1598
|
const recordsRemainSessionIds: string[] = [];
|
|
1080
1599
|
const errors: Array<{ sessionId: string; error: string }> = [];
|
|
@@ -1109,6 +1628,12 @@ export class DaemonCommandRouter {
|
|
|
1109
1628
|
const completed = this.isCompletedHostedSession(record);
|
|
1110
1629
|
const surfaceKind = getSessionHostSurfaceKind(record);
|
|
1111
1630
|
const liveRuntime = surfaceKind === 'live_runtime';
|
|
1631
|
+
const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
|
|
1632
|
+
if (!hasExplicitSessionIds && coordinatorSession) {
|
|
1633
|
+
skippedSessionIds.push(sessionId);
|
|
1634
|
+
skippedCoordinatorSessionIds.push(sessionId);
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1112
1637
|
if (!hasExplicitSessionIds && liveRuntime) {
|
|
1113
1638
|
skippedSessionIds.push(sessionId);
|
|
1114
1639
|
skippedLiveSessionIds.push(sessionId);
|
|
@@ -1178,6 +1703,7 @@ export class DaemonCommandRouter {
|
|
|
1178
1703
|
deletedSessionIds,
|
|
1179
1704
|
skippedSessionIds,
|
|
1180
1705
|
skippedLiveSessionIds,
|
|
1706
|
+
skippedCoordinatorSessionIds,
|
|
1181
1707
|
...(deleteUnsupported ? {
|
|
1182
1708
|
deleteUnsupported: true,
|
|
1183
1709
|
effectiveCleanup: args.mode === 'stop_and_delete'
|
|
@@ -1332,7 +1858,8 @@ export class DaemonCommandRouter {
|
|
|
1332
1858
|
}
|
|
1333
1859
|
|
|
1334
1860
|
case 'get_pending_mesh_events': {
|
|
1335
|
-
const
|
|
1861
|
+
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
1862
|
+
const events = drainPendingMeshCoordinatorEvents(meshId || undefined);
|
|
1336
1863
|
return { success: true, events };
|
|
1337
1864
|
}
|
|
1338
1865
|
|
|
@@ -1937,15 +2464,44 @@ export class DaemonCommandRouter {
|
|
|
1937
2464
|
case 'get_mesh': {
|
|
1938
2465
|
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
1939
2466
|
if (!meshId) return { success: false, error: 'meshId required' };
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2467
|
+
const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
|
|
2468
|
+
if (!meshRecord?.mesh) return { success: false, error: 'Mesh not found' };
|
|
2469
|
+
|
|
2470
|
+
const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
|
|
2471
|
+
const directTruth = await hydrateInlineMeshDirectTruth({
|
|
2472
|
+
mesh: meshRecord.mesh,
|
|
2473
|
+
meshSource: meshRecord.source,
|
|
2474
|
+
dispatchMeshCommand: this.deps.dispatchMeshCommand,
|
|
2475
|
+
statusInstanceId: this.deps.statusInstanceId,
|
|
2476
|
+
localMachineId: loadConfig().machineId || '',
|
|
2477
|
+
});
|
|
2478
|
+
const directTruthSatisfied = meshRecord.source !== 'inline_bootstrap' || directTruth.directEvidenceCount > 0;
|
|
2479
|
+
const sourceOfTruth = {
|
|
2480
|
+
membership: meshRecord.source === 'inline_cache'
|
|
2481
|
+
? 'coordinator_inline_mesh_cache'
|
|
2482
|
+
: meshRecord.source === 'local_config'
|
|
2483
|
+
? 'local_mesh_config'
|
|
2484
|
+
: 'inline_bootstrap_snapshot',
|
|
2485
|
+
coordinatorOwnsLiveTruth: directTruthSatisfied,
|
|
2486
|
+
directPeerTruth: {
|
|
2487
|
+
required: requireDirectPeerTruth,
|
|
2488
|
+
satisfied: directTruthSatisfied,
|
|
2489
|
+
directEvidenceCount: directTruth.directEvidenceCount,
|
|
2490
|
+
localConfirmedCount: directTruth.localConfirmedCount,
|
|
2491
|
+
peerAttemptedCount: directTruth.peerAttemptedCount,
|
|
2492
|
+
peerConfirmedCount: directTruth.peerConfirmedCount,
|
|
2493
|
+
unavailableNodeIds: directTruth.unavailableNodeIds,
|
|
2494
|
+
},
|
|
2495
|
+
};
|
|
2496
|
+
if (requireDirectPeerTruth && !directTruthSatisfied) {
|
|
2497
|
+
return {
|
|
2498
|
+
success: false,
|
|
2499
|
+
code: 'mesh_direct_peer_truth_unavailable',
|
|
2500
|
+
error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct get_mesh probes succeed.',
|
|
2501
|
+
sourceOfTruth,
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
return { success: true, mesh: meshRecord.mesh, sourceOfTruth };
|
|
1949
2505
|
}
|
|
1950
2506
|
|
|
1951
2507
|
case 'create_mesh': {
|
|
@@ -2531,7 +3087,16 @@ export class DaemonCommandRouter {
|
|
|
2531
3087
|
cliType,
|
|
2532
3088
|
};
|
|
2533
3089
|
}
|
|
2534
|
-
const
|
|
3090
|
+
const sessionHostRecords = this.deps.sessionHostControl?.listSessions
|
|
3091
|
+
? await this.deps.sessionHostControl.listSessions().catch(() => [])
|
|
3092
|
+
: [];
|
|
3093
|
+
const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
|
|
3094
|
+
const workspace = readLiveMeshNodeWorkspace({
|
|
3095
|
+
meshId,
|
|
3096
|
+
nodeId: String(coordinatorNode.id || coordinatorNode.nodeId || preferredCoordinatorNodeId || ''),
|
|
3097
|
+
liveSessionRecords: liveMeshSessions,
|
|
3098
|
+
allowCoordinatorSession: true,
|
|
3099
|
+
}) || (typeof coordinatorNode.workspace === 'string' ? coordinatorNode.workspace.trim() : '');
|
|
2535
3100
|
if (!workspace) return { success: false, error: 'Coordinator node workspace required', meshId, cliType };
|
|
2536
3101
|
if (!cliType) {
|
|
2537
3102
|
const resolved = await resolveProviderTypeFromPriority({
|
|
@@ -2884,98 +3449,210 @@ export class DaemonCommandRouter {
|
|
|
2884
3449
|
const { readLedgerEntries, getLedgerSummary } = await import('../mesh/mesh-ledger.js');
|
|
2885
3450
|
const ledgerEntries = readLedgerEntries(meshId, { tail: 20 });
|
|
2886
3451
|
const ledgerSummary = getLedgerSummary(meshId);
|
|
3452
|
+
const sessionHostRecords = this.deps.sessionHostControl?.listSessions
|
|
3453
|
+
? await this.deps.sessionHostControl.listSessions().catch(() => [])
|
|
3454
|
+
: [];
|
|
3455
|
+
const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
|
|
2887
3456
|
|
|
3457
|
+
const localMachineId = loadConfig().machineId || '';
|
|
3458
|
+
const selectedCoordinatorNodeId = readStringValue(
|
|
3459
|
+
mesh.coordinator?.preferredNodeId,
|
|
3460
|
+
(mesh.nodes?.[0] as any)?.id,
|
|
3461
|
+
(mesh.nodes?.[0] as any)?.nodeId,
|
|
3462
|
+
);
|
|
3463
|
+
const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes)
|
|
3464
|
+
? selectedCoordinatorNodeId
|
|
3465
|
+
: undefined;
|
|
3466
|
+
const refreshedAt = new Date().toISOString();
|
|
2888
3467
|
const nodeStatuses = [];
|
|
2889
|
-
for (const node of mesh.nodes || []) {
|
|
3468
|
+
for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
|
|
3469
|
+
const nodeId = String(node.id || node.nodeId || '');
|
|
3470
|
+
const daemonId = readStringValue(node.daemonId);
|
|
3471
|
+
const providerPriority = readProviderPriorityFromPolicy(node.policy);
|
|
3472
|
+
const isSelfNode = Boolean(
|
|
3473
|
+
nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId,
|
|
3474
|
+
) || Boolean(
|
|
3475
|
+
daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId),
|
|
3476
|
+
) || Boolean(meshRecord?.inline && nodeIndex === 0);
|
|
2890
3477
|
const status: Record<string, unknown> = {
|
|
2891
|
-
nodeId
|
|
3478
|
+
nodeId,
|
|
2892
3479
|
machineLabel: node.machineLabel || node.id || node.nodeId,
|
|
2893
3480
|
workspace: node.workspace,
|
|
2894
3481
|
repoRoot: node.repoRoot,
|
|
2895
3482
|
isLocalWorktree: node.isLocalWorktree,
|
|
2896
3483
|
worktreeBranch: node.worktreeBranch,
|
|
2897
|
-
daemonId
|
|
3484
|
+
daemonId,
|
|
2898
3485
|
machineId: node.machineId,
|
|
3486
|
+
machineStatus: node.machineStatus,
|
|
2899
3487
|
health: 'unknown',
|
|
2900
3488
|
providers: node.providers || [],
|
|
3489
|
+
providerPriority,
|
|
2901
3490
|
activeSessions: [],
|
|
3491
|
+
activeSessionDetails: [],
|
|
3492
|
+
launchReady: false,
|
|
2902
3493
|
};
|
|
2903
|
-
if (
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3494
|
+
if (isSelfNode) {
|
|
3495
|
+
status.connection = {
|
|
3496
|
+
perspective: 'selected_coordinator',
|
|
3497
|
+
source: 'mesh_peer_status',
|
|
3498
|
+
state: 'self',
|
|
3499
|
+
transport: 'local',
|
|
3500
|
+
reported: true,
|
|
3501
|
+
reason: 'Selected coordinator daemon',
|
|
3502
|
+
lastStateChangeAt: refreshedAt,
|
|
3503
|
+
};
|
|
3504
|
+
} else if (daemonId) {
|
|
3505
|
+
const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
|
|
3506
|
+
status.connection = connection ?? {
|
|
3507
|
+
perspective: 'selected_coordinator',
|
|
3508
|
+
source: 'not_reported',
|
|
3509
|
+
state: 'unknown',
|
|
3510
|
+
transport: 'unknown',
|
|
3511
|
+
reported: false,
|
|
3512
|
+
reason: 'No live mesh peer telemetry reported by the selected coordinator yet.',
|
|
3513
|
+
};
|
|
3514
|
+
} else {
|
|
3515
|
+
status.connection = {
|
|
3516
|
+
perspective: 'selected_coordinator',
|
|
3517
|
+
source: 'not_reported',
|
|
3518
|
+
state: 'unknown',
|
|
3519
|
+
transport: 'unknown',
|
|
3520
|
+
reported: false,
|
|
3521
|
+
reason: 'Node has no daemon id, so mesh transport cannot be reported from the selected coordinator.',
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
const matchedLiveSessionRecords = collectLiveMeshSessionRecords({
|
|
3525
|
+
meshId,
|
|
3526
|
+
node,
|
|
3527
|
+
nodeId,
|
|
3528
|
+
liveSessionRecords: liveMeshSessions,
|
|
3529
|
+
allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
|
|
3530
|
+
});
|
|
3531
|
+
const workspace = readLiveMeshNodeWorkspace({
|
|
3532
|
+
meshId,
|
|
3533
|
+
nodeId,
|
|
3534
|
+
liveSessionRecords: matchedLiveSessionRecords,
|
|
3535
|
+
allowCoordinatorSession: nodeId === selectedCoordinatorNodeId,
|
|
3536
|
+
}) || (typeof node.workspace === 'string' ? node.workspace : '');
|
|
3537
|
+
status.workspace = workspace || node.workspace;
|
|
3538
|
+
if (matchedLiveSessionRecords.length > 0) {
|
|
3539
|
+
const sessionIds = matchedLiveSessionRecords
|
|
3540
|
+
.map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
|
|
3541
|
+
.filter(Boolean);
|
|
3542
|
+
const providerTypes = matchedLiveSessionRecords
|
|
3543
|
+
.map((record: any) => readStringValue(record?.providerType))
|
|
3544
|
+
.filter(Boolean) as string[];
|
|
3545
|
+
status.activeSessions = sessionIds;
|
|
3546
|
+
status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
|
|
3547
|
+
if (providerTypes.length > 0) {
|
|
3548
|
+
status.providers = Array.from(new Set([...(Array.isArray(status.providers) ? status.providers as string[] : []), ...providerTypes]));
|
|
2907
3549
|
}
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3550
|
+
}
|
|
3551
|
+
if (workspace) {
|
|
3552
|
+
if (!fs.existsSync(workspace)) {
|
|
3553
|
+
// Workspace not local — prefer direct live inline truth, then attempt a P2P git probe.
|
|
3554
|
+
const inlineTransitGit = buildInlineMeshTransitGitStatus(node);
|
|
3555
|
+
let remoteProbeApplied = false;
|
|
3556
|
+
if (inlineTransitGit) {
|
|
3557
|
+
status.git = inlineTransitGit;
|
|
3558
|
+
status.health = inlineTransitGit.isGitRepo
|
|
3559
|
+
? deriveMeshNodeHealthFromGit(inlineTransitGit as unknown as Record<string, unknown>)
|
|
3560
|
+
: 'degraded';
|
|
3561
|
+
remoteProbeApplied = true;
|
|
3562
|
+
} else if (!isSelfNode && daemonId && this.deps.dispatchMeshCommand) {
|
|
3563
|
+
try {
|
|
3564
|
+
const remoteGit = await probeRemoteMeshGitStatus({
|
|
3565
|
+
dispatchMeshCommand: this.deps.dispatchMeshCommand,
|
|
3566
|
+
daemonId,
|
|
3567
|
+
workspace,
|
|
3568
|
+
timeoutMs: 8000,
|
|
3569
|
+
});
|
|
3570
|
+
if (remoteGit) {
|
|
3571
|
+
status.git = remoteGit;
|
|
3572
|
+
status.health = remoteGit.isGitRepo
|
|
3573
|
+
? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
|
|
3574
|
+
: 'degraded';
|
|
3575
|
+
recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
|
|
3576
|
+
remoteProbeApplied = true;
|
|
3577
|
+
}
|
|
3578
|
+
} catch {
|
|
3579
|
+
const refreshedConnection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
|
|
3580
|
+
const refreshedConnectionState = readStringValue(refreshedConnection?.state);
|
|
3581
|
+
if (refreshedConnection && refreshedConnectionState === 'connected') {
|
|
3582
|
+
status.connection = refreshedConnection;
|
|
3583
|
+
try {
|
|
3584
|
+
const remoteGit = await probeRemoteMeshGitStatus({
|
|
3585
|
+
dispatchMeshCommand: this.deps.dispatchMeshCommand,
|
|
3586
|
+
daemonId,
|
|
3587
|
+
workspace,
|
|
3588
|
+
timeoutMs: 12000,
|
|
3589
|
+
});
|
|
3590
|
+
if (remoteGit) {
|
|
3591
|
+
status.git = remoteGit;
|
|
3592
|
+
status.health = remoteGit.isGitRepo
|
|
3593
|
+
? deriveMeshNodeHealthFromGit(remoteGit as unknown as Record<string, unknown>)
|
|
3594
|
+
: 'degraded';
|
|
3595
|
+
recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
|
|
3596
|
+
remoteProbeApplied = true;
|
|
3597
|
+
}
|
|
3598
|
+
} catch {
|
|
3599
|
+
// Probe timed out again or P2P unavailable — fall back to cached status
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
2935
3602
|
}
|
|
2936
3603
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
3604
|
+
if (!remoteProbeApplied) {
|
|
3605
|
+
const connectionState = readStringValue((status.connection as any)?.state);
|
|
3606
|
+
const pendingPeerGitProbe = !inlineTransitGit
|
|
3607
|
+
&& !isSelfNode
|
|
3608
|
+
&& !!daemonId
|
|
3609
|
+
&& (
|
|
3610
|
+
readStringValue(status.machineStatus) === 'online'
|
|
3611
|
+
|| readStringValue(status.health) === 'online'
|
|
3612
|
+
|| connectionState === 'connecting'
|
|
3613
|
+
|| connectionState === 'connected'
|
|
3614
|
+
|| connectionState === 'unknown'
|
|
3615
|
+
);
|
|
3616
|
+
if (pendingPeerGitProbe) {
|
|
3617
|
+
status.gitProbePending = true;
|
|
3618
|
+
status.health = 'unknown';
|
|
3619
|
+
}
|
|
3620
|
+
if (applyCachedInlineMeshNodeStatus(
|
|
3621
|
+
status,
|
|
3622
|
+
node,
|
|
3623
|
+
pendingPeerGitProbe ? { skipGit: true, skipError: true, skipHealth: true } : undefined,
|
|
3624
|
+
)) {
|
|
3625
|
+
finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
|
|
3626
|
+
nodeStatuses.push(status);
|
|
3627
|
+
continue;
|
|
3628
|
+
}
|
|
3629
|
+
if (meshRecord?.source === 'inline_cache' && !isSelfNode) {
|
|
3630
|
+
finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
|
|
3631
|
+
nodeStatuses.push(status);
|
|
3632
|
+
continue;
|
|
3633
|
+
}
|
|
2948
3634
|
}
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
workspace:
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
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
|
-
} catch {
|
|
2972
|
-
if (!applyCachedInlineMeshNodeStatus(status, node)) {
|
|
2973
|
-
status.health = 'degraded';
|
|
3635
|
+
} else {
|
|
3636
|
+
try {
|
|
3637
|
+
const gitStatus = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
|
|
3638
|
+
status.git = gitStatus;
|
|
3639
|
+
recordInlineMeshDirectGitTruth(node, gitStatus as unknown as Record<string, unknown>, 'selected_coordinator_local_git');
|
|
3640
|
+
if (gitStatus.isGitRepo) {
|
|
3641
|
+
status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
|
|
3642
|
+
} else {
|
|
3643
|
+
status.health = 'degraded';
|
|
3644
|
+
if (gitStatus.error && !status.error) status.error = gitStatus.error;
|
|
3645
|
+
}
|
|
3646
|
+
} catch {
|
|
3647
|
+
if (!applyCachedInlineMeshNodeStatus(status, node)) {
|
|
3648
|
+
status.health = 'degraded';
|
|
3649
|
+
}
|
|
2974
3650
|
}
|
|
2975
3651
|
}
|
|
2976
3652
|
} else {
|
|
2977
3653
|
applyCachedInlineMeshNodeStatus(status, node);
|
|
2978
3654
|
}
|
|
3655
|
+
finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
|
|
2979
3656
|
nodeStatuses.push(status);
|
|
2980
3657
|
}
|
|
2981
3658
|
|
|
@@ -2985,6 +3662,16 @@ export class DaemonCommandRouter {
|
|
|
2985
3662
|
meshName: mesh.name,
|
|
2986
3663
|
repoIdentity: mesh.repoIdentity,
|
|
2987
3664
|
defaultBranch: mesh.defaultBranch,
|
|
3665
|
+
refreshedAt: new Date().toISOString(),
|
|
3666
|
+
sourceOfTruth: {
|
|
3667
|
+
membership: meshRecord?.source === 'inline_cache'
|
|
3668
|
+
? 'coordinator_inline_mesh_cache'
|
|
3669
|
+
: meshRecord?.source === 'local_config'
|
|
3670
|
+
? 'local_mesh_config'
|
|
3671
|
+
: 'inline_bootstrap_snapshot',
|
|
3672
|
+
coordinatorOwnsLiveTruth: meshRecord?.source !== 'inline_bootstrap',
|
|
3673
|
+
historicalEvidenceOnly: ['recoveryHints', 'ledger.summary', 'queue.summary'],
|
|
3674
|
+
},
|
|
2988
3675
|
nodes: nodeStatuses,
|
|
2989
3676
|
queue: { tasks: queue, summary: queueSummary },
|
|
2990
3677
|
ledger: { entries: ledgerEntries, summary: ledgerSummary },
|