@adhdev/daemon-core 1.0.28-rc.7 → 1.0.28-rc.9

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.
@@ -15,7 +15,7 @@
15
15
  import { EventEmitter } from 'events';
16
16
  import { MeshRuntimeStore } from './mesh-runtime-store.js';
17
17
  import { type MeshLedgerOriginatingCoordinatorV2 } from './contracts.js';
18
- export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'task_question_pending' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'event_held_requeued' | 'task_reclaimed' | 'coordinator_operating_note' | 'coordinator_operating_note_tombstone' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated' | 'magi_dispatched' | 'magi_synthesis' | 'key_injection' | 'worktree_cleanup_candidate';
18
+ export type MeshLedgerKind = 'task_dispatched' | 'task_claimed' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'task_question_pending' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'event_held_requeued' | 'task_reclaimed' | 'coordinator_operating_note' | 'coordinator_operating_note_tombstone' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated' | 'magi_dispatched' | 'magi_synthesis' | 'key_injection' | 'worktree_cleanup_candidate';
19
19
  export interface MeshLedgerEntry {
20
20
  id: string;
21
21
  meshId: string;
@@ -24,8 +24,12 @@ export interface MeshLedgerEntry {
24
24
  nodeId?: string;
25
25
  sessionId?: string;
26
26
  providerType?: string;
27
+ taskId?: string;
27
28
  payload: Record<string, unknown>;
28
29
  }
30
+ /** Resolve the taskId for a ledger entry, preferring the base field and falling
31
+ * back to payload.taskId (legacy rows / entries that only carry it in payload). */
32
+ export declare function ledgerEntryTaskId(entry: Pick<MeshLedgerEntry, 'taskId' | 'payload'>): string | undefined;
29
33
  export declare function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind' | 'payload'>): boolean;
30
34
  export type MeshWorkerResultStatus = 'completed' | 'failed' | 'blocked' | 'partial' | 'unknown';
31
35
  export type MeshProcessArtifactKind = 'process' | 'log' | 'port' | 'window' | 'session' | 'file' | 'url' | 'other';
@@ -5,7 +5,25 @@ import type { RepoMeshSchedulingStrategy } from '../repo-mesh-types.js';
5
5
  export declare function isWorkspaceAutoFastForwardInFlight(workspace: string | undefined): boolean;
6
6
  export declare function __resetIdleAutoFastForwardForTests(): void;
7
7
  export declare function getMeshWithCache(components: DaemonComponents, meshId: string): any | undefined;
8
- export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
8
+ export interface MeshTaskRoutingDecision {
9
+ source: 'queue' | 'autoLaunch' | 'direct';
10
+ fitnessScore?: number;
11
+ skippedCandidates?: Array<{
12
+ nodeId: string;
13
+ reason: string;
14
+ }>;
15
+ requiredTagsResult?: {
16
+ required: string[];
17
+ satisfied: boolean;
18
+ missing: string[];
19
+ };
20
+ resolvedProviderType?: string;
21
+ resolvedModel?: string;
22
+ resolvedThinkingLevel?: string;
23
+ resolvedDifficulty?: string;
24
+ reason?: string;
25
+ }
26
+ export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string, routingDecision?: MeshTaskRoutingDecision): boolean;
9
27
  export declare const AUTO_LAUNCH_AWAIT_CLAIM_MS = 90000;
10
28
  interface AwaitClaimBackoffState {
11
29
  cycles: number;
@@ -355,6 +355,7 @@ export declare class MeshRuntimeStore {
355
355
  nodeId?: string | null;
356
356
  sessionId?: string | null;
357
357
  providerType?: string | null;
358
+ taskId?: string | null;
358
359
  payload?: unknown;
359
360
  }): void;
