@adhdev/daemon-core 0.9.82-rc.350 → 0.9.82-rc.351

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.
@@ -6,6 +6,7 @@ export declare class MeshRuntimeStore {
6
6
  private readonly migratedMeshIds;
7
7
  private fingerprintSweepCounter;
8
8
  private walWriteCounter;
9
+ private toolCallLogCounter;
9
10
  private static readonly WAL_CHECK_INTERVAL;
10
11
  private static readonly WAL_MAX_BYTES;
11
12
  private constructor();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.350",
3
+ "version": "0.9.82-rc.351",
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.350",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.351",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
package/src/index.ts CHANGED
@@ -252,6 +252,11 @@ export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHost
252
252
  // ── Mesh Events ──
253
253
  export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent, reconcileDirectDispatchCompletionFromTranscript } from './mesh/mesh-events.js';
254
254
  export type { PendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
255
+ // The coordinator-side preview surfaced from a worker's completion/status event
256
+ // (finalSummary / workerResult.summary / lastMessagePreview). Same data the mobile
257
+ // inbox is fed; reused by mesh_read_chat's cache fallback when the live P2P read path
258
+ // is unavailable (saturated/unreachable peer).
259
+ export { resolveMeshSurfacedSessionPreview, readMeshCompletionSummary } from './mesh/mesh-events-utils.js';
255
260
 
256
261
  // ── Mesh Delivery Policy ──
257
262
  export { resolveDeliveryDecision, createSessionDelivery, updateSessionDeliveryStatus, getActiveSessionDeliveries, markSessionDeliveriesTerminal, recordCompletionConflict, getRecentCompletionConflicts } from './mesh/mesh-delivery-policy.js';
@@ -168,6 +168,46 @@ function statusFromTerminal(entry: MeshLedgerEntry): MeshActiveWorkStatus {
168
168
  return 'failed';
169
169
  }
170
170
 
171
+ const LEDGER_ONLY_STALE_REASON =
172
+ 'direct task dispatch has no provider acknowledgement, transcript append, or active runtime transition';
173
+
174
+ /**
175
+ * Single source of truth for classifying whether a direct dispatch is ledger-only
176
+ * stale (dispatched but never acknowledged: no provider ack, no transcript append,
177
+ * no runtime transition) and whether that staleness is "fresh unacknowledged"
178
+ * (target session still live) vs. an orphaned historical record.
179
+ *
180
+ * Previously this predicate was inlined in all three dispatch-classification blocks
181
+ * (MeshRuntimeStore path, remote-ledger path, full-ledger path) and had drifted —
182
+ * e.g. one block gated `isIdleUnacknowledged` on `!isTerminal` and another did not.
183
+ * Extracting it keeps the three callers byte-for-byte consistent. Status derivation
184
+ * stays per-block: it genuinely differs by data source and is passed in via `status`.
185
+ */
186
+ function classifyDirectDispatch(params: {
187
+ /** Final classified status of the dispatch. */
188
+ status: MeshActiveWorkStatus;
189
+ /** True when a real terminal row applies (completed/failed/stale); excludes approval_needed. Gates the stale verdict. */
190
+ isTerminalRow: boolean;
191
+ /** True when any terminal-derived status exists (incl. approval_needed). Used to detect "no transition". */
192
+ hasTerminalStatus: boolean;
193
+ /** Live session status from the mesh nodes, if any. */
194
+ liveStatus?: string;
195
+ /** Live stale reason from the mesh nodes, if any. */
196
+ liveStaleReason?: string;
197
+ /** Whether the dispatch targeted an already-idle session. */
198
+ dispatchedToIdleSession: boolean;
199
+ }): { ledgerOnlyStaleReason: string | undefined; isFreshUnacknowledged: boolean } {
200
+ const { status, isTerminalRow, hasTerminalStatus, liveStatus, liveStaleReason, dispatchedToIdleSession } = params;
201
+ const isNoTransition = !hasTerminalStatus && !liveStatus;
202
+ const isIdleUnacknowledged = status === 'idle';
203
+ const ledgerOnlyStaleReason =
204
+ !isTerminalRow && (isIdleUnacknowledged || isNoTransition || (dispatchedToIdleSession && isIdleUnacknowledged))
205
+ ? LEDGER_ONLY_STALE_REASON
206
+ : undefined;
207
+ const isFreshUnacknowledged = Boolean(ledgerOnlyStaleReason && !liveStaleReason);
208
+ return { ledgerOnlyStaleReason, isFreshUnacknowledged };
209
+ }
210
+
171
211
  export function buildMeshActiveWorkSummary(activeWork: MeshActiveWorkRecord[]): MeshActiveWorkSummary {
172
212
  const statusCounts: Record<MeshActiveWorkStatus, number> = {
173
213
  pending: 0,
@@ -239,12 +279,14 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
239
279
  const status: MeshActiveWorkStatus = isTerminal
240
280
  ? (dbStatus === 'completed' ? 'idle' : 'failed')
241
281
  : live.status || (dbStatus === 'acked' ? 'generating' : 'assigned');
242
- const isNoTransition = !isTerminal && !live.status;
243
- const isIdleUnacknowledged = status === 'idle' && !isTerminal;
244
- const ledgerOnlyStaleReason = !isTerminal && (isIdleUnacknowledged || isNoTransition || (dispatch.dispatchedToIdleSession && isIdleUnacknowledged))
245
- ? 'direct task dispatch has no provider acknowledgement, transcript append, or active runtime transition'
246
- : undefined;
247
- const isFreshUnacknowledged = Boolean(ledgerOnlyStaleReason && !live.staleReason);
282
+ const { ledgerOnlyStaleReason, isFreshUnacknowledged } = classifyDirectDispatch({
283
+ status,
284
+ isTerminalRow: isTerminal,
285
+ hasTerminalStatus: isTerminal,
286
+ liveStatus: live.status,
287
+ liveStaleReason: live.staleReason,
288
+ dispatchedToIdleSession: dispatch.dispatchedToIdleSession === true,
289
+ });
248
290
  const { title, summary } = summarizeMessage(dispatch.message || '');
249
291
  const record: MeshActiveWorkRecord = {
250
292
  taskId: dispatch.taskId,
@@ -291,15 +333,16 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
291
333
  const live = sessionStatusFromNodes(opts.nodes, dispatch.nodeId, dispatch.sessionId);
292
334
  const status = terminalStatus || live.status || 'assigned';
293
335
  const terminalRow = Boolean(terminal && terminal.kind !== 'task_approval_needed');
294
- const dispatchedToIdleSession = dispatch.payload?.dispatchedToIdleSession === true;
295
- const isNoTransition = !terminalStatus && !live.status;
296
- const isIdleUnacknowledged = status === 'idle';
297
- const ledgerOnlyStaleReason = !terminalRow && (isIdleUnacknowledged || isNoTransition || (dispatchedToIdleSession && isIdleUnacknowledged))
298
- ? 'direct task dispatch has no provider acknowledgement, transcript append, or active runtime transition'
299
- : undefined;
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
+ });
300
344
  const message = readString(dispatch.payload?.message) || readString(dispatch.payload?.summary) || '';
301
345
  const { title, summary } = summarizeMessage(message);
302
- const isFreshUnacknowledged = Boolean(ledgerOnlyStaleReason && !live.staleReason);
303
346
  const record: MeshActiveWorkRecord = {
304
347
  taskId,
305
348
  source: 'direct',
@@ -344,15 +387,16 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
344
387
  const live = sessionStatusFromNodes(opts.nodes, dispatch.nodeId, dispatch.sessionId);
345
388
  const status = terminalStatus || live.status || 'assigned';
346
389
  const terminalRow = Boolean(terminal && terminal.kind !== 'task_approval_needed');
347
- const dispatchedToIdleSession = dispatch.payload?.dispatchedToIdleSession === true;
348
- const isNoTransition = !terminalStatus && !live.status;
349
- const isIdleUnacknowledged = status === 'idle';
350
- const ledgerOnlyStaleReason = !terminalRow && (isIdleUnacknowledged || isNoTransition || (dispatchedToIdleSession && isIdleUnacknowledged))
351
- ? 'direct task dispatch has no provider acknowledgement, transcript append, or active runtime transition'
352
- : undefined;
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
+ });
353
398
  const message = readString(dispatch.payload?.message) || readString(dispatch.payload?.summary) || '';
354
399
  const { title, summary } = summarizeMessage(message);
355
- const isFreshUnacknowledged = Boolean(ledgerOnlyStaleReason && !live.staleReason);
356
400
  const record: MeshActiveWorkRecord = {
357
401
  taskId,
358
402
  source: 'direct',
@@ -251,6 +251,55 @@ function isDuplicateRefineTerminalEvent(meshId: string, eventName: string, metad
251
251
  return false;
252
252
  }
253
253
 
254
+ // A worker/coordinator "false idle": the provider dropped to idle WITHOUT a confirmed
255
+ // final assistant message for the turn (a finalization timeout, or a "scheduled fallback"
256
+ // idle). This is the signal cli-provider-instance emits as
257
+ // completionDiagnostic.blockReason='missing_final_assistant' / finalAssistantPresent=false.
258
+ // Such a completion is NOT trustworthy terminal evidence: it must neither permanently
259
+ // terminate a direct-dispatch task nor suppress the genuine completion a later turn
260
+ // (commonly driven by a coordinator nudge / re-dispatch) produces.
261
+ function isFalseIdleCompletion(metadataEvent: Record<string, unknown>): boolean {
262
+ const diag = readRecord(metadataEvent.completionDiagnostic);
263
+ if (!diag) return false;
264
+ return diag.finalAssistantPresent === false || diag.blockReason === 'missing_final_assistant';
265
+ }
266
+
267
+ // The genuine-completion counterpart: a real final summary / worker result is present and
268
+ // the completion is not flagged as a missing-final-assistant false idle. Used to decide
269
+ // whether a new completion may supersede a prior WEAK (false-idle) terminal.
270
+ function isGenuineCompletionEvidence(metadataEvent: Record<string, unknown>): boolean {
271
+ if (isFalseIdleCompletion(metadataEvent)) return false;
272
+ return !!readWorkerResultMetadata(metadataEvent) || !!readNonEmptyString(metadataEvent.finalSummary);
273
+ }
274
+
275
+ // True when a terminal ledger payload was recorded from WEAK completion evidence (a false
276
+ // idle): insufficient evidence level, review-recommended, or a missing-final-assistant
277
+ // completion diagnostic. A weak terminal is non-authoritative — a later genuine completion
278
+ // (live path) or a transcript reconcile (fallback path) may supersede it.
279
+ function isWeakTerminalLedgerPayload(payload: Record<string, unknown> | undefined): boolean {
280
+ if (!payload) return false;
281
+ if (payload.evidenceLevel === 'insufficient' || payload.reviewRecommended === true) return true;
282
+ const diag = readRecord(payload.completionDiagnostic);
283
+ return diag?.finalAssistantPresent === false || diag?.blockReason === 'missing_final_assistant';
284
+ }
285
+
286
+ // The latest still-active direct-dispatch taskId for a session, resolved BEFORE the
287
+ // completion flips the dispatch row terminal. Direct dispatches (mesh_send_task) have no
288
+ // work-queue row, so this is the only taskId available to attribute the terminal ledger
289
+ // entry (and thus mesh task-stats) to — without it the terminal carries no taskId and the
290
+ // task surfaces as status='unknown' / terminalKind=null in computeMeshTaskStats.
291
+ function resolveActiveDirectDispatchTaskId(meshId: string, sessionId: string): string | undefined {
292
+ try {
293
+ const matches = getActiveDirectDispatches(meshId).filter(d => d.sessionId === sessionId);
294
+ if (!matches.length) return undefined;
295
+ // getActiveDirectDispatches returns rows ordered by dispatched_at ASC; the last is
296
+ // the most recent dispatch (the re-dispatch / nudge whose completion this is).
297
+ return readNonEmptyString(matches[matches.length - 1].taskId) || undefined;
298
+ } catch {
299
+ return undefined;
300
+ }
301
+ }
302
+
254
303
  // ---------------------------------------------------------------------------
255
304
  // Queue assignment
256
305
  // ---------------------------------------------------------------------------
@@ -1555,7 +1604,17 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1555
1604
  });
