@adhdev/daemon-core 0.9.82-rc.285 → 0.9.82-rc.287
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 +24 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/mesh/mesh-reconcile-loop.ts +67 -15
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.287",
|
|
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,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.287",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -101,6 +101,13 @@ function resolveCoordinatorDaemonIds(components: DaemonComponents): string[] {
|
|
|
101
101
|
// standalone-compat meshes with no host metadata) AND, when a hostDaemonId is
|
|
102
102
|
// pinned, it resolves to one of this daemon's ids. Member-only daemons return
|
|
103
103
|
// false — their own queue is pulled BY the host, not the other way around.
|
|
104
|
+
//
|
|
105
|
+
// `daemonIds` here is the EXPANDED self-identity set (runtime drain ids ∪ this
|
|
106
|
+
// daemon's mesh-config node id forms) — see resolveCoordinatorSelfIds. The
|
|
107
|
+
// pinned hostDaemonId is itself a config-form id and frequently does NOT equal a
|
|
108
|
+
// runtime id (bare machineId / status id), so gating on the runtime ids alone
|
|
109
|
+
// would wrongly classify the real host as a non-host and skip the remote pull
|
|
110
|
+
// entirely.
|
|
104
111
|
function daemonHostsMesh(mesh: LocalMeshEntry, daemonIds: string[]): boolean {
|
|
105
112
|
const host = mesh.meshHost;
|
|
106
113
|
// No metadata → default host (standalone compatibility, see createDefaultMeshHostMetadata).
|
|
@@ -112,6 +119,39 @@ function daemonHostsMesh(mesh: LocalMeshEntry, daemonIds: string[]): boolean {
|
|
|
112
119
|
return daemonIds.includes(hostDaemonId);
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
// Resolve EVERY id-form this daemon answers to FOR A GIVEN MESH: the runtime drain
|
|
123
|
+
// ids (status id + bare machineId) unioned with this daemon's mesh-config identity
|
|
124
|
+
// forms — the self node's daemonId/machineId (the node whose daemonId/machineId
|
|
125
|
+
// matches a runtime id) and the pinned meshHost.hostDaemonId WHEN it is provably
|
|
126
|
+
// ours. This is the single source of truth for "is this id me?" across both the
|
|
127
|
+
// host gate and the remote pull filter; the worker's meshCoordinatorDaemonId stamp
|
|
128
|
+
// is guaranteed to be one of these forms (it comes from resolveCoordinatorDaemonId,
|
|
129
|
+
// which prefers the coordinator node's config-form daemonId over the runtime status id).
|
|
130
|
+
function resolveCoordinatorSelfIds(mesh: LocalMeshEntry, drainDaemonIds: string[]): string[] {
|
|
131
|
+
const ids = new Set<string>(drainDaemonIds);
|
|
132
|
+
// Expand with the config-form id(s) of the self node — the mesh node whose
|
|
133
|
+
// daemonId/machineId matches a runtime id. Its config-form daemonId is exactly
|
|
134
|
+
// what resolveCoordinatorNode()→resolveCoordinatorDaemonId() stamps onto a worker.
|
|
135
|
+
for (const node of mesh.nodes) {
|
|
136
|
+
const nodeDaemonId = readNonEmptyString(node.daemonId);
|
|
137
|
+
const nodeMachineId = readNonEmptyString(node.machineId);
|
|
138
|
+
const isSelf = (nodeDaemonId && drainDaemonIds.includes(nodeDaemonId))
|
|
139
|
+
|| (nodeMachineId && drainDaemonIds.includes(nodeMachineId));
|
|
140
|
+
if (!isSelf) continue;
|
|
141
|
+
if (nodeDaemonId) ids.add(nodeDaemonId);
|
|
142
|
+
if (nodeMachineId) ids.add(nodeMachineId);
|
|
143
|
+
}
|
|
144
|
+
// The pinned host id is included ONLY when it is provably one of THIS daemon's ids
|
|
145
|
+
// (it already matches a runtime id or a resolved self-node id). A hostDaemonId that
|
|
146
|
+
// names a DIFFERENT daemon must NOT be claimed — that would make a member-only
|
|
147
|
+
// daemon believe it is the host and pull queues it does not own. Having a node on
|
|
148
|
+
// this daemon does not make this daemon the host; daemonHostsMesh still honours a
|
|
149
|
+
// foreign hostDaemonId and rejects ownership.
|
|
150
|
+
const hostDaemonId = readNonEmptyString(mesh.meshHost?.hostDaemonId);
|
|
151
|
+
if (hostDaemonId && ids.has(hostDaemonId)) ids.add(hostDaemonId);
|
|
152
|
+
return [...ids];
|
|
153
|
+
}
|
|
154
|
+
|
|
115
155
|
// Find live CLI coordinator instances on THIS daemon, keyed by mesh.
|
|
116
156
|
function findLiveCoordinators(components: DaemonComponents): LiveCoordinator[] {
|
|
117
157
|
const out: LiveCoordinator[] = [];
|
|
@@ -179,9 +219,13 @@ export async function runMeshReconcileTick(components: DaemonComponents): Promis
|
|
|
179
219
|
// remote worker's completion.
|
|
180
220
|
if (dispatchMeshCommand) {
|
|
181
221
|
for (const mesh of listMeshes()) {
|
|
182
|
-
|
|
222
|
+
// Expand to every id-form this daemon answers to for this mesh (runtime
|
|
223
|
+
// drain ids ∪ config-form node/host ids) and use it for BOTH the host gate
|
|
224
|
+
// and the remote pull filter, so a worker stamp in any form is recovered.
|
|
225
|
+
const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
|
|
226
|
+
if (!daemonHostsMesh(mesh, selfIds)) continue;
|
|
183
227
|
try {
|
|
184
|
-
await pullRemoteNodeQueues(components, mesh, localDaemonId,
|
|
228
|
+
await pullRemoteNodeQueues(components, mesh, localDaemonId, selfIds);
|
|
185
229
|
} catch (e: any) {
|
|
186
230
|
LOG.warn('MeshReconcile', `Remote node pull failed for mesh ${mesh.id}: ${e?.message || e}`);
|
|
187
231
|
}
|
|
@@ -258,35 +302,43 @@ export async function runMeshReconcileTick(components: DaemonComponents): Promis
|
|
|
258
302
|
// Scoping: the remote handler (get_pending_mesh_events) drains its queue filtered
|
|
259
303
|
// by coordinatorDaemonId — returning events targeted at that id OR unscoped, and
|
|
260
304
|
// leaving events targeted at a *different* coordinator. A remote worker stamps the
|
|
261
|
-
// coordinator id in one of
|
|
262
|
-
// `daemon_<machineId>` stamped by the MCP layer,
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
305
|
+
// coordinator id in one of SEVERAL forms (the canonical status id `standalone_`/
|
|
306
|
+
// `daemon_<machineId>` stamped by the MCP layer, the bare machineId stamped by the
|
|
307
|
+
// local queue path, OR — most commonly for remote launches — the coordinator mesh
|
|
308
|
+
// node's config-form `daemonId`, which resolveCoordinatorDaemonId prefers and which
|
|
309
|
+
// is NOT canonicalised). `candidateDaemonIds` is the already-expanded self-identity
|
|
310
|
+
// set (resolveCoordinatorSelfIds: runtime drain ids ∪ this daemon's mesh-config node/
|
|
311
|
+
// host id forms), so we pull ONCE PER candidate id and a completion stamped with any
|
|
312
|
+
// of them is recovered. The remote drain is atomic (drained=1), so issuing multiple
|
|
313
|
+
// pulls cannot double-deliver — the first pull that matches consumes the event; the
|
|
314
|
+
// rest see nothing. When no ids resolve we fall back to a single unscoped pull.
|
|
268
315
|
async function pullRemoteNodeQueues(
|
|
269
316
|
components: DaemonComponents,
|
|
270
317
|
mesh: LocalMeshEntry,
|
|
271
318
|
localDaemonId: string | undefined,
|
|
272
|
-
|
|
319
|
+
candidateDaemonIds: string[],
|
|
273
320
|
): Promise<void> {
|
|
274
321
|
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
275
322
|
if (!dispatchMeshCommand) return;
|
|
276
323
|
const meshId = mesh.id;
|
|
277
324
|
|
|
278
|
-
// One args object per coordinator-id form
|
|
279
|
-
//
|
|
280
|
-
const pulls: Array<Record<string, unknown>> =
|
|
281
|
-
?
|
|
325
|
+
// One args object per candidate coordinator-id form, or a single unscoped pull
|
|
326
|
+
// when none resolve.
|
|
327
|
+
const pulls: Array<Record<string, unknown>> = candidateDaemonIds.length > 0
|
|
328
|
+
? candidateDaemonIds.map(id => ({ meshId, coordinatorDaemonId: id }))
|
|
282
329
|
: [{ meshId }];
|
|
283
330
|
|
|
284
331
|
for (const node of mesh.nodes) {
|
|
285
332
|
const nodeDaemonId = readNonEmptyString(node.daemonId);
|
|
286
333
|
// Skip nodes without a daemon, and nodes on THIS daemon (their events are
|
|
287
|
-
// already in the local queue drained in PHASE 2).
|
|
334
|
+
// already in the local queue drained in PHASE 2). "This daemon" is matched
|
|
335
|
+
// against the full self-identity set (candidateDaemonIds), not just the bare
|
|
336
|
+
// localDaemonId — a self node can be registered under the config-form daemonId
|
|
337
|
+
// (`daemon_<machineId>`) which would NOT equal bare localDaemonId, and pulling
|
|
338
|
+
// from ourselves over P2P is both wasteful and a self-dispatch hazard.
|
|
288
339
|
if (!nodeDaemonId) continue;
|
|
289
340
|
if (localDaemonId && nodeDaemonId === localDaemonId) continue;
|
|
341
|
+
if (candidateDaemonIds.includes(nodeDaemonId)) continue;
|
|
290
342
|
|
|
291
343
|
for (const pendingEventArgs of pulls) {
|
|
292
344
|
let events: unknown;
|