@adhdev/daemon-core 0.9.82-rc.364 → 0.9.82-rc.366
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/commands/high-family/index.d.ts +3 -0
- package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
- package/dist/commands/high-family/mesh-events.d.ts +2 -0
- package/dist/commands/high-family/mesh-status.d.ts +2 -0
- package/dist/commands/high-family/types.d.ts +60 -0
- package/dist/commands/med-family/cli-agent.d.ts +2 -0
- package/dist/commands/med-family/fast-forward.d.ts +2 -0
- package/dist/commands/med-family/ide.d.ts +10 -0
- package/dist/commands/med-family/index.d.ts +3 -0
- package/dist/commands/med-family/mesh-crud.d.ts +2 -0
- package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
- package/dist/commands/med-family/mesh-queue.d.ts +2 -0
- package/dist/commands/med-family/types.d.ts +116 -0
- package/dist/commands/router.d.ts +291 -0
- package/dist/index.js +3824 -3565
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3811 -3553
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-coordinator.d.ts +8 -0
- package/dist/system/hash.d.ts +8 -0
- package/package.json +2 -2
- package/src/commands/cli-manager.ts +30 -3
- package/src/commands/high-family/index.ts +28 -0
- package/src/commands/high-family/mesh-coordinator-launch.ts +592 -0
- package/src/commands/high-family/mesh-events.ts +47 -0
- package/src/commands/high-family/mesh-status.ts +639 -0
- package/src/commands/high-family/types.ts +76 -0
- package/src/commands/med-family/cli-agent.ts +218 -0
- package/src/commands/med-family/fast-forward.ts +198 -0
- package/src/commands/med-family/ide.ts +163 -0
- package/src/commands/med-family/index.ts +35 -0
- package/src/commands/med-family/mesh-crud.ts +788 -0
- package/src/commands/med-family/mesh-host-pairing.ts +234 -0
- package/src/commands/med-family/mesh-queue.ts +131 -0
- package/src/commands/med-family/types.ts +120 -0
- package/src/commands/mesh-coordinator.ts +2 -2
- package/src/commands/router.ts +328 -2847
- package/src/config/mesh-config.ts +3 -2
- package/src/mesh/mesh-active-work.ts +59 -81
- package/src/mesh/mesh-events-coordinator.ts +35 -1
- package/src/system/hash.ts +23 -0
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
-
import {
|
|
11
|
+
import { randomBytes, randomUUID } from 'crypto';
|
|
12
|
+
import { shortHash } from '../system/hash.js';
|
|
12
13
|
import { getConfigDir } from './config.js';
|
|
13
14
|
import type {
|
|
14
15
|
LocalMeshConfig,
|
|
@@ -309,7 +310,7 @@ function normalizeManualHostAddress(hostAddress: string): string {
|
|
|
309
310
|
}
|
|
310
311
|
|
|
311
312
|
export function tokenIdForManualPairing(token: string): string {
|
|
312
|
-
return `tok_${
|
|
313
|
+
return `tok_${shortHash(token)}`;
|
|
313
314
|
}
|
|
314
315
|
|
|
315
316
|
function normalizeTokenExpiry(value: unknown): string | undefined {
|
|
@@ -208,6 +208,60 @@ function classifyDirectDispatch(params: {
|
|
|
208
208
|
return { ledgerOnlyStaleReason, isFreshUnacknowledged };
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Build a direct-dispatch MeshActiveWorkRecord from a `task_dispatched` ledger entry,
|
|
213
|
+
* matching it against the terminal ledger entries and live mesh nodes. Shared by the
|
|
214
|
+
* remote-ledger scan (inside the MeshRuntimeStore branch) and the full-ledger scan
|
|
215
|
+
* (standalone branch) — those two loops were previously byte-identical except for a
|
|
216
|
+
* single `dbTaskIds.has(taskId)` skip guard that stays in the caller. Returns the record
|
|
217
|
+
* plus `terminalRow` so the caller can route it into terminal/stale/active buckets.
|
|
218
|
+
*/
|
|
219
|
+
function buildLedgerDirectDispatchRecord(
|
|
220
|
+
dispatch: MeshLedgerEntry,
|
|
221
|
+
ctx: { terminals: MeshLedgerEntry[]; nodes: any[] | undefined; now: number },
|
|
222
|
+
): { record: MeshActiveWorkRecord; terminalRow: boolean } {
|
|
223
|
+
const taskId = directDispatchTaskId(dispatch);
|
|
224
|
+
const terminal = ctx.terminals
|
|
225
|
+
.filter(entry => new Date(entry.timestamp).getTime() >= new Date(dispatch.timestamp).getTime())
|
|
226
|
+
.find(entry => terminalMatchesDispatch(entry, dispatch, taskId));
|
|
227
|
+
const terminalStatus = terminal ? statusFromTerminal(terminal) : undefined;
|
|
228
|
+
const live = sessionStatusFromNodes(ctx.nodes, dispatch.nodeId, dispatch.sessionId);
|
|
229
|
+
const status = terminalStatus || live.status || 'assigned';
|
|
230
|
+
const terminalRow = Boolean(terminal && terminal.kind !== 'task_approval_needed');
|
|
231
|
+
const { ledgerOnlyStaleReason, isFreshUnacknowledged } = classifyDirectDispatch({
|
|
232
|
+
status,
|
|
233
|
+
isTerminalRow: terminalRow,
|
|
234
|
+
hasTerminalStatus: Boolean(terminalStatus),
|
|
235
|
+
liveStatus: live.status,
|
|
236
|
+
liveStaleReason: live.staleReason,
|
|
237
|
+
dispatchedToIdleSession: dispatch.payload?.dispatchedToIdleSession === true,
|
|
238
|
+
});
|
|
239
|
+
const message = readString(dispatch.payload?.message) || readString(dispatch.payload?.summary) || '';
|
|
240
|
+
const { title, summary } = summarizeMessage(message);
|
|
241
|
+
const record: MeshActiveWorkRecord = {
|
|
242
|
+
taskId,
|
|
243
|
+
source: 'direct',
|
|
244
|
+
status,
|
|
245
|
+
nodeId: dispatch.nodeId,
|
|
246
|
+
sessionId: dispatch.sessionId,
|
|
247
|
+
providerType: dispatch.providerType || readString(dispatch.payload?.providerType),
|
|
248
|
+
taskTitle: readString(dispatch.payload?.taskTitle) || title,
|
|
249
|
+
taskSummary: readString(dispatch.payload?.taskSummary) || summary,
|
|
250
|
+
message,
|
|
251
|
+
taskMode: readString(dispatch.payload?.taskMode),
|
|
252
|
+
createdAt: dispatch.timestamp,
|
|
253
|
+
updatedAt: terminal?.timestamp || dispatch.timestamp,
|
|
254
|
+
dispatchedAt: dispatch.timestamp,
|
|
255
|
+
elapsedMs: elapsedSince(dispatch.timestamp, ctx.now),
|
|
256
|
+
terminal: terminalRow,
|
|
257
|
+
terminalKind: terminal?.kind,
|
|
258
|
+
terminalAt: terminal?.timestamp,
|
|
259
|
+
staleReason: live.staleReason || ledgerOnlyStaleReason,
|
|
260
|
+
...(isFreshUnacknowledged ? { staleDispatchUnacknowledged: true } : {}),
|
|
261
|
+
};
|
|
262
|
+
return { record, terminalRow };
|
|
263
|
+
}
|
|
264
|
+
|
|
211
265
|
export function buildMeshActiveWorkSummary(activeWork: MeshActiveWorkRecord[]): MeshActiveWorkSummary {
|
|
212
266
|
const statusCounts: Record<MeshActiveWorkStatus, number> = {
|
|
213
267
|
pending: 0,
|
|
@@ -324,51 +378,13 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
|
|
|
324
378
|
const ledgerEntries = (opts.ledgerEntries || []).slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
325
379
|
const terminals = ledgerEntries.filter(entry => TERMINAL_LEDGER_KINDS.has(entry.kind) || entry.kind === 'task_approval_needed');
|
|
326
380
|
for (const dispatch of ledgerEntries.filter(isDirectDispatch)) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const terminal = terminals
|
|
330
|
-
.filter(entry => new Date(entry.timestamp).getTime() >= new Date(dispatch.timestamp).getTime())
|
|
331
|
-
.find(entry => terminalMatchesDispatch(entry, dispatch, taskId));
|
|
332
|
-
const terminalStatus = terminal ? statusFromTerminal(terminal) : undefined;
|
|
333
|
-
const live = sessionStatusFromNodes(opts.nodes, dispatch.nodeId, dispatch.sessionId);
|
|
334
|
-
const status = terminalStatus || live.status || 'assigned';
|
|
335
|
-
const terminalRow = Boolean(terminal && terminal.kind !== 'task_approval_needed');
|
|
336
|
-
const { ledgerOnlyStaleReason, isFreshUnacknowledged } = classifyDirectDispatch({
|
|
337
|
-
status,
|
|
338
|
-
isTerminalRow: terminalRow,
|
|
339
|
-
hasTerminalStatus: Boolean(terminalStatus),
|
|
340
|
-
liveStatus: live.status,
|
|
341
|
-
liveStaleReason: live.staleReason,
|
|
342
|
-
dispatchedToIdleSession: dispatch.payload?.dispatchedToIdleSession === true,
|
|
343
|
-
});
|
|
344
|
-
const message = readString(dispatch.payload?.message) || readString(dispatch.payload?.summary) || '';
|
|
345
|
-
const { title, summary } = summarizeMessage(message);
|
|
346
|
-
const record: MeshActiveWorkRecord = {
|
|
347
|
-
taskId,
|
|
348
|
-
source: 'direct',
|
|
349
|
-
status,
|
|
350
|
-
nodeId: dispatch.nodeId,
|
|
351
|
-
sessionId: dispatch.sessionId,
|
|
352
|
-
providerType: dispatch.providerType || readString(dispatch.payload?.providerType),
|
|
353
|
-
taskTitle: readString(dispatch.payload?.taskTitle) || title,
|
|
354
|
-
taskSummary: readString(dispatch.payload?.taskSummary) || summary,
|
|
355
|
-
message,
|
|
356
|
-
taskMode: readString(dispatch.payload?.taskMode),
|
|
357
|
-
createdAt: dispatch.timestamp,
|
|
358
|
-
updatedAt: terminal?.timestamp || dispatch.timestamp,
|
|
359
|
-
dispatchedAt: dispatch.timestamp,
|
|
360
|
-
elapsedMs: elapsedSince(dispatch.timestamp, now),
|
|
361
|
-
terminal: terminalRow,
|
|
362
|
-
terminalKind: terminal?.kind,
|
|
363
|
-
terminalAt: terminal?.timestamp,
|
|
364
|
-
staleReason: live.staleReason || ledgerOnlyStaleReason,
|
|
365
|
-
...(isFreshUnacknowledged ? { staleDispatchUnacknowledged: true } : {}),
|
|
366
|
-
};
|
|
381
|
+
if (dbTaskIds.has(directDispatchTaskId(dispatch))) continue; // already covered by MeshRuntimeStore path above
|
|
382
|
+
const { record, terminalRow } = buildLedgerDirectDispatchRecord(dispatch, { terminals, nodes: opts.nodes, now });
|
|
367
383
|
if (terminalRow) {
|
|
368
384
|
terminalDirectWork.push(record);
|
|
369
385
|
if (opts.includeTerminalDirect !== true) continue;
|
|
370
386
|
}
|
|
371
|
-
if (
|
|
387
|
+
if (record.staleReason && !terminalRow) {
|
|
372
388
|
staleDirectWork.push(record);
|
|
373
389
|
continue;
|
|
374
390
|
}
|
|
@@ -379,50 +395,12 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
|
|
|
379
395
|
const ledgerEntries = (opts.ledgerEntries || []).slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
380
396
|
const terminals = ledgerEntries.filter(entry => TERMINAL_LEDGER_KINDS.has(entry.kind) || entry.kind === 'task_approval_needed');
|
|
381
397
|
for (const dispatch of ledgerEntries.filter(isDirectDispatch)) {
|
|
382
|
-
const
|
|
383
|
-
const terminal = terminals
|
|
384
|
-
.filter(entry => new Date(entry.timestamp).getTime() >= new Date(dispatch.timestamp).getTime())
|
|
385
|
-
.find(entry => terminalMatchesDispatch(entry, dispatch, taskId));
|
|
386
|
-
const terminalStatus = terminal ? statusFromTerminal(terminal) : undefined;
|
|
387
|
-
const live = sessionStatusFromNodes(opts.nodes, dispatch.nodeId, dispatch.sessionId);
|
|
388
|
-
const status = terminalStatus || live.status || 'assigned';
|
|
389
|
-
const terminalRow = Boolean(terminal && terminal.kind !== 'task_approval_needed');
|
|
390
|
-
const { ledgerOnlyStaleReason, isFreshUnacknowledged } = classifyDirectDispatch({
|
|
391
|
-
status,
|
|
392
|
-
isTerminalRow: terminalRow,
|
|
393
|
-
hasTerminalStatus: Boolean(terminalStatus),
|
|
394
|
-
liveStatus: live.status,
|
|
395
|
-
liveStaleReason: live.staleReason,
|
|
396
|
-
dispatchedToIdleSession: dispatch.payload?.dispatchedToIdleSession === true,
|
|
397
|
-
});
|
|
398
|
-
const message = readString(dispatch.payload?.message) || readString(dispatch.payload?.summary) || '';
|
|
399
|
-
const { title, summary } = summarizeMessage(message);
|
|
400
|
-
const record: MeshActiveWorkRecord = {
|
|
401
|
-
taskId,
|
|
402
|
-
source: 'direct',
|
|
403
|
-
status,
|
|
404
|
-
nodeId: dispatch.nodeId,
|
|
405
|
-
sessionId: dispatch.sessionId,
|
|
406
|
-
providerType: dispatch.providerType || readString(dispatch.payload?.providerType),
|
|
407
|
-
taskTitle: readString(dispatch.payload?.taskTitle) || title,
|
|
408
|
-
taskSummary: readString(dispatch.payload?.taskSummary) || summary,
|
|
409
|
-
message,
|
|
410
|
-
taskMode: readString(dispatch.payload?.taskMode),
|
|
411
|
-
createdAt: dispatch.timestamp,
|
|
412
|
-
updatedAt: terminal?.timestamp || dispatch.timestamp,
|
|
413
|
-
dispatchedAt: dispatch.timestamp,
|
|
414
|
-
elapsedMs: elapsedSince(dispatch.timestamp, now),
|
|
415
|
-
terminal: terminalRow,
|
|
416
|
-
terminalKind: terminal?.kind,
|
|
417
|
-
terminalAt: terminal?.timestamp,
|
|
418
|
-
staleReason: live.staleReason || ledgerOnlyStaleReason,
|
|
419
|
-
...(isFreshUnacknowledged ? { staleDispatchUnacknowledged: true } : {}),
|
|
420
|
-
};
|
|
398
|
+
const { record, terminalRow } = buildLedgerDirectDispatchRecord(dispatch, { terminals, nodes: opts.nodes, now });
|
|
421
399
|
if (terminalRow) {
|
|
422
400
|
terminalDirectWork.push(record);
|
|
423
401
|
if (opts.includeTerminalDirect !== true) continue;
|
|
424
402
|
}
|
|
425
|
-
if (
|
|
403
|
+
if (record.staleReason && !terminalRow) {
|
|
426
404
|
staleDirectWork.push(record);
|
|
427
405
|
continue;
|
|
428
406
|
}
|
|
@@ -1325,6 +1325,14 @@ export interface MeshQueueTriggerResult {
|
|
|
1325
1325
|
status?: string;
|
|
1326
1326
|
}>;
|
|
1327
1327
|
autoLaunchStarted: boolean;
|
|
1328
|
+
/**
|
|
1329
|
+
* True when a worker session is already on its way to claim a still-pending task —
|
|
1330
|
+
* either launched this tick (autoLaunchStarted) or launched on a prior tick and still
|
|
1331
|
+
* booting/awaiting-claim. Callers MUST treat this as "wait, do not launch another
|
|
1332
|
+
* session": a second launch double-edits the worktree. Mutually informative with
|
|
1333
|
+
* `noIdleMeshSessionAvailable`, which is suppressed whenever this is true.
|
|
1334
|
+
*/
|
|
1335
|
+
autoLaunchPending?: boolean;
|
|
1328
1336
|
noIdleMeshSessionAvailable?: boolean;
|
|
1329
1337
|
}
|
|
1330
1338
|
|
|
@@ -1485,6 +1493,28 @@ export async function triggerMeshQueue(components: DaemonComponents, meshId: str
|
|
|
1485
1493
|
nodeId: task.assignedNodeId,
|
|
1486
1494
|
sessionId: task.assignedSessionId,
|
|
1487
1495
|
}));
|
|
1496
|
+
|
|
1497
|
+
// An auto-launch is "pending" when the coordinator has already spun a session up
|
|
1498
|
+
// for a still-pending task and is waiting on that session's idle→claim. This covers
|
|
1499
|
+
// two ticks:
|
|
1500
|
+
// - THIS tick fired the launch (autoLaunchStarted), or
|
|
1501
|
+
// - a PRIOR tick launched a session that is still booting/awaiting-claim — the
|
|
1502
|
+
// per-task await-claim guard (maybeAutoLaunchOneQueueSession) deliberately
|
|
1503
|
+
// declines to launch again, so autoLaunchStarted is false even though a session
|
|
1504
|
+
// is on its way to claim this task.
|
|
1505
|
+
// Without this signal, the second tick reports `noIdleMeshSessionAvailable` and the
|
|
1506
|
+
// MCP guidance tells the coordinator to launch ANOTHER worker — producing a duplicate
|
|
1507
|
+
// session that double-edits the worktree. The claim itself is fine; only the wording
|
|
1508
|
+
// was wrong, so we surface `autoLaunchPending` to suppress the bad "launch one more"
|
|
1509
|
+
// advice while the just-launched session converges.
|
|
1510
|
+
const autoLaunchPending = autoLaunchStarted || afterQueue.some(task => {
|
|
1511
|
+
if (task.status !== 'pending') return false;
|
|
1512
|
+
const al = task.autoLaunch;
|
|
1513
|
+
if (!al || (al.status !== 'started' && al.status !== 'completed')) return false;
|
|
1514
|
+
const launchedAtMs = Date.parse(al.updatedAt);
|
|
1515
|
+
return Number.isFinite(launchedAtMs) && Date.now() - launchedAtMs < AUTO_LAUNCH_AWAIT_CLAIM_MS;
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1488
1518
|
return {
|
|
1489
1519
|
success: true,
|
|
1490
1520
|
meshId,
|
|
@@ -1498,7 +1528,11 @@ export async function triggerMeshQueue(components: DaemonComponents, meshId: str
|
|
|
1498
1528
|
remoteIdleSessionsChecked,
|
|
1499
1529
|
skippedSessions,
|
|
1500
1530
|
autoLaunchStarted,
|
|
1501
|
-
...(
|
|
1531
|
+
...(autoLaunchPending ? { autoLaunchPending: true } : {}),
|
|
1532
|
+
// Only report "no idle session, go launch one" when nothing is already on its way.
|
|
1533
|
+
// A pending auto-launch (this tick or a prior still-converging one) means a session
|
|
1534
|
+
// WILL claim shortly, so it is not a no-session-available situation.
|
|
1535
|
+
...(pendingAfter > 0 && newlyAssignedTasks.length === 0 && localIdleSessionsChecked === 0 && remoteIdleSessionsChecked === 0 && !autoLaunchPending
|
|
1502
1536
|
? { noIdleMeshSessionAvailable: true }
|
|
1503
1537
|
: {}),
|
|
1504
1538
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SHA-256 hashing helpers for daemon-core (Node runtime).
|
|
3
|
+
*
|
|
4
|
+
* daemon-core runs under Node, so it uses `node:crypto` directly and must NOT
|
|
5
|
+
* import the server's WebCrypto-based `utils/crypto.ts` (different runtime +
|
|
6
|
+
* cross-package dependency direction). Output is byte-identical to that helper
|
|
7
|
+
* for the same input — lowercase hex SHA-256.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
|
|
11
|
+
/** Full lowercase hex SHA-256 digest of `input`. */
|
|
12
|
+
export function sha256Hex(input: string): string {
|
|
13
|
+
return createHash('sha256').update(input).digest('hex');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Truncated SHA-256 hex digest — the first `length` hex chars (default 16).
|
|
18
|
+
* Used for stable short identifiers (workspace hashes, token ids, coordinator
|
|
19
|
+
* home dirs) where collision risk at 16 hex chars (64 bits) is negligible.
|
|
20
|
+
*/
|
|
21
|
+
export function shortHash(input: string, length = 16): string {
|
|
22
|
+
return sha256Hex(input).slice(0, length);
|
|
23
|
+
}
|