@adhdev/daemon-core 0.9.77-rc.31 → 0.9.77-rc.33

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.77-rc.31",
3
+ "version": "0.9.77-rc.33",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1331,6 +1331,21 @@ export class DaemonCommandRouter {
1331
1331
  }
1332
1332
  }
1333
1333
 
1334
+ case 'get_mesh_queue': {
1335
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1336
+ if (!meshId) return { success: false, error: 'meshId required' };
1337
+ try {
1338
+ const { getQueue } = await import('../mesh/mesh-work-queue.js');
1339
+ const status = Array.isArray(args?.status)
1340
+ ? args.status.map((s: any) => typeof s === 'string' ? s.trim() : '').filter(Boolean)
1341
+ : undefined;
1342
+ const queue = getQueue(meshId, { status: status as any });
1343
+ return { success: true, queue };
1344
+ } catch (e: any) {
1345
+ return { success: false, error: e.message };
1346
+ }
1347
+ }
1348
+
1334
1349
  case 'add_mesh_node': {
1335
1350
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
1336
1351
  const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
@@ -46,7 +46,15 @@ function readNonEmptyString(value: unknown): string {
46
46
  return typeof value === 'string' && value.trim() ? value.trim() : '';
47
47
  }
48
48
 
49
+ function resolveEventSessionId(event: Record<string, unknown>, fallback?: unknown): string {
50
+ return readNonEmptyString(event.targetSessionId)
51
+ || readNonEmptyString(event.sessionId)
52
+ || readNonEmptyString(event.instanceId)
53
+ || readNonEmptyString(fallback);
54
+ }
55
+
49
56
  const MESH_COORDINATOR_EVENTS = new Set([
57
+ 'agent:generating_started',
50
58
  'agent:generating_completed',
51
59
  'agent:waiting_approval',
52
60
  'agent:stopped',
@@ -145,13 +153,17 @@ export function triggerMeshQueue(components: DaemonComponents, meshId: string) {
145
153
  const settings = state.settings as Record<string, unknown> || {};
146
154
 
147
155
  const instMeshId = readNonEmptyString(settings.meshNodeFor);
148
- if (instMeshId !== meshId && !settings.launchedByCoordinator) continue;
156
+ if (instMeshId !== meshId) continue;
149
157
 
150
158
  const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
151
159
  if (!nodeId) continue;
152
160
 
153
- // Is it idle? (online and waiting for input)
154
- if (state.status !== 'idle' && state.status !== 'stopped' && state.activeChat?.status !== 'waiting_input') continue;
161
+ // Only genuinely idle live sessions can pull work. Restored/stopped
162
+ // records are kept for transcript/recovery visibility, but assigning
163
+ // queue items to them strands tasks in assigned/pending without chat.
164
+ const status = readNonEmptyString(state.status).toLowerCase();
165
+ if (['stopped', 'failed', 'terminated', 'exited', 'closed'].includes(status)) continue;
166
+ if (status !== 'idle' && state.activeChat?.status !== 'waiting_input') continue;
155
167
 
156
168
  const sessionId = state.instanceId;
157
169
  const providerType = state.type || readNonEmptyString(settings.providerType);
@@ -229,7 +241,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
229
241
  }) {
230
242
  // ── Task Queue & Ledger ──
231
243
  if (args.event === 'agent:generating_completed') {
232
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
244
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
233
245
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
234
246
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
235
247
 
@@ -243,9 +255,31 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
243
255
  }
244
256
  }
245
257
  } else if (args.event === 'agent:ready') {
246
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
258
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
247
259
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
248
260
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
261
+ const completedTask = sessionId
262
+ ? updateSessionTaskStatus(args.meshId, sessionId, 'completed')
263
+ : null;
264
+ if (completedTask) {
265
+ try {
266
+ appendLedgerEntry(args.meshId, {
267
+ kind: 'task_completed',
268
+ nodeId: nodeId || undefined,
269
+ sessionId,
270
+ providerType: providerType || undefined,
271
+ payload: {
272
+ event: args.event,
273
+ nodeLabel: args.nodeLabel,
274
+ taskId: completedTask.id,
275
+ completedViaReady: true,
276
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
277
+ },
278
+ });
279
+ } catch (e: any) {
280
+ LOG.warn('MeshLedger', `Failed to record task_completed from ready: ${e?.message || e}`);
281
+ }
282
+ }
249
283
 
250
284
  if (sessionId && nodeId && providerType) {
251
285
  remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
@@ -257,13 +291,13 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
257
291
  }, 500);
258
292
  }
259
293
  } else if (args.event === 'agent:generating_started') {
260
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
294
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
261
295
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
262
296
  if (sessionId && nodeId) {
263
297
  remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
264
298
  }
265
299
  } else if (args.event === 'agent:stopped') {
266
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
300
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
267
301
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
268
302
  if (sessionId && nodeId) {
269
303
  remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
@@ -279,7 +313,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
279
313
  appendLedgerEntry(args.meshId, {
280
314
  kind: ledgerKind,
281
315
  nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || undefined,
282
- sessionId: readNonEmptyString(args.metadataEvent.targetSessionId) || undefined,
316
+ sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || undefined,
283
317
  providerType: readNonEmptyString(args.metadataEvent.providerType) || undefined,
284
318
  payload: {
285
319
  event: args.event,
@@ -301,7 +335,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
301
335
  const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
302
336
 
303
337
  recoveryContext = getSessionRecoveryContext(args.meshId, {
304
- sessionId: readNonEmptyString(args.metadataEvent.targetSessionId) || undefined,
338
+ sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || undefined,
305
339
  nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || undefined,
306
340
  maxRetries,
307
341
  });
@@ -414,7 +448,7 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
414
448
  nodeLabel,
415
449
  event: eventName,
416
450
  metadataEvent: {
417
- targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
451
+ targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
418
452
  providerType: readNonEmptyString(payload.providerType),
419
453
  providerSessionId: readNonEmptyString(payload.providerSessionId),
420
454
  },
@@ -84,6 +84,14 @@ export function getQueue(meshId: string, opts?: { status?: MeshTaskStatus[] }):
84
84
  */
85
85
  export function claimNextTask(meshId: string, nodeId: string, sessionId: string): MeshWorkQueueEntry | null {
86
86
  const queue = readQueue(meshId);
87
+
88
+ // A worker must finish or fail its current queued assignment before it can
89
+ // claim another one. maxParallelTasks limits total mesh concurrency; it is
90
+ // not permission for one node/session to accumulate multiple assigned items.
91
+ const hasActiveAssignment = queue.some(q => q.status === 'assigned' && (
92
+ q.assignedSessionId === sessionId || q.assignedNodeId === nodeId
93
+ ));
94
+ if (hasActiveAssignment) return null;
87
95
 
88
96
  // Find highest priority task:
89
97
  // 1. Pending tasks explicitly targeted at this runtime session