1556
1605
  if (terminal?.kind === 'task_completed' && !sessionHasActiveAssignment(args.meshId, eventSessionId)) {
1557
1606
  const newDispatchAfterTerminal = hasDispatchAfterTerminal(args.meshId, eventSessionId, terminal.id);
1558
- if (!newDispatchAfterTerminal) {
1607
+ // Fix B (re-dispatch 2nd-completion routing): a prior terminal recorded from a FALSE
1608
+ // idle (weak evidence / no confirmed final assistant) must NOT permanently suppress a
1609
+ // later GENUINE completion of the same session. providerSessionId is stable across a
1610
+ // session's turns, so the providerSessionId/finalSummary dedup below would otherwise
1611
+ // swallow the real 2nd-turn completion that a coordinator nudge (direct re-dispatch)
1612
+ // drove — exactly the missed-event bug. When the prior terminal was weak and the new
1613
+ // event carries genuine completion evidence, let it through so it is recorded and
1614
+ // re-attributed to the latest task (the normal task_completed path below).
1615
+ const supersedesWeakTerminal = isWeakTerminalLedgerPayload(terminal.payload)
1616
+ && isGenuineCompletionEvidence(args.metadataEvent);
1617
+ if (!newDispatchAfterTerminal && !supersedesWeakTerminal) {
1559
1618
  const terminalProviderSessionId = readNonEmptyString(terminal.payload.providerSessionId);
1560
1619
  const terminalFinalSummary = readNonEmptyString(terminal.payload.finalSummary);
1561
1620
  const eventProviderSessionId = readNonEmptyString(args.metadataEvent.providerSessionId);
@@ -1606,7 +1665,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1606
1665
  }
1607
1666
  }
1608
1667
 
1609
- function markSessionTerminal(sessionId: string, outcome: 'completed' | 'failed', occurredAtMs?: number | null): { id?: string } | null {
1668
+ function markSessionTerminal(sessionId: string, outcome: 'completed' | 'failed', occurredAtMs?: number | null, opts?: { tentativeIfDirect?: boolean }): { id?: string } | null {
1610
1669
  // C2: prefer an exact taskId match when the completion event carries one —
1611
1670
  // it's immune to coordinator↔worker clock skew that can hide the assigned row.
1612
1671
  const eventTaskId = readNonEmptyString(args.metadataEvent.taskId) || undefined;
@@ -1614,20 +1673,36 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1614
1673
  occurredAt: occurredAtMs != null ? new Date(occurredAtMs).toISOString() : undefined,
1615
1674
  taskId: eventTaskId,
1616
1675
  });
1617
- updateDirectDispatchStatus(args.meshId, sessionId, outcome);
1676
+ // Fix A (early-terminal prevention): a false-idle completion (no confirmed final
1677
+ // assistant) for a DIRECT dispatch — i.e. no work-queue row matched — must not flip the
1678
+ // dispatch row terminal. Leaving it active lets the reconcile loop (PHASE 4) re-read the
1679
+ // transcript and record the genuine completion once the worker truly finishes (commonly
1680
+ // after a coordinator nudge / re-dispatch). A matched queue task, or a completion with
1681
+ // genuine evidence, is marked terminal as before.
1682
+ const leaveDirectDispatchActive = !task && opts?.tentativeIfDirect === true;
1683
+ if (!leaveDirectDispatchActive) {
1684
+ updateDirectDispatchStatus(args.meshId, sessionId, outcome);
1685
+ }
1618
1686
  markSessionDeliveriesTerminal(args.meshId, sessionId, outcome);
1619
1687
  setImmediate(() => cleanupTerminalDirectDispatches());
1620
1688
  return task ? { id: task.id } : null;
1621
1689
  }
