@adhdev/daemon-core 0.9.82-rc.190 → 0.9.82-rc.192
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.js +104 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +104 -60
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/beads-db.d.ts +10 -0
- package/package.json +1 -1
- package/src/mesh/beads-db.ts +37 -0
- package/src/mesh/mesh-events.ts +79 -75
package/dist/mesh/beads-db.d.ts
CHANGED
|
@@ -70,4 +70,14 @@ export declare class BeadsDB {
|
|
|
70
70
|
cleanupTerminalDirectDispatches(olderThanMs: number): void;
|
|
71
71
|
deleteDirectDispatches(meshId: string): void;
|
|
72
72
|
markStaleDirectDispatches(meshId: string, olderThanMs: number): void;
|
|
73
|
+
setRemoteIdleSession(nodeId: string, sessionId: string, providerType: string, expiresAt: number, metadata?: any): void;
|
|
74
|
+
getRemoteIdleSessions(): Array<{
|
|
75
|
+
nodeId: string;
|
|
76
|
+
sessionId: string;
|
|
77
|
+
providerType: string;
|
|
78
|
+
expiresAt: number;
|
|
79
|
+
metadata?: any;
|
|
80
|
+
}>;
|
|
81
|
+
deleteRemoteIdleSession(nodeId: string, sessionId: string): void;
|
|
82
|
+
pruneExpiredRemoteIdleSessions(): void;
|
|
73
83
|
}
|
package/package.json
CHANGED
package/src/mesh/beads-db.ts
CHANGED
|
@@ -111,6 +111,15 @@ export class BeadsDB {
|
|
|
111
111
|
|
|
112
112
|
CREATE INDEX IF NOT EXISTS idx_direct_dispatches_mesh_session
|
|
113
113
|
ON mesh_direct_dispatches(mesh_id, session_id, status);
|
|
114
|
+
|
|
115
|
+
CREATE TABLE IF NOT EXISTS remote_idle_sessions (
|
|
116
|
+
node_id TEXT NOT NULL,
|
|
117
|
+
session_id TEXT NOT NULL,
|
|
118
|
+
provider_type TEXT NOT NULL,
|
|
119
|
+
expires_at INTEGER NOT NULL,
|
|
120
|
+
metadata TEXT,
|
|
121
|
+
PRIMARY KEY (node_id, session_id)
|
|
122
|
+
);
|
|
114
123
|
`);
|
|
115
124
|
}
|
|
116
125
|
|
|
@@ -487,4 +496,32 @@ export class BeadsDB {
|
|
|
487
496
|
WHERE mesh_id = ? AND status = 'dispatched' AND dispatched_at < ?
|
|
488
497
|
`).run(now, meshId, cutoff);
|
|
489
498
|
}
|
|
499
|
+
|
|
500
|
+
// ── Remote Idle Sessions ─────────────────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
setRemoteIdleSession(nodeId: string, sessionId: string, providerType: string, expiresAt: number, metadata?: any): void {
|
|
503
|
+
this.db.prepare(`
|
|
504
|
+
INSERT OR REPLACE INTO remote_idle_sessions (node_id, session_id, provider_type, expires_at, metadata)
|
|
505
|
+
VALUES (?, ?, ?, ?, ?)
|
|
506
|
+
`).run(nodeId, sessionId, providerType, expiresAt, metadata ? JSON.stringify(metadata) : null);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
getRemoteIdleSessions(): Array<{ nodeId: string; sessionId: string; providerType: string; expiresAt: number; metadata?: any }> {
|
|
510
|
+
const rows = this.db.prepare('SELECT node_id, session_id, provider_type, expires_at, metadata FROM remote_idle_sessions').all() as Array<any>;
|
|
511
|
+
return rows.map(r => ({
|
|
512
|
+
nodeId: r.node_id,
|
|
513
|
+
sessionId: r.session_id,
|
|
514
|
+
providerType: r.provider_type,
|
|
515
|
+
expiresAt: r.expires_at,
|
|
516
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : undefined,
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
deleteRemoteIdleSession(nodeId: string, sessionId: string): void {
|
|
521
|
+
this.db.prepare('DELETE FROM remote_idle_sessions WHERE node_id = ? AND session_id = ?').run(nodeId, sessionId);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
pruneExpiredRemoteIdleSessions(): void {
|
|
525
|
+
this.db.prepare('DELETE FROM remote_idle_sessions WHERE expires_at <= ?').run(Date.now());
|
|
526
|
+
}
|
|
490
527
|
}
|
package/src/mesh/mesh-events.ts
CHANGED
|
@@ -18,14 +18,7 @@ import { fastForwardMeshNode } from './mesh-fast-forward.js';
|
|
|
18
18
|
// can assign tasks to them. Each entry carries an expiresAt timestamp;
|
|
19
19
|
// entries are swept on insertion to prevent unbounded growth.
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
|
-
interface RemoteIdleSession {
|
|
22
|
-
nodeId: string;
|
|
23
|
-
sessionId: string;
|
|
24
|
-
providerType: string;
|
|
25
|
-
expiresAt: number;
|
|
26
|
-
}
|
|
27
21
|
const REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
28
|
-
const remoteIdleSessions = new Map<string, RemoteIdleSession>(); // key: `${nodeId}:${sessionId}`
|
|
29
22
|
|
|
30
23
|
// ---------------------------------------------------------------------------
|
|
31
24
|
// Workspace-to-mesh lookup cache
|
|
@@ -55,10 +48,9 @@ export function __resetIdleAutoFastForwardForTests(): void {
|
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
function sweepExpiredRemoteIdleSessions(): void {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
51
|
+
try {
|
|
52
|
+
BeadsDB.getInstance().pruneExpiredRemoteIdleSessions();
|
|
53
|
+
} catch { /* best-effort */ }
|
|
62
54
|
}
|
|
63
55
|
|
|
64
56
|
// ---------------------------------------------------------------------------
|
|
@@ -1286,14 +1278,21 @@ export async function triggerMeshQueue(components: DaemonComponents, meshId: str
|
|
|
1286
1278
|
}
|
|
1287
1279
|
|
|
1288
1280
|
// Also check known idle remote sessions
|
|
1289
|
-
|
|
1281
|
+
let remoteSessions: Array<{ nodeId: string; sessionId: string; providerType: string }> = [];
|
|
1282
|
+
try {
|
|
1283
|
+
remoteSessions = BeadsDB.getInstance().getRemoteIdleSessions();
|
|
1284
|
+
} catch { /* best-effort */ }
|
|
1285
|
+
|
|
1286
|
+
for (const idle of remoteSessions) {
|
|
1290
1287
|
// Find if this node is in the same mesh
|
|
1291
1288
|
const node = mesh.nodes.find((n: any) => n.id === idle.nodeId);
|
|
1292
1289
|
if (node) {
|
|
1293
1290
|
remoteIdleSessionsChecked += 1;
|
|
1294
1291
|
const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
|
|
1295
1292
|
if (assigned) {
|
|
1296
|
-
|
|
1293
|
+
try {
|
|
1294
|
+
BeadsDB.getInstance().deleteRemoteIdleSession(idle.nodeId, idle.sessionId);
|
|
1295
|
+
} catch { /* best-effort */ }
|
|
1297
1296
|
}
|
|
1298
1297
|
}
|
|
1299
1298
|
}
|
|
@@ -1534,7 +1533,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1534
1533
|
});
|
|
1535
1534
|
if (intentionalCleanupStop) {
|
|
1536
1535
|
if (eventSessionId && eventNodeId) {
|
|
1537
|
-
|
|
1536
|
+
try {
|
|
1537
|
+
BeadsDB.getInstance().deleteRemoteIdleSession(eventNodeId, eventSessionId);
|
|
1538
|
+
} catch { /* best-effort */ }
|
|
1538
1539
|
}
|
|
1539
1540
|
LOG.info('MeshEvents', `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || '(unknown session)'}`);
|
|
1540
1541
|
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
@@ -1632,19 +1633,26 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1632
1633
|
}
|
|
1633
1634
|
|
|
1634
1635
|
// ── Task Queue & Ledger ──
|
|
1636
|
+
// Helpers that keep queue and direct-dispatch status transitions symmetric.
|
|
1637
|
+
// Both paths must move together so buildMeshActiveWork and the coordinator
|
|
1638
|
+
// view stay consistent regardless of which dispatch path was used.
|
|
1639
|
+
function markSessionTerminal(sessionId: string, outcome: 'completed' | 'failed', occurredAtMs?: number | null): { id?: string } | null {
|
|
1640
|
+
const task = updateSessionTaskStatus(args.meshId, sessionId, outcome, {
|
|
1641
|
+
occurredAt: occurredAtMs != null ? new Date(occurredAtMs).toISOString() : undefined,
|
|
1642
|
+
});
|
|
1643
|
+
updateDirectDispatchStatus(args.meshId, sessionId, outcome);
|
|
1644
|
+
setImmediate(() => cleanupTerminalDirectDispatches());
|
|
1645
|
+
return task ? { id: task.id } : null;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1635
1648
|
let completedTaskForLedger: { id?: string } | null = null;
|
|
1636
1649
|
if (args.event === 'agent:generating_completed') {
|
|
1637
1650
|
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
1638
1651
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1639
1652
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
1640
|
-
|
|
1653
|
+
|
|
1641
1654
|
if (sessionId) {
|
|
1642
|
-
|
|
1643
|
-
occurredAt: eventTimestamp !== null ? new Date(eventTimestamp).toISOString() : undefined,
|
|
1644
|
-
});
|
|
1645
|
-
completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
|
|
1646
|
-
updateDirectDispatchStatus(args.meshId, sessionId, 'completed');
|
|
1647
|
-
setImmediate(() => cleanupTerminalDirectDispatches());
|
|
1655
|
+
completedTaskForLedger = markSessionTerminal(sessionId, 'completed', eventTimestamp);
|
|
1648
1656
|
if (nodeId && providerType) {
|
|
1649
1657
|
runIdleMaintenanceThenAssignQueue(components, { meshId: args.meshId, nodeId, sessionId, providerType });
|
|
1650
1658
|
}
|
|
@@ -1657,55 +1665,51 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1657
1665
|
const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
|
|
1658
1666
|
const workerResult = readWorkerResultMetadata(args.metadataEvent);
|
|
1659
1667
|
const hasCompletionEvidence = !!finalSummary || !!workerResult;
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
event: args.event,
|
|
1675
|
-
nodeLabel: args.nodeLabel,
|
|
1676
|
-
taskId: completedTask.id,
|
|
1677
|
-
completedViaReady: true,
|
|
1678
|
-
providerSessionId,
|
|
1679
|
-
finalSummary,
|
|
1680
|
-
workerResult,
|
|
1681
|
-
evidence: buildTaskCompletionEvidence({
|
|
1682
|
-
event: 'agent:ready',
|
|
1683
|
-
nodeId,
|
|
1684
|
-
sessionId,
|
|
1685
|
-
providerType: providerType || undefined,
|
|
1668
|
+
if (sessionId && hasCompletionEvidence) {
|
|
1669
|
+
completedTaskForLedger = markSessionTerminal(sessionId, 'completed');
|
|
1670
|
+
if (completedTaskForLedger) {
|
|
1671
|
+
try {
|
|
1672
|
+
appendLedgerEntry(args.meshId, {
|
|
1673
|
+
kind: 'task_completed',
|
|
1674
|
+
nodeId: nodeId || undefined,
|
|
1675
|
+
sessionId,
|
|
1676
|
+
providerType: providerType || undefined,
|
|
1677
|
+
payload: {
|
|
1678
|
+
event: args.event,
|
|
1679
|
+
nodeLabel: args.nodeLabel,
|
|
1680
|
+
taskId: completedTaskForLedger.id,
|
|
1681
|
+
completedViaReady: true,
|
|
1686
1682
|
providerSessionId,
|
|
1687
1683
|
finalSummary,
|
|
1688
1684
|
workerResult,
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1685
|
+
evidence: buildTaskCompletionEvidence({
|
|
1686
|
+
event: 'agent:ready',
|
|
1687
|
+
nodeId,
|
|
1688
|
+
sessionId,
|
|
1689
|
+
providerType: providerType || undefined,
|
|
1690
|
+
providerSessionId,
|
|
1691
|
+
finalSummary,
|
|
1692
|
+
workerResult,
|
|
1693
|
+
}),
|
|
1694
|
+
},
|
|
1695
|
+
});
|
|
1696
|
+
} catch (e: any) {
|
|
1697
|
+
LOG.warn('MeshLedger', `Failed to record task_completed from ready: ${e?.message || e}`);
|
|
1698
|
+
}
|
|
1694
1699
|
}
|
|
1695
1700
|
}
|
|
1696
|
-
|
|
1701
|
+
|
|
1697
1702
|
if (sessionId && nodeId && providerType) {
|
|
1698
1703
|
sweepExpiredRemoteIdleSessions();
|
|
1699
|
-
|
|
1700
|
-
nodeId, sessionId, providerType,
|
|
1701
|
-
|
|
1702
|
-
});
|
|
1704
|
+
try {
|
|
1705
|
+
BeadsDB.getInstance().setRemoteIdleSession(nodeId, sessionId, providerType, Date.now() + REMOTE_IDLE_SESSION_TTL_MS);
|
|
1706
|
+
} catch { /* best-effort */ }
|
|
1703
1707
|
setImmediate(() => {
|
|
1704
1708
|
maybeAutoFastForwardIdleNode(components, { meshId: args.meshId, nodeId, sessionId, providerType })
|
|
1705
1709
|
.finally(() => {
|
|
1706
1710
|
try {
|
|
1707
1711
|
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
1708
|
-
if (assigned)
|
|
1712
|
+
if (assigned) BeadsDB.getInstance().deleteRemoteIdleSession(nodeId, sessionId);
|
|
1709
1713
|
} catch (e: any) {
|
|
1710
1714
|
LOG.warn('MeshQueue', `Failed to assign idle queue task after maintenance for ${nodeId}: ${e?.message || e}`);
|
|
1711
1715
|
}
|
|
@@ -1716,22 +1720,23 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1716
1720
|
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
1717
1721
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1718
1722
|
if (sessionId && nodeId) {
|
|
1719
|
-
|
|
1723
|
+
try {
|
|
1724
|
+
BeadsDB.getInstance().deleteRemoteIdleSession(nodeId, sessionId);
|
|
1725
|
+
} catch { /* best-effort */ }
|
|
1720
1726
|
}
|
|
1721
1727
|
if (sessionId) {
|
|
1722
|
-
// Mark direct dispatch as acknowledged — the session started generating.
|
|
1723
1728
|
updateDirectDispatchStatus(args.meshId, sessionId, 'acked');
|
|
1724
1729
|
}
|
|
1725
1730
|
} else if (args.event === 'agent:stopped') {
|
|
1726
1731
|
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
1727
1732
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1728
1733
|
if (sessionId && nodeId) {
|
|
1729
|
-
|
|
1734
|
+
try {
|
|
1735
|
+
BeadsDB.getInstance().deleteRemoteIdleSession(nodeId, sessionId);
|
|
1736
|
+
} catch { /* best-effort */ }
|
|
1730
1737
|
}
|
|
1731
1738
|
if (sessionId) {
|
|
1732
|
-
|
|
1733
|
-
completedTaskForLedger = failedTask ? { id: failedTask.id } : null;
|
|
1734
|
-
updateDirectDispatchStatus(args.meshId, sessionId, 'failed');
|
|
1739
|
+
completedTaskForLedger = markSessionTerminal(sessionId, 'failed');
|
|
1735
1740
|
}
|
|
1736
1741
|
}
|
|
1737
1742
|
|
|
@@ -2019,21 +2024,20 @@ export function setupMeshEventForwarding(components: DaemonComponents) {
|
|
|
2019
2024
|
if (!workspace) return;
|
|
2020
2025
|
const settings = state.settings && typeof state.settings === 'object' ? state.settings as Record<string, unknown> : {};
|
|
2021
2026
|
|
|
2022
|
-
//
|
|
2023
|
-
// coordinator
|
|
2024
|
-
//
|
|
2025
|
-
//
|
|
2026
|
-
//
|
|
2027
|
-
//
|
|
2028
|
-
// session is purely a coordinator with no in-flight direct dispatch waiting on it.
|
|
2027
|
+
// A coordinator session normally must not inject events into itself. However,
|
|
2028
|
+
// a coordinator can also be the direct-dispatch target of mesh_send_task from
|
|
2029
|
+
// another coordinator. In that case the completion event must flow through
|
|
2030
|
+
// injectMeshSystemMessage so the ledger records task_completed and the other
|
|
2031
|
+
// coordinator's pendingCoordinatorEvents queue is populated. Skip only when
|
|
2032
|
+
// this session has no in-flight direct dispatch.
|
|
2029
2033
|
const coordinatorMeshId = readNonEmptyString(settings.meshCoordinatorFor);
|
|
2030
2034
|
let meshIdFromDirectDispatch = '';
|
|
2031
2035
|
if (coordinatorMeshId) {
|
|
2032
2036
|
try {
|
|
2033
|
-
const
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
+
const hasActiveDispatch =
|
|
2038
|
+
getActiveDirectDispatches(coordinatorMeshId).some(d => d.sessionId === instanceId)
|
|
2039
|
+
|| hasUnterminalDirectDispatchLedgerEntry(coordinatorMeshId, instanceId);
|
|
2040
|
+
if (hasActiveDispatch) meshIdFromDirectDispatch = coordinatorMeshId;
|
|
2037
2041
|
} catch { /* best-effort */ }
|
|
2038
2042
|
if (!meshIdFromDirectDispatch) return;
|
|
2039
2043
|
}
|