@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.
- package/README.md +8 -17
- package/dist/cli.js +42 -21
- package/dist/commands/auth.js +116 -0
- package/dist/commands/integrations.js +84 -0
- package/dist/commands/login.js +145 -0
- package/dist/commands/run.js +21 -6
- package/dist/commands/service.js +18 -36
- package/dist/commands/sharing.js +399 -0
- package/dist/commands/status.js +67 -0
- package/dist/commands/teams.js +64 -0
- package/dist/nera/client.js +156 -0
- package/dist/nera/process.js +78 -0
- package/dist/nera/runtimeFile.js +25 -0
- package/dist/platform/binary.js +21 -0
- package/dist/platform/openBrowser.js +14 -0
- package/dist/platform/paths.js +13 -0
- package/dist/platform/table.js +85 -0
- package/package.json +9 -5
- package/vendor/nera/darwin-arm64/nera +0 -0
- package/vendor/nera/darwin-x64/nera +0 -0
- package/vendor/nera/linux-arm64/nera +0 -0
- package/vendor/nera/linux-x64/nera +0 -0
- package/vendor/nera/win32-arm64/nera.exe +0 -0
- package/vendor/nera/win32-x64/nera.exe +0 -0
- package/vendor/proto/nessie.edge.v1.proto +475 -0
- package/dist/api/http.js +0 -44
- package/dist/auth/deviceFlow.js +0 -36
- package/dist/auth/tokens.js +0 -30
- package/dist/commands/setup.js +0 -75
- package/dist/config/endpoints.js +0 -2
- package/dist/config/paths.js +0 -19
- package/dist/config/store.js +0 -27
- package/dist/git/repoResolver.js +0 -215
- package/dist/localStore/daemonGraphStore.js +0 -494
- package/dist/localStore/messageComparison.js +0 -37
- package/dist/localStore/pushMapping.js +0 -69
- package/dist/localStore/rowTypes.js +0 -1
- package/dist/localStore/schema.js +0 -70
- package/dist/localStore/uuid.js +0 -12
- package/dist/parser/claudeCode.js +0 -202
- package/dist/parser/codex.js +0 -91
- package/dist/parser/jsonl.js +0 -24
- package/dist/parser/types.js +0 -15
- package/dist/redact/secrets.js +0 -60
- package/dist/service/process.js +0 -134
- package/dist/service/systemd.js +0 -38
- package/dist/sources/detect.js +0 -43
- package/dist/sync/batch.js +0 -50
- package/dist/sync/client.js +0 -90
- package/dist/sync/exclusions.js +0 -10
- package/dist/sync/hash.js +0 -4
- package/dist/sync/loop.js +0 -106
- package/dist/sync/scan.js +0 -245
- package/dist/sync/slices.js +0 -63
- 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
|
-
}
|
package/dist/localStore/uuid.js
DELETED
|
@@ -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
|
-
}
|