@adhdev/daemon-core 0.9.82-rc.6 → 0.9.82-rc.8
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 +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +172 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +172 -11
- package/dist/index.mjs.map +1 -1
- package/dist/repo-mesh-types.d.ts +121 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +3 -0
- package/src/commands/router.ts +193 -10
- package/src/index.ts +8 -0
- package/src/repo-mesh-types.ts +131 -0
|
@@ -213,14 +213,135 @@ export interface RepoMeshStatus {
|
|
|
213
213
|
repoIdentity: string;
|
|
214
214
|
refreshedAt: string;
|
|
215
215
|
nodes: RepoMeshNodeStatus[];
|
|
216
|
+
queue?: RepoMeshQueueStatus;
|
|
217
|
+
ledger?: RepoMeshLedgerStatus;
|
|
218
|
+
}
|
|
219
|
+
export interface RepoMeshSessionStatus {
|
|
220
|
+
sessionId: string;
|
|
221
|
+
providerType?: string;
|
|
222
|
+
state?: string;
|
|
223
|
+
lifecycle?: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';
|
|
224
|
+
surfaceKind?: 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
|
|
225
|
+
recoveryState?: string | null;
|
|
226
|
+
workspace?: string | null;
|
|
227
|
+
title?: string | null;
|
|
228
|
+
lastActivityAt?: string | null;
|
|
229
|
+
isCached?: boolean;
|
|
230
|
+
}
|
|
231
|
+
export type RepoMeshPeerConnectionState = 'self' | 'connected' | 'connecting' | 'disconnected' | 'failed' | 'closed' | 'unknown';
|
|
232
|
+
export type RepoMeshPeerConnectionTransport = 'local' | 'direct' | 'relay' | 'unknown';
|
|
233
|
+
export interface RepoMeshPeerConnectionStatus {
|
|
234
|
+
perspective: 'selected_coordinator';
|
|
235
|
+
source: 'mesh_peer_status' | 'not_reported';
|
|
236
|
+
state: RepoMeshPeerConnectionState;
|
|
237
|
+
transport: RepoMeshPeerConnectionTransport;
|
|
238
|
+
reported: boolean;
|
|
239
|
+
reason?: string;
|
|
240
|
+
lastStateChangeAt?: string;
|
|
241
|
+
lastConnectedAt?: string;
|
|
242
|
+
lastCommandAt?: string;
|
|
216
243
|
}
|
|
217
244
|
export interface RepoMeshNodeStatus {
|
|
218
245
|
nodeId: string;
|
|
219
246
|
machineLabel: string;
|
|
220
247
|
workspace: string;
|
|
248
|
+
repoRoot?: string;
|
|
249
|
+
daemonId?: string;
|
|
250
|
+
machineId?: string;
|
|
251
|
+
machineStatus?: string;
|
|
252
|
+
isLocalWorktree?: boolean;
|
|
253
|
+
worktreeBranch?: string;
|
|
221
254
|
health: RepoMeshNodeHealth;
|
|
222
255
|
git?: GitRepoStatus;
|
|
223
256
|
providers: string[];
|
|
224
257
|
activeSessions: string[];
|
|
258
|
+
activeSessionDetails?: RepoMeshSessionStatus[];
|
|
259
|
+
providerPriority?: string[];
|
|
260
|
+
launchReady?: boolean;
|
|
261
|
+
lastSeenAt?: string;
|
|
262
|
+
updatedAt?: string;
|
|
263
|
+
connection?: RepoMeshPeerConnectionStatus;
|
|
225
264
|
error?: string;
|
|
226
265
|
}
|
|
266
|
+
export type RepoMeshQueueTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
267
|
+
export interface RepoMeshQueueTask {
|
|
268
|
+
id: string;
|
|
269
|
+
meshId: string;
|
|
270
|
+
message: string;
|
|
271
|
+
status: RepoMeshQueueTaskStatus;
|
|
272
|
+
targetNodeId?: string;
|
|
273
|
+
targetSessionId?: string;
|
|
274
|
+
assignedNodeId?: string;
|
|
275
|
+
assignedSessionId?: string;
|
|
276
|
+
cancelReason?: string;
|
|
277
|
+
cancelledAt?: string;
|
|
278
|
+
requeueReason?: string;
|
|
279
|
+
requeuedAt?: string;
|
|
280
|
+
requeueCount?: number;
|
|
281
|
+
autoLaunch?: {
|
|
282
|
+
status: 'skipped' | 'started' | 'failed' | 'completed';
|
|
283
|
+
reason?: string;
|
|
284
|
+
nodeId?: string;
|
|
285
|
+
providerType?: string;
|
|
286
|
+
sessionId?: string;
|
|
287
|
+
updatedAt: string;
|
|
288
|
+
};
|
|
289
|
+
dispatchTimestamp?: string;
|
|
290
|
+
createdAt: string;
|
|
291
|
+
updatedAt: string;
|
|
292
|
+
}
|
|
293
|
+
export interface RepoMeshQueueSummary {
|
|
294
|
+
total: number;
|
|
295
|
+
active: number;
|
|
296
|
+
historical: number;
|
|
297
|
+
pending: number;
|
|
298
|
+
assigned: number;
|
|
299
|
+
completed: number;
|
|
300
|
+
failed: number;
|
|
301
|
+
cancelled: number;
|
|
302
|
+
activeCounts: {
|
|
303
|
+
pending: number;
|
|
304
|
+
assigned: number;
|
|
305
|
+
};
|
|
306
|
+
historicalCounts: {
|
|
307
|
+
completed: number;
|
|
308
|
+
failed: number;
|
|
309
|
+
cancelled: number;
|
|
310
|
+
};
|
|
311
|
+
activeAssignments: Array<{
|
|
312
|
+
id: string;
|
|
313
|
+
nodeId?: string;
|
|
314
|
+
sessionId?: string;
|
|
315
|
+
message: string;
|
|
316
|
+
}>;
|
|
317
|
+
}
|
|
318
|
+
export interface RepoMeshQueueStatus {
|
|
319
|
+
tasks: RepoMeshQueueTask[];
|
|
320
|
+
summary: RepoMeshQueueSummary;
|
|
321
|
+
}
|
|
322
|
+
export interface RepoMeshLedgerEntryStatus {
|
|
323
|
+
id: string;
|
|
324
|
+
meshId: string;
|
|
325
|
+
timestamp: string;
|
|
326
|
+
kind: string;
|
|
327
|
+
nodeId?: string;
|
|
328
|
+
sessionId?: string;
|
|
329
|
+
providerType?: string;
|
|
330
|
+
payload: Record<string, unknown>;
|
|
331
|
+
}
|
|
332
|
+
export interface RepoMeshLedgerSummaryStatus {
|
|
333
|
+
meshId: string;
|
|
334
|
+
totalEntries: number;
|
|
335
|
+
taskDispatched: number;
|
|
336
|
+
taskCompleted: number;
|
|
337
|
+
taskFailed: number;
|
|
338
|
+
taskStalled: number;
|
|
339
|
+
sessionLaunched: number;
|
|
340
|
+
checkpointCreated: number;
|
|
341
|
+
lastActivityAt: string | null;
|
|
342
|
+
recentFailures: number;
|
|
343
|
+
}
|
|
344
|
+
export interface RepoMeshLedgerStatus {
|
|
345
|
+
entries: RepoMeshLedgerEntryStatus[];
|
|
346
|
+
summary: RepoMeshLedgerSummaryStatus;
|
|
347
|
+
}
|
package/package.json
CHANGED
|
@@ -86,6 +86,8 @@ export interface DaemonInitConfig {
|
|
|
86
86
|
|
|
87
87
|
/** Relays a command to a remote mesh node daemon */
|
|
88
88
|
dispatchMeshCommand?: (daemonId: string, command: string, args: Record<string, unknown>) => Promise<any>;
|
|
89
|
+
/** Returns selected-coordinator mesh peer telemetry for a target daemon when available. */
|
|
90
|
+
getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
// ─── Result ───
|
|
@@ -306,6 +308,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
306
308
|
sessionHostControl: config.sessionHostControl,
|
|
307
309
|
statusInstanceId: config.statusInstanceId,
|
|
308
310
|
statusVersion: config.statusVersion,
|
|
311
|
+
getMeshPeerConnectionStatus: config.getMeshPeerConnectionStatus,
|
|
309
312
|
getCdpLogFn: config.getCdpLogFn || ((ideType: string) => LOG.forComponent(`CDP:${ideType}`).asLogFn()),
|
|
310
313
|
});
|
|
311
314
|
|
package/src/commands/router.ts
CHANGED
|
@@ -222,26 +222,139 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
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
|
+
|
|
225
331
|
function applyCachedInlineMeshNodeStatus(status: Record<string, unknown>, node: any): boolean {
|
|
226
332
|
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
227
333
|
const git = buildCachedInlineMeshGitStatus(node);
|
|
228
334
|
const error = readStringValue(cachedStatus.error, node?.error);
|
|
229
335
|
const health = readStringValue(cachedStatus.health, node?.health);
|
|
230
336
|
const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
|
|
231
|
-
|
|
232
|
-
|
|
337
|
+
const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
|
|
338
|
+
const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
|
|
339
|
+
const activeSessions = readCachedInlineMeshActiveSessions(node);
|
|
340
|
+
const activeSessionDetails = readCachedInlineMeshActiveSessionDetails(node);
|
|
341
|
+
if (!git && !error && !health && !machineStatus && !lastSeenAt && !updatedAt && activeSessions.length === 0) return false;
|
|
233
342
|
if (git) status.git = git;
|
|
234
343
|
if (error) status.error = error;
|
|
344
|
+
if (machineStatus) status.machineStatus = machineStatus;
|
|
345
|
+
if (lastSeenAt) status.lastSeenAt = lastSeenAt;
|
|
346
|
+
if (updatedAt) status.updatedAt = updatedAt;
|
|
347
|
+
if (activeSessions.length > 0) status.activeSessions = activeSessions;
|
|
348
|
+
if (activeSessionDetails.length > 0) status.activeSessionDetails = activeSessionDetails;
|
|
235
349
|
if (health) {
|
|
236
350
|
status.health = health;
|
|
237
351
|
return true;
|
|
238
352
|
}
|
|
239
353
|
if (git) {
|
|
240
|
-
|
|
241
|
-
status.health = git.isGitRepo === false ? 'degraded' : dirty ? 'dirty' : 'online';
|
|
354
|
+
status.health = deriveMeshNodeHealthFromGit(git);
|
|
242
355
|
return true;
|
|
243
356
|
}
|
|
244
|
-
return
|
|
357
|
+
return activeSessions.length > 0 || !!machineStatus || !!lastSeenAt || !!updatedAt;
|
|
245
358
|
}
|
|
246
359
|
|
|
247
360
|
async function resolveProviderTypeFromPriority(args: {
|
|
@@ -661,6 +774,8 @@ export interface CommandRouterDeps {
|
|
|
661
774
|
statusVersion?: string;
|
|
662
775
|
/** Session host control plane */
|
|
663
776
|
sessionHostControl?: SessionHostControlPlane | null;
|
|
777
|
+
/** Selected-coordinator mesh peer telemetry surface for target daemons, when supported by the runtime. */
|
|
778
|
+
getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
|
|
664
779
|
}
|
|
665
780
|
|
|
666
781
|
export interface CommandRouterResult {
|
|
@@ -2913,24 +3028,91 @@ export class DaemonCommandRouter {
|
|
|
2913
3028
|
const { readLedgerEntries, getLedgerSummary } = await import('../mesh/mesh-ledger.js');
|
|
2914
3029
|
const ledgerEntries = readLedgerEntries(meshId, { tail: 20 });
|
|
2915
3030
|
const ledgerSummary = getLedgerSummary(meshId);
|
|
3031
|
+
const sessionHostRecords = this.deps.sessionHostControl?.listSessions
|
|
3032
|
+
? await this.deps.sessionHostControl.listSessions().catch(() => [])
|
|
3033
|
+
: [];
|
|
3034
|
+
const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
|
|
2916
3035
|
|
|
3036
|
+
const localMachineId = loadConfig().machineId || '';
|
|
3037
|
+
const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes)
|
|
3038
|
+
? readStringValue((mesh.nodes[0] as any)?.id, (mesh.nodes[0] as any)?.nodeId)
|
|
3039
|
+
: undefined;
|
|
3040
|
+
const refreshedAt = new Date().toISOString();
|
|
2917
3041
|
const nodeStatuses = [];
|
|
2918
|
-
for (const node of mesh.nodes || []) {
|
|
3042
|
+
for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
|
|
3043
|
+
const nodeId = String(node.id || node.nodeId || '');
|
|
3044
|
+
const daemonId = readStringValue(node.daemonId);
|
|
3045
|
+
const providerPriority = readProviderPriorityFromPolicy(node.policy);
|
|
3046
|
+
const isSelfNode = Boolean(
|
|
3047
|
+
nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId,
|
|
3048
|
+
) || Boolean(
|
|
3049
|
+
daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId),
|
|
3050
|
+
) || Boolean(meshRecord?.inline && nodeIndex === 0);
|
|
2919
3051
|
const status: Record<string, unknown> = {
|
|
2920
|
-
nodeId
|
|
3052
|
+
nodeId,
|
|
2921
3053
|
machineLabel: node.machineLabel || node.id || node.nodeId,
|
|
2922
3054
|
workspace: node.workspace,
|
|
2923
3055
|
repoRoot: node.repoRoot,
|
|
2924
3056
|
isLocalWorktree: node.isLocalWorktree,
|
|
2925
3057
|
worktreeBranch: node.worktreeBranch,
|
|
2926
|
-
daemonId
|
|
3058
|
+
daemonId,
|
|
2927
3059
|
machineId: node.machineId,
|
|
3060
|
+
machineStatus: node.machineStatus,
|
|
2928
3061
|
health: 'unknown',
|
|
2929
3062
|
providers: node.providers || [],
|
|
3063
|
+
providerPriority,
|
|
2930
3064
|
activeSessions: [],
|
|
3065
|
+
activeSessionDetails: [],
|
|
3066
|
+
launchReady: false,
|
|
2931
3067
|
};
|
|
3068
|
+
if (isSelfNode) {
|
|
3069
|
+
status.connection = {
|
|
3070
|
+
perspective: 'selected_coordinator',
|
|
3071
|
+
source: 'mesh_peer_status',
|
|
3072
|
+
state: 'self',
|
|
3073
|
+
transport: 'local',
|
|
3074
|
+
reported: true,
|
|
3075
|
+
reason: 'Selected coordinator daemon',
|
|
3076
|
+
lastStateChangeAt: refreshedAt,
|
|
3077
|
+
};
|
|
3078
|
+
} else if (daemonId) {
|
|
3079
|
+
const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
|
|
3080
|
+
status.connection = connection ?? {
|
|
3081
|
+
perspective: 'selected_coordinator',
|
|
3082
|
+
source: 'not_reported',
|
|
3083
|
+
state: 'unknown',
|
|
3084
|
+
transport: 'unknown',
|
|
3085
|
+
reported: false,
|
|
3086
|
+
reason: 'No live mesh peer telemetry reported by the selected coordinator yet.',
|
|
3087
|
+
};
|
|
3088
|
+
} else {
|
|
3089
|
+
status.connection = {
|
|
3090
|
+
perspective: 'selected_coordinator',
|
|
3091
|
+
source: 'not_reported',
|
|
3092
|
+
state: 'unknown',
|
|
3093
|
+
transport: 'unknown',
|
|
3094
|
+
reported: false,
|
|
3095
|
+
reason: 'Node has no daemon id, so mesh transport cannot be reported from the selected coordinator.',
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
const matchedLiveSessionRecords = liveMeshSessions
|
|
3099
|
+
.filter((record) => this.sessionMatchesMeshNode(record, node, nodeId));
|
|
3100
|
+
if (matchedLiveSessionRecords.length > 0) {
|
|
3101
|
+
const sessionIds = matchedLiveSessionRecords
|
|
3102
|
+
.map((record: any) => typeof record?.sessionId === 'string' ? record.sessionId : '')
|
|
3103
|
+
.filter(Boolean);
|
|
3104
|
+
const providerTypes = matchedLiveSessionRecords
|
|
3105
|
+
.map((record: any) => readStringValue(record?.providerType))
|
|
3106
|
+
.filter(Boolean) as string[];
|
|
3107
|
+
status.activeSessions = sessionIds;
|
|
3108
|
+
status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
|
|
3109
|
+
if (providerTypes.length > 0) {
|
|
3110
|
+
status.providers = Array.from(new Set([...(Array.isArray(status.providers) ? status.providers as string[] : []), ...providerTypes]));
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
2932
3113
|
if (node.workspace && typeof node.workspace === 'string') {
|
|
2933
3114
|
if (!fs.existsSync(node.workspace as string) && applyCachedInlineMeshNodeStatus(status, node)) {
|
|
3115
|
+
status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === 'online' || isSelfNode);
|
|
2934
3116
|
nodeStatuses.push(status);
|
|
2935
3117
|
continue;
|
|
2936
3118
|
}
|
|
@@ -2938,8 +3120,7 @@ export class DaemonCommandRouter {
|
|
|
2938
3120
|
const gitStatus = await getGitRepoStatus(node.workspace as string, { timeoutMs: 10_000 });
|
|
2939
3121
|
status.git = gitStatus;
|
|
2940
3122
|
if (gitStatus.isGitRepo) {
|
|
2941
|
-
|
|
2942
|
-
status.health = gitStatus.branch ? (dirty ? 'dirty' : 'online') : 'degraded';
|
|
3123
|
+
status.health = deriveMeshNodeHealthFromGit(gitStatus as unknown as Record<string, unknown>);
|
|
2943
3124
|
} else {
|
|
2944
3125
|
status.health = 'degraded';
|
|
2945
3126
|
if (gitStatus.error && !status.error) status.error = gitStatus.error;
|
|
@@ -2952,6 +3133,7 @@ export class DaemonCommandRouter {
|
|
|
2952
3133
|
} else {
|
|
2953
3134
|
applyCachedInlineMeshNodeStatus(status, node);
|
|
2954
3135
|
}
|
|
3136
|
+
status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === 'online' || isSelfNode);
|
|
2955
3137
|
nodeStatuses.push(status);
|
|
2956
3138
|
}
|
|
2957
3139
|
|
|
@@ -2961,6 +3143,7 @@ export class DaemonCommandRouter {
|
|
|
2961
3143
|
meshName: mesh.name,
|
|
2962
3144
|
repoIdentity: mesh.repoIdentity,
|
|
2963
3145
|
defaultBranch: mesh.defaultBranch,
|
|
3146
|
+
refreshedAt: new Date().toISOString(),
|
|
2964
3147
|
nodes: nodeStatuses,
|
|
2965
3148
|
queue: { tasks: queue, summary: queueSummary },
|
|
2966
3149
|
ledger: { entries: ledgerEntries, summary: ledgerSummary },
|
package/src/index.ts
CHANGED
|
@@ -103,6 +103,14 @@ export type {
|
|
|
103
103
|
LocalMeshNodeEntry,
|
|
104
104
|
RepoMeshStatus,
|
|
105
105
|
RepoMeshNodeStatus,
|
|
106
|
+
RepoMeshSessionStatus,
|
|
107
|
+
RepoMeshQueueTask,
|
|
108
|
+
RepoMeshQueueTaskStatus,
|
|
109
|
+
RepoMeshQueueSummary,
|
|
110
|
+
RepoMeshQueueStatus,
|
|
111
|
+
RepoMeshLedgerEntryStatus,
|
|
112
|
+
RepoMeshLedgerSummaryStatus,
|
|
113
|
+
RepoMeshLedgerStatus,
|
|
106
114
|
} from './repo-mesh-types.js';
|
|
107
115
|
export { DEFAULT_MESH_POLICY } from './repo-mesh-types.js';
|
|
108
116
|
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -261,15 +261,146 @@ export interface RepoMeshStatus {
|
|
|
261
261
|
repoIdentity: string;
|
|
262
262
|
refreshedAt: string;
|
|
263
263
|
nodes: RepoMeshNodeStatus[];
|
|
264
|
+
queue?: RepoMeshQueueStatus;
|
|
265
|
+
ledger?: RepoMeshLedgerStatus;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface RepoMeshSessionStatus {
|
|
269
|
+
sessionId: string;
|
|
270
|
+
providerType?: string;
|
|
271
|
+
state?: string;
|
|
272
|
+
lifecycle?: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';
|
|
273
|
+
surfaceKind?: 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
|
|
274
|
+
recoveryState?: string | null;
|
|
275
|
+
workspace?: string | null;
|
|
276
|
+
title?: string | null;
|
|
277
|
+
lastActivityAt?: string | null;
|
|
278
|
+
isCached?: boolean;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export type RepoMeshPeerConnectionState = 'self' | 'connected' | 'connecting' | 'disconnected' | 'failed' | 'closed' | 'unknown';
|
|
282
|
+
export type RepoMeshPeerConnectionTransport = 'local' | 'direct' | 'relay' | 'unknown';
|
|
283
|
+
|
|
284
|
+
export interface RepoMeshPeerConnectionStatus {
|
|
285
|
+
perspective: 'selected_coordinator';
|
|
286
|
+
source: 'mesh_peer_status' | 'not_reported';
|
|
287
|
+
state: RepoMeshPeerConnectionState;
|
|
288
|
+
transport: RepoMeshPeerConnectionTransport;
|
|
289
|
+
reported: boolean;
|
|
290
|
+
reason?: string;
|
|
291
|
+
lastStateChangeAt?: string;
|
|
292
|
+
lastConnectedAt?: string;
|
|
293
|
+
lastCommandAt?: string;
|
|
264
294
|
}
|
|
265
295
|
|
|
266
296
|
export interface RepoMeshNodeStatus {
|
|
267
297
|
nodeId: string;
|
|
268
298
|
machineLabel: string;
|
|
269
299
|
workspace: string;
|
|
300
|
+
repoRoot?: string;
|
|
301
|
+
daemonId?: string;
|
|
302
|
+
machineId?: string;
|
|
303
|
+
machineStatus?: string;
|
|
304
|
+
isLocalWorktree?: boolean;
|
|
305
|
+
worktreeBranch?: string;
|
|
270
306
|
health: RepoMeshNodeHealth;
|
|
271
307
|
git?: GitRepoStatus;
|
|
272
308
|
providers: string[];
|
|
273
309
|
activeSessions: string[];
|
|
310
|
+
activeSessionDetails?: RepoMeshSessionStatus[];
|
|
311
|
+
providerPriority?: string[];
|
|
312
|
+
launchReady?: boolean;
|
|
313
|
+
lastSeenAt?: string;
|
|
314
|
+
updatedAt?: string;
|
|
315
|
+
connection?: RepoMeshPeerConnectionStatus;
|
|
274
316
|
error?: string;
|
|
275
317
|
}
|
|
318
|
+
|
|
319
|
+
export type RepoMeshQueueTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
320
|
+
|
|
321
|
+
export interface RepoMeshQueueTask {
|
|
322
|
+
id: string;
|
|
323
|
+
meshId: string;
|
|
324
|
+
message: string;
|
|
325
|
+
status: RepoMeshQueueTaskStatus;
|
|
326
|
+
targetNodeId?: string;
|
|
327
|
+
targetSessionId?: string;
|
|
328
|
+
assignedNodeId?: string;
|
|
329
|
+
assignedSessionId?: string;
|
|
330
|
+
cancelReason?: string;
|
|
331
|
+
cancelledAt?: string;
|
|
332
|
+
requeueReason?: string;
|
|
333
|
+
requeuedAt?: string;
|
|
334
|
+
requeueCount?: number;
|
|
335
|
+
autoLaunch?: {
|
|
336
|
+
status: 'skipped' | 'started' | 'failed' | 'completed';
|
|
337
|
+
reason?: string;
|
|
338
|
+
nodeId?: string;
|
|
339
|
+
providerType?: string;
|
|
340
|
+
sessionId?: string;
|
|
341
|
+
updatedAt: string;
|
|
342
|
+
};
|
|
343
|
+
dispatchTimestamp?: string;
|
|
344
|
+
createdAt: string;
|
|
345
|
+
updatedAt: string;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export interface RepoMeshQueueSummary {
|
|
349
|
+
total: number;
|
|
350
|
+
active: number;
|
|
351
|
+
historical: number;
|
|
352
|
+
pending: number;
|
|
353
|
+
assigned: number;
|
|
354
|
+
completed: number;
|
|
355
|
+
failed: number;
|
|
356
|
+
cancelled: number;
|
|
357
|
+
activeCounts: {
|
|
358
|
+
pending: number;
|
|
359
|
+
assigned: number;
|
|
360
|
+
};
|
|
361
|
+
historicalCounts: {
|
|
362
|
+
completed: number;
|
|
363
|
+
failed: number;
|
|
364
|
+
cancelled: number;
|
|
365
|
+
};
|
|
366
|
+
activeAssignments: Array<{
|
|
367
|
+
id: string;
|
|
368
|
+
nodeId?: string;
|
|
369
|
+
sessionId?: string;
|
|
370
|
+
message: string;
|
|
371
|
+
}>;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export interface RepoMeshQueueStatus {
|
|
375
|
+
tasks: RepoMeshQueueTask[];
|
|
376
|
+
summary: RepoMeshQueueSummary;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export interface RepoMeshLedgerEntryStatus {
|
|
380
|
+
id: string;
|
|
381
|
+
meshId: string;
|
|
382
|
+
timestamp: string;
|
|
383
|
+
kind: string;
|
|
384
|
+
nodeId?: string;
|
|
385
|
+
sessionId?: string;
|
|
386
|
+
providerType?: string;
|
|
387
|
+
payload: Record<string, unknown>;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface RepoMeshLedgerSummaryStatus {
|
|
391
|
+
meshId: string;
|
|
392
|
+
totalEntries: number;
|
|
393
|
+
taskDispatched: number;
|
|
394
|
+
taskCompleted: number;
|
|
395
|
+
taskFailed: number;
|
|
396
|
+
taskStalled: number;
|
|
397
|
+
sessionLaunched: number;
|
|
398
|
+
checkpointCreated: number;
|
|
399
|
+
lastActivityAt: string | null;
|
|
400
|
+
recentFailures: number;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface RepoMeshLedgerStatus {
|
|
404
|
+
entries: RepoMeshLedgerEntryStatus[];
|
|
405
|
+
summary: RepoMeshLedgerSummaryStatus;
|
|
406
|
+
}
|