360
361
  readLedgerEntries(meshId: string, opts?: {
@@ -370,6 +371,7 @@ export declare class MeshRuntimeStore {
370
371
  nodeId: string | null;
371
372
  sessionId: string | null;
372
373
  providerType: string | null;
374
+ taskId: string | null;
373
375
  payload: unknown;
374
376
  }>;
375
377
  /**
@@ -390,6 +392,7 @@ export declare class MeshRuntimeStore {
390
392
  nodeId: string | null;
391
393
  sessionId: string | null;
392
394
  providerType: string | null;
395
+ taskId: string | null;
393
396
  payload: unknown;
394
397
  }>;
395
398
  /** Remove all ledger entries for a mesh (mesh deletion / test cleanup). */
@@ -406,6 +409,7 @@ export declare class MeshRuntimeStore {
406
409
  nodeId?: string | null;
407
410
  sessionId?: string | null;
408
411
  providerType?: string | null;
412
+ taskId?: string | null;
409
413
  payload?: unknown;
410
414
  }>): number;
411
415
  /**
@@ -39,6 +39,7 @@ export type ExternalTranscriptProbe = {
39
39
  export declare const COMPLETED_FINALIZATION_RETRY_MS = 1000;
40
40
  export declare const COMPLETED_FINALIZATION_MAX_WAIT_MS = 30000;
41
41
  export declare const CANON_C_MISSING_ASSISTANT_MIN_ELAPSED_MS = 20000;
42
+ export declare const MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS = 60000;
42
43
  export declare const NATIVE_HISTORY_MESH_IDLE_SETTLE_MS = 4000;
43
44
  export declare const PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS = 1200;
44
45
  export declare const ANTIGRAVITY_HOLD_QUIET_DWELL_MS = 3000;
@@ -475,4 +475,17 @@ export declare class FsmDriver implements ISpecDriver {
475
475
  * not register as a region change. No filter → lines returned unchanged.
476
476
  * Exported for unit tests of the stable_ms `ignore_lines` change-detection. */
477
477
  export declare function filterIgnoredLines(lines: string[], ignoreRe: RegExp | undefined): string[];
478
+ /** Compute the [start, end) line window a cursor_above stable region measures,
479
+ * reconciling the backend's raw cursor row with the blank-trimmed viewport
480
+ * line array (see the CODEX-FSM-DEGENERATE-STABLE note in trackRegionChanges).
481
+ * The window end is clamped to the content length so an overshooting cursor
482
+ * row measures the content tail instead of slicing past the array into a
483
+ * permanently-empty — and therefore permanently "unchanged" — window.
484
+ * Returns null when no measurable window exists (cursor at/above row 0, or no
485
+ * content): the caller must treat the region as CHANGED, never stable.
486
+ * Exported for unit tests. */
487
+ export declare function stableCursorWindow(lineCount: number, cursorRow: number, cursorAbove: number): {
488
+ start: number;
489
+ end: number;
490
+ } | null;
478
491
  export {};
@@ -268,6 +268,31 @@ export interface SectionDef {
268
268
  * for that anchor. A scalar `anchor` ignores array form beyond index 0.
269
269
  */
270
270
  anchor_context?: AnchorContext | (AnchorContext | null)[];
271
+ /**
272
+ * Extends an anchored section N lines ABOVE its anchor line. The default
273
+ * anchored geometry starts the section AT the anchor line, which cannot
274
+ * express a status block that sits directly ABOVE its landmark (e.g.
275
+ * codex's live `Working (…)` spinner above the `› ` composer). With
276
+ * `above: K` the section starts at max(0, anchorLine - K), so the window
277
+ * stays pinned to the landmark regardless of output volume — unlike
278
+ * `from_bottom`, which counts from the LAST NON-BLANK line of a
279
+ * blank-trimmed viewport and lets the live status block escape the window
280
+ * once long output fills the tail (CODEX-FSM-DEGENERATE-STABLE RCA,
281
+ * defect 1: the spinner left the from_bottom:12 window while the worker
282
+ * was still generating).
283
+ */
284
+ above?: number;
285
+ /**
286
+ * Anchor-miss policy. Default (absent): an anchored section whose anchor
287
+ * matches NOTHING falls back to the whole screen (from=0, to=total) — the
288
+ * historical behavior modal sections rely on ("whole-screen fallback in
289
+ * non-modal frames is harmless"). 'empty' resolves the section to EMPTY
290
+ * instead, for sections whose guard regexes must never see unrelated body
291
+ * text: status_tail's spinner cues, whose whole-screen fallback would
292
+ * re-open the SPINNER-BODY-SELFMATCH defect on any frame where the
293
+ * composer landmark is momentarily absent (mid-redraw).
294
+ */
295
+ anchor_miss?: 'empty';
271
296
  lines?: number;
272
297
  until_regex?: string;
273
298
  until_regex_flags?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "1.0.28-rc.7",
3
+ "version": "1.0.28-rc.9",
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",
@@ -49,8 +49,8 @@
49
49
  "author": "vilmire",
50
50
  "license": "AGPL-3.0-or-later",
51
51
  "dependencies": {
52
- "@adhdev/mesh-shared": "1.0.28-rc.7",
53
- "@adhdev/session-host-core": "1.0.28-rc.7",
52
+ "@adhdev/mesh-shared": "1.0.28-rc.9",
53
+ "@adhdev/session-host-core": "1.0.28-rc.9",
54
54
  "@agentclientprotocol/sdk": "^0.16.1",
55
55
  "ajv": "^8.20.0",
56
56
  "ajv-formats": "^3.0.1",
@@ -677,6 +677,20 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
677
677
  ...(capabilities && capabilities.length ? { capabilities } : {}),
678
678
  });
679
679
  if (!node) return { success: false, error: 'Mesh not found' };
680
+ // MESH-MEMBERSHIP-INLINE-CACHE-SYNC: addNode() above only wrote the new
681
+ // node to the file-backed meshes.json. getMeshForCommand's inline-cache-
682
+ // preferred read (the default for mesh_status/mesh_list_nodes/get_mesh)
683
+ // resolves this SAME meshId from `inlineMeshCache` whenever a prior
684
+ // command warmed it (e.g. a cloud coordinator launch with inlineMesh —
685
+ // see mesh-coordinator-launch.ts). Without pushing the new node into
686
+ // that cache too, the live view keeps serving the pre-add snapshot until
687
+ // the daemon restarts and the cache is re-emptied. Only touch the cache
688
+ // when it already holds this mesh (nothing to fix for a pure local-config
689
+ // mesh that no caller has ever warmed).
690
+ const cachedMesh = ctx.getCachedInlineMesh(meshId);
691
+ if (cachedMesh) {
692
+ ctx.updateInlineMeshNode(meshId, cachedMesh, node);
693
+ }
680
694
  // mesh_status hands back a coordinator-memory aggregate
681
695
  // snapshot keyed on (meshId, queueRevision). Adding a
682
696
  // node touches neither, so without an explicit cache
@@ -1036,6 +1050,19 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
1036
1050
  // the response is accurate.
1037
1051
  if (!removed && !node) removed = true;
1038
1052
  if (removed) ctx.invalidateAggregateMeshStatus(meshId);
1053
+ // MESH-MEMBERSHIP-INLINE-CACHE-SYNC: mirror-image of the inline
1054
+ // branch above. This mesh resolved from local_config (nothing had
1055
+ // warmed inlineMeshCache for it YET at getMeshForCommand time), but
1056
+ // another command (e.g. a cloud coordinator launch with inlineMesh)
1057
+ // may have already warmed the cache for this same meshId from an
1058
+ // earlier read. Splice the node out of the cached copy too, and
1059
+ // tombstone it, so a dashboard's stale inlineMesh echo cannot merge
1060
+ // the removed node back in (see removeInlineMeshNode's tombstone
1061
+ // comment).
1062
+ if (removed) {
1063
+ const cachedMesh = ctx.getCachedInlineMesh(meshId);
1064
+ if (cachedMesh) ctx.removeInlineMeshNode(meshId, cachedMesh, nodeId);
1065
+ }
1039
1066
  }
1040
1067
 
1041
1068
  // Record in task ledger
package/src/index.ts CHANGED
@@ -288,7 +288,7 @@ export { loadRepoSettings } from './config/repo-settings.js';
288
288
  export type { RepoSettings, LoadRepoSettingsOptions } from './config/repo-settings.js';
289
289
 
290
290
  // ── Mesh Task Ledger ──
291
- export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, readLedgerSliceFromStore, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT, tombstoneOperatingNote, readOperatingNotes, pruneOperatingNotes, isOperatingNoteTombstoned, OPERATING_NOTE_KIND, OPERATING_NOTE_TOMBSTONE_KIND, OPERATING_NOTE_DEDUPE_WINDOW, OPERATING_NOTE_KEEP_LATEST } from './mesh/mesh-ledger.js';
291
+ export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, readLedgerSliceFromStore, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, ledgerEntryTaskId, MAX_LEDGER_SLICE_LIMIT, tombstoneOperatingNote, readOperatingNotes, pruneOperatingNotes, isOperatingNoteTombstoned, OPERATING_NOTE_KIND, OPERATING_NOTE_TOMBSTONE_KIND, OPERATING_NOTE_DEDUPE_WINDOW, OPERATING_NOTE_KEEP_LATEST } from './mesh/mesh-ledger.js';
292
292
  export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext, MeshTaskCompletionEvidence, MeshWorkerResultArtifact, MeshProcessArtifact, MeshValidationResultArtifact } from './mesh/mesh-ledger.js';
293
293
  export { fastForwardMeshNode } from './mesh/mesh-fast-forward.js';
294
294
  export type { MeshFastForwardNodeArgs, MeshFastForwardPlannedStep, MeshFastForwardResult } from './mesh/mesh-fast-forward.js';
@@ -29,6 +29,11 @@ import {
29
29
 
30
30
  export type MeshLedgerKind =
31
31
  | 'task_dispatched'
32
+ // LEDGER-TASK-TRACEABILITY (C): a queue task transitioned pending→assigned (a
33
+ // node/session claimed it). Distinct from task_dispatched (the message was handed
34
+ // to the transport): claim precedes dispatch and marks the lifecycle handoff.
35
+ // payload: { taskId, nodeId, sessionId, providerType?, claimedAt }
36
+ | 'task_claimed'
32
37
  | 'task_completed'
33
38
  | 'task_failed'
34
39
  | 'task_stalled'
@@ -111,9 +116,40 @@ export interface MeshLedgerEntry {
111
116
  nodeId?: string;
112
117
  sessionId?: string;
113
118
  providerType?: string;
119
+ // LEDGER-TASK-TRACEABILITY (B): the task this entry pertains to, promoted from
120
+ // payload.taskId to a top-level base field so a task's lifecycle
121
+ // (task_dispatched → task_claimed → task_completed/failed/stalled/reclaimed) can
122
+ // be joined by kind+taskId without an O(n) per-entry payload scan. Optional and
123
+ // back-compat: legacy rows never carried it — readers fall back to payload.taskId,
124
+ // and appendLedgerEntry auto-derives it from payload.taskId for task-lifecycle
125
+ // kinds so every such entry is uniformly queryable.
126
+ taskId?: string;
114
127
  payload: Record<string, unknown>;
115
128
  }
116
129
 
130
+ // LEDGER-TASK-TRACEABILITY (B): the kinds whose taskId base field is auto-derived
131
+ // from payload.taskId at append time (so a join by kind+taskId is uniform). Other
132
+ // kinds may still set taskId explicitly; this only backfills the common lifecycle.
133
+ const TASK_LIFECYCLE_LEDGER_KINDS: ReadonlySet<MeshLedgerKind> = new Set<MeshLedgerKind>([
134
+ 'task_dispatched',
135
+ 'task_claimed',
136
+ 'task_completed',
137
+ 'task_failed',
138
+ 'task_stalled',
139
+ 'task_reclaimed',
140
+ 'task_approval_needed',
141
+ 'task_question_pending',
142
+ 'p2p_dispatch_failed',
143
+ ]);
144
+
145
+ /** Resolve the taskId for a ledger entry, preferring the base field and falling
146
+ * back to payload.taskId (legacy rows / entries that only carry it in payload). */
147
+ export function ledgerEntryTaskId(entry: Pick<MeshLedgerEntry, 'taskId' | 'payload'>): string | undefined {
148
+ if (typeof entry.taskId === 'string' && entry.taskId.trim()) return entry.taskId.trim();
149
+ const fromPayload = entry.payload && typeof entry.payload === 'object' ? (entry.payload as Record<string, unknown>).taskId : undefined;
150
+ return typeof fromPayload === 'string' && fromPayload.trim() ? fromPayload.trim() : undefined;
151
+ }
152
+
117
153
  export function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind' | 'payload'>): boolean {
118
154
  if (entry.kind !== 'session_stopped' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') return false;
119
155
  const payload = entry.payload && typeof entry.payload === 'object' && !Array.isArray(entry.payload)
@@ -796,6 +832,14 @@ export function appendLedgerEntry(
796
832
  ...partial,
797
833
  };
798
834
 
835
+ // LEDGER-TASK-TRACEABILITY (B): backfill the taskId base field from payload.taskId
836
+ // for task-lifecycle kinds so every dispatch/claim/complete/fail/stall/reclaim
837
+ // entry is uniformly join-able by kind+taskId. An explicit partial.taskId wins.
838
+ if (!entry.taskId && TASK_LIFECYCLE_LEDGER_KINDS.has(entry.kind)) {
839
+ const derived = ledgerEntryTaskId(entry);
840
+ if (derived) entry.taskId = derived;
841
+ }
842
+
799
843
  const filePath = getLedgerPath(meshId);
800
844
 
801
845
  // Compact or rotate based on file size
@@ -822,6 +866,7 @@ export function appendLedgerEntry(
822
866
  nodeId: entry.nodeId ?? null,
823
867
  sessionId: entry.sessionId ?? null,
824
868
  providerType: entry.providerType ?? null,
869
+ taskId: entry.taskId ?? null,
825
870
  payload: entry.payload,
826
871
  });
827
872
  } catch {
@@ -1074,7 +1119,15 @@ function readLedgerFile(meshId: string): MeshLedgerEntry[] {
1074
1119
  if (!line.trim()) continue;
1075
1120
  try {
1076
1121
  const entry = JSON.parse(line) as MeshLedgerEntry;
1077
- if (entry.id && entry.kind) entries.push(entry);
1122
+ if (entry.id && entry.kind) {
1123
+ // LEDGER-TASK-TRACEABILITY (B): backfill the base taskId from
1124
+ // payload.taskId for legacy JSONL lines that predate the field.
1125
+ if (!entry.taskId) {
1126
+ const derived = ledgerEntryTaskId(entry);
1127
+ if (derived) entry.taskId = derived;
1128
+ }
1129
+ entries.push(entry);
1130
+ }
1078
1131
  } catch { /* skip malformed lines */ }
1079
1132
  }
1080
1133
  return entries;
@@ -1108,6 +1161,7 @@ function ensureLedgerImported(store: MeshRuntimeStore, meshId: string): void {
1108
1161
  nodeId: e.nodeId ?? null,
1109
1162
  sessionId: e.sessionId ?? null,
1110
1163
  providerType: e.providerType ?? null,
1164
+ taskId: ledgerEntryTaskId(e) ?? null,
1111
1165
  payload: e.payload ?? {},
1112
1166
  })));
1113
1167
  } catch { /* import is best-effort; reads fall back to JSONL on store failure */ }
@@ -1116,16 +1170,23 @@ function ensureLedgerImported(store: MeshRuntimeStore, meshId: string): void {
1116
1170
  function readLedgerFromStore(meshId: string): MeshLedgerEntry[] {
1117
1171
  const store = MeshRuntimeStore.getInstance();
1118
1172
  ensureLedgerImported(store, meshId);
1119
- return store.readLedgerEntriesOrdered(meshId).map(r => ({
1120
- id: r.id,
1121
- meshId: r.meshId,
1122
- timestamp: r.timestamp,
1123
- kind: r.kind as MeshLedgerKind,
1124
- ...(r.nodeId ? { nodeId: r.nodeId } : {}),
1125
- ...(r.sessionId ? { sessionId: r.sessionId } : {}),
1126
- ...(r.providerType ? { providerType: r.providerType } : {}),
1127
- payload: (r.payload && typeof r.payload === 'object' ? r.payload : {}) as Record<string, unknown>,
1128
- }));
1173
+ return store.readLedgerEntriesOrdered(meshId).map(r => {
1174
+ const payload = (r.payload && typeof r.payload === 'object' ? r.payload : {}) as Record<string, unknown>;
1175
+ // LEDGER-TASK-TRACEABILITY (B): prefer the column, fall back to payload.taskId
1176
+ // for legacy rows written before the column existed (back-compat join).
1177
+ const taskId = ledgerEntryTaskId({ taskId: r.taskId ?? undefined, payload });
1178
+ return {
1179
+ id: r.id,
1180
+ meshId: r.meshId,
1181
+ timestamp: r.timestamp,
1182
+ kind: r.kind as MeshLedgerKind,
1183
+ ...(r.nodeId ? { nodeId: r.nodeId } : {}),
1184
+ ...(r.sessionId ? { sessionId: r.sessionId } : {}),
1185
+ ...(r.providerType ? { providerType: r.providerType } : {}),
1186
+ ...(taskId ? { taskId } : {}),
1187
+ payload,
1188
+ };
1189
+ });
1129
1190
  }
1130
1191
 
1131
1192
  function getCachedRawEntries(meshId: string): MeshLedgerEntry[] {