@nessielabs/daemon 0.4.0 → 0.5.26

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.
Files changed (55) hide show
  1. package/README.md +8 -17
  2. package/dist/cli.js +42 -21
  3. package/dist/commands/auth.js +116 -0
  4. package/dist/commands/integrations.js +84 -0
  5. package/dist/commands/login.js +145 -0
  6. package/dist/commands/run.js +21 -6
  7. package/dist/commands/service.js +18 -36
  8. package/dist/commands/sharing.js +399 -0
  9. package/dist/commands/status.js +67 -0
  10. package/dist/commands/teams.js +64 -0
  11. package/dist/nera/client.js +156 -0
  12. package/dist/nera/process.js +78 -0
  13. package/dist/nera/runtimeFile.js +25 -0
  14. package/dist/platform/binary.js +21 -0
  15. package/dist/platform/openBrowser.js +14 -0
  16. package/dist/platform/paths.js +13 -0
  17. package/dist/platform/table.js +85 -0
  18. package/package.json +9 -5
  19. package/vendor/nera/darwin-arm64/nera +0 -0
  20. package/vendor/nera/darwin-x64/nera +0 -0
  21. package/vendor/nera/linux-arm64/nera +0 -0
  22. package/vendor/nera/linux-x64/nera +0 -0
  23. package/vendor/nera/win32-arm64/nera.exe +0 -0
  24. package/vendor/nera/win32-x64/nera.exe +0 -0
  25. package/vendor/proto/nessie.edge.v1.proto +475 -0
  26. package/dist/api/http.js +0 -44
  27. package/dist/auth/deviceFlow.js +0 -36
  28. package/dist/auth/tokens.js +0 -30
  29. package/dist/commands/setup.js +0 -75
  30. package/dist/config/endpoints.js +0 -2
  31. package/dist/config/paths.js +0 -19
  32. package/dist/config/store.js +0 -27
  33. package/dist/git/repoResolver.js +0 -215
  34. package/dist/localStore/daemonGraphStore.js +0 -494
  35. package/dist/localStore/messageComparison.js +0 -37
  36. package/dist/localStore/pushMapping.js +0 -69
  37. package/dist/localStore/rowTypes.js +0 -1
  38. package/dist/localStore/schema.js +0 -70
  39. package/dist/localStore/uuid.js +0 -12
  40. package/dist/parser/claudeCode.js +0 -202
  41. package/dist/parser/codex.js +0 -91
  42. package/dist/parser/jsonl.js +0 -24
  43. package/dist/parser/types.js +0 -15
  44. package/dist/redact/secrets.js +0 -60
  45. package/dist/service/process.js +0 -134
  46. package/dist/service/systemd.js +0 -38
  47. package/dist/sources/detect.js +0 -43
  48. package/dist/sync/batch.js +0 -50
  49. package/dist/sync/client.js +0 -90
  50. package/dist/sync/exclusions.js +0 -10
  51. package/dist/sync/hash.js +0 -4
  52. package/dist/sync/loop.js +0 -106
  53. package/dist/sync/scan.js +0 -245
  54. package/dist/sync/slices.js +0 -63
  55. package/dist/sync/types.js +0 -1
@@ -1,494 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { mkdirSync } from "node:fs";
3
- import { dirname } from "node:path";
4
- import Database from "better-sqlite3";
5
- import { redactSecrets } from "../redact/secrets.js";
6
- import { contentHash } from "../sync/hash.js";
7
- import { splitContent } from "../sync/slices.js";
8
- import { messageMatches, messageMetadataChanged, messageNodeName, messageTimestamp, } from "./messageComparison.js";
9
- import { mapEdgePushItem, mapNodePushItem } from "./pushMapping.js";
10
- import { createDaemonGraphSchema } from "./schema.js";
11
- import { uuidFromBytes, uuidToBytes } from "./uuid.js";
12
- const NODE_CANDIDATE_LIMIT = 500;
13
- export class DaemonGraphStore {
14
- db;
15
- lastTimestampMs = 0;
16
- constructor(db) {
17
- this.db = db;
18
- createDaemonGraphSchema(db);
19
- }
20
- close() {
21
- this.db.close();
22
- }
23
- persistSourceSessions(source, sessions, options) {
24
- const persist = this.db.transaction(() => {
25
- this.ensureIntegrationRoot(source);
26
- for (const session of sessions) {
27
- this.persistSession(source, session, options);
28
- }
29
- });
30
- persist();
31
- }
32
- conversationsNeedingFetch(source, conversations, parserVersion) {
33
- const kind = source.kind === "codex" ? "codex_chat" : "claude_code_chat";
34
- const select = this.db.prepare(`
35
- SELECT parserVersion, originalUpdatedAt
36
- FROM node
37
- WHERE kind = ? AND sourceId = ? AND deletedAt IS NULL
38
- ORDER BY createdAt ASC
39
- LIMIT 1
40
- `);
41
- this.ensureIntegrationRoot(source);
42
- return conversations.filter((conversation) => {
43
- const row = select.get(kind, conversation.sourceId);
44
- if (!row)
45
- return true;
46
- const remoteUpdatedAt = conversation.lastMessageTime ?? conversation.updatedAt;
47
- const isUpdated = remoteUpdatedAt
48
- ? Date.parse(remoteUpdatedAt) - Date.parse(row.originalUpdatedAt) > 5000
49
- : false;
50
- if (row.parserVersion !== null && row.parserVersion < parserVersion)
51
- return true;
52
- return isUpdated;
53
- });
54
- }
55
- pendingPushItems() {
56
- const nodes = this.dirtyNodes().map((node) => this.mapNodePushItem(node));
57
- const edges = this.dirtyEdges().map((edge) => this.mapEdgePushItem(edge));
58
- return { nodes, edges };
59
- }
60
- nextPushBatch(options) {
61
- const nodeRows = this.dirtyNodes(NODE_CANDIDATE_LIMIT);
62
- const nodes = [];
63
- const pushedNodeVersions = {};
64
- let sliceCount = 0;
65
- for (const row of nodeRows) {
66
- const node = this.mapNodePushItem(row);
67
- const nodeSliceCount = node.slices.length;
68
- if (nodes.length > 0 && sliceCount + nodeSliceCount > options.targetSliceCount)
69
- break;
70
- nodes.push(node);
71
- pushedNodeVersions[node.node.id] = row.updatedAt;
72
- sliceCount += nodeSliceCount;
73
- }
74
- if (nodes.length > 0)
75
- return { nodes, edges: [], pushedNodeVersions, pushedEdgeVersions: {} };
76
- const edgeRows = this.dirtyEdges(options.edgeLimit);
77
- const pushedEdgeVersions = {};
78
- const edges = edgeRows.map((edge) => {
79
- const item = this.mapEdgePushItem(edge);
80
- pushedEdgeVersions[item.edge.id] = edge.updatedAt;
81
- return item;
82
- });
83
- return edges.length > 0 ? { nodes: [], edges, pushedNodeVersions: {}, pushedEdgeVersions } : null;
84
- }
85
- applyPushAcknowledgements(batch, response) {
86
- const apply = this.db.transaction(() => {
87
- const updateNode = this.db.prepare(`
88
- UPDATE node SET lastPushedAt = ?, cloudUpdatedAt = ?
89
- WHERE id = ?
90
- `);
91
- for (const ack of response.nodes) {
92
- const pushedUpdatedAt = batch.pushedNodeVersions[ack.id];
93
- if (!pushedUpdatedAt)
94
- continue;
95
- updateNode.run(pushedUpdatedAt, ack.updatedAt, uuidToBytes(ack.id));
96
- }
97
- const updateEdge = this.db.prepare(`
98
- UPDATE node_edge SET lastPushedAt = ?, cloudUpdatedAt = ?
99
- WHERE id = ?
100
- `);
101
- for (const ack of response.edges) {
102
- const pushedUpdatedAt = batch.pushedEdgeVersions[ack.id];
103
- if (!pushedUpdatedAt)
104
- continue;
105
- updateEdge.run(pushedUpdatedAt, ack.updatedAt, uuidToBytes(ack.id));
106
- }
107
- });
108
- apply();
109
- }
110
- applyCloudPull(response) {
111
- const apply = this.db.transaction(() => {
112
- let skippedEdges = 0;
113
- for (const bundle of response.nodes) {
114
- this.applyCloudPullNodeBundle(bundle);
115
- }
116
- for (const edge of response.edges) {
117
- if (!this.applyCloudPullEdge(edge))
118
- skippedEdges += 1;
119
- }
120
- return { skippedEdges };
121
- });
122
- return apply();
123
- }
124
- getAppSetting(key) {
125
- const row = this.db.prepare("SELECT value FROM app_setting WHERE key = ?").get(key);
126
- return row?.value ?? null;
127
- }
128
- getSyncCursor(key) {
129
- const value = this.getAppSetting(key);
130
- if (!value)
131
- return null;
132
- try {
133
- const parsed = JSON.parse(value);
134
- return typeof parsed.updatedAt === "string" && typeof parsed.id === "string"
135
- ? { updatedAt: parsed.updatedAt, id: parsed.id }
136
- : null;
137
- }
138
- catch {
139
- return null;
140
- }
141
- }
142
- setAppSetting(key, value) {
143
- this.db.prepare(`
144
- INSERT INTO app_setting (key, value) VALUES (?, ?)
145
- ON CONFLICT(key) DO UPDATE SET value = excluded.value
146
- `).run(key, value);
147
- }
148
- setSyncCursor(key, cursor) {
149
- this.setAppSetting(key, JSON.stringify(cursor));
150
- }
151
- ensureIntegrationRoot(source) {
152
- const now = this.nextTimestamp();
153
- const id = uuidToBytes(source.localId);
154
- const existing = this.nodeById(id);
155
- const name = redactSecrets(source.displayName);
156
- if (!existing) {
157
- this.insertNode({
158
- id,
159
- integrationId: id,
160
- sourceId: source.basePath,
161
- name,
162
- kind: "integration_root",
163
- originalCreatedAt: now,
164
- originalUpdatedAt: now,
165
- now,
166
- });
167
- return id;
168
- }
169
- if (existing.integrationId?.equals(id) !== true ||
170
- existing.sourceId !== source.basePath ||
171
- existing.name !== name ||
172
- existing.kind !== "integration_root" ||
173
- existing.deletedAt !== null) {
174
- this.db.prepare(`
175
- UPDATE node
176
- SET integrationId = ?, sourceId = ?, name = ?, kind = 'integration_root',
177
- updatedAt = ?, originalUpdatedAt = ?, deletedAt = NULL
178
- WHERE id = ?
179
- `).run(id, source.basePath, name, now, now, id);
180
- }
181
- return id;
182
- }
183
- persistSession(source, session, options) {
184
- const now = this.nextTimestamp();
185
- const transcriptKind = session.sourceKind === "codex" ? "codex_chat" : "claude_code_chat";
186
- const messageKind = session.sourceKind === "codex" ? "codex_chat_message" : "claude_code_chat_message";
187
- const integrationId = uuidToBytes(source.localId);
188
- const repoInfo = session.cwd ? options.resolveRepo?.(session.cwd) ?? null : null;
189
- const transcript = this.upsertTranscript({
190
- integrationId,
191
- sourceId: session.sourceId,
192
- name: redactSecrets(session.title),
193
- kind: transcriptKind,
194
- workspacePath: session.cwd,
195
- repoRemoteUrl: repoInfo?.remoteUrl ?? null,
196
- repoKey: repoInfo?.repoKey ?? null,
197
- parserVersion: options.parserVersion,
198
- originalCreatedAt: session.createdAt,
199
- originalUpdatedAt: session.updatedAt,
200
- now,
201
- });
202
- this.ensureSourceEdge(integrationId, transcript.id, now);
203
- const existingMessages = this.existingMessages(transcript.id);
204
- const pushableMessages = session.messages.filter((message) => message.role !== "system");
205
- const keptIds = new Set();
206
- for (const [index, message] of pushableMessages.entries()) {
207
- const existing = existingMessages[index];
208
- if (existing && messageMatches(existing, message, messageKind)) {
209
- const timestamp = messageTimestamp(message, session);
210
- if (messageMetadataChanged(existing.node, message, integrationId, messageKind, timestamp)) {
211
- this.db.prepare(`
212
- UPDATE node
213
- SET integrationId = ?, name = ?, kind = ?, originalCreatedAt = ?,
214
- originalUpdatedAt = ?, updatedAt = ?, deletedAt = NULL
215
- WHERE id = ?
216
- `).run(integrationId, messageNodeName(message), messageKind, timestamp, timestamp, now, existing.node.id);
217
- }
218
- this.ensureSourceEdge(integrationId, existing.node.id, now);
219
- this.ensureContainsEdge(transcript.id, existing.node.id, index, now);
220
- keptIds.add(uuidFromBytes(existing.node.id));
221
- continue;
222
- }
223
- const nodeId = uuidToBytes(randomUUID());
224
- const content = redactSecrets(message.content);
225
- const timestamp = messageTimestamp(message, session);
226
- this.insertNode({
227
- id: nodeId,
228
- integrationId,
229
- sourceId: null,
230
- name: messageNodeName(message),
231
- kind: messageKind,
232
- originalCreatedAt: timestamp,
233
- originalUpdatedAt: timestamp,
234
- now,
235
- });
236
- this.db.prepare(`
237
- INSERT INTO ai_chat_message (nodeId, role, author, toolCallId, toolName, toolFriendlyName, toolStatus)
238
- VALUES (?, ?, NULL, NULL, NULL, NULL, NULL)
239
- `).run(nodeId, message.role);
240
- this.replaceContentSlices(nodeId, content);
241
- this.ensureContainsEdge(transcript.id, nodeId, index, now);
242
- this.ensureSourceEdge(integrationId, nodeId, now);
243
- keptIds.add(uuidFromBytes(nodeId));
244
- }
245
- for (const existing of existingMessages) {
246
- if (!keptIds.has(uuidFromBytes(existing.node.id)))
247
- this.softDeleteNode(existing.node.id, now);
248
- }
249
- }
250
- upsertTranscript(input) {
251
- const existing = this.db.prepare(`
252
- SELECT * FROM node
253
- WHERE kind = ? AND sourceId = ?
254
- ORDER BY createdAt ASC
255
- LIMIT 1
256
- `).get(input.kind, input.sourceId);
257
- if (!existing) {
258
- const id = uuidToBytes(randomUUID());
259
- this.insertNode({ id, ...input });
260
- return this.nodeById(id);
261
- }
262
- if (existing.integrationId?.equals(input.integrationId) !== true ||
263
- existing.name !== input.name ||
264
- existing.workspacePath !== input.workspacePath ||
265
- existing.repoRemoteUrl !== input.repoRemoteUrl ||
266
- existing.repoKey !== input.repoKey ||
267
- existing.parserVersion !== input.parserVersion ||
268
- existing.originalCreatedAt !== input.originalCreatedAt ||
269
- existing.originalUpdatedAt !== input.originalUpdatedAt ||
270
- existing.deletedAt !== null) {
271
- this.db.prepare(`
272
- UPDATE node
273
- SET integrationId = ?, name = ?, workspacePath = ?, repoRemoteUrl = ?,
274
- repoKey = ?, parserVersion = ?, originalCreatedAt = ?, originalUpdatedAt = ?,
275
- updatedAt = ?, deletedAt = NULL
276
- WHERE id = ?
277
- `).run(input.integrationId, input.name, input.workspacePath, input.repoRemoteUrl, input.repoKey, input.parserVersion, input.originalCreatedAt, input.originalUpdatedAt, input.now, existing.id);
278
- return this.nodeById(existing.id);
279
- }
280
- return existing;
281
- }
282
- insertNode(input) {
283
- this.db.prepare(`
284
- INSERT INTO node (
285
- id, integrationId, sourceId, name, kind, pinned, workspacePath,
286
- repoRemoteUrl, repoKey, createdAt, updatedAt, parserVersion,
287
- originalCreatedAt, originalUpdatedAt
288
- )
289
- VALUES (?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?)
290
- `).run(input.id, input.integrationId, input.sourceId, input.name, input.kind, input.workspacePath ?? null, input.repoRemoteUrl ?? null, input.repoKey ?? null, input.now, input.now, input.parserVersion ?? null, input.originalCreatedAt, input.originalUpdatedAt);
291
- }
292
- ensureContainsEdge(fromNodeId, toNodeId, sortIndex, now) {
293
- this.ensureEdge(fromNodeId, toNodeId, "contains", sortIndex, now);
294
- }
295
- ensureSourceEdge(fromNodeId, toNodeId, now) {
296
- this.ensureEdge(fromNodeId, toNodeId, "source_of", 0, now);
297
- }
298
- ensureEdge(fromNodeId, toNodeId, type, sortIndex, now) {
299
- const existing = this.db.prepare(`
300
- SELECT * FROM node_edge
301
- WHERE fromNodeId = ? AND toNodeId = ? AND type = ?
302
- LIMIT 1
303
- `).get(fromNodeId, toNodeId, type);
304
- if (!existing) {
305
- this.db.prepare(`
306
- INSERT INTO node_edge (id, fromNodeId, toNodeId, type, sortIndex, createdAt, updatedAt)
307
- VALUES (?, ?, ?, ?, ?, ?, ?)
308
- `).run(uuidToBytes(randomUUID()), fromNodeId, toNodeId, type, sortIndex, now, now);
309
- return;
310
- }
311
- if (existing.sortIndex !== sortIndex || existing.deletedAt !== null) {
312
- this.db.prepare(`
313
- UPDATE node_edge
314
- SET sortIndex = ?, updatedAt = ?, deletedAt = NULL
315
- WHERE id = ?
316
- `).run(sortIndex, now, existing.id);
317
- }
318
- }
319
- replaceContentSlices(nodeId, content) {
320
- this.db.prepare("DELETE FROM node_content_slice WHERE nodeId = ?").run(nodeId);
321
- const insert = this.db.prepare(`
322
- INSERT INTO node_content_slice (id, nodeId, "index", content, contentHash)
323
- VALUES (?, ?, ?, ?, ?)
324
- `);
325
- for (const [index, piece] of splitContent(content).entries()) {
326
- insert.run(uuidToBytes(randomUUID()), nodeId, index, piece, contentHash(piece));
327
- }
328
- }
329
- applyCloudPullNodeBundle(bundle) {
330
- const nodeId = uuidToBytes(bundle.node.id);
331
- const existing = this.nodeById(nodeId);
332
- if (existing?.cloudUpdatedAt === bundle.node.updatedAt)
333
- return;
334
- const now = this.nextTimestamp();
335
- this.db.prepare(`
336
- INSERT INTO node (
337
- id, integrationId, sourceId, name, kind, emoji, pinned, label, labelSliceCount,
338
- workspacePath, repoRemoteUrl, repoKey, createdAt, updatedAt, lastPushedAt,
339
- cloudUpdatedAt, parserVersion, deletedAt, originalCreatedAt, originalUpdatedAt
340
- )
341
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
342
- ON CONFLICT(id) DO UPDATE SET
343
- integrationId = excluded.integrationId,
344
- sourceId = excluded.sourceId,
345
- name = excluded.name,
346
- kind = excluded.kind,
347
- emoji = excluded.emoji,
348
- pinned = excluded.pinned,
349
- label = excluded.label,
350
- labelSliceCount = excluded.labelSliceCount,
351
- workspacePath = excluded.workspacePath,
352
- repoRemoteUrl = excluded.repoRemoteUrl,
353
- repoKey = excluded.repoKey,
354
- updatedAt = excluded.updatedAt,
355
- lastPushedAt = excluded.lastPushedAt,
356
- cloudUpdatedAt = excluded.cloudUpdatedAt,
357
- deletedAt = excluded.deletedAt,
358
- originalCreatedAt = excluded.originalCreatedAt,
359
- originalUpdatedAt = excluded.originalUpdatedAt
360
- `).run(nodeId, bundle.node.integrationId ? uuidToBytes(bundle.node.integrationId) : null, bundle.node.sourceId ?? null, bundle.node.name, bundle.node.kind, bundle.node.emoji ?? null, bundle.node.pinned === true ? 1 : 0, bundle.node.label ?? null, bundle.node.labelSliceCount ?? null, bundle.node.workspacePath ?? null, bundle.node.repoRemoteUrl ?? null, bundle.node.repoKey ?? null, existing?.createdAt ?? now, now, now, bundle.node.updatedAt, existing?.parserVersion ?? null, bundle.node.deletedAt ?? null, bundle.node.originalCreatedAt, bundle.node.originalUpdatedAt);
361
- this.replacePulledContentSlices(nodeId, bundle.slices);
362
- this.replacePulledAiChatMessage(nodeId, bundle.aiChatMessage ?? null);
363
- }
364
- applyCloudPullEdge(edge) {
365
- const id = uuidToBytes(edge.id);
366
- const fromNodeId = uuidToBytes(edge.fromNodeId);
367
- const toNodeId = uuidToBytes(edge.toNodeId);
368
- if (!this.nodeById(fromNodeId) || !this.nodeById(toNodeId))
369
- return false;
370
- const existing = this.db.prepare("SELECT createdAt, cloudUpdatedAt FROM node_edge WHERE id = ?")
371
- .get(id);
372
- if (existing?.cloudUpdatedAt === edge.updatedAt)
373
- return true;
374
- const now = this.nextTimestamp();
375
- this.db.prepare(`
376
- INSERT INTO node_edge (
377
- id, fromNodeId, toNodeId, type, sortIndex, createdAt, updatedAt,
378
- lastPushedAt, cloudUpdatedAt, deletedAt
379
- )
380
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
381
- ON CONFLICT(id) DO UPDATE SET
382
- fromNodeId = excluded.fromNodeId,
383
- toNodeId = excluded.toNodeId,
384
- type = excluded.type,
385
- sortIndex = excluded.sortIndex,
386
- updatedAt = excluded.updatedAt,
387
- lastPushedAt = excluded.lastPushedAt,
388
- cloudUpdatedAt = excluded.cloudUpdatedAt,
389
- deletedAt = excluded.deletedAt
390
- `).run(id, fromNodeId, toNodeId, edge.type, edge.sortIndex ?? null, existing?.createdAt ?? now, now, now, edge.updatedAt, edge.deletedAt ?? null);
391
- return true;
392
- }
393
- replacePulledContentSlices(nodeId, slices) {
394
- this.db.prepare("DELETE FROM node_content_slice WHERE nodeId = ?").run(nodeId);
395
- const insert = this.db.prepare(`
396
- INSERT INTO node_content_slice (id, nodeId, "index", content, contentHash)
397
- VALUES (?, ?, ?, ?, ?)
398
- `);
399
- for (const slice of slices) {
400
- insert.run(uuidToBytes(slice.id), nodeId, slice.index, slice.content, slice.contentHash);
401
- }
402
- }
403
- replacePulledAiChatMessage(nodeId, message) {
404
- this.db.prepare("DELETE FROM ai_chat_message WHERE nodeId = ?").run(nodeId);
405
- if (!message)
406
- return;
407
- this.db.prepare(`
408
- INSERT INTO ai_chat_message (nodeId, role, author, toolCallId, toolName, toolFriendlyName, toolStatus)
409
- VALUES (?, ?, ?, ?, ?, ?, ?)
410
- `).run(nodeId, message.role, message.author ?? null, message.toolCallId ?? null, message.toolName ?? null, message.toolFriendlyName ?? null, message.toolStatus ?? null);
411
- }
412
- softDeleteNode(id, now) {
413
- this.db.prepare("UPDATE node SET deletedAt = ?, updatedAt = ? WHERE id = ? AND deletedAt IS NULL")
414
- .run(now, now, id);
415
- this.db.prepare(`
416
- UPDATE node_edge
417
- SET deletedAt = ?, updatedAt = ?
418
- WHERE (fromNodeId = ? OR toNodeId = ?) AND deletedAt IS NULL
419
- `).run(now, now, id, id);
420
- }
421
- existingMessages(transcriptId) {
422
- const rows = this.db.prepare(`
423
- SELECT n.*, ai.role, ai.author, ai.toolCallId, ai.toolName, ai.toolStatus
424
- FROM node_edge e
425
- JOIN node n ON n.id = e.toNodeId
426
- LEFT JOIN ai_chat_message ai ON ai.nodeId = n.id
427
- WHERE e.fromNodeId = ? AND e.type = 'contains'
428
- AND e.deletedAt IS NULL AND n.deletedAt IS NULL
429
- ORDER BY e.sortIndex ASC, n.createdAt ASC
430
- `).all(transcriptId);
431
- return rows.map((row) => ({
432
- node: row,
433
- role: row.role,
434
- author: row.author,
435
- toolCallId: row.toolCallId,
436
- toolName: row.toolName,
437
- toolStatus: row.toolStatus,
438
- contentHashes: this.contentHashes(row.id),
439
- }));
440
- }
441
- contentHashes(nodeId) {
442
- const rows = this.db.prepare(`
443
- SELECT contentHash
444
- FROM node_content_slice
445
- WHERE nodeId = ?
446
- ORDER BY "index" ASC
447
- `).all(nodeId);
448
- return rows.map((row) => row.contentHash);
449
- }
450
- dirtyNodes(limit) {
451
- return this.db.prepare(`
452
- SELECT *
453
- FROM node
454
- WHERE lastPushedAt IS NULL OR updatedAt > lastPushedAt
455
- ORDER BY
456
- CASE kind
457
- WHEN 'integration_root' THEN 0
458
- WHEN 'codex_chat' THEN 1
459
- WHEN 'claude_code_chat' THEN 1
460
- ELSE 2
461
- END ASC,
462
- updatedAt ASC,
463
- hex(id) ASC
464
- ${limit === undefined ? "" : "LIMIT ?"}
465
- `).all(...(limit === undefined ? [] : [limit]));
466
- }
467
- dirtyEdges(limit) {
468
- return this.db.prepare(`
469
- SELECT *
470
- FROM node_edge
471
- WHERE lastPushedAt IS NULL OR updatedAt > lastPushedAt
472
- ORDER BY updatedAt ASC, hex(id) ASC
473
- ${limit === undefined ? "" : "LIMIT ?"}
474
- `).all(...(limit === undefined ? [] : [limit]));
475
- }
476
- mapNodePushItem(row) {
477
- return mapNodePushItem(this.db, row);
478
- }
479
- mapEdgePushItem(row) {
480
- return mapEdgePushItem(row);
481
- }
482
- nodeById(id) {
483
- return this.db.prepare("SELECT * FROM node WHERE id = ?").get(id);
484
- }
485
- nextTimestamp() {
486
- const ms = Math.max(Date.now(), this.lastTimestampMs + 1);
487
- this.lastTimestampMs = ms;
488
- return new Date(ms).toISOString();
489
- }
490
- }
491
- export function openDaemonGraphStore(databaseFile) {
492
- mkdirSync(dirname(databaseFile), { recursive: true });
493
- return new DaemonGraphStore(new Database(databaseFile));
494
- }
@@ -1,37 +0,0 @@
1
- import { redactSecrets } from "../redact/secrets.js";
2
- import { contentHashesFor } from "../sync/slices.js";
3
- export function messageMatches(existing, draft, kind) {
4
- return existing.node.kind === kind &&
5
- existing.role === draft.role &&
6
- existing.author === null &&
7
- existing.toolCallId === null &&
8
- existing.toolName === null &&
9
- existing.toolStatus === null &&
10
- arrayEquals(existing.contentHashes, contentHashesFor(redactSecrets(draft.content)));
11
- }
12
- export function messageMetadataChanged(node, draft, integrationId, kind, timestamp) {
13
- return node.integrationId?.equals(integrationId) !== true ||
14
- node.name !== messageNodeName(draft) ||
15
- node.kind !== kind ||
16
- node.originalCreatedAt !== timestamp ||
17
- node.originalUpdatedAt !== timestamp ||
18
- node.deletedAt !== null;
19
- }
20
- export function messageTimestamp(message, session) {
21
- return message.timestamp ?? session.updatedAt;
22
- }
23
- export function messageNodeName(message) {
24
- switch (message.role) {
25
- case "user":
26
- return "User Message";
27
- case "assistant":
28
- return "Assistant Message";
29
- case "tool":
30
- return "Tool Message";
31
- case "system":
32
- return "System Message";
33
- }
34
- }
35
- function arrayEquals(left, right) {
36
- return left.length === right.length && left.every((value, index) => value === right[index]);
37
- }
@@ -1,69 +0,0 @@
1
- import { uuidFromBytes } from "./uuid.js";
2
- export function mapNodePushItem(db, row) {
3
- const id = uuidFromBytes(row.id);
4
- return {
5
- node: {
6
- id,
7
- integrationId: row.integrationId ? uuidFromBytes(row.integrationId) : null,
8
- sourceId: row.sourceId,
9
- name: row.name,
10
- kind: row.kind,
11
- emoji: row.emoji,
12
- pinned: row.pinned === 1,
13
- label: row.label,
14
- labelSliceCount: row.labelSliceCount,
15
- workspacePath: row.workspacePath,
16
- repoRemoteUrl: row.repoRemoteUrl,
17
- repoKey: row.repoKey,
18
- deletedAt: row.deletedAt,
19
- originalCreatedAt: row.originalCreatedAt,
20
- originalUpdatedAt: row.originalUpdatedAt,
21
- },
22
- baseCloudUpdatedAt: row.cloudUpdatedAt,
23
- slices: nodeSlices(db, row.id),
24
- aiChatMessage: aiChatMessage(db, id, row.id),
25
- nessieChatMessage: null,
26
- };
27
- }
28
- export function mapEdgePushItem(row) {
29
- return {
30
- edge: {
31
- id: uuidFromBytes(row.id),
32
- fromNodeId: uuidFromBytes(row.fromNodeId),
33
- toNodeId: uuidFromBytes(row.toNodeId),
34
- type: row.type,
35
- sortIndex: row.sortIndex,
36
- deletedAt: row.deletedAt,
37
- },
38
- baseCloudUpdatedAt: row.cloudUpdatedAt,
39
- };
40
- }
41
- function nodeSlices(db, nodeId) {
42
- const rows = db.prepare(`
43
- SELECT id, nodeId, "index", content, contentHash
44
- FROM node_content_slice
45
- WHERE nodeId = ?
46
- ORDER BY "index" ASC
47
- `).all(nodeId);
48
- return rows.map((row) => ({
49
- id: uuidFromBytes(row.id),
50
- nodeId: uuidFromBytes(row.nodeId),
51
- index: row.index,
52
- content: row.content,
53
- contentHash: row.contentHash,
54
- }));
55
- }
56
- function aiChatMessage(db, id, nodeId) {
57
- const row = db.prepare("SELECT * FROM ai_chat_message WHERE nodeId = ?").get(nodeId);
58
- if (!row)
59
- return null;
60
- return {
61
- nodeId: id,
62
- role: row.role,
63
- author: row.author,
64
- toolCallId: row.toolCallId,
65
- toolName: row.toolName,
66
- toolFriendlyName: row.toolFriendlyName,
67
- toolStatus: row.toolStatus,
68
- };
69
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,70 +0,0 @@
1
- export function createDaemonGraphSchema(db) {
2
- db.pragma("foreign_keys = ON");
3
- db.exec(`
4
- CREATE TABLE IF NOT EXISTS node (
5
- id BLOB PRIMARY KEY,
6
- integrationId BLOB,
7
- sourceId TEXT,
8
- name TEXT NOT NULL,
9
- kind TEXT NOT NULL,
10
- emoji TEXT,
11
- pinned BOOLEAN NOT NULL DEFAULT 0,
12
- label TEXT,
13
- labelSliceCount INTEGER,
14
- workspacePath TEXT,
15
- repoRemoteUrl TEXT,
16
- repoKey TEXT,
17
- createdAt DATETIME NOT NULL,
18
- updatedAt DATETIME NOT NULL,
19
- lastPushedAt DATETIME,
20
- cloudUpdatedAt DATETIME,
21
- parserVersion INTEGER,
22
- deletedAt DATETIME,
23
- originalCreatedAt DATETIME NOT NULL,
24
- originalUpdatedAt DATETIME NOT NULL
25
- );
26
- CREATE INDEX IF NOT EXISTS node_kind_source_id_idx ON node(kind, sourceId);
27
-
28
- CREATE TABLE IF NOT EXISTS node_edge (
29
- id BLOB PRIMARY KEY,
30
- fromNodeId BLOB NOT NULL REFERENCES node(id) ON DELETE CASCADE,
31
- toNodeId BLOB NOT NULL REFERENCES node(id) ON DELETE CASCADE,
32
- type TEXT NOT NULL,
33
- sortIndex INTEGER,
34
- createdAt DATETIME NOT NULL,
35
- updatedAt DATETIME NOT NULL,
36
- lastPushedAt DATETIME,
37
- cloudUpdatedAt DATETIME,
38
- deletedAt DATETIME
39
- );
40
- CREATE INDEX IF NOT EXISTS node_edge_from_to_type_idx ON node_edge(fromNodeId, toNodeId, type);
41
- CREATE INDEX IF NOT EXISTS node_edge_from_type_sort_null_to_idx
42
- ON node_edge("fromNodeId", "type", "sortIndex" IS NULL, "sortIndex", "toNodeId");
43
- CREATE INDEX IF NOT EXISTS node_edge_to_type_idx ON node_edge(toNodeId, type);
44
-
45
- CREATE TABLE IF NOT EXISTS node_content_slice (
46
- id BLOB PRIMARY KEY,
47
- nodeId BLOB NOT NULL REFERENCES node(id) ON DELETE CASCADE,
48
- "index" INTEGER NOT NULL,
49
- content TEXT NOT NULL,
50
- contentHash TEXT NOT NULL
51
- );
52
- CREATE UNIQUE INDEX IF NOT EXISTS node_content_slice_node_index_idx ON node_content_slice(nodeId, "index");
53
-
54
- CREATE TABLE IF NOT EXISTS ai_chat_message (
55
- nodeId BLOB PRIMARY KEY REFERENCES node(id) ON DELETE CASCADE,
56
- role TEXT NOT NULL,
57
- author TEXT,
58
- toolCallId TEXT,
59
- toolName TEXT,
60
- toolFriendlyName TEXT,
61
- toolStatus TEXT
62
- );
63
-
64
- CREATE TABLE IF NOT EXISTS app_setting (
65
- key TEXT PRIMARY KEY,
66
- value TEXT NOT NULL
67
- );
68
-
69
- `);
70
- }
@@ -1,12 +0,0 @@
1
- export function uuidToBytes(uuid) {
2
- const hex = uuid.replaceAll("-", "");
3
- if (!/^[0-9a-fA-F]{32}$/.test(hex))
4
- throw new Error(`Invalid UUID: ${uuid}`);
5
- return Buffer.from(hex, "hex");
6
- }
7
- export function uuidFromBytes(bytes) {
8
- const hex = bytes.toString("hex");
9
- if (hex.length !== 32)
10
- throw new Error(`Invalid UUID byte length: ${bytes.length}`);
11
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
12
- }