@adhdev/daemon-core 0.9.76 → 0.9.77-rc.2
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/index.d.ts +5 -0
- package/dist/index.js +903 -205
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +898 -212
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +11 -1
- package/dist/mesh/mesh-ledger.d.ts +90 -0
- package/dist/mesh/mesh-sync.d.ts +10 -0
- package/dist/mesh/mesh-work-queue.d.ts +50 -0
- package/dist/repo-mesh-types.d.ts +6 -0
- package/dist/shared-types.d.ts +12 -0
- package/package.json +1 -1
- package/src/commands/router.ts +123 -0
- package/src/index.ts +11 -0
- package/src/mesh/coordinator-prompt.ts +27 -12
- package/src/mesh/mesh-events.ts +200 -1
- package/src/mesh/mesh-ledger.ts +378 -0
- package/src/mesh/mesh-sync.ts +32 -0
- package/src/mesh/mesh-work-queue.ts +164 -0
- package/src/repo-mesh-types.ts +7 -0
- package/src/shared-types.ts +12 -0
- package/src/status/builders.ts +13 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
import { getLedgerDir } from './mesh-ledger.js';
|
|
5
|
+
|
|
6
|
+
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed';
|
|
7
|
+
|
|
8
|
+
export interface MeshWorkQueueEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
meshId: string;
|
|
11
|
+
message: string;
|
|
12
|
+
status: MeshTaskStatus;
|
|
13
|
+
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
14
|
+
targetNodeId?: string;
|
|
15
|
+
/** The node that actually claimed and is executing the task */
|
|
16
|
+
assignedNodeId?: string;
|
|
17
|
+
/** The session currently executing the task */
|
|
18
|
+
assignedSessionId?: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getQueuePath(meshId: string): string {
|
|
24
|
+
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
25
|
+
return join(getLedgerDir(), `${safe}.queue.json`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readQueue(meshId: string): MeshWorkQueueEntry[] {
|
|
29
|
+
const path = getQueuePath(meshId);
|
|
30
|
+
if (!existsSync(path)) return [];
|
|
31
|
+
try {
|
|
32
|
+
const content = readFileSync(path, 'utf-8');
|
|
33
|
+
return JSON.parse(content) as MeshWorkQueueEntry[];
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
40
|
+
const path = getQueuePath(meshId);
|
|
41
|
+
writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add a new task to the mesh queue.
|
|
46
|
+
*/
|
|
47
|
+
export function enqueueTask(
|
|
48
|
+
meshId: string,
|
|
49
|
+
message: string,
|
|
50
|
+
opts?: { targetNodeId?: string }
|
|
51
|
+
): MeshWorkQueueEntry {
|
|
52
|
+
const queue = readQueue(meshId);
|
|
53
|
+
const entry: MeshWorkQueueEntry = {
|
|
54
|
+
id: randomUUID(),
|
|
55
|
+
meshId,
|
|
56
|
+
message,
|
|
57
|
+
status: 'pending',
|
|
58
|
+
targetNodeId: opts?.targetNodeId,
|
|
59
|
+
createdAt: new Date().toISOString(),
|
|
60
|
+
updatedAt: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
queue.push(entry);
|
|
63
|
+
writeQueue(meshId, queue);
|
|
64
|
+
return entry;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get all tasks in the queue, optionally filtered by status.
|
|
69
|
+
*/
|
|
70
|
+
export function getQueue(meshId: string, opts?: { status?: MeshTaskStatus[] }): MeshWorkQueueEntry[] {
|
|
71
|
+
let queue = readQueue(meshId);
|
|
72
|
+
if (opts?.status?.length) {
|
|
73
|
+
const statuses = new Set(opts.status);
|
|
74
|
+
queue = queue.filter(q => statuses.has(q.status));
|
|
75
|
+
}
|
|
76
|
+
return queue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Find the next pending task that this node is allowed to claim, and mark it as assigned.
|
|
81
|
+
*/
|
|
82
|
+
export function claimNextTask(meshId: string, nodeId: string, sessionId: string): MeshWorkQueueEntry | null {
|
|
83
|
+
const queue = readQueue(meshId);
|
|
84
|
+
|
|
85
|
+
// Find highest priority task:
|
|
86
|
+
// 1. Pending tasks explicitly targeted at this node
|
|
87
|
+
// 2. Pending tasks with no target node
|
|
88
|
+
let targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetNodeId === nodeId);
|
|
89
|
+
if (targetIdx === -1) {
|
|
90
|
+
targetIdx = queue.findIndex(q => q.status === 'pending' && !q.targetNodeId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (targetIdx === -1) return null;
|
|
94
|
+
|
|
95
|
+
const entry = queue[targetIdx];
|
|
96
|
+
entry.status = 'assigned';
|
|
97
|
+
entry.assignedNodeId = nodeId;
|
|
98
|
+
entry.assignedSessionId = sessionId;
|
|
99
|
+
entry.updatedAt = new Date().toISOString();
|
|
100
|
+
|
|
101
|
+
writeQueue(meshId, queue);
|
|
102
|
+
return entry;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Update the status of a specific task.
|
|
107
|
+
* Used when a session completes, fails, or stalls.
|
|
108
|
+
*/
|
|
109
|
+
export function updateTaskStatus(
|
|
110
|
+
meshId: string,
|
|
111
|
+
taskId: string,
|
|
112
|
+
status: MeshTaskStatus,
|
|
113
|
+
): MeshWorkQueueEntry | null {
|
|
114
|
+
const queue = readQueue(meshId);
|
|
115
|
+
const idx = queue.findIndex(q => q.id === taskId);
|
|
116
|
+
if (idx === -1) return null;
|
|
117
|
+
|
|
118
|
+
queue[idx].status = status;
|
|
119
|
+
queue[idx].updatedAt = new Date().toISOString();
|
|
120
|
+
writeQueue(meshId, queue);
|
|
121
|
+
return queue[idx];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Update the status of the task currently assigned to a specific session.
|
|
126
|
+
*/
|
|
127
|
+
export function updateSessionTaskStatus(
|
|
128
|
+
meshId: string,
|
|
129
|
+
sessionId: string,
|
|
130
|
+
status: MeshTaskStatus,
|
|
131
|
+
): MeshWorkQueueEntry | null {
|
|
132
|
+
const queue = readQueue(meshId);
|
|
133
|
+
// Find the most recently assigned task for this session that isn't already terminal
|
|
134
|
+
// (In case multiple tasks were assigned to the same session over time, though rare)
|
|
135
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
136
|
+
if (queue[i].assignedSessionId === sessionId && queue[i].status === 'assigned') {
|
|
137
|
+
queue[i].status = status;
|
|
138
|
+
queue[i].updatedAt = new Date().toISOString();
|
|
139
|
+
writeQueue(meshId, queue);
|
|
140
|
+
return queue[i];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface MeshWorkQueueStats {
|
|
147
|
+
pending: number;
|
|
148
|
+
assigned: number;
|
|
149
|
+
completed: number;
|
|
150
|
+
failed: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Return aggregate queue statistics for the given mesh.
|
|
155
|
+
*/
|
|
156
|
+
export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
|
|
157
|
+
const queue = readQueue(meshId);
|
|
158
|
+
return {
|
|
159
|
+
pending: queue.filter(q => q.status === 'pending').length,
|
|
160
|
+
assigned: queue.filter(q => q.status === 'assigned').length,
|
|
161
|
+
completed: queue.filter(q => q.status === 'completed').length,
|
|
162
|
+
failed: queue.filter(q => q.status === 'failed').length,
|
|
163
|
+
};
|
|
164
|
+
}
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -78,6 +78,12 @@ export interface RepoMeshPolicy {
|
|
|
78
78
|
* runtimes are never stopped/deleted unless the mesh owner opts in.
|
|
79
79
|
*/
|
|
80
80
|
sessionCleanupOnNodeRemove?: RepoMeshSessionCleanupMode;
|
|
81
|
+
/**
|
|
82
|
+
* Maximum number of automatic retry recommendations for a failed task on the
|
|
83
|
+
* same node before the daemon advises the coordinator to escalate or reassign.
|
|
84
|
+
* Defaults to 1 (allow one retry). Set to 0 to disable auto-recovery advice.
|
|
85
|
+
*/
|
|
86
|
+
maxTaskRetries?: number;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
export interface RepoMeshRelatedRepo {
|
|
@@ -110,6 +116,7 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
|
|
|
110
116
|
maxParallelTasks: 2,
|
|
111
117
|
spawnedSessionVisibility: 'visible',
|
|
112
118
|
sessionCleanupOnNodeRemove: 'preserve',
|
|
119
|
+
maxTaskRetries: 1,
|
|
113
120
|
};
|
|
114
121
|
|
|
115
122
|
// ─── Capabilities ───────────────────────────────
|
package/src/shared-types.ts
CHANGED
|
@@ -389,6 +389,12 @@ export interface SessionEntry {
|
|
|
389
389
|
seenCompletionMarker?: string;
|
|
390
390
|
surfaceHidden?: boolean;
|
|
391
391
|
settings?: Record<string, any>;
|
|
392
|
+
meshQueueStats?: {
|
|
393
|
+
pending: number;
|
|
394
|
+
assigned: number;
|
|
395
|
+
completed: number;
|
|
396
|
+
failed: number;
|
|
397
|
+
};
|
|
392
398
|
}
|
|
393
399
|
|
|
394
400
|
/**
|
|
@@ -428,6 +434,12 @@ export interface CompactSessionEntry {
|
|
|
428
434
|
providerControls?: ProviderControlSchema[];
|
|
429
435
|
summaryMetadata?: ProviderSummaryMetadata;
|
|
430
436
|
settings?: Record<string, any>;
|
|
437
|
+
meshQueueStats?: {
|
|
438
|
+
pending: number;
|
|
439
|
+
assigned: number;
|
|
440
|
+
completed: number;
|
|
441
|
+
failed: number;
|
|
442
|
+
};
|
|
431
443
|
}
|
|
432
444
|
|
|
433
445
|
export type VersionUpdateReason =
|
package/src/status/builders.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
normalizeManagedStatus,
|
|
25
25
|
type NormalizeActiveChatOptions,
|
|
26
26
|
} from './normalize.js';
|
|
27
|
+
import { getMeshQueueStats } from '../mesh/mesh-work-queue.js';
|
|
27
28
|
import { normalizeProviderStateControlValues } from '../providers/provider-patch-state.js';
|
|
28
29
|
import { normalizeProviderSummaryMetadata } from '../providers/summary-metadata.js';
|
|
29
30
|
import {
|
|
@@ -177,6 +178,8 @@ function buildIdeWorkspaceSession(
|
|
|
177
178
|
const workspace = state.workspace || null;
|
|
178
179
|
const git = getGitSummaryForWorkspace(workspace, options);
|
|
179
180
|
const title = activeChat?.title || state.name;
|
|
181
|
+
const meshCoordinatorFor = state.settings?.meshCoordinatorFor as string | undefined;
|
|
182
|
+
const meshQueueStats = meshCoordinatorFor ? getMeshQueueStats(meshCoordinatorFor) : undefined;
|
|
180
183
|
return {
|
|
181
184
|
id: state.instanceId || state.type,
|
|
182
185
|
parentId: null,
|
|
@@ -200,6 +203,7 @@ function buildIdeWorkspaceSession(
|
|
|
200
203
|
errorReason: state.errorReason,
|
|
201
204
|
lastUpdated: state.lastUpdated,
|
|
202
205
|
settings: state.settings,
|
|
206
|
+
...(meshQueueStats && { meshQueueStats }),
|
|
203
207
|
};
|
|
204
208
|
}
|
|
205
209
|
|
|
@@ -216,6 +220,8 @@ function buildExtensionAgentSession(
|
|
|
216
220
|
const includeSessionControls = shouldIncludeSessionControls(profile);
|
|
217
221
|
const workspace = parent.workspace || null;
|
|
218
222
|
const git = getGitSummaryForWorkspace(workspace, options);
|
|
223
|
+
const meshCoordinatorFor = ext.settings?.meshCoordinatorFor as string | undefined;
|
|
224
|
+
const meshQueueStats = meshCoordinatorFor ? getMeshQueueStats(meshCoordinatorFor) : undefined;
|
|
219
225
|
return {
|
|
220
226
|
id: ext.instanceId || `${parent.instanceId}:${ext.type}`,
|
|
221
227
|
parentId: parent.instanceId || parent.type,
|
|
@@ -239,6 +245,7 @@ function buildExtensionAgentSession(
|
|
|
239
245
|
errorReason: ext.errorReason,
|
|
240
246
|
lastUpdated: ext.lastUpdated,
|
|
241
247
|
settings: ext.settings,
|
|
248
|
+
...(meshQueueStats && { meshQueueStats }),
|
|
242
249
|
};
|
|
243
250
|
}
|
|
244
251
|
|
|
@@ -279,6 +286,8 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
|
|
|
279
286
|
const includeSessionControls = shouldIncludeSessionControls(profile);
|
|
280
287
|
const workspace = state.workspace || null;
|
|
281
288
|
const git = getGitSummaryForWorkspace(workspace, options);
|
|
289
|
+
const meshCoordinatorFor = state.settings?.meshCoordinatorFor as string | undefined;
|
|
290
|
+
const meshQueueStats = meshCoordinatorFor ? getMeshQueueStats(meshCoordinatorFor) : undefined;
|
|
282
291
|
return {
|
|
283
292
|
id: state.instanceId,
|
|
284
293
|
parentId: null,
|
|
@@ -318,6 +327,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
|
|
|
318
327
|
errorReason: state.errorReason,
|
|
319
328
|
lastUpdated: state.lastUpdated,
|
|
320
329
|
settings: state.settings,
|
|
330
|
+
...(meshQueueStats && { meshQueueStats }),
|
|
321
331
|
};
|
|
322
332
|
}
|
|
323
333
|
|
|
@@ -330,6 +340,8 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
|
|
|
330
340
|
const includeSessionControls = shouldIncludeSessionControls(profile);
|
|
331
341
|
const workspace = state.workspace || null;
|
|
332
342
|
const git = getGitSummaryForWorkspace(workspace, options);
|
|
343
|
+
const meshCoordinatorFor = state.settings?.meshCoordinatorFor as string | undefined;
|
|
344
|
+
const meshQueueStats = meshCoordinatorFor ? getMeshQueueStats(meshCoordinatorFor) : undefined;
|
|
333
345
|
return {
|
|
334
346
|
id: state.instanceId,
|
|
335
347
|
parentId: null,
|
|
@@ -352,6 +364,7 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
|
|
|
352
364
|
errorReason: state.errorReason,
|
|
353
365
|
lastUpdated: state.lastUpdated,
|
|
354
366
|
settings: state.settings,
|
|
367
|
+
...(meshQueueStats && { meshQueueStats }),
|
|
355
368
|
};
|
|
356
369
|
}
|
|
357
370
|
|