1622
1690
 
1623
1691
  let completedTaskForLedger: { id?: string } | null = null;
1692
+ // Fix B: direct-dispatch taskId used to attribute the terminal ledger entry when no
1693
+ // work-queue row matches (resolved BEFORE markSessionTerminal flips the dispatch terminal).
1694
+ let directDispatchTaskIdForLedger: string | undefined;
1624
1695
  if (args.event === 'agent:generating_completed') {
1625
1696
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
1626
1697
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
1627
1698
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
1628
1699
 
1629
1700
  if (sessionId) {
1630
- completedTaskForLedger = markSessionTerminal(sessionId, 'completed', eventTimestamp);
1701
+ directDispatchTaskIdForLedger = resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
1702
+ // A false-idle completion of a direct dispatch is recorded but kept tentative (the
1703
+ // dispatch row stays active for the reconcile fallback); a genuine completion is terminal.
1704
+ const isFalseIdle = isFalseIdleCompletion(args.metadataEvent);
1705
+ completedTaskForLedger = markSessionTerminal(sessionId, 'completed', eventTimestamp, { tentativeIfDirect: isFalseIdle });
1631
1706
  if (nodeId && providerType) {
1632
1707
  runIdleMaintenanceThenAssignQueue(components, { meshId: args.meshId, nodeId, sessionId, providerType });
1633
1708
  }
@@ -1729,6 +1804,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1729
1804
  } catch { /* best-effort */ }
1730
1805
  }
