@adhdev/daemon-core 0.9.82-rc.451 → 0.9.82-rc.453
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 +45 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/mesh/mesh-active-work.ts +7 -5
- package/src/mesh/mesh-event-forwarding.ts +9 -10
- package/src/mesh/mesh-events-stale.ts +7 -7
- package/src/mesh/mesh-ledger.ts +4 -3
- package/src/mesh/mesh-node-identity.ts +9 -6
- package/src/mesh/mesh-queue-assignment.ts +4 -4
- package/src/mesh/mesh-reconcile-loop.ts +5 -6
- package/src/mesh/mesh-review-inbox.ts +4 -3
- package/src/mesh/mesh-routing.ts +2 -2
- package/src/mesh/mesh-runtime-store.ts +6 -8
- package/src/mesh/mesh-work-queue.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.453",
|
|
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",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
50
|
-
"@adhdev/session-host-core": "
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.453",
|
|
50
|
+
"@adhdev/session-host-core": "0.9.82-rc.453",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
53
53
|
"ajv-formats": "^3.0.1",
|
|
@@ -2,7 +2,7 @@ import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
|
2
2
|
import { appendLedgerEntry } from './mesh-ledger.js';
|
|
3
3
|
import type { MeshWorkQueueEntry, DirectDispatchRecord } from './mesh-work-queue.js';
|
|
4
4
|
import { deleteDirectDispatchesByTaskId } from './mesh-work-queue.js';
|
|
5
|
-
import { meshNodeIdMatches } from '@adhdev/mesh-shared';
|
|
5
|
+
import { meshNodeIdMatches, daemonIdsEquivalent, sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
6
6
|
|
|
7
7
|
export type MeshActiveWorkSource = 'queue' | 'direct';
|
|
8
8
|
export type MeshActiveWorkStatus = 'pending' | 'assigned' | 'generating' | 'idle' | 'failed' | 'awaiting_approval';
|
|
@@ -128,9 +128,9 @@ function sessionStatusFromNodes(nodes: any[] | undefined, nodeId?: string, sessi
|
|
|
128
128
|
if (value && typeof value === 'object') candidates.push(value);
|
|
129
129
|
}
|
|
130
130
|
const session = candidates.find(item => {
|
|
131
|
-
if (typeof item === 'string') return item
|
|
131
|
+
if (typeof item === 'string') return sessionIdsEquivalent(item, sessionId);
|
|
132
132
|
const id = readString(item?.id) || readString(item?.sessionId) || readString(item?.session_id) || readString(item?.runtimeSessionId) || readString(item?.instanceId);
|
|
133
|
-
return id
|
|
133
|
+
return sessionIdsEquivalent(id, sessionId);
|
|
134
134
|
});
|
|
135
135
|
if (!session) return { staleReason: 'direct task session is not present in live session records' };
|
|
136
136
|
if (typeof session === 'string') return {};
|
|
@@ -158,8 +158,10 @@ function terminalMatchesDispatch(terminal: MeshLedgerEntry, dispatch: MeshLedger
|
|
|
158
158
|
const terminalTaskId = readString(terminal.payload?.taskId);
|
|
159
159
|
if (terminalTaskId && terminalTaskId === taskId) return true;
|
|
160
160
|
if (terminalTaskId && terminalTaskId !== taskId) return false;
|
|
161
|
-
if (dispatch.sessionId && terminal.sessionId
|
|
162
|
-
|
|
161
|
+
if (dispatch.sessionId && sessionIdsEquivalent(terminal.sessionId, dispatch.sessionId)) return true;
|
|
162
|
+
// Node ids can carry interchangeable daemon-id forms (bare `mach_X` vs
|
|
163
|
+
// `daemon_mach_X`); compare under the canonical machine core, not raw `===`.
|
|
164
|
+
return Boolean(dispatch.nodeId && daemonIdsEquivalent(terminal.nodeId, dispatch.nodeId) && !dispatch.sessionId);
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
function statusFromTerminal(entry: MeshLedgerEntry): MeshActiveWorkStatus {
|
|
@@ -15,7 +15,7 @@ import { enqueueUnresolvedDelegateForward, peekUnresolvedDelegateForwards, ackUn
|
|
|
15
15
|
import { traceMeshEventStage, traceMeshEventDrop } from './mesh-event-trace.js';
|
|
16
16
|
import { getLastDisplayMessage } from '../status/snapshot.js';
|
|
17
17
|
import { resolveDelegatedWorkerAutoApprove } from '../repo-mesh-types.js';
|
|
18
|
-
import { meshNodeIdMatches, daemonIdsEquivalent, expandDaemonIdForms, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
18
|
+
import { meshNodeIdMatches, daemonIdsEquivalent, expandDaemonIdForms, sessionIdsEquivalent, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
19
19
|
import {
|
|
20
20
|
findRecentTerminalLedgerEvidence,
|
|
21
21
|
hasDispatchAfterTerminal,
|
|
@@ -204,10 +204,9 @@ function hasRecentIntentionalCleanupStop(meshId: string, sessionId?: string, nod
|
|
|
204
204
|
const timestamp = new Date(entry.timestamp).getTime();
|
|
205
205
|
if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
|
|
206
206
|
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
if (sessionId && entry.sessionId === sessionId) return true;
|
|
207
|
+
// Session ids are single-form; sessionIdsEquivalent is the one canonical
|
|
208
|
+
// exact-match predicate (see its doc for why no form expansion is needed).
|
|
209
|
+
if (sessionId && sessionIdsEquivalent(entry.sessionId, sessionId)) return true;
|
|
211
210
|
// Normalized node-id match (P4): the cleanup-stop entry's node id may be stored as
|
|
212
211
|
// `nodeId` or `node_id` and the `nodeId` arg can be in either form — a raw `===`
|
|
213
212
|
// would miss a genuine intentional-cleanup entry and fail to suppress the stop event.
|
|
@@ -439,9 +438,9 @@ function supersedesTruncatedTerminalSummary(args: {
|
|
|
439
438
|
// task surfaces as status='unknown' / terminalKind=null in computeMeshTaskStats.
|
|
440
439
|
function resolveActiveDirectDispatchTaskId(meshId: string, sessionId: string): string | undefined {
|
|
441
440
|
try {
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
const matches = getActiveDirectDispatches(meshId).filter(d => d.sessionId
|
|
441
|
+
// Session ids are single-form; sessionIdsEquivalent is the one canonical
|
|
442
|
+
// exact-match predicate — no node-id-style form normalization needed.
|
|
443
|
+
const matches = getActiveDirectDispatches(meshId).filter(d => sessionIdsEquivalent(d.sessionId, sessionId));
|
|
445
444
|
if (!matches.length) return undefined;
|
|
446
445
|
// getActiveDirectDispatches returns rows ordered by dispatched_at ASC; the last is
|
|
447
446
|
// the most recent dispatch (the re-dispatch / nudge whose completion this is).
|
|
@@ -1747,7 +1746,7 @@ export function flushPendingForMeshIdleCoordinators(components: DaemonComponents
|
|
|
1747
1746
|
for (const pending of pendingEvents) {
|
|
1748
1747
|
const wantSession = readNonEmptyString(pending.targetCoordinatorSessionId);
|
|
1749
1748
|
const targets = wantSession
|
|
1750
|
-
? idleCoordinators.filter(c => c.sessionId
|
|
1749
|
+
? idleCoordinators.filter(c => sessionIdsEquivalent(c.sessionId, wantSession))
|
|
1751
1750
|
: idleCoordinators;
|
|
1752
1751
|
// Not deliverable into an idle target here (wrong/absent session), or a message-less
|
|
1753
1752
|
// lifecycle event (agent:ready / generating_started carry no coordinatorMessage and
|
|
@@ -1826,7 +1825,7 @@ export function setupMeshEventForwarding(components: DaemonComponents) {
|
|
|
1826
1825
|
let hasDirectDispatch = false;
|
|
1827
1826
|
try {
|
|
1828
1827
|
hasDirectDispatch =
|
|
1829
|
-
getActiveDirectDispatches(coordinatorMeshId).some(d => d.sessionId
|
|
1828
|
+
getActiveDirectDispatches(coordinatorMeshId).some(d => sessionIdsEquivalent(d.sessionId, flushInstanceId))
|
|
1830
1829
|
|| hasUnterminalDirectDispatchLedgerEntry(coordinatorMeshId, flushInstanceId);
|
|
1831
1830
|
} catch { /* best-effort */ }
|
|
1832
1831
|
if (!hasDirectDispatch) return;
|
|
@@ -4,7 +4,7 @@ import { updateDirectDispatchStatus, cleanupTerminalDirectDispatches } from './m
|
|
|
4
4
|
import { markSessionDeliveriesTerminal } from './mesh-delivery-policy.js';
|
|
5
5
|
import { queuePendingMeshCoordinatorEvent } from './mesh-events-pending.js';
|
|
6
6
|
import { readNonEmptyString, readRecord, resolveEventSessionId, readWorkerResultMetadata, isWeakCompletionEvidence, buildMeshSystemMessage } from './mesh-events-utils.js';
|
|
7
|
-
import { meshNodeIdMatches, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
7
|
+
import { meshNodeIdMatches, sessionIdsEquivalent, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
8
8
|
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Stale direct-dispatch detection & transcript reconciliation
|
|
@@ -42,7 +42,7 @@ export function findRecentTerminalLedgerEvidence(args: {
|
|
|
42
42
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
43
43
|
const entry = entries[i];
|
|
44
44
|
if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') continue;
|
|
45
|
-
if (args.sessionId && entry.sessionId
|
|
45
|
+
if (args.sessionId && sessionIdsEquivalent(entry.sessionId, args.sessionId)) {
|
|
46
46
|
return { id: entry.id, kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
|
|
47
47
|
}
|
|
48
48
|
// Normalized node-id match (P4): a ledger entry may store its node id as `nodeId`
|
|
@@ -70,7 +70,7 @@ export function hasDispatchAfterTerminal(meshId: string, sessionId: string, term
|
|
|
70
70
|
if (entry.id === terminalId) pastTerminal = true;
|
|
71
71
|
continue;
|
|
72
72
|
}
|
|
73
|
-
if (entry.kind === 'task_dispatched' && entry.sessionId
|
|
73
|
+
if (entry.kind === 'task_dispatched' && sessionIdsEquivalent(entry.sessionId, sessionId)) return true;
|
|
74
74
|
}
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
@@ -82,7 +82,7 @@ export function hasUnterminalDirectDispatchLedgerEntry(meshId: string, sessionId
|
|
|
82
82
|
const entries = readLedgerEntries(meshId, { tail: 200 });
|
|
83
83
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
84
84
|
const entry = entries[i];
|
|
85
|
-
if (entry.sessionId
|
|
85
|
+
if (!sessionIdsEquivalent(entry.sessionId, sessionId)) continue;
|
|
86
86
|
if (entry.kind === 'task_completed' || entry.kind === 'task_failed' || entry.kind === 'task_stalled') {
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
@@ -109,7 +109,7 @@ export function findTerminalLedgerEvidenceForTask(args: {
|
|
|
109
109
|
const terminalTaskId = readNonEmptyString(entry.payload?.taskId);
|
|
110
110
|
if (terminalTaskId !== taskId) continue;
|
|
111
111
|
if (entry.kind === 'task_completed' && isWeakCompletionEvidence(entry.payload)) continue;
|
|
112
|
-
if (args.sessionId && entry.sessionId && entry.sessionId
|
|
112
|
+
if (args.sessionId && entry.sessionId && !sessionIdsEquivalent(entry.sessionId, args.sessionId)) continue;
|
|
113
113
|
if (!args.sessionId && args.nodeId && entry.nodeId && !meshNodeIdMatches(entry as unknown as MeshNodeIdentified, args.nodeId)) continue;
|
|
114
114
|
return { id: entry.id, kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
|
|
115
115
|
}
|
|
@@ -127,7 +127,7 @@ function findDirectDispatchLedgerEntry(args: {
|
|
|
127
127
|
if (entry.kind !== 'task_dispatched') continue;
|
|
128
128
|
const payloadTaskId = readNonEmptyString(entry.payload?.taskId);
|
|
129
129
|
if (payloadTaskId !== args.taskId) continue;
|
|
130
|
-
if (args.sessionId && entry.sessionId && entry.sessionId
|
|
130
|
+
if (args.sessionId && entry.sessionId && !sessionIdsEquivalent(entry.sessionId, args.sessionId)) continue;
|
|
131
131
|
return {
|
|
132
132
|
id: entry.id,
|
|
133
133
|
timestamp: entry.timestamp,
|
|
@@ -172,7 +172,7 @@ function hasTerminalLedgerAfterDispatch(args: {
|
|
|
172
172
|
const terminalTaskId = readNonEmptyString(entry.payload?.taskId);
|
|
173
173
|
if (terminalTaskId && terminalTaskId === args.taskId) return true;
|
|
174
174
|
if (terminalTaskId && terminalTaskId !== args.taskId) continue;
|
|
175
|
-
if (args.sessionId && entry.sessionId
|
|
175
|
+
if (args.sessionId && sessionIdsEquivalent(entry.sessionId, args.sessionId)) return true;
|
|
176
176
|
}
|
|
177
177
|
return false;
|
|
178
178
|
}
|
package/src/mesh/mesh-ledger.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, renameSy
|
|
|
17
17
|
import { join } from 'path';
|
|
18
18
|
import { randomUUID } from 'crypto';
|
|
19
19
|
import { getConfigDir } from '../config/config.js';
|
|
20
|
+
import { daemonIdsEquivalent, sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
20
21
|
import { EventEmitter } from 'events';
|
|
21
22
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
22
23
|
// ─── Types ──────────────────────────────────────
|
|
@@ -1224,7 +1225,7 @@ export function getSessionRecoveryContext(
|
|
|
1224
1225
|
if (!failureCountDone) {
|
|
1225
1226
|
if (ts < recentWindow) {
|
|
1226
1227
|
failureCountDone = true;
|
|
1227
|
-
} else if (opts.nodeId && e.nodeId
|
|
1228
|
+
} else if (opts.nodeId && !daemonIdsEquivalent(e.nodeId, opts.nodeId)) {
|
|
1228
1229
|
// Entry for a different node — skip for failure counting but continue scanning for dispatch
|
|
1229
1230
|
} else if (e.kind === 'task_failed') {
|
|
1230
1231
|
if (!isIntentionalCleanupStopEntry(e)) consecutiveNodeFailures++;
|
|
@@ -1236,8 +1237,8 @@ export function getSessionRecoveryContext(
|
|
|
1236
1237
|
|
|
1237
1238
|
// Dispatch search: find the last dispatch matching this session or node
|
|
1238
1239
|
if (lastDispatch === null && e.kind === 'task_dispatched') {
|
|
1239
|
-
if (opts.sessionId && e.sessionId
|
|
1240
|
-
else if (!opts.sessionId && opts.nodeId && e.nodeId
|
|
1240
|
+
if (opts.sessionId && sessionIdsEquivalent(e.sessionId, opts.sessionId)) { lastDispatch = e; }
|
|
1241
|
+
else if (!opts.sessionId && opts.nodeId && daemonIdsEquivalent(e.nodeId, opts.nodeId)) { lastDispatch = e; }
|
|
1241
1242
|
}
|
|
1242
1243
|
|
|
1243
1244
|
// Stop once both tasks are done
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
15
15
|
import { detectCLI } from '../detection/cli-detector.js';
|
|
16
16
|
import { getGitRepoStatus } from '../git/git-status.js';
|
|
17
|
-
import { normalizeGitStatus as sharedNormalizeGitStatus, pickBestTransitGitStatus as sharedPickBestTransitGitStatus, summarizeGitShape as sharedSummarizeGitShape, normalizeMeshNodeId, daemonIdsEquivalent, meshWorkspacesEquivalent } from '@adhdev/mesh-shared';
|
|
17
|
+
import { normalizeGitStatus as sharedNormalizeGitStatus, pickBestTransitGitStatus as sharedPickBestTransitGitStatus, summarizeGitShape as sharedSummarizeGitShape, normalizeMeshNodeId, daemonIdsEquivalent, meshWorkspacesEquivalent, sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
18
18
|
import { LOG } from '../logging/logger.js';
|
|
19
19
|
import { getSessionHostSurfaceKind } from '../session-host/runtime-surface.js';
|
|
20
20
|
import { awaitWithWarmupDeadline, resolveWarmupDeadlineOpts } from '../mesh/mesh-warmup-deadline.js';
|
|
@@ -1606,7 +1606,7 @@ export async function hydrateInlineMeshDirectTruth(args: {
|
|
|
1606
1606
|
const workspace = readStringValue(node?.workspace);
|
|
1607
1607
|
const daemonId = readStringValue(node?.daemonId);
|
|
1608
1608
|
const isSelfNode = Boolean(
|
|
1609
|
-
nodeId && selectedCoordinatorNodeId && nodeId
|
|
1609
|
+
nodeId && selectedCoordinatorNodeId && daemonIdsEquivalent(nodeId, selectedCoordinatorNodeId),
|
|
1610
1610
|
) || Boolean(
|
|
1611
1611
|
daemonId && (daemonIdsEquivalent(daemonId, args.localMachineId) || daemonIdsEquivalent(daemonId, args.statusInstanceId)),
|
|
1612
1612
|
) || Boolean(args.meshSource !== 'local_config' && nodeIndex === 0);
|
|
@@ -1720,7 +1720,7 @@ export async function hydrateInlineMeshDirectTruth(args: {
|
|
|
1720
1720
|
const nodeId = normalizeMeshNodeId(node) || `node_${nodeIndex}`;
|
|
1721
1721
|
const daemonId = readStringValue(node?.daemonId);
|
|
1722
1722
|
const isSelfNode = Boolean(
|
|
1723
|
-
nodeId && selectedCoordinatorNodeId && nodeId
|
|
1723
|
+
nodeId && selectedCoordinatorNodeId && daemonIdsEquivalent(nodeId, selectedCoordinatorNodeId),
|
|
1724
1724
|
) || Boolean(
|
|
1725
1725
|
daemonId && (daemonIdsEquivalent(daemonId, args.localMachineId) || daemonIdsEquivalent(daemonId, args.statusInstanceId)),
|
|
1726
1726
|
);
|
|
@@ -1794,7 +1794,10 @@ export function summarizeMeshSessionRecord(record: any): Record<string, unknown>
|
|
|
1794
1794
|
|
|
1795
1795
|
function liveSessionRecordMatchesMeshNode(record: any, meshId: string, nodeId: string, nodeWorkspace = '', nodeIsMissingLocalWorktree = false): boolean {
|
|
1796
1796
|
const recordNodeId = readStringValue(record?.meta?.meshNodeId);
|
|
1797
|
-
|
|
1797
|
+
// A session's stamped meshNodeId and the node's id can carry interchangeable
|
|
1798
|
+
// daemon-id forms (bare `mach_X` vs `daemon_mach_X`) — compare under the
|
|
1799
|
+
// canonical machine core, not raw `!==` (CANON-IDENTITY class).
|
|
1800
|
+
if (!recordNodeId || !daemonIdsEquivalent(recordNodeId, nodeId)) return false;
|
|
1798
1801
|
if (nodeIsMissingLocalWorktree) return false;
|
|
1799
1802
|
const recordWorkspace = readStringValue(record?.workspace);
|
|
1800
1803
|
// Normalized compare (shared WTCLAIM rule): a base node and a co-located worktree
|
|
@@ -1855,7 +1858,7 @@ export function collectLiveMeshSessionRecords(args: {
|
|
|
1855
1858
|
&& !fs.existsSync(nodeWorkspace);
|
|
1856
1859
|
const matches = args.liveSessionRecords.filter((record) => {
|
|
1857
1860
|
const recordNodeId = readStringValue(record?.meta?.meshNodeId);
|
|
1858
|
-
if (recordNodeId && recordNodeId
|
|
1861
|
+
if (recordNodeId && !daemonIdsEquivalent(recordNodeId, args.nodeId)) return false;
|
|
1859
1862
|
if (liveSessionRecordMatchesMeshNode(record, args.meshId, args.nodeId, nodeWorkspace || '', nodeIsMissingLocalWorktree)) return true;
|
|
1860
1863
|
if (nodeIsMissingLocalWorktree) return false;
|
|
1861
1864
|
return !!nodeWorkspace && liveSessionRecordMatchesMeshWorkspace(record, args.meshId, nodeWorkspace);
|
|
@@ -1865,7 +1868,7 @@ export function collectLiveMeshSessionRecords(args: {
|
|
|
1865
1868
|
for (const record of args.liveSessionRecords) {
|
|
1866
1869
|
if (readStringValue(record?.meta?.meshCoordinatorFor) !== args.meshId) continue;
|
|
1867
1870
|
const sessionId = readStringValue(record?.sessionId);
|
|
1868
|
-
if (sessionId && matches.some((entry) => readStringValue(entry?.sessionId)
|
|
1871
|
+
if (sessionId && matches.some((entry) => sessionIdsEquivalent(readStringValue(entry?.sessionId), sessionId))) continue;
|
|
1869
1872
|
matches.push(record);
|
|
1870
1873
|
}
|
|
1871
1874
|
}
|
|
@@ -15,7 +15,7 @@ import { traceMeshEventDrop } from './mesh-event-trace.js';
|
|
|
15
15
|
import { awaitWithWarmupDeadline, resolveWarmupDeadlineOpts } from './mesh-warmup-deadline.js';
|
|
16
16
|
import { resolveDelegatedWorkerAutoApprove, resolveProviderMaxParallel, resolveNodeSchedulingPriority, normalizeMeshSchedulingStrategy, resolveMaxParallelTasks, resolveMaxReadonlyParallelTasks } from '../repo-mesh-types.js';
|
|
17
17
|
import type { RepoMeshSchedulingStrategy } from '../repo-mesh-types.js';
|
|
18
|
-
import { normalizeMeshNodeId, meshNodeIdMatches, daemonIdsEquivalent, canonicalDaemonId, normalizeMeshWorkspaceForCompare, meshWorkspacesEquivalent, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
18
|
+
import { normalizeMeshNodeId, meshNodeIdMatches, daemonIdsEquivalent, canonicalDaemonId, normalizeMeshWorkspaceForCompare, meshWorkspacesEquivalent, sessionIdsEquivalent, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
19
19
|
import { findTerminalLedgerEvidenceForTask, hasUnterminalDirectDispatchLedgerEntry } from './mesh-events-stale.js';
|
|
20
20
|
import { readNonEmptyString } from './mesh-events-utils.js';
|
|
21
21
|
import { queuePendingMeshCoordinatorEvent, retractPendingDispatchBlockedEvent } from './mesh-events-pending.js';
|
|
@@ -834,7 +834,7 @@ function nodeHasActiveMeshWork(components: DaemonComponents, meshId: string, nod
|
|
|
834
834
|
// task already running here — the CANON-IDENTITY duplicate dispatch.
|
|
835
835
|
if (!daemonIdsEquivalent(instNodeId, nodeId)) return false;
|
|
836
836
|
const sessionId = readNonEmptyString(state.instanceId);
|
|
837
|
-
if (currentSessionId && sessionId
|
|
837
|
+
if (currentSessionId && sessionIdsEquivalent(sessionId, currentSessionId) && isIdleSessionState(state)) return false;
|
|
838
838
|
return sessionStateLooksActive(state);
|
|
839
839
|
});
|
|
840
840
|
}
|
|
@@ -1042,7 +1042,7 @@ function activeProviderAssignedCount(meshId: string, nodeId: string, providerTyp
|
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
1044
1044
|
export function sessionHasActiveAssignment(meshId: string, sessionId: string): boolean {
|
|
1045
|
-
if (getQueue(meshId, { status: ['assigned'] as any }).some(task => task.assignedSessionId
|
|
1045
|
+
if (getQueue(meshId, { status: ['assigned'] as any }).some(task => sessionIdsEquivalent(task.assignedSessionId, sessionId))) {
|
|
1046
1046
|
return true;
|
|
1047
1047
|
}
|
|
1048
1048
|
// Direct dispatches (mesh_send_task) are tracked in mesh_direct_dispatches, not the
|
|
@@ -1053,7 +1053,7 @@ export function sessionHasActiveAssignment(meshId: string, sessionId: string): b
|
|
|
1053
1053
|
// session goes silently idle. This check runs before markSessionTerminal marks the
|
|
1054
1054
|
// dispatch terminal, so the in-flight dispatch is still observable here.
|
|
1055
1055
|
try {
|
|
1056
|
-
if (getActiveDirectDispatches(meshId).some(d => d.sessionId
|
|
1056
|
+
if (getActiveDirectDispatches(meshId).some(d => sessionIdsEquivalent(d.sessionId, sessionId))) return true;
|
|
1057
1057
|
if (hasUnterminalDirectDispatchLedgerEntry(meshId, sessionId)) return true;
|
|
1058
1058
|
} catch { /* best-effort — fall through to false */ }
|
|
1059
1059
|
return false;
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
} from './mesh-unresolved-forward-outbox.js';
|
|
58
58
|
import { readNonEmptyString, readMeshCompletionSummary, buildMeshSystemMessage } from './mesh-events-utils.js';
|
|
59
59
|
import { traceMeshEventStage, traceMeshEventDrop } from './mesh-event-trace.js';
|
|
60
|
-
import { expandDaemonIdForms, daemonIdsEquivalent } from '@adhdev/mesh-shared';
|
|
60
|
+
import { expandDaemonIdForms, daemonIdsEquivalent, sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
61
61
|
import { getActiveDirectDispatches, getQueue, reclaimStrandedAssignedTask, updateTaskStatus } from './mesh-work-queue.js';
|
|
62
62
|
import { readLedgerEntries } from './mesh-ledger.js';
|
|
63
63
|
import { pruneStaleDirectDispatches } from './mesh-active-work.js';
|
|
@@ -753,11 +753,10 @@ function drainAndInjectIntoTargets(
|
|
|
753
753
|
// (unchanged behaviour — regression-0 for the common case).
|
|
754
754
|
const wantSession = readNonEmptyString(pending.targetCoordinatorSessionId);
|
|
755
755
|
if (wantSession) {
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
//
|
|
759
|
-
|
|
760
|
-
const matched = targetCoordinators.filter(c => c.sessionId === wantSession);
|
|
756
|
+
// Session ids are single-form; sessionIdsEquivalent is the one canonical
|
|
757
|
+
// exact-match predicate — unlike the daemon-level set below it needs no
|
|
758
|
+
// form expansion.
|
|
759
|
+
const matched = targetCoordinators.filter(c => sessionIdsEquivalent(c.sessionId, wantSession));
|
|
761
760
|
if (matched.length === 0) {
|
|
762
761
|
// The originating coordinator session is not deliverable on this daemon
|
|
763
762
|
// right now (gone, or modal-parked and excluded from targets). Strict mode
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
14
14
|
import { buildMeshAsyncRefineJobs } from './mesh-refine-status.js';
|
|
15
|
+
import { daemonIdsEquivalent } from '@adhdev/mesh-shared';
|
|
15
16
|
|
|
16
17
|
export type MeshReviewInboxReason = 'merge_candidate' | 'refine_blocked_review';
|
|
17
18
|
|
|
@@ -171,7 +172,7 @@ function resolveNodeEvidence(nodeId: string, ledgerEntries: MeshLedgerEntry[]):
|
|
|
171
172
|
|
|
172
173
|
for (let i = ledgerEntries.length - 1; i >= 0; i--) {
|
|
173
174
|
const entry = ledgerEntries[i];
|
|
174
|
-
if (entry.nodeId
|
|
175
|
+
if (!daemonIdsEquivalent(entry.nodeId, nodeId) || !isTerminalLedgerKind(entry.kind)) continue;
|
|
175
176
|
const payload = readRecord(entry.payload) ?? {};
|
|
176
177
|
|
|
177
178
|
if (!evidence.available) {
|
|
@@ -227,7 +228,7 @@ function resolveNodeEvidence(nodeId: string, ledgerEntries: MeshLedgerEntry[]):
|
|
|
227
228
|
function hasBlockedReviewRefineResult(nodeId: string, ledgerEntries: MeshLedgerEntry[]): boolean {
|
|
228
229
|
for (let i = ledgerEntries.length - 1; i >= 0; i--) {
|
|
229
230
|
const entry = ledgerEntries[i];
|
|
230
|
-
if (entry.nodeId
|
|
231
|
+
if (!daemonIdsEquivalent(entry.nodeId, nodeId) || !isTerminalLedgerKind(entry.kind)) continue;
|
|
231
232
|
const payload = readRecord(entry.payload) ?? {};
|
|
232
233
|
if (payload.source !== 'refine_mesh_node_async_job') continue;
|
|
233
234
|
const result = readRecord(payload.result);
|
|
@@ -275,7 +276,7 @@ export function deriveMeshReviewInboxItems(args: {
|
|
|
275
276
|
|
|
276
277
|
const { evidence, transcriptHandle } = resolveNodeEvidence(nodeId, args.ledgerEntries);
|
|
277
278
|
const activeRefineJob = [...refineJobs].reverse().find(job =>
|
|
278
|
-
(job.nodeId
|
|
279
|
+
(daemonIdsEquivalent(job.nodeId, nodeId) || daemonIdsEquivalent(job.targetNodeId, nodeId))
|
|
279
280
|
&& (job.status === 'accepted' || job.status === 'running'),
|
|
280
281
|
) ?? null;
|
|
281
282
|
|
package/src/mesh/mesh-routing.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { hasUnterminalDirectDispatchLedgerEntry } from './mesh-events-stale.js';
|
|
|
4
4
|
import { appendLedgerEntry, readLedgerEntries } from './mesh-ledger.js';
|
|
5
5
|
import { LOG } from '../logging/logger.js';
|
|
6
6
|
import { readNonEmptyString } from './mesh-events-utils.js';
|
|
7
|
-
import { meshNodeIdMatches } from '@adhdev/mesh-shared';
|
|
7
|
+
import { meshNodeIdMatches, sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
8
8
|
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// R1: single-source coordinator routing resolution
|
|
@@ -117,7 +117,7 @@ export function resolveWorkerDelegateRouting(
|
|
|
117
117
|
let hasActiveDispatch = false;
|
|
118
118
|
try {
|
|
119
119
|
hasActiveDispatch =
|
|
120
|
-
getActiveDirectDispatches(coordinatorMeshId).some(d => d.sessionId
|
|
120
|
+
getActiveDirectDispatches(coordinatorMeshId).some(d => sessionIdsEquivalent(d.sessionId, instanceId))
|
|
121
121
|
|| hasUnterminalDirectDispatchLedgerEntry(coordinatorMeshId, instanceId);
|
|
122
122
|
} catch { /* best-effort */ }
|
|
123
123
|
if (!hasActiveDispatch) return reject('coordinator_not_dispatch_target');
|
|
@@ -4,7 +4,7 @@ import { LOG } from '../logging/logger.js';
|
|
|
4
4
|
import { loadBetterSqlite3 } from '../system/load-better-sqlite3.js';
|
|
5
5
|
import { getLedgerDir } from './mesh-ledger.js';
|
|
6
6
|
import { nodeSatisfiesRequiredTags, isTaskReadonly } from './mesh-work-queue.js';
|
|
7
|
-
import { meshNodeIdMatches, daemonIdsEquivalent, expandDaemonIdForms } from '@adhdev/mesh-shared';
|
|
7
|
+
import { meshNodeIdMatches, daemonIdsEquivalent, expandDaemonIdForms, sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
8
8
|
import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
9
9
|
import type BetterSqlite3 from 'better-sqlite3';
|
|
10
10
|
import type { Database as DatabaseHandle } from 'better-sqlite3';
|
|
@@ -795,13 +795,11 @@ export class MeshRuntimeStore {
|
|
|
795
795
|
// an empty session. Accept the candidate when the target resolves to the
|
|
796
796
|
// same node under ANY equivalent form; keep targetSessionId an exact match.
|
|
797
797
|
const targetMatches = (candidate: MeshWorkQueueEntry): boolean => {
|
|
798
|
-
//
|
|
799
|
-
// forms requiring expandDaemonIdForms)
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
|
|
803
|
-
// is correct here and needs no normalization helper.
|
|
804
|
-
if (candidate.targetSessionId && candidate.targetSessionId !== sessionId) return false;
|
|
798
|
+
// Session ids are single-form (unlike node/daemon ids with their 3
|
|
799
|
+
// serialization forms requiring expandDaemonIdForms) — see the
|
|
800
|
+
// sessionIdsEquivalent doc; it is the one canonical exact-match
|
|
801
|
+
// predicate for them.
|
|
802
|
+
if (candidate.targetSessionId && !sessionIdsEquivalent(candidate.targetSessionId, sessionId)) return false;
|
|
805
803
|
if (
|
|
806
804
|
candidate.targetNodeId
|
|
807
805
|
&& !daemonIdsEquivalent(candidate.targetNodeId, nodeId)
|
|
@@ -9,6 +9,7 @@ import { appendLedgerEntry } from './mesh-ledger.js';
|
|
|
9
9
|
import type { MeshLedgerKind } from './mesh-ledger.js';
|
|
10
10
|
import { createSessionDelivery } from './mesh-delivery-policy.js';
|
|
11
11
|
import { isTaskDispatchInFlight, endTaskDispatchInFlight } from './mesh-task-inflight.js';
|
|
12
|
+
import { sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
12
13
|
|
|
13
14
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
14
15
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
@@ -1248,7 +1249,7 @@ export function updateSessionTaskStatus(
|
|
|
1248
1249
|
// `assigned` for 19 minutes. If the session still has an assigned row we
|
|
1249
1250
|
// failed to resolve, surface it loudly instead of dropping the completion.
|
|
1250
1251
|
const assignedRows = store.getActiveAssignmentDetails(meshId)
|
|
1251
|
-
.filter(r => r.sessionId
|
|
1252
|
+
.filter(r => sessionIdsEquivalent(r.sessionId, sessionId));
|
|
1252
1253
|
if (assignedRows.length > 0) {
|
|
1253
1254
|
LOG.warn('MeshQueue', `No assigned queue row matched completion for mesh ${meshId} session ${sessionId} `
|
|
1254
1255
|
+ `(taskId=${opts?.taskId ?? 'none'}, occurredAt=${occurredAtIso ?? 'none'}); `
|