@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.
- package/dist/boot/daemon-lifecycle.d.ts +2 -0
- package/dist/commands/router.d.ts +3 -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 +389 -101
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +388 -101
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +3 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/repo-mesh-types.d.ts +122 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +3 -0
- package/src/commands/router.ts +348 -88
- 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 +14 -3
- package/src/providers/chat-message-normalization.ts +3 -1
- package/src/repo-mesh-types.ts +132 -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,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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
212
|
-
status.health = git.isGitRepo === false ? 'degraded' : dirty ? 'dirty' : 'online';
|
|
407
|
+
status.health = deriveMeshNodeHealthFromGit(git);
|
|
213
408
|
return true;
|
|
214
409
|
}
|
|
215
|
-
return
|
|
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.
|
|
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
|
|
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
|
|
781
|
-
|
|
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
|
-
|
|
1941
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
2904
|
-
|
|
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
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
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 },
|
package/src/git/git-commands.ts
CHANGED
|
@@ -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
|
|