1731
1806
  if (sessionId) {
1807
+ directDispatchTaskIdForLedger = resolveActiveDirectDispatchTaskId(args.meshId, sessionId);
1732
1808
  completedTaskForLedger = markSessionTerminal(sessionId, 'failed');
1733
1809
  }
1734
1810
  }
@@ -1761,7 +1837,10 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1761
1837
  payload: {
1762
1838
  event: args.event,
1763
1839
  nodeLabel: args.nodeLabel,
1764
- taskId: completedTaskForLedger?.id || undefined,
1840
+ // Fix B: fall back to the direct-dispatch taskId when no work-queue row
1841
+ // matched, so the terminal entry is attributable in mesh task-stats
1842
+ // (otherwise the direct task shows status='unknown' / terminalKind=null).
1843
+ taskId: completedTaskForLedger?.id || directDispatchTaskIdForLedger || undefined,
1765
1844
  providerSessionId,
1766
1845
  finalSummary,
1767
1846
  workerResult,
@@ -303,6 +303,7 @@ export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEv
303
303
  const fingerprint = buildPendingEventFingerprint(event);
304
304
 
305
305
  // G3: Write to SQLite inbox (primary path going forward)
306
+ let sqliteOk = false;
306
307
  try {
307
308
  MeshRuntimeStore.getInstance().insertPendingEvent({
308
309
  id: randomUUID(),
@@ -313,14 +314,22 @@ export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEv
313
314
  fingerprint: fingerprint || null,
314
315
  queuedAt: event.queuedAt,
315
316
  });
317
+ sqliteOk = true;
316
318
  } catch {
317
319
  // SQLite write failure is non-fatal; JSONL fallback below still works.
318
320
  }
319
321
 
320
- // Also write to JSONL (retained as legacy/export artifact)
321
- const path = getPendingEventsPath(event.meshId, event.targetCoordinatorDaemonId);
322
- trimPendingEventsIfNeeded(path);
323
- appendFileSync(path, JSON.stringify(event) + '\n', 'utf-8');
322
+ // Also write to JSONL (retained as legacy/export artifact). Best-effort once
323
+ // SQLite (the primary store) has the event: a JSONL append failure (disk full,
324
+ // permissions) must NOT report the whole persist as failed when SQLite holds it.
325
+ try {
326
+ const path = getPendingEventsPath(event.meshId, event.targetCoordinatorDaemonId);
327
+ trimPendingEventsIfNeeded(path);
328
+ appendFileSync(path, JSON.stringify(event) + '\n', 'utf-8');
329
+ } catch (e: any) {
330
+ if (!sqliteOk) throw e; // neither store has it — surface as a real failure
331
+ LOG.warn('MeshEvents', `JSONL append failed for mesh ${event.meshId}; SQLite holds the event: ${e?.message || e}`);
332
+ }
324
333
  return true;
325
334
  } catch (e: any) {
326
335
  LOG.warn('MeshEvents', `Failed to persist pending coordinator event: ${e?.message || e}`);
@@ -74,6 +74,17 @@ export function hasUnterminalDirectDispatchLedgerEntry(meshId: string, sessionId
74
74
  return false;
75
75
  }
76
76
 
77
+ // True when a terminal ledger payload was recorded from WEAK completion evidence (a false
78
+ // idle): insufficient evidence level, review-recommended, or a missing-final-assistant
79
+ // completion diagnostic. Mirrors isWeakTerminalLedgerPayload in mesh-events-coordinator —
80
+ // a weak terminal is non-authoritative and may be superseded by a genuine completion.
81
+ function isWeakCompletionLedgerPayload(payload: Record<string, unknown> | undefined): boolean {
82
+ if (!payload) return false;
83
+ if (payload.evidenceLevel === 'insufficient' || payload.reviewRecommended === true) return true;
84
+ const diag = readRecord(payload.completionDiagnostic);
85
+ return diag?.finalAssistantPresent === false || diag?.blockReason === 'missing_final_assistant';
86
+ }
87
+
77
88
  function findDirectDispatchLedgerEntry(args: {
78
89
  meshId: string;
79
90
  taskId: string;
@@ -121,6 +132,12 @@ function hasTerminalLedgerAfterDispatch(args: {
121
132
  if (!afterDispatch) continue;
122
133
  }
123
134
  if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') continue;
135
+ // Fix C (reconcile fallback expansion): a task_completed recorded from a FALSE idle
136
+ // (weak evidence / no confirmed final assistant) is NOT authoritative terminal
137
+ // evidence. Skip it so the transcript reconcile can still synthesize the GENUINE
138
+ // completion for a re-dispatched / prematurely-terminated direct task instead of
139
+ // bailing with alreadyTerminal.
140
+ if (entry.kind === 'task_completed' && isWeakCompletionLedgerPayload(entry.payload)) continue;
124
141
  const terminalTaskId = readNonEmptyString(entry.payload?.taskId);
125
142
  if (terminalTaskId && terminalTaskId === args.taskId) return true;
126
143
  if (terminalTaskId && terminalTaskId !== args.taskId) continue;
@@ -70,6 +70,10 @@ export class MeshRuntimeStore {
70
70
  private readonly migratedMeshIds = new Set<string>();
71
71
  private fingerprintSweepCounter = 0;
72
72
  private walWriteCounter = 0;
73
+ // Independent cadence for the tool-call-log sweep. Must NOT share walWriteCounter:
74
+ // sharing makes each store's threshold drift by the other's write volume (WAL
75
+ // checkpoint at 500 vs tool-log sweep at 200 would interfere arbitrarily).
76
+ private toolCallLogCounter = 0;
73
77
  private static readonly WAL_CHECK_INTERVAL = 500;
74
78
  private static readonly WAL_MAX_BYTES = 50 * 1024 * 1024; // 50 MB
75
79
 
@@ -1171,7 +1175,7 @@ export class MeshRuntimeStore {
1171
1175
  const callsInWindow = row?.cnt ?? 0;
1172
1176
 
1173
1177
  // Sweep old entries periodically to keep the table lean (every 200 calls across all tools).
1174
- if (++this.walWriteCounter % 200 === 0) {
1178
+ if (++this.toolCallLogCounter % 200 === 0) {
1175
1179
  this.db.prepare(
1176
1180
  'DELETE FROM mesh_tool_call_log WHERE called_at < ?'
1177
1181
  ).run(now - Math.max(windowMs * 10, 60_000));
@@ -91,9 +91,16 @@ export function computeMeshTaskStats(meshId: string, opts?: { taskIds?: string[]
91
91
 
92
92
  return targetIds.map(taskId => {
93
93
  const queueEntry = queueById.get(taskId);
94
- const status = queueEntry?.status ?? 'unknown';
95
94
  const dispatch = dispatches.get(taskId);
96
95
  const terminal = terminals.get(taskId);
96
+ // Direct dispatches (mesh_send_task) have no work-queue row, so queueEntry is undefined.
97
+ // Derive a terminal status from the attributed terminal ledger entry instead of reporting
98
+ // status='unknown' — without this a completed direct task showed unknown + terminalKind=null
99
+ // even though its task_completed event fired (see mesh-events-coordinator Fix B attribution).
100
+ const status = queueEntry?.status
101
+ ?? (terminal
102
+ ? (terminal.kind === 'task_completed' ? 'completed' : 'failed')
103
+ : 'unknown');
97
104
  const isTerminalStatus = status === 'completed' || status === 'failed' || status === 'cancelled';
98
105
  const dispatchTime = parseTime(dispatch?.first ?? queueEntry?.dispatchTimestamp);
99
106
  const terminalTime = parseTime(terminal?.at);