@deeplake/hivemind 0.7.45 → 0.7.47

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 (39) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +64 -0
  4. package/bundle/cli.js +22702 -7775
  5. package/codex/bundle/capture.js +228 -0
  6. package/codex/bundle/commands/auth-login.js +228 -0
  7. package/codex/bundle/graph-pull-worker.js +1370 -0
  8. package/codex/bundle/pre-tool-use.js +228 -0
  9. package/codex/bundle/session-start-setup.js +228 -0
  10. package/codex/bundle/session-start.js +255 -4
  11. package/codex/bundle/shell/deeplake-shell.js +1028 -28
  12. package/codex/bundle/skillify-worker.js +94 -3
  13. package/codex/bundle/stop.js +282 -50
  14. package/codex/skills/hivemind-goals/SKILL.md +157 -0
  15. package/cursor/bundle/capture.js +282 -50
  16. package/cursor/bundle/commands/auth-login.js +228 -0
  17. package/cursor/bundle/graph-pull-worker.js +1370 -0
  18. package/cursor/bundle/pre-tool-use.js +228 -0
  19. package/cursor/bundle/session-end.js +65 -44
  20. package/cursor/bundle/session-start.js +662 -6
  21. package/cursor/bundle/shell/deeplake-shell.js +1028 -28
  22. package/cursor/bundle/skillify-worker.js +94 -3
  23. package/hermes/bundle/capture.js +282 -50
  24. package/hermes/bundle/commands/auth-login.js +228 -0
  25. package/hermes/bundle/graph-pull-worker.js +1370 -0
  26. package/hermes/bundle/pre-tool-use.js +228 -0
  27. package/hermes/bundle/session-end.js +65 -44
  28. package/hermes/bundle/session-start.js +662 -6
  29. package/hermes/bundle/shell/deeplake-shell.js +1028 -28
  30. package/hermes/bundle/skillify-worker.js +94 -3
  31. package/mcp/bundle/server.js +228 -0
  32. package/openclaw/dist/chunks/config-FH6JYSJW.js +53 -0
  33. package/openclaw/dist/index.js +307 -2
  34. package/openclaw/dist/skillify-worker.js +94 -3
  35. package/openclaw/openclaw.plugin.json +4 -2
  36. package/openclaw/package.json +1 -1
  37. package/openclaw/skills/hivemind-goals/SKILL.md +30 -0
  38. package/package.json +4 -1
  39. package/openclaw/dist/chunks/config-XEK4MJJS.js +0 -36
@@ -0,0 +1,1370 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // dist/src/index-marker-store.js
13
+ var index_marker_store_exports = {};
14
+ __export(index_marker_store_exports, {
15
+ buildIndexMarkerPath: () => buildIndexMarkerPath,
16
+ getIndexMarkerDir: () => getIndexMarkerDir,
17
+ hasFreshIndexMarker: () => hasFreshIndexMarker,
18
+ writeIndexMarker: () => writeIndexMarker
19
+ });
20
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
21
+ import { join as join5 } from "node:path";
22
+ import { tmpdir } from "node:os";
23
+ function getIndexMarkerDir() {
24
+ return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join5(tmpdir(), "hivemind-deeplake-indexes");
25
+ }
26
+ function buildIndexMarkerPath(workspaceId, orgId, table, suffix) {
27
+ const markerKey = [workspaceId, orgId, table, suffix].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
28
+ return join5(getIndexMarkerDir(), `${markerKey}.json`);
29
+ }
30
+ function hasFreshIndexMarker(markerPath) {
31
+ if (!existsSync2(markerPath))
32
+ return false;
33
+ try {
34
+ const raw = JSON.parse(readFileSync4(markerPath, "utf-8"));
35
+ const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
36
+ if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
37
+ return false;
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+ function writeIndexMarker(markerPath) {
44
+ mkdirSync3(getIndexMarkerDir(), { recursive: true });
45
+ writeFileSync3(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
46
+ }
47
+ var INDEX_MARKER_TTL_MS;
48
+ var init_index_marker_store = __esm({
49
+ "dist/src/index-marker-store.js"() {
50
+ "use strict";
51
+ INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
52
+ }
53
+ });
54
+
55
+ // dist/src/hooks/graph-pull-worker.js
56
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "node:fs";
57
+ import { join as join10 } from "node:path";
58
+
59
+ // dist/src/graph/deeplake-pull.js
60
+ import { execFileSync } from "node:child_process";
61
+ import { createHash as createHash3 } from "node:crypto";
62
+ import { existsSync as existsSync5, mkdirSync as mkdirSync7, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "node:fs";
63
+ import { dirname as dirname4, join as join9 } from "node:path";
64
+
65
+ // dist/src/config.js
66
+ import { readFileSync, existsSync } from "node:fs";
67
+ import { join } from "node:path";
68
+ import { homedir, userInfo } from "node:os";
69
+ function loadConfig() {
70
+ const home = homedir();
71
+ const credPath = join(home, ".deeplake", "credentials.json");
72
+ let creds = null;
73
+ if (existsSync(credPath)) {
74
+ try {
75
+ creds = JSON.parse(readFileSync(credPath, "utf-8"));
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ const token = process.env.HIVEMIND_TOKEN ?? creds?.token;
81
+ const orgId = process.env.HIVEMIND_ORG_ID ?? creds?.orgId;
82
+ if (!token || !orgId)
83
+ return null;
84
+ return {
85
+ token,
86
+ orgId,
87
+ orgName: creds?.orgName ?? orgId,
88
+ userName: creds?.userName || userInfo().username || "unknown",
89
+ workspaceId: process.env.HIVEMIND_WORKSPACE_ID ?? creds?.workspaceId ?? "default",
90
+ apiUrl: process.env.HIVEMIND_API_URL ?? creds?.apiUrl ?? "https://api.deeplake.ai",
91
+ tableName: process.env.HIVEMIND_TABLE ?? "memory",
92
+ sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
93
+ skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
94
+ // Defaults match the table name written into the SQL — keep aligned
95
+ // with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
96
+ // deeplake-schema.ts and with the e2e test-org override convention
97
+ // (memory_test / sessions_test → goals_test, etc.) documented in
98
+ // CLAUDE.md.
99
+ rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
100
+ tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
101
+ taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
102
+ // Goals + KPIs (refined design — VFS path classifier maps
103
+ // memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
104
+ // memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
105
+ // See src/shell/deeplake-fs.ts for the translation logic and
106
+ // GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
107
+ // table shape.
108
+ goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
109
+ kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
110
+ codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
111
+ memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
112
+ };
113
+ }
114
+
115
+ // dist/src/deeplake-api.js
116
+ import { randomUUID } from "node:crypto";
117
+
118
+ // dist/src/utils/debug.js
119
+ import { appendFileSync } from "node:fs";
120
+ import { join as join2 } from "node:path";
121
+ import { homedir as homedir2 } from "node:os";
122
+ var LOG = join2(homedir2(), ".deeplake", "hook-debug.log");
123
+ function isDebug() {
124
+ return process.env.HIVEMIND_DEBUG === "1";
125
+ }
126
+ function log(tag, msg) {
127
+ if (!isDebug())
128
+ return;
129
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
130
+ `);
131
+ }
132
+
133
+ // dist/src/utils/sql.js
134
+ function sqlStr(value) {
135
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/\0/g, "").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
136
+ }
137
+ function sqlIdent(name) {
138
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
139
+ throw new Error(`Invalid SQL identifier: ${JSON.stringify(name)}`);
140
+ }
141
+ return name;
142
+ }
143
+
144
+ // dist/src/embeddings/columns.js
145
+ var SUMMARY_EMBEDDING_COL = "summary_embedding";
146
+
147
+ // dist/src/utils/client-header.js
148
+ var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
149
+ function deeplakeClientValue() {
150
+ return "hivemind";
151
+ }
152
+ function deeplakeClientHeader() {
153
+ return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
154
+ }
155
+
156
+ // dist/src/deeplake-schema.js
157
+ var MEMORY_COLUMNS = Object.freeze([
158
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
159
+ { name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
160
+ { name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
161
+ { name: "summary", sql: "TEXT NOT NULL DEFAULT ''" },
162
+ { name: "summary_embedding", sql: "FLOAT4[]" },
163
+ { name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
164
+ { name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'text/plain'" },
165
+ { name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
166
+ { name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
167
+ { name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
168
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
169
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
170
+ { name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
171
+ { name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
172
+ ]);
173
+ var SESSIONS_COLUMNS = Object.freeze([
174
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
175
+ { name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
176
+ { name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
177
+ { name: "message", sql: "JSONB" },
178
+ { name: "message_embedding", sql: "FLOAT4[]" },
179
+ { name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
180
+ { name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'application/json'" },
181
+ { name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
182
+ { name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
183
+ { name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
184
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
185
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
186
+ { name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
187
+ { name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
188
+ ]);
189
+ var SKILLS_COLUMNS = Object.freeze([
190
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
191
+ { name: "name", sql: "TEXT NOT NULL DEFAULT ''" },
192
+ { name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
193
+ { name: "project_key", sql: "TEXT NOT NULL DEFAULT ''" },
194
+ { name: "local_path", sql: "TEXT NOT NULL DEFAULT ''" },
195
+ { name: "install", sql: "TEXT NOT NULL DEFAULT 'project'" },
196
+ { name: "source_sessions", sql: "TEXT NOT NULL DEFAULT '[]'" },
197
+ { name: "source_agent", sql: "TEXT NOT NULL DEFAULT ''" },
198
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
199
+ { name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
200
+ { name: "contributors", sql: "TEXT NOT NULL DEFAULT '[]'" },
201
+ { name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
202
+ { name: "trigger_text", sql: "TEXT NOT NULL DEFAULT ''" },
203
+ { name: "body", sql: "TEXT NOT NULL DEFAULT ''" },
204
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
205
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
206
+ { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
207
+ ]);
208
+ var RULES_COLUMNS = Object.freeze([
209
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
210
+ { name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
211
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
212
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
213
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
214
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
215
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
216
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
217
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
218
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
219
+ ]);
220
+ var TASKS_COLUMNS = Object.freeze([
221
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
222
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
223
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
224
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
225
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
226
+ { name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
227
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
228
+ { name: "kpis", sql: "JSONB" },
229
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
230
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
231
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
232
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
233
+ ]);
234
+ var TASK_EVENTS_COLUMNS = Object.freeze([
235
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
236
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
237
+ { name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
238
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
239
+ { name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
240
+ { name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
241
+ { name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
242
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
243
+ { name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
244
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
245
+ ]);
246
+ var GOALS_COLUMNS = Object.freeze([
247
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
248
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
249
+ { name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
250
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
251
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
252
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
253
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
254
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
255
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
256
+ ]);
257
+ var KPIS_COLUMNS = Object.freeze([
258
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
259
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
260
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
261
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
262
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
263
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
264
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
265
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
266
+ ]);
267
+ function validateSchema(label, cols) {
268
+ const seen = /* @__PURE__ */ new Set();
269
+ for (const col of cols) {
270
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col.name)) {
271
+ throw new Error(`${label}: column name "${col.name}" is not a valid SQL identifier`);
272
+ }
273
+ if (seen.has(col.name)) {
274
+ throw new Error(`${label}: duplicate column "${col.name}"`);
275
+ }
276
+ seen.add(col.name);
277
+ const notNull = /\bNOT\s+NULL\b/i.test(col.sql);
278
+ const hasDefault = /\bDEFAULT\b/i.test(col.sql);
279
+ if (notNull && !hasDefault) {
280
+ throw new Error(`${label}: column "${col.name}" is NOT NULL but has no DEFAULT \u2014 ALTER TABLE ADD COLUMN on a populated table would fail.`);
281
+ }
282
+ }
283
+ }
284
+ var CODEBASE_COLUMNS = Object.freeze([
285
+ // Identity key (matches the PK below)
286
+ { name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
287
+ { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
288
+ { name: "repo_slug", sql: "TEXT NOT NULL DEFAULT ''" },
289
+ { name: "user_id", sql: "TEXT NOT NULL DEFAULT ''" },
290
+ { name: "worktree_id", sql: "TEXT NOT NULL DEFAULT ''" },
291
+ { name: "commit_sha", sql: "TEXT NOT NULL DEFAULT ''" },
292
+ // Observation metadata
293
+ { name: "parent_sha", sql: "TEXT NOT NULL DEFAULT ''" },
294
+ { name: "branch", sql: "TEXT NOT NULL DEFAULT ''" },
295
+ { name: "ts", sql: "TIMESTAMP" },
296
+ { name: "pushed_by", sql: "TEXT NOT NULL DEFAULT ''" },
297
+ // Snapshot payload
298
+ { name: "snapshot_sha256", sql: "TEXT NOT NULL DEFAULT ''" },
299
+ { name: "snapshot_jsonb", sql: "TEXT NOT NULL DEFAULT ''" },
300
+ { name: "node_count", sql: "BIGINT NOT NULL DEFAULT 0" },
301
+ { name: "edge_count", sql: "BIGINT NOT NULL DEFAULT 0" },
302
+ // Generator metadata (for drift diagnostics — what hivemind version produced this?)
303
+ { name: "generator", sql: "TEXT NOT NULL DEFAULT 'hivemind-graph'" },
304
+ { name: "generator_version", sql: "TEXT NOT NULL DEFAULT ''" },
305
+ { name: "schema_version", sql: "BIGINT NOT NULL DEFAULT 1" }
306
+ ]);
307
+ validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
308
+ validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
309
+ validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
310
+ validateSchema("RULES_COLUMNS", RULES_COLUMNS);
311
+ validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
312
+ validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
313
+ validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
314
+ validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
315
+ validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
316
+ function buildCreateTableSql(tableName, cols) {
317
+ const safe = sqlIdent(tableName);
318
+ const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
319
+ return `CREATE TABLE IF NOT EXISTS "${safe}" (${colSql}) USING deeplake`;
320
+ }
321
+ function buildIntrospectionSql(tableName, workspaceId) {
322
+ return `SELECT column_name FROM information_schema.columns WHERE table_name = '${sqlStr(tableName)}' AND table_schema = '${sqlStr(workspaceId)}'`;
323
+ }
324
+ async function healMissingColumns(args) {
325
+ const safeTable = sqlIdent(args.tableName);
326
+ const introspectSql = buildIntrospectionSql(args.tableName, args.workspaceId);
327
+ const rows = await args.query(introspectSql);
328
+ const existing = /* @__PURE__ */ new Set();
329
+ for (const row of rows) {
330
+ const v = row?.column_name;
331
+ if (typeof v === "string")
332
+ existing.add(v.toLowerCase());
333
+ }
334
+ const missingCols = args.columns.filter((c) => !existing.has(c.name.toLowerCase()));
335
+ const missing = missingCols.map((c) => c.name);
336
+ if (missingCols.length === 0)
337
+ return { missing, altered: [] };
338
+ const altered = [];
339
+ for (const col of missingCols) {
340
+ try {
341
+ await args.query(`ALTER TABLE "${safeTable}" ADD COLUMN ${col.name} ${col.sql}`);
342
+ altered.push(col.name);
343
+ args.log?.(`schema-heal: added "${args.tableName}"."${col.name}"`);
344
+ } catch (e) {
345
+ const msg = e instanceof Error ? e.message : String(e);
346
+ if (!/already exists/i.test(msg))
347
+ throw e;
348
+ const recheck = await args.query(introspectSql);
349
+ const present = recheck.some((r) => {
350
+ const v = r?.column_name;
351
+ return typeof v === "string" && v.toLowerCase() === col.name.toLowerCase();
352
+ });
353
+ if (!present)
354
+ throw e;
355
+ args.log?.(`schema-heal: "${args.tableName}"."${col.name}" appeared via race, treating as success`);
356
+ }
357
+ }
358
+ return { missing, altered };
359
+ }
360
+
361
+ // dist/src/notifications/queue.js
362
+ import { readFileSync as readFileSync2, writeFileSync, renameSync, mkdirSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";
363
+ import { join as join3, resolve } from "node:path";
364
+ import { homedir as homedir3 } from "node:os";
365
+ import { setTimeout as sleep } from "node:timers/promises";
366
+ var log2 = (msg) => log("notifications-queue", msg);
367
+ var LOCK_RETRY_MAX = 50;
368
+ var LOCK_RETRY_BASE_MS = 5;
369
+ var LOCK_STALE_MS = 5e3;
370
+ function queuePath() {
371
+ return join3(homedir3(), ".deeplake", "notifications-queue.json");
372
+ }
373
+ function lockPath() {
374
+ return `${queuePath()}.lock`;
375
+ }
376
+ function readQueue() {
377
+ try {
378
+ const raw = readFileSync2(queuePath(), "utf-8");
379
+ const parsed = JSON.parse(raw);
380
+ if (!parsed || !Array.isArray(parsed.queue)) {
381
+ log2(`queue malformed \u2192 treating as empty`);
382
+ return { queue: [] };
383
+ }
384
+ return { queue: parsed.queue };
385
+ } catch {
386
+ return { queue: [] };
387
+ }
388
+ }
389
+ function _isQueuePathInsideHome(path, home) {
390
+ const r = resolve(path);
391
+ const h = resolve(home);
392
+ return r.startsWith(h + "/") || r === h;
393
+ }
394
+ function writeQueue(q) {
395
+ const path = queuePath();
396
+ const home = resolve(homedir3());
397
+ if (!_isQueuePathInsideHome(path, home)) {
398
+ throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
399
+ }
400
+ mkdirSync(join3(home, ".deeplake"), { recursive: true, mode: 448 });
401
+ const tmp = `${path}.${process.pid}.tmp`;
402
+ writeFileSync(tmp, JSON.stringify(q, null, 2), { mode: 384 });
403
+ renameSync(tmp, path);
404
+ }
405
+ async function withQueueLock(fn) {
406
+ const path = lockPath();
407
+ mkdirSync(join3(homedir3(), ".deeplake"), { recursive: true, mode: 448 });
408
+ let fd = null;
409
+ for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
410
+ try {
411
+ fd = openSync(path, "wx", 384);
412
+ break;
413
+ } catch (e) {
414
+ const code = e.code;
415
+ if (code !== "EEXIST")
416
+ throw e;
417
+ try {
418
+ const age = Date.now() - statSync(path).mtimeMs;
419
+ if (age > LOCK_STALE_MS) {
420
+ unlinkSync(path);
421
+ continue;
422
+ }
423
+ } catch {
424
+ }
425
+ const delay = LOCK_RETRY_BASE_MS * (attempt + 1);
426
+ await sleep(delay);
427
+ }
428
+ }
429
+ if (fd === null) {
430
+ log2(`lock acquisition gave up after ${LOCK_RETRY_MAX} attempts \u2014 proceeding unlocked (last-writer-wins)`);
431
+ return fn();
432
+ }
433
+ try {
434
+ return fn();
435
+ } finally {
436
+ try {
437
+ closeSync(fd);
438
+ } catch {
439
+ }
440
+ try {
441
+ unlinkSync(path);
442
+ } catch {
443
+ }
444
+ }
445
+ }
446
+ function sameDedupKey(a, b) {
447
+ if (a.id !== b.id)
448
+ return false;
449
+ return JSON.stringify(a.dedupKey) === JSON.stringify(b.dedupKey);
450
+ }
451
+ async function enqueueNotification(n) {
452
+ await withQueueLock(() => {
453
+ const q = readQueue();
454
+ if (q.queue.some((existing) => sameDedupKey(existing, n))) {
455
+ return;
456
+ }
457
+ q.queue.push(n);
458
+ writeQueue(q);
459
+ });
460
+ }
461
+
462
+ // dist/src/commands/auth-creds.js
463
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "node:fs";
464
+ import { join as join4 } from "node:path";
465
+ import { homedir as homedir4 } from "node:os";
466
+ function configDir() {
467
+ return join4(homedir4(), ".deeplake");
468
+ }
469
+ function credsPath() {
470
+ return join4(configDir(), "credentials.json");
471
+ }
472
+ function loadCredentials() {
473
+ try {
474
+ return JSON.parse(readFileSync3(credsPath(), "utf-8"));
475
+ } catch {
476
+ return null;
477
+ }
478
+ }
479
+
480
+ // dist/src/deeplake-api.js
481
+ var indexMarkerStorePromise = null;
482
+ function getIndexMarkerStore() {
483
+ if (!indexMarkerStorePromise)
484
+ indexMarkerStorePromise = Promise.resolve().then(() => (init_index_marker_store(), index_marker_store_exports));
485
+ return indexMarkerStorePromise;
486
+ }
487
+ var log3 = (msg) => log("sdk", msg);
488
+ function summarizeSql(sql, maxLen = 220) {
489
+ const compact = sql.replace(/\s+/g, " ").trim();
490
+ return compact.length > maxLen ? `${compact.slice(0, maxLen)}...` : compact;
491
+ }
492
+ function traceSql(msg) {
493
+ const traceEnabled = process.env.HIVEMIND_TRACE_SQL === "1" || process.env.HIVEMIND_DEBUG === "1";
494
+ if (!traceEnabled)
495
+ return;
496
+ process.stderr.write(`[deeplake-sql] ${msg}
497
+ `);
498
+ if (process.env.HIVEMIND_DEBUG === "1")
499
+ log3(msg);
500
+ }
501
+ var _signalledBalanceExhausted = false;
502
+ function maybeSignalBalanceExhausted(status, bodyText) {
503
+ if (status !== 402)
504
+ return;
505
+ if (!bodyText.includes("balance_cents"))
506
+ return;
507
+ if (_signalledBalanceExhausted)
508
+ return;
509
+ _signalledBalanceExhausted = true;
510
+ log3(`balance exhausted \u2014 enqueuing session-start banner (body=${bodyText.slice(0, 120)})`);
511
+ enqueueNotification({
512
+ id: "balance-exhausted",
513
+ severity: "warn",
514
+ transient: true,
515
+ title: "Hivemind credits exhausted \u2014 top up to keep capturing",
516
+ body: `Sessions are not being saved and memory recall is returning empty. Top up at ${billingUrl()} to restore capture and recall.`,
517
+ dedupKey: { reason: "balance-zero" }
518
+ }).catch((e) => {
519
+ log3(`enqueue balance-exhausted failed: ${e instanceof Error ? e.message : String(e)}`);
520
+ });
521
+ }
522
+ function billingUrl() {
523
+ try {
524
+ const c = loadCredentials();
525
+ if (c?.orgName && c?.workspaceId) {
526
+ return `https://deeplake.ai/${encodeURIComponent(c.orgName)}/workspace/${encodeURIComponent(c.workspaceId)}/billing`;
527
+ }
528
+ } catch {
529
+ }
530
+ return "https://deeplake.ai";
531
+ }
532
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
533
+ var MAX_RETRIES = 3;
534
+ var BASE_DELAY_MS = 500;
535
+ var MAX_CONCURRENCY = 5;
536
+ function getQueryTimeoutMs() {
537
+ return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
538
+ }
539
+ function sleep2(ms) {
540
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
541
+ }
542
+ function isTimeoutError(error) {
543
+ const name = error instanceof Error ? error.name.toLowerCase() : "";
544
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
545
+ return name.includes("timeout") || name === "aborterror" || message.includes("timeout") || message.includes("timed out");
546
+ }
547
+ function isDuplicateIndexError(error) {
548
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
549
+ return message.includes("duplicate key value violates unique constraint") || message.includes("pg_class_relname_nsp_index") || message.includes("already exists");
550
+ }
551
+ function isSessionInsertQuery(sql) {
552
+ return /^\s*insert\s+into\s+"[^"]+"\s*\(\s*id\s*,\s*path\s*,\s*filename\s*,\s*message\s*,/i.test(sql);
553
+ }
554
+ function isTransientHtml403(text) {
555
+ const body = text.toLowerCase();
556
+ return body.includes("<html") || body.includes("403 forbidden") || body.includes("cloudflare") || body.includes("nginx");
557
+ }
558
+ var Semaphore = class {
559
+ max;
560
+ waiting = [];
561
+ active = 0;
562
+ constructor(max) {
563
+ this.max = max;
564
+ }
565
+ async acquire() {
566
+ if (this.active < this.max) {
567
+ this.active++;
568
+ return;
569
+ }
570
+ await new Promise((resolve3) => this.waiting.push(resolve3));
571
+ }
572
+ release() {
573
+ this.active--;
574
+ const next = this.waiting.shift();
575
+ if (next) {
576
+ this.active++;
577
+ next();
578
+ }
579
+ }
580
+ };
581
+ var DeeplakeApi = class {
582
+ token;
583
+ apiUrl;
584
+ orgId;
585
+ workspaceId;
586
+ tableName;
587
+ _pendingRows = [];
588
+ _sem = new Semaphore(MAX_CONCURRENCY);
589
+ _tablesCache = null;
590
+ constructor(token, apiUrl, orgId, workspaceId, tableName) {
591
+ this.token = token;
592
+ this.apiUrl = apiUrl;
593
+ this.orgId = orgId;
594
+ this.workspaceId = workspaceId;
595
+ this.tableName = tableName;
596
+ }
597
+ /** Execute SQL with retry on transient errors and bounded concurrency. */
598
+ async query(sql) {
599
+ const startedAt = Date.now();
600
+ const summary = summarizeSql(sql);
601
+ traceSql(`query start: ${summary}`);
602
+ await this._sem.acquire();
603
+ try {
604
+ const rows = await this._queryWithRetry(sql);
605
+ traceSql(`query ok (${Date.now() - startedAt}ms, rows=${rows.length}): ${summary}`);
606
+ return rows;
607
+ } catch (e) {
608
+ const message = e instanceof Error ? e.message : String(e);
609
+ traceSql(`query fail (${Date.now() - startedAt}ms): ${summary} :: ${message}`);
610
+ throw e;
611
+ } finally {
612
+ this._sem.release();
613
+ }
614
+ }
615
+ async _queryWithRetry(sql) {
616
+ let lastError;
617
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
618
+ let resp;
619
+ const timeoutMs = getQueryTimeoutMs();
620
+ try {
621
+ const signal = AbortSignal.timeout(timeoutMs);
622
+ resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables/query`, {
623
+ method: "POST",
624
+ headers: {
625
+ Authorization: `Bearer ${this.token}`,
626
+ "Content-Type": "application/json",
627
+ "X-Activeloop-Org-Id": this.orgId,
628
+ ...deeplakeClientHeader()
629
+ },
630
+ signal,
631
+ body: JSON.stringify({ query: sql })
632
+ });
633
+ } catch (e) {
634
+ if (isTimeoutError(e)) {
635
+ lastError = new Error(`Query timeout after ${timeoutMs}ms`);
636
+ throw lastError;
637
+ }
638
+ lastError = e instanceof Error ? e : new Error(String(e));
639
+ if (attempt < MAX_RETRIES) {
640
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
641
+ log3(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
642
+ await sleep2(delay);
643
+ continue;
644
+ }
645
+ throw lastError;
646
+ }
647
+ if (resp.ok) {
648
+ const raw = await resp.json();
649
+ if (!raw?.rows || !raw?.columns)
650
+ return [];
651
+ return raw.rows.map((row) => Object.fromEntries(raw.columns.map((col, i) => [col, row[i]])));
652
+ }
653
+ const text = await resp.text().catch(() => "");
654
+ const retryable403 = isSessionInsertQuery(sql) && (resp.status === 401 || resp.status === 403 && (text.length === 0 || isTransientHtml403(text)));
655
+ const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
656
+ if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
657
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
658
+ log3(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
659
+ await sleep2(delay);
660
+ continue;
661
+ }
662
+ maybeSignalBalanceExhausted(resp.status, text);
663
+ throw new Error(`Query failed: ${resp.status}: ${text.slice(0, 200)}`);
664
+ }
665
+ throw lastError ?? new Error("Query failed: max retries exceeded");
666
+ }
667
+ // ── Writes ──────────────────────────────────────────────────────────────────
668
+ /** Queue rows for writing. Call commit() to flush. */
669
+ appendRows(rows) {
670
+ this._pendingRows.push(...rows);
671
+ }
672
+ /** Flush pending rows via SQL. */
673
+ async commit() {
674
+ if (this._pendingRows.length === 0)
675
+ return;
676
+ const rows = this._pendingRows;
677
+ this._pendingRows = [];
678
+ const CONCURRENCY = 10;
679
+ for (let i = 0; i < rows.length; i += CONCURRENCY) {
680
+ const chunk = rows.slice(i, i + CONCURRENCY);
681
+ await Promise.allSettled(chunk.map((r) => this.upsertRowSql(r)));
682
+ }
683
+ log3(`commit: ${rows.length} rows`);
684
+ }
685
+ async upsertRowSql(row) {
686
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
687
+ const cd = row.creationDate ?? ts;
688
+ const lud = row.lastUpdateDate ?? ts;
689
+ const exists = await this.query(`SELECT path FROM "${this.tableName}" WHERE path = '${sqlStr(row.path)}' LIMIT 1`);
690
+ if (exists.length > 0) {
691
+ let setClauses = `summary = E'${sqlStr(row.contentText)}', ${SUMMARY_EMBEDDING_COL} = NULL, mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
692
+ if (row.project !== void 0)
693
+ setClauses += `, project = '${sqlStr(row.project)}'`;
694
+ if (row.description !== void 0)
695
+ setClauses += `, description = '${sqlStr(row.description)}'`;
696
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(row.path)}'`);
697
+ } else {
698
+ const id = randomUUID();
699
+ let cols = `id, path, filename, summary, ${SUMMARY_EMBEDDING_COL}, mime_type, size_bytes, creation_date, last_update_date`;
700
+ let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', NULL, '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
701
+ if (row.project !== void 0) {
702
+ cols += ", project";
703
+ vals += `, '${sqlStr(row.project)}'`;
704
+ }
705
+ if (row.description !== void 0) {
706
+ cols += ", description";
707
+ vals += `, '${sqlStr(row.description)}'`;
708
+ }
709
+ await this.query(`INSERT INTO "${this.tableName}" (${cols}) VALUES (${vals})`);
710
+ }
711
+ }
712
+ /** Update specific columns on a row by path. */
713
+ async updateColumns(path, columns) {
714
+ const setClauses = Object.entries(columns).map(([col, val]) => typeof val === "number" ? `${col} = ${val}` : `${col} = '${sqlStr(String(val))}'`).join(", ");
715
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(path)}'`);
716
+ }
717
+ // ── Convenience ─────────────────────────────────────────────────────────────
718
+ /** Create a BM25 search index on a column. */
719
+ async createIndex(column) {
720
+ await this.query(`CREATE INDEX IF NOT EXISTS idx_${sqlStr(column)}_bm25 ON "${this.tableName}" USING deeplake_index ("${column}")`);
721
+ }
722
+ buildLookupIndexName(table, suffix) {
723
+ return `idx_${table}_${suffix}`.replace(/[^a-zA-Z0-9_]/g, "_");
724
+ }
725
+ async ensureLookupIndex(table, suffix, columnsSql) {
726
+ const markers = await getIndexMarkerStore();
727
+ const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, suffix);
728
+ if (markers.hasFreshIndexMarker(markerPath))
729
+ return;
730
+ const indexName = this.buildLookupIndexName(table, suffix);
731
+ try {
732
+ await this.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" ${columnsSql}`);
733
+ markers.writeIndexMarker(markerPath);
734
+ } catch (e) {
735
+ if (isDuplicateIndexError(e)) {
736
+ markers.writeIndexMarker(markerPath);
737
+ return;
738
+ }
739
+ log3(`index "${indexName}" skipped: ${e.message}`);
740
+ }
741
+ }
742
+ /**
743
+ * Heal any missing columns on a table so it matches one of the schema
744
+ * definitions in `deeplake-schema.ts`. One SELECT against
745
+ * `information_schema.columns` per call, then `ALTER TABLE ADD COLUMN`
746
+ * only the genuinely missing ones — never blanket, never `IF NOT
747
+ * EXISTS`.
748
+ *
749
+ * History: an earlier path used a local marker file (`col_<name>` under
750
+ * the index-marker dir) to skip even the SELECT after the first
751
+ * confirmation, plus per-column ALTERs for `summary_embedding`,
752
+ * `message_embedding`, `agent`, `plugin_version`. The marker existed
753
+ * because Deeplake used to expose a ~30s post-ALTER bug where
754
+ * subsequent INSERTs failed, so we wanted to keep ALTER traffic to a
755
+ * minimum. The bug was re-verified on 2026-05-18 against
756
+ * `api.deeplake.ai` (`test_plugin` org) and no longer reproduces
757
+ * (71/71 INSERTs OK, first success 2ms after ALTER). The single SELECT
758
+ * + targeted ALTER pattern survives the marker removal because: each
759
+ * ALTER still costs ~800ms (so blanket sweeps are wasteful) and the
760
+ * diff produces clearer logs than "ALTER all with IF NOT EXISTS".
761
+ */
762
+ async healSchema(table, columns) {
763
+ await healMissingColumns({
764
+ query: (sql) => this.query(sql),
765
+ tableName: table,
766
+ workspaceId: this.workspaceId,
767
+ columns,
768
+ log: log3
769
+ });
770
+ }
771
+ /** List all tables in the workspace (with retry). */
772
+ async listTables(forceRefresh = false) {
773
+ if (!forceRefresh && this._tablesCache)
774
+ return [...this._tablesCache];
775
+ const { tables, cacheable } = await this._fetchTables();
776
+ if (cacheable)
777
+ this._tablesCache = [...tables];
778
+ return tables;
779
+ }
780
+ async _fetchTables() {
781
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
782
+ try {
783
+ const resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables`, {
784
+ headers: {
785
+ Authorization: `Bearer ${this.token}`,
786
+ "X-Activeloop-Org-Id": this.orgId,
787
+ ...deeplakeClientHeader()
788
+ }
789
+ });
790
+ if (resp.ok) {
791
+ const data = await resp.json();
792
+ return {
793
+ tables: (data.tables ?? []).map((t) => t.table_name),
794
+ cacheable: true
795
+ };
796
+ }
797
+ if (attempt < MAX_RETRIES && RETRYABLE_CODES.has(resp.status)) {
798
+ await sleep2(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
799
+ continue;
800
+ }
801
+ return { tables: [], cacheable: false };
802
+ } catch {
803
+ if (attempt < MAX_RETRIES) {
804
+ await sleep2(BASE_DELAY_MS * Math.pow(2, attempt));
805
+ continue;
806
+ }
807
+ return { tables: [], cacheable: false };
808
+ }
809
+ }
810
+ return { tables: [], cacheable: false };
811
+ }
812
+ /**
813
+ * Run a `CREATE TABLE` with an extra outer retry budget. The base
814
+ * `query()` already retries 3 times on fetch errors (~3.5s total), but a
815
+ * failed CREATE is permanent corruption — every subsequent SELECT against
816
+ * the missing table fails. Wrapping in an outer loop with longer backoff
817
+ * (2s, 5s, then 10s) gives us ~17s of reach across transient network
818
+ * blips before giving up. Failures still propagate; getApi() resets its
819
+ * cache on init failure (openclaw plugin) so the next call retries the
820
+ * whole init flow.
821
+ */
822
+ async createTableWithRetry(sql, label) {
823
+ const OUTER_BACKOFFS_MS = [2e3, 5e3, 1e4];
824
+ let lastErr = null;
825
+ for (let attempt = 0; attempt <= OUTER_BACKOFFS_MS.length; attempt++) {
826
+ try {
827
+ await this.query(sql);
828
+ return;
829
+ } catch (err) {
830
+ lastErr = err;
831
+ const msg = err instanceof Error ? err.message : String(err);
832
+ log3(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
833
+ if (attempt < OUTER_BACKOFFS_MS.length) {
834
+ await sleep2(OUTER_BACKOFFS_MS[attempt]);
835
+ }
836
+ }
837
+ }
838
+ throw lastErr;
839
+ }
840
+ /** Create the memory table if it doesn't already exist. Heal missing columns on existing tables. */
841
+ async ensureTable(name) {
842
+ if (!MEMORY_COLUMNS.some((c) => c.name === SUMMARY_EMBEDDING_COL)) {
843
+ throw new Error(`MEMORY_COLUMNS missing "${SUMMARY_EMBEDDING_COL}" (embeddings/columns.ts drift)`);
844
+ }
845
+ const tbl = sqlIdent(name ?? this.tableName);
846
+ const tables = await this.listTables();
847
+ if (!tables.includes(tbl)) {
848
+ log3(`table "${tbl}" not found, creating`);
849
+ await this.createTableWithRetry(buildCreateTableSql(tbl, MEMORY_COLUMNS), tbl);
850
+ log3(`table "${tbl}" created`);
851
+ if (!tables.includes(tbl))
852
+ this._tablesCache = [...tables, tbl];
853
+ }
854
+ await this.healSchema(tbl, MEMORY_COLUMNS);
855
+ }
856
+ /** Create the sessions table (uses JSONB for message since every row is a JSON event). */
857
+ async ensureSessionsTable(name) {
858
+ const safe = sqlIdent(name);
859
+ const tables = await this.listTables();
860
+ if (!tables.includes(safe)) {
861
+ log3(`table "${safe}" not found, creating`);
862
+ await this.createTableWithRetry(buildCreateTableSql(safe, SESSIONS_COLUMNS), safe);
863
+ log3(`table "${safe}" created`);
864
+ if (!tables.includes(safe))
865
+ this._tablesCache = [...tables, safe];
866
+ }
867
+ await this.healSchema(safe, SESSIONS_COLUMNS);
868
+ await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
869
+ }
870
+ /**
871
+ * Create the skills table.
872
+ *
873
+ * One row per skill version. Workers INSERT a fresh row on every KEEP /
874
+ * MERGE rather than UPDATE-ing in place, so the full version history is
875
+ * recoverable. Uniqueness in the *current* state is by (project_key, name)
876
+ * — newer rows shadow older ones at read time (ORDER BY version DESC).
877
+ * This sidesteps the Deeplake UPDATE-coalescing quirk that bit the wiki
878
+ * worker.
879
+ */
880
+ /**
881
+ * Create the codebase table. One row per (org, workspace, repo, user,
882
+ * worktree, commit) — see CODEBASE_COLUMNS for the schema. Healing
883
+ * + index follow the same pattern as ensureSessionsTable.
884
+ */
885
+ async ensureCodebaseTable(name) {
886
+ const safe = sqlIdent(name);
887
+ const tables = await this.listTables();
888
+ if (!tables.includes(safe)) {
889
+ log3(`table "${safe}" not found, creating`);
890
+ await this.createTableWithRetry(buildCreateTableSql(safe, CODEBASE_COLUMNS), safe);
891
+ log3(`table "${safe}" created`);
892
+ if (!tables.includes(safe))
893
+ this._tablesCache = [...tables, safe];
894
+ }
895
+ await this.healSchema(safe, CODEBASE_COLUMNS);
896
+ await this.ensureLookupIndex(safe, "codebase_identity", `("org_id", "workspace_id", "repo_slug", "user_id", "worktree_id", "commit_sha")`);
897
+ }
898
+ async ensureSkillsTable(name) {
899
+ const safe = sqlIdent(name);
900
+ const tables = await this.listTables();
901
+ if (!tables.includes(safe)) {
902
+ log3(`table "${safe}" not found, creating`);
903
+ await this.createTableWithRetry(buildCreateTableSql(safe, SKILLS_COLUMNS), safe);
904
+ log3(`table "${safe}" created`);
905
+ if (!tables.includes(safe))
906
+ this._tablesCache = [...tables, safe];
907
+ }
908
+ await this.healSchema(safe, SKILLS_COLUMNS);
909
+ await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
910
+ }
911
+ /**
912
+ * Create the rules table.
913
+ *
914
+ * One row per rule version (same write pattern as skills): edits INSERT
915
+ * a fresh row with version+1, reads pick latest per rule_id via
916
+ * `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
917
+ * UPDATE-coalescing quirk by never UPDATEing.
918
+ */
919
+ async ensureRulesTable(name) {
920
+ const safe = sqlIdent(name);
921
+ const tables = await this.listTables();
922
+ if (!tables.includes(safe)) {
923
+ log3(`table "${safe}" not found, creating`);
924
+ await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
925
+ log3(`table "${safe}" created`);
926
+ if (!tables.includes(safe))
927
+ this._tablesCache = [...tables, safe];
928
+ }
929
+ await this.healSchema(safe, RULES_COLUMNS);
930
+ await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
931
+ }
932
+ /**
933
+ * Create the tasks table.
934
+ *
935
+ * Same write pattern as rules + skills. `kpis` is a nullable JSONB
936
+ * column with the agent's KPI metadata; KPI current values come from
937
+ * `task_events` (SUM(value)), not this snapshot.
938
+ */
939
+ async ensureTasksTable(name) {
940
+ const safe = sqlIdent(name);
941
+ const tables = await this.listTables();
942
+ if (!tables.includes(safe)) {
943
+ log3(`table "${safe}" not found, creating`);
944
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
945
+ log3(`table "${safe}" created`);
946
+ if (!tables.includes(safe))
947
+ this._tablesCache = [...tables, safe];
948
+ }
949
+ await this.healSchema(safe, TASKS_COLUMNS);
950
+ await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
951
+ }
952
+ /**
953
+ * Create the task-events table.
954
+ *
955
+ * Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
956
+ * value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
957
+ * (task_id, kpi_id) is the canonical aggregation key.
958
+ */
959
+ async ensureTaskEventsTable(name) {
960
+ const safe = sqlIdent(name);
961
+ const tables = await this.listTables();
962
+ if (!tables.includes(safe)) {
963
+ log3(`table "${safe}" not found, creating`);
964
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
965
+ log3(`table "${safe}" created`);
966
+ if (!tables.includes(safe))
967
+ this._tablesCache = [...tables, safe];
968
+ }
969
+ await this.healSchema(safe, TASK_EVENTS_COLUMNS);
970
+ await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
971
+ }
972
+ /**
973
+ * Create the goals table.
974
+ *
975
+ * Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
976
+ * INSERT-only version-bumped: rm and mv operations translate to fresh
977
+ * v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
978
+ * The (goal_id, version) index lets the VFS dispatch a cheap latest-row
979
+ * read on cat / Read of a single goal.
980
+ */
981
+ async ensureGoalsTable(name) {
982
+ const safe = sqlIdent(name);
983
+ const tables = await this.listTables();
984
+ if (!tables.includes(safe)) {
985
+ log3(`table "${safe}" not found, creating`);
986
+ await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
987
+ log3(`table "${safe}" created`);
988
+ if (!tables.includes(safe))
989
+ this._tablesCache = [...tables, safe];
990
+ }
991
+ await this.healSchema(safe, GOALS_COLUMNS);
992
+ await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
993
+ await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
994
+ }
995
+ /**
996
+ * Create the kpis table.
997
+ *
998
+ * Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
999
+ * owner — ownership derives from the parent goal via logical join on
1000
+ * goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
1001
+ * canonical lookup the VFS uses on Read and Write.
1002
+ */
1003
+ async ensureKpisTable(name) {
1004
+ const safe = sqlIdent(name);
1005
+ const tables = await this.listTables();
1006
+ if (!tables.includes(safe)) {
1007
+ log3(`table "${safe}" not found, creating`);
1008
+ await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
1009
+ log3(`table "${safe}" created`);
1010
+ if (!tables.includes(safe))
1011
+ this._tablesCache = [...tables, safe];
1012
+ }
1013
+ await this.healSchema(safe, KPIS_COLUMNS);
1014
+ await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
1015
+ }
1016
+ };
1017
+
1018
+ // dist/src/utils/repo-identity.js
1019
+ import { execSync } from "node:child_process";
1020
+ import { createHash } from "node:crypto";
1021
+ import { basename, resolve as resolve2 } from "node:path";
1022
+ var DEFAULT_PORTS = {
1023
+ http: "80",
1024
+ https: "443",
1025
+ ssh: "22",
1026
+ git: "9418"
1027
+ };
1028
+ function normalizeGitRemoteUrl(url) {
1029
+ let s = url.trim();
1030
+ const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
1031
+ const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
1032
+ if (schemeMatch)
1033
+ s = s.slice(schemeMatch[0].length);
1034
+ if (!scheme) {
1035
+ const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
1036
+ if (scp)
1037
+ s = `${scp[1]}/${scp[2]}`;
1038
+ }
1039
+ s = s.replace(/^[^@/]+@/, "");
1040
+ if (scheme && DEFAULT_PORTS[scheme]) {
1041
+ s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
1042
+ }
1043
+ s = s.replace(/\.git\/?$/i, "");
1044
+ s = s.replace(/\/+$/, "");
1045
+ return s.toLowerCase();
1046
+ }
1047
+ function deriveProjectKey(cwd) {
1048
+ const absCwd = resolve2(cwd);
1049
+ const project = basename(absCwd) || "unknown";
1050
+ let signature = null;
1051
+ try {
1052
+ const raw = execSync("git config --get remote.origin.url", {
1053
+ cwd: absCwd,
1054
+ encoding: "utf-8",
1055
+ stdio: ["ignore", "pipe", "ignore"]
1056
+ }).trim();
1057
+ signature = raw ? normalizeGitRemoteUrl(raw) : null;
1058
+ } catch {
1059
+ }
1060
+ const input = signature ?? absCwd;
1061
+ const key = createHash("sha1").update(input).digest("hex").slice(0, 16);
1062
+ return { key, project };
1063
+ }
1064
+
1065
+ // dist/src/graph/last-build.js
1066
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
1067
+ import { dirname, join as join6 } from "node:path";
1068
+ function lastBuildPath(baseDir, worktreeId) {
1069
+ if (worktreeId !== void 0) {
1070
+ return join6(baseDir, "worktrees", worktreeId, ".last-build.json");
1071
+ }
1072
+ return join6(baseDir, ".last-build.json");
1073
+ }
1074
+ function writeLastBuild(baseDir, state, worktreeId) {
1075
+ const path = lastBuildPath(baseDir, worktreeId);
1076
+ try {
1077
+ mkdirSync4(dirname(path), { recursive: true });
1078
+ const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
1079
+ writeFileSync4(tmp, JSON.stringify(state));
1080
+ renameSync2(tmp, path);
1081
+ } catch {
1082
+ }
1083
+ }
1084
+ function readLastBuild(baseDir, worktreeId) {
1085
+ let path = lastBuildPath(baseDir, worktreeId);
1086
+ if (!existsSync3(path)) {
1087
+ if (worktreeId === void 0)
1088
+ return null;
1089
+ const legacy = lastBuildPath(baseDir, void 0);
1090
+ if (!existsSync3(legacy))
1091
+ return null;
1092
+ path = legacy;
1093
+ }
1094
+ let raw;
1095
+ try {
1096
+ raw = readFileSync5(path, "utf8");
1097
+ } catch {
1098
+ return null;
1099
+ }
1100
+ let parsed;
1101
+ try {
1102
+ parsed = JSON.parse(raw);
1103
+ } catch {
1104
+ return null;
1105
+ }
1106
+ if (parsed === null || typeof parsed !== "object")
1107
+ return null;
1108
+ const o = parsed;
1109
+ if (typeof o.ts !== "number" || !Number.isFinite(o.ts))
1110
+ return null;
1111
+ if (o.commit_sha !== null && typeof o.commit_sha !== "string")
1112
+ return null;
1113
+ if (typeof o.snapshot_sha256 !== "string")
1114
+ return null;
1115
+ const out = { ts: o.ts, commit_sha: o.commit_sha, snapshot_sha256: o.snapshot_sha256 };
1116
+ if (typeof o.node_count === "number" && Number.isFinite(o.node_count) && o.node_count >= 0) {
1117
+ out.node_count = o.node_count;
1118
+ }
1119
+ if (typeof o.edge_count === "number" && Number.isFinite(o.edge_count) && o.edge_count >= 0) {
1120
+ out.edge_count = o.edge_count;
1121
+ }
1122
+ return out;
1123
+ }
1124
+
1125
+ // dist/src/graph/history.js
1126
+ import { appendFileSync as appendFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6 } from "node:fs";
1127
+ import { dirname as dirname2, join as join7 } from "node:path";
1128
+ function historyPath(baseDir) {
1129
+ return join7(baseDir, "history.jsonl");
1130
+ }
1131
+ function appendHistoryEntry(baseDir, entry) {
1132
+ const path = historyPath(baseDir);
1133
+ try {
1134
+ mkdirSync5(dirname2(path), { recursive: true });
1135
+ appendFileSync2(path, JSON.stringify(entry) + "\n");
1136
+ } catch {
1137
+ }
1138
+ }
1139
+
1140
+ // dist/src/graph/snapshot.js
1141
+ import { createHash as createHash2 } from "node:crypto";
1142
+ import { mkdirSync as mkdirSync6, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
1143
+ import { homedir as homedir5 } from "node:os";
1144
+ import { dirname as dirname3, join as join8 } from "node:path";
1145
+ function graphsRoot() {
1146
+ return process.env.HIVEMIND_GRAPHS_HOME ?? join8(homedir5(), ".hivemind", "graphs");
1147
+ }
1148
+ function repoDir(repoKey) {
1149
+ return join8(graphsRoot(), repoKey);
1150
+ }
1151
+
1152
+ // dist/src/graph/deeplake-pull.js
1153
+ function workTreeIdFor(cwd) {
1154
+ return createHash3("sha256").update(cwd).digest("hex").slice(0, 16);
1155
+ }
1156
+ async function pullSnapshot(cwd, deps = {}) {
1157
+ if (process.env.HIVEMIND_GRAPH_PULL === "0") {
1158
+ return { kind: "skipped-disabled" };
1159
+ }
1160
+ const config = (deps.loadConfig ?? loadConfig)();
1161
+ if (config === null) {
1162
+ return { kind: "skipped-no-auth" };
1163
+ }
1164
+ const head = (deps.readHead ?? defaultReadHead)(cwd);
1165
+ if (head === null) {
1166
+ return { kind: "skipped-no-head" };
1167
+ }
1168
+ const api = (deps.makeApi ?? defaultMakeApi)(config);
1169
+ try {
1170
+ await api.ensureCodebaseTable(config.codebaseTableName);
1171
+ } catch (err) {
1172
+ return errorOutcome("ensureCodebaseTable", err);
1173
+ }
1174
+ const tableId = sqlIdent(config.codebaseTableName);
1175
+ const { key: repoKey } = deriveProjectKey(cwd);
1176
+ const selectSql = `SELECT snapshot_jsonb, snapshot_sha256, ts, node_count, edge_count, branch, generator_version, worktree_id FROM "${tableId}" WHERE org_id = '${sqlStr(config.orgId)}' AND workspace_id = '${sqlStr(config.workspaceId)}' AND repo_slug = '${sqlStr(repoKey)}' AND user_id = '${sqlStr(config.userName)}' AND commit_sha = '${sqlStr(head)}' ORDER BY ts DESC LIMIT 1`;
1177
+ let rows;
1178
+ try {
1179
+ rows = await api.query(selectSql);
1180
+ } catch (err) {
1181
+ return errorOutcome("SELECT cloud row", err);
1182
+ }
1183
+ if (rows.length === 0) {
1184
+ return { kind: "no-cloud-row", commitSha: head };
1185
+ }
1186
+ const row = rows[0];
1187
+ const cloudSha256 = String(row.snapshot_sha256 ?? "").trim();
1188
+ const cloudPayload = coerceSnapshotPayload(row.snapshot_jsonb);
1189
+ if (cloudPayload === null) {
1190
+ return errorOutcome("SELECT cloud row", new Error("invalid snapshot_jsonb payload"));
1191
+ }
1192
+ if (cloudSha256 !== "") {
1193
+ const computedSha = createHash3("sha256").update(cloudPayload).digest("hex");
1194
+ if (cloudSha256 !== computedSha) {
1195
+ return errorOutcome("SELECT cloud row", new Error(`snapshot_sha256 mismatch (expected ${cloudSha256}, got ${computedSha})`));
1196
+ }
1197
+ }
1198
+ const cloudTs = parseTs(row.ts);
1199
+ const baseDir = repoDir(repoKey);
1200
+ const worktreeId = workTreeIdFor(cwd);
1201
+ const local = readLastBuild(baseDir, worktreeId);
1202
+ if (local !== null && local.commit_sha === head) {
1203
+ if (cloudSha256 !== "" && local.snapshot_sha256 === cloudSha256) {
1204
+ return { kind: "up-to-date", commitSha: head, snapshotSha256: cloudSha256 };
1205
+ }
1206
+ if (local.ts > cloudTs) {
1207
+ return {
1208
+ kind: "local-newer",
1209
+ commitSha: head,
1210
+ localTs: local.ts,
1211
+ cloudTs
1212
+ };
1213
+ }
1214
+ }
1215
+ const snapshotsDir = join9(baseDir, "snapshots");
1216
+ const snapshotPath = join9(snapshotsDir, `${head}.json`);
1217
+ const worktreeRoot = join9(baseDir, "worktrees", worktreeId);
1218
+ try {
1219
+ writeFileAtomic(snapshotPath, cloudPayload);
1220
+ writeFileAtomic(join9(worktreeRoot, "latest-commit.txt"), `${head}
1221
+ `);
1222
+ writeLastBuild(baseDir, {
1223
+ ts: cloudTs,
1224
+ commit_sha: head,
1225
+ snapshot_sha256: cloudSha256,
1226
+ node_count: numOrUndefined(row.node_count),
1227
+ edge_count: numOrUndefined(row.edge_count)
1228
+ }, worktreeId);
1229
+ appendHistoryEntry(baseDir, {
1230
+ ts: new Date(cloudTs).toISOString(),
1231
+ commit_sha: head,
1232
+ snapshot_sha256: cloudSha256,
1233
+ node_count: Number(row.node_count ?? 0),
1234
+ edge_count: Number(row.edge_count ?? 0),
1235
+ trigger: "pull"
1236
+ });
1237
+ } catch (err) {
1238
+ return errorOutcome("write local files", err);
1239
+ }
1240
+ return {
1241
+ kind: "pulled",
1242
+ commitSha: head,
1243
+ snapshotSha256: cloudSha256,
1244
+ bytes: Buffer.byteLength(cloudPayload, "utf8"),
1245
+ cloudTs,
1246
+ sourceWorktreePath: String(row.worktree_id ?? "")
1247
+ };
1248
+ }
1249
+ function defaultReadHead(cwd) {
1250
+ try {
1251
+ return execFileSync("git", ["rev-parse", "HEAD"], {
1252
+ cwd,
1253
+ encoding: "utf8",
1254
+ stdio: ["ignore", "pipe", "ignore"]
1255
+ }).trim();
1256
+ } catch {
1257
+ return null;
1258
+ }
1259
+ }
1260
+ function defaultMakeApi(config) {
1261
+ return new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.tableName);
1262
+ }
1263
+ function parseTs(raw) {
1264
+ if (typeof raw === "number" && Number.isFinite(raw)) {
1265
+ return raw < 1e12 ? raw * 1e3 : raw;
1266
+ }
1267
+ if (typeof raw === "string") {
1268
+ const parsed = Date.parse(raw);
1269
+ return Number.isFinite(parsed) ? parsed : 0;
1270
+ }
1271
+ return 0;
1272
+ }
1273
+ function numOrUndefined(raw) {
1274
+ if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0)
1275
+ return raw;
1276
+ if (typeof raw === "string") {
1277
+ const n = Number(raw);
1278
+ if (Number.isFinite(n) && n >= 0)
1279
+ return n;
1280
+ }
1281
+ return void 0;
1282
+ }
1283
+ function coerceSnapshotPayload(raw) {
1284
+ if (typeof raw === "string")
1285
+ return raw;
1286
+ if (raw !== null && typeof raw === "object")
1287
+ return JSON.stringify(raw);
1288
+ return null;
1289
+ }
1290
+ function writeFileAtomic(filePath, contents) {
1291
+ mkdirSync7(dirname4(filePath), { recursive: true });
1292
+ const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}`;
1293
+ writeFileSync6(tmp, contents);
1294
+ renameSync4(tmp, filePath);
1295
+ }
1296
+ function errorOutcome(stage, err) {
1297
+ const message = err instanceof Error ? err.message : String(err);
1298
+ return { kind: "error", message: `${stage}: ${message}` };
1299
+ }
1300
+
1301
+ // dist/src/hooks/graph-pull-worker.js
1302
+ function getArg(name) {
1303
+ const idx = process.argv.indexOf(name);
1304
+ if (idx === -1 || idx + 1 >= process.argv.length)
1305
+ return null;
1306
+ return process.argv[idx + 1];
1307
+ }
1308
+ function workerTimeoutMs() {
1309
+ const raw = process.env.HIVEMIND_GRAPH_PULL_TIMEOUT_MS;
1310
+ if (raw === void 0)
1311
+ return 3e4;
1312
+ const n = parseInt(raw, 10);
1313
+ return Number.isFinite(n) && n > 0 ? n : 3e4;
1314
+ }
1315
+ async function main() {
1316
+ const cwd = getArg("--cwd") ?? process.cwd();
1317
+ const t0 = Date.now();
1318
+ const timeoutPromise = new Promise((resolve3) => {
1319
+ setTimeout(() => resolve3("timeout"), workerTimeoutMs()).unref();
1320
+ });
1321
+ let logLine;
1322
+ try {
1323
+ const outcome = await Promise.race([pullSnapshot(cwd), timeoutPromise]);
1324
+ if (outcome === "timeout") {
1325
+ const dur2 = Date.now() - t0;
1326
+ logLine = `[${(/* @__PURE__ */ new Date()).toISOString()}] timeout (${dur2}ms) \u2014 pullSnapshot did not return within ${workerTimeoutMs()}ms; forcing exit
1327
+ `;
1328
+ try {
1329
+ const { key } = deriveProjectKey(cwd);
1330
+ const dir = repoDir(key);
1331
+ mkdirSync8(dir, { recursive: true });
1332
+ appendFileSync3(join10(dir, ".graph-pull.log"), logLine);
1333
+ } catch {
1334
+ }
1335
+ process.exit(0);
1336
+ }
1337
+ const dur = Date.now() - t0;
1338
+ const extras = [];
1339
+ if (outcome.kind === "pulled") {
1340
+ extras.push(`commit=${outcome.commitSha.slice(0, 7)}`);
1341
+ extras.push(`bytes=${outcome.bytes}`);
1342
+ extras.push(`sha256=${outcome.snapshotSha256.slice(0, 12)}`);
1343
+ } else if (outcome.kind === "up-to-date") {
1344
+ extras.push(`commit=${outcome.commitSha.slice(0, 7)}`);
1345
+ } else if (outcome.kind === "local-newer") {
1346
+ extras.push(`commit=${outcome.commitSha.slice(0, 7)}`);
1347
+ extras.push(`localTs=${outcome.localTs}`);
1348
+ extras.push(`cloudTs=${outcome.cloudTs}`);
1349
+ } else if (outcome.kind === "no-cloud-row") {
1350
+ extras.push(`commit=${outcome.commitSha.slice(0, 7)}`);
1351
+ } else if (outcome.kind === "error") {
1352
+ extras.push(outcome.message);
1353
+ }
1354
+ logLine = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${outcome.kind} (${dur}ms)` + (extras.length ? ` \u2014 ${extras.join(" ")}` : "") + "\n";
1355
+ } catch (err) {
1356
+ const dur = Date.now() - t0;
1357
+ logLine = `[${(/* @__PURE__ */ new Date()).toISOString()}] threw ${err instanceof Error ? err.message : String(err)} (${dur}ms)
1358
+ `;
1359
+ }
1360
+ try {
1361
+ const { key } = deriveProjectKey(cwd);
1362
+ const dir = repoDir(key);
1363
+ mkdirSync8(dir, { recursive: true });
1364
+ appendFileSync3(join10(dir, ".graph-pull.log"), logLine);
1365
+ } catch {
1366
+ }
1367
+ }
1368
+ main().catch(() => {
1369
+ process.exit(0);
1370
+ });