@adhdev/daemon-core 0.9.82-rc.439 → 0.9.82-rc.440

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.439",
3
+ "version": "0.9.82-rc.440",
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.439",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.440",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -732,6 +732,29 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
732
732
  // keeps showing up in the dashboard graph until the cache
733
733
  // ages out on its own.
734
734
  if (removed) ctx.invalidateAggregateMeshStatus(meshId);
735
+ // MESH-INLINE-NODE-RESURRECTION: removeInlineMeshNode only mutates
736
+ // the in-memory inlineMeshCache + tombstones — it does NOT touch
737
+ // the file-backed config (meshes.json). When the SAME node also
738
+ // lives in meshes.json (a worktree node that was persisted to the
739
+ // file config, then resolved through the inline cache on removal),
740
+ // the file record survives. On daemon restart the in-memory
741
+ // tombstone is lost, getMesh falls back to the file config, and
742
+ // mesh-status renders the dead node again. So a node present in
743
+ // BOTH the inline cache AND meshes.json must be spliced+saved from
744
+ // the file config here too. Pure-inline meshes (no matching file
745
+ // mesh/node) are untouched — getMesh returns undefined and we skip.
746
+ try {
747
+ const { getMesh, removeNode } = await import('../../config/mesh-config.js');
748
+ const fileMesh = getMesh(meshId);
749
+ const fileNode = fileMesh?.nodes?.find((n: any) => meshNodeIdMatches(n, nodeId));
750
+ if (fileNode?.id) {
751
+ const fileRemoved = removeNode(meshId, fileNode.id);
752
+ if (fileRemoved) {
753
+ removed = true;
754
+ ctx.invalidateAggregateMeshStatus(meshId);
755
+ }
756
+ }
757
+ } catch { /* file config absent / unreadable — inline-only mesh, nothing to durably delete */ }
735
758
  // Node was already absent from the inline mesh (e.g. removed by a
736
759
  // prior refine cleanup). Treat as removed so caller gets removed:true.
737
760
  if (!removed && !node) removed = true;
@@ -70,10 +70,17 @@ function openDb(): any | null {
70
70
  }
71
71
 
72
72
  function loadMessagesForSession(db: any, sessionId: string): NativeHistoryMessage[] {
73
+ // Assistant turns whose finish_reason='tool_calls' persist an EMPTY
74
+ // `content` — their payload lives in the `tool_calls` column. Filtering on
75
+ // `content != ''` alone drops those rows, so a turn whose terminal message
76
+ // is a tool call surfaces zero assistant bubbles. Accept a row when either
77
+ // `content` OR `tool_calls` is non-empty, and project `tool_calls` into the
78
+ // content slot when `content` is empty so the bubble still carries text.
73
79
  const rows: any[] = db.prepare(
74
- `SELECT id, role, content, timestamp
80
+ `SELECT id, role, COALESCE(NULLIF(content, ''), tool_calls) AS content, timestamp
75
81
  FROM messages
76
- WHERE session_id = ? AND content IS NOT NULL AND content != ''
82
+ WHERE session_id = ?
83
+ AND ((content IS NOT NULL AND content != '') OR (tool_calls IS NOT NULL AND tool_calls != ''))
77
84
  ORDER BY timestamp ASC, id ASC`,
78
85
  ).all(sessionId);
79
86
  const out: NativeHistoryMessage[] = [];
@@ -115,10 +122,16 @@ export function readSession(sessionPath: string, requestedSessionId?: string): N
115
122
  sessionId = pinned;
116
123
  } else {
117
124
  // No bound id yet (discovery): pick the newest source='cli'
118
- // session that has at least one persisted message.
125
+ // session that has at least one persisted message. Use an
126
+ // EXISTS check against `messages` rather than the denormalized
127
+ // `message_count` column — a freshly-started session already
128
+ // has message rows while `message_count` (DEFAULT 0) can still
129
+ // lag at 0, and the old predicate excluded exactly that
130
+ // just-created session, resolving to nothing.
119
131
  const row: any = db.prepare(
120
132
  `SELECT id, started_at FROM sessions
121
- WHERE source = 'cli' AND message_count > 0
133
+ WHERE source = 'cli'
134
+ AND EXISTS (SELECT 1 FROM messages m WHERE m.session_id = sessions.id)
122
135
  ORDER BY started_at DESC LIMIT 1`,
123
136
  ).get();
124
137
  if (!row) return null;
@@ -185,7 +198,8 @@ export async function listSessions(_watchPath: string): Promise<NativeHistorySes
185
198
  const rows: any[] = db.prepare(
186
199
  `SELECT id, started_at, ended_at, message_count, title
187
200
  FROM sessions
188
- WHERE source = 'cli' AND message_count > 0
201
+ WHERE source = 'cli'
202
+ AND EXISTS (SELECT 1 FROM messages m WHERE m.session_id = sessions.id)
189
203
  ORDER BY started_at DESC LIMIT 100`,
190
204
  ).all();
191
205
  const mtime = statMtimeMs(HERMES_STATE_DB);