@askexenow/exe-os 0.9.66 → 0.9.67
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/deploy/stack-manifests/v0.9.json +54 -5
- package/dist/bin/age-ontology-load.js +61 -0
- package/dist/bin/agentic-ontology-backfill.js +4708 -0
- package/dist/bin/agentic-reflection-backfill.js +4144 -0
- package/dist/bin/{exe-link.js → agentic-semantic-label.js} +1523 -2275
- package/dist/bin/backfill-conversations.js +506 -20
- package/dist/bin/backfill-responses.js +506 -20
- package/dist/bin/backfill-vectors.js +233 -20
- package/dist/bin/bulk-sync-postgres.js +4876 -0
- package/dist/bin/cleanup-stale-review-tasks.js +507 -21
- package/dist/bin/cli.js +2450 -1530
- package/dist/bin/exe-assign.js +506 -20
- package/dist/bin/exe-boot.js +410 -60
- package/dist/bin/exe-cloud.js +795 -105
- package/dist/bin/exe-dispatch.js +516 -22
- package/dist/bin/exe-doctor.js +587 -30
- package/dist/bin/exe-export-behaviors.js +518 -24
- package/dist/bin/exe-forget.js +507 -21
- package/dist/bin/exe-gateway.js +571 -25
- package/dist/bin/exe-heartbeat.js +518 -24
- package/dist/bin/exe-kill.js +507 -21
- package/dist/bin/exe-launch-agent.js +2312 -1069
- package/dist/bin/exe-new-employee.js +197 -165
- package/dist/bin/exe-pending-messages.js +507 -21
- package/dist/bin/exe-pending-notifications.js +507 -21
- package/dist/bin/exe-pending-reviews.js +507 -21
- package/dist/bin/exe-rename.js +507 -21
- package/dist/bin/exe-review.js +507 -21
- package/dist/bin/exe-search.js +518 -24
- package/dist/bin/exe-session-cleanup.js +516 -22
- package/dist/bin/exe-settings.js +4 -0
- package/dist/bin/exe-start-codex.js +682 -143
- package/dist/bin/exe-start-opencode.js +627 -79
- package/dist/bin/exe-status.js +507 -21
- package/dist/bin/exe-team.js +507 -21
- package/dist/bin/git-sweep.js +516 -22
- package/dist/bin/graph-backfill.js +558 -21
- package/dist/bin/graph-export.js +507 -21
- package/dist/bin/graph-layer-benchmark.js +109 -0
- package/dist/bin/install.js +305 -288
- package/dist/bin/intercom-check.js +516 -22
- package/dist/bin/postgres-agentic-reflection-backfill.js +187 -0
- package/dist/bin/postgres-agentic-semantic-backfill.js +237 -0
- package/dist/bin/scan-tasks.js +516 -22
- package/dist/bin/setup.js +412 -62
- package/dist/bin/shard-migrate.js +506 -20
- package/dist/gateway/index.js +569 -23
- package/dist/hooks/bug-report-worker.js +519 -25
- package/dist/hooks/codex-stop-task-finalizer.js +516 -22
- package/dist/hooks/commit-complete.js +516 -22
- package/dist/hooks/error-recall.js +518 -24
- package/dist/hooks/ingest.js +516 -22
- package/dist/hooks/instructions-loaded.js +507 -21
- package/dist/hooks/notification.js +507 -21
- package/dist/hooks/post-compact.js +507 -21
- package/dist/hooks/post-tool-combined.js +519 -25
- package/dist/hooks/pre-compact.js +516 -22
- package/dist/hooks/pre-tool-use.js +507 -21
- package/dist/hooks/prompt-submit.js +519 -25
- package/dist/hooks/session-end.js +516 -22
- package/dist/hooks/session-start.js +520 -26
- package/dist/hooks/stop.js +517 -23
- package/dist/hooks/subagent-stop.js +507 -21
- package/dist/hooks/summary-worker.js +411 -61
- package/dist/index.js +569 -23
- package/dist/lib/cloud-sync.js +391 -53
- package/dist/lib/config.js +13 -1
- package/dist/lib/consolidation.js +1 -1
- package/dist/lib/database.js +124 -0
- package/dist/lib/db.js +124 -0
- package/dist/lib/device-registry.js +124 -0
- package/dist/lib/embedder.js +13 -1
- package/dist/lib/exe-daemon.js +2184 -561
- package/dist/lib/hybrid-search.js +518 -24
- package/dist/lib/identity.js +3 -0
- package/dist/lib/keychain.js +178 -22
- package/dist/lib/messaging.js +3 -0
- package/dist/lib/reminders.js +3 -0
- package/dist/lib/schedules.js +233 -20
- package/dist/lib/skill-learning.js +16 -1
- package/dist/lib/store.js +506 -20
- package/dist/lib/tasks.js +16 -1
- package/dist/lib/tmux-routing.js +16 -1
- package/dist/lib/token-spend.js +3 -0
- package/dist/mcp/server.js +1757 -428
- package/dist/mcp/tools/complete-reminder.js +3 -0
- package/dist/mcp/tools/create-reminder.js +3 -0
- package/dist/mcp/tools/create-task.js +16 -1
- package/dist/mcp/tools/deactivate-behavior.js +3 -0
- package/dist/mcp/tools/list-reminders.js +3 -0
- package/dist/mcp/tools/list-tasks.js +3 -0
- package/dist/mcp/tools/send-message.js +3 -0
- package/dist/mcp/tools/update-task.js +16 -1
- package/dist/runtime/index.js +516 -22
- package/dist/tui/App.js +594 -29
- package/package.json +8 -5
- package/src/commands/exe/cloud.md +6 -10
- package/stack.release.json +3 -3
- package/src/commands/exe/link.md +0 -18
package/dist/bin/exe-cloud.js
CHANGED
|
@@ -20,12 +20,13 @@ var keychain_exports = {};
|
|
|
20
20
|
__export(keychain_exports, {
|
|
21
21
|
deleteMasterKey: () => deleteMasterKey,
|
|
22
22
|
exportMnemonic: () => exportMnemonic,
|
|
23
|
+
getKeyStorageInfo: () => getKeyStorageInfo,
|
|
23
24
|
getMasterKey: () => getMasterKey,
|
|
24
25
|
importMnemonic: () => importMnemonic,
|
|
25
26
|
setMasterKey: () => setMasterKey
|
|
26
27
|
});
|
|
27
28
|
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
28
|
-
import { existsSync } from "fs";
|
|
29
|
+
import { existsSync, statSync } from "fs";
|
|
29
30
|
import { execSync } from "child_process";
|
|
30
31
|
import path from "path";
|
|
31
32
|
import os from "os";
|
|
@@ -35,29 +36,65 @@ function getKeyDir() {
|
|
|
35
36
|
function getKeyPath() {
|
|
36
37
|
return path.join(getKeyDir(), "master.key");
|
|
37
38
|
}
|
|
38
|
-
function
|
|
39
|
+
function nativeKeychainAllowed() {
|
|
40
|
+
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
41
|
+
}
|
|
42
|
+
function linuxSecretAvailable() {
|
|
43
|
+
if (!nativeKeychainAllowed()) return false;
|
|
44
|
+
if (process.platform !== "linux") return false;
|
|
45
|
+
if (linuxSecretAvailability !== null) return linuxSecretAvailability;
|
|
46
|
+
try {
|
|
47
|
+
execSync("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
|
|
48
|
+
} catch {
|
|
49
|
+
linuxSecretAvailability = false;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
execSync("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
|
|
54
|
+
linuxSecretAvailability = true;
|
|
55
|
+
} catch {
|
|
56
|
+
linuxSecretAvailability = false;
|
|
57
|
+
}
|
|
58
|
+
return linuxSecretAvailability;
|
|
59
|
+
}
|
|
60
|
+
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
61
|
+
if (process.platform !== "linux") return false;
|
|
62
|
+
try {
|
|
63
|
+
const uid = typeof os.userInfo().uid === "number" ? os.userInfo().uid : -1;
|
|
64
|
+
const st = statSync(keyPath);
|
|
65
|
+
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
66
|
+
if (uid === 0) return true;
|
|
67
|
+
const exeOsDir = process.env.EXE_OS_DIR;
|
|
68
|
+
return Boolean(exeOsDir && path.resolve(keyPath).startsWith(path.resolve(exeOsDir) + path.sep));
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function macKeychainGet(service = SERVICE) {
|
|
74
|
+
if (!nativeKeychainAllowed()) return null;
|
|
39
75
|
if (process.platform !== "darwin") return null;
|
|
40
76
|
try {
|
|
41
77
|
return execSync(
|
|
42
|
-
`security find-generic-password -s "${
|
|
78
|
+
`security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
43
79
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
44
80
|
).trim();
|
|
45
81
|
} catch {
|
|
46
82
|
return null;
|
|
47
83
|
}
|
|
48
84
|
}
|
|
49
|
-
function macKeychainSet(value) {
|
|
85
|
+
function macKeychainSet(value, service = SERVICE) {
|
|
86
|
+
if (!nativeKeychainAllowed()) return false;
|
|
50
87
|
if (process.platform !== "darwin") return false;
|
|
51
88
|
try {
|
|
52
89
|
try {
|
|
53
90
|
execSync(
|
|
54
|
-
`security delete-generic-password -s "${
|
|
91
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
55
92
|
{ timeout: 5e3 }
|
|
56
93
|
);
|
|
57
94
|
} catch {
|
|
58
95
|
}
|
|
59
96
|
execSync(
|
|
60
|
-
`security add-generic-password -s "${
|
|
97
|
+
`security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
|
|
61
98
|
{ timeout: 5e3 }
|
|
62
99
|
);
|
|
63
100
|
return true;
|
|
@@ -65,11 +102,12 @@ function macKeychainSet(value) {
|
|
|
65
102
|
return false;
|
|
66
103
|
}
|
|
67
104
|
}
|
|
68
|
-
function macKeychainDelete() {
|
|
105
|
+
function macKeychainDelete(service = SERVICE) {
|
|
106
|
+
if (!nativeKeychainAllowed()) return false;
|
|
69
107
|
if (process.platform !== "darwin") return false;
|
|
70
108
|
try {
|
|
71
109
|
execSync(
|
|
72
|
-
`security delete-generic-password -s "${
|
|
110
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
73
111
|
{ timeout: 5e3 }
|
|
74
112
|
);
|
|
75
113
|
return true;
|
|
@@ -77,22 +115,22 @@ function macKeychainDelete() {
|
|
|
77
115
|
return false;
|
|
78
116
|
}
|
|
79
117
|
}
|
|
80
|
-
function linuxSecretGet() {
|
|
81
|
-
if (
|
|
118
|
+
function linuxSecretGet(service = SERVICE) {
|
|
119
|
+
if (!linuxSecretAvailable()) return null;
|
|
82
120
|
try {
|
|
83
121
|
return execSync(
|
|
84
|
-
`secret-tool lookup service "${
|
|
122
|
+
`secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
85
123
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
86
124
|
).trim();
|
|
87
125
|
} catch {
|
|
88
126
|
return null;
|
|
89
127
|
}
|
|
90
128
|
}
|
|
91
|
-
function linuxSecretSet(value) {
|
|
92
|
-
if (
|
|
129
|
+
function linuxSecretSet(value, service = SERVICE) {
|
|
130
|
+
if (!linuxSecretAvailable()) return false;
|
|
93
131
|
try {
|
|
94
132
|
execSync(
|
|
95
|
-
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${
|
|
133
|
+
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
96
134
|
{ timeout: 5e3 }
|
|
97
135
|
);
|
|
98
136
|
return true;
|
|
@@ -100,11 +138,12 @@ function linuxSecretSet(value) {
|
|
|
100
138
|
return false;
|
|
101
139
|
}
|
|
102
140
|
}
|
|
103
|
-
function linuxSecretDelete() {
|
|
141
|
+
function linuxSecretDelete(service = SERVICE) {
|
|
142
|
+
if (!nativeKeychainAllowed()) return false;
|
|
104
143
|
if (process.platform !== "linux") return false;
|
|
105
144
|
try {
|
|
106
145
|
execSync(
|
|
107
|
-
`secret-tool clear service "${
|
|
146
|
+
`secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
108
147
|
{ timeout: 5e3 }
|
|
109
148
|
);
|
|
110
149
|
return true;
|
|
@@ -113,6 +152,7 @@ function linuxSecretDelete() {
|
|
|
113
152
|
}
|
|
114
153
|
}
|
|
115
154
|
async function tryKeytar() {
|
|
155
|
+
if (!nativeKeychainAllowed()) return null;
|
|
116
156
|
try {
|
|
117
157
|
return await import("keytar");
|
|
118
158
|
} catch {
|
|
@@ -186,7 +226,19 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
186
226
|
return "plaintext";
|
|
187
227
|
}
|
|
188
228
|
async function getMasterKey() {
|
|
189
|
-
|
|
229
|
+
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
230
|
+
if (!nativeValue) {
|
|
231
|
+
const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
|
|
232
|
+
if (legacyValue) {
|
|
233
|
+
const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
|
|
234
|
+
if (migrated) {
|
|
235
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
236
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
237
|
+
process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
|
|
238
|
+
}
|
|
239
|
+
nativeValue = legacyValue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
190
242
|
if (nativeValue) {
|
|
191
243
|
return Buffer.from(nativeValue, "base64");
|
|
192
244
|
}
|
|
@@ -194,12 +246,17 @@ async function getMasterKey() {
|
|
|
194
246
|
if (keytar) {
|
|
195
247
|
try {
|
|
196
248
|
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
197
|
-
|
|
198
|
-
|
|
249
|
+
const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
|
|
250
|
+
if (legacyKeytarValue) {
|
|
251
|
+
const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
|
|
199
252
|
if (migrated) {
|
|
200
253
|
process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
|
|
254
|
+
try {
|
|
255
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
201
258
|
}
|
|
202
|
-
return Buffer.from(
|
|
259
|
+
return Buffer.from(legacyKeytarValue, "base64");
|
|
203
260
|
}
|
|
204
261
|
} catch {
|
|
205
262
|
}
|
|
@@ -224,7 +281,7 @@ async function getMasterKey() {
|
|
|
224
281
|
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
225
282
|
if (!decrypted) {
|
|
226
283
|
process.stderr.write(
|
|
227
|
-
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os
|
|
284
|
+
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
|
|
228
285
|
);
|
|
229
286
|
return null;
|
|
230
287
|
}
|
|
@@ -233,6 +290,9 @@ async function getMasterKey() {
|
|
|
233
290
|
b64Value = content;
|
|
234
291
|
}
|
|
235
292
|
const key = Buffer.from(b64Value, "base64");
|
|
293
|
+
if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
294
|
+
return key;
|
|
295
|
+
}
|
|
236
296
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
237
297
|
if (migrated) {
|
|
238
298
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
@@ -260,6 +320,97 @@ async function getMasterKey() {
|
|
|
260
320
|
return null;
|
|
261
321
|
}
|
|
262
322
|
}
|
|
323
|
+
async function getKeyStorageInfo() {
|
|
324
|
+
if (macKeychainGet()) {
|
|
325
|
+
return {
|
|
326
|
+
kind: "macos-keychain",
|
|
327
|
+
secure: true,
|
|
328
|
+
note: "stored in macOS Keychain via built-in security CLI"
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (macKeychainGet(LEGACY_SERVICE)) {
|
|
332
|
+
return {
|
|
333
|
+
kind: "macos-keychain",
|
|
334
|
+
secure: true,
|
|
335
|
+
note: "stored in legacy macOS Keychain service exe-mem; next key read migrates it to exe-os"
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (linuxSecretGet()) {
|
|
339
|
+
return {
|
|
340
|
+
kind: "linux-secret-service",
|
|
341
|
+
secure: true,
|
|
342
|
+
note: "stored in Linux Secret Service via secret-tool"
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (linuxSecretGet(LEGACY_SERVICE)) {
|
|
346
|
+
return {
|
|
347
|
+
kind: "linux-secret-service",
|
|
348
|
+
secure: true,
|
|
349
|
+
note: "stored in legacy Linux Secret Service service exe-mem; next key read migrates it to exe-os"
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const keytar = await tryKeytar();
|
|
353
|
+
if (keytar) {
|
|
354
|
+
try {
|
|
355
|
+
if (await keytar.getPassword(SERVICE, ACCOUNT)) {
|
|
356
|
+
return {
|
|
357
|
+
kind: "legacy-keytar",
|
|
358
|
+
secure: true,
|
|
359
|
+
note: "stored in legacy keytar backend; will migrate to native keychain when possible"
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (await keytar.getPassword(LEGACY_SERVICE, ACCOUNT)) {
|
|
363
|
+
return {
|
|
364
|
+
kind: "legacy-keytar",
|
|
365
|
+
secure: true,
|
|
366
|
+
note: "stored in legacy keytar service exe-mem; will migrate to native exe-os keychain when possible"
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const keyPath = getKeyPath();
|
|
373
|
+
if (!existsSync(keyPath)) {
|
|
374
|
+
return {
|
|
375
|
+
kind: "missing",
|
|
376
|
+
secure: false,
|
|
377
|
+
path: keyPath,
|
|
378
|
+
note: "no key found in OS keychain, legacy keytar, or file fallback"
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
const content = (await readFile(keyPath, "utf-8")).trim();
|
|
383
|
+
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
384
|
+
return {
|
|
385
|
+
kind: "encrypted-file",
|
|
386
|
+
secure: true,
|
|
387
|
+
path: keyPath,
|
|
388
|
+
note: "stored in machine-bound encrypted file fallback"
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
392
|
+
return {
|
|
393
|
+
kind: "server-secret-file",
|
|
394
|
+
secure: true,
|
|
395
|
+
path: keyPath,
|
|
396
|
+
note: "stored as root-only trusted server secret file"
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
kind: "plaintext-file",
|
|
401
|
+
secure: false,
|
|
402
|
+
path: keyPath,
|
|
403
|
+
note: "stored in legacy plaintext file; reading it will migrate or encrypt it"
|
|
404
|
+
};
|
|
405
|
+
} catch {
|
|
406
|
+
return {
|
|
407
|
+
kind: "missing",
|
|
408
|
+
secure: false,
|
|
409
|
+
path: keyPath,
|
|
410
|
+
note: "key file exists but could not be read"
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
263
414
|
async function setMasterKey(key) {
|
|
264
415
|
const b64 = key.toString("base64");
|
|
265
416
|
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
@@ -285,10 +436,13 @@ async function setMasterKey(key) {
|
|
|
285
436
|
async function deleteMasterKey() {
|
|
286
437
|
macKeychainDelete();
|
|
287
438
|
linuxSecretDelete();
|
|
439
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
440
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
288
441
|
const keytar = await tryKeytar();
|
|
289
442
|
if (keytar) {
|
|
290
443
|
try {
|
|
291
444
|
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
445
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
292
446
|
} catch {
|
|
293
447
|
}
|
|
294
448
|
}
|
|
@@ -326,12 +480,14 @@ async function importMnemonic(mnemonic) {
|
|
|
326
480
|
const entropy = mnemonicToEntropy(trimmed);
|
|
327
481
|
return Buffer.from(entropy, "hex");
|
|
328
482
|
}
|
|
329
|
-
var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
|
|
483
|
+
var SERVICE, LEGACY_SERVICE, ACCOUNT, linuxSecretAvailability, ENCRYPTED_PREFIX;
|
|
330
484
|
var init_keychain = __esm({
|
|
331
485
|
"src/lib/keychain.ts"() {
|
|
332
486
|
"use strict";
|
|
333
|
-
SERVICE = "exe-
|
|
487
|
+
SERVICE = "exe-os";
|
|
488
|
+
LEGACY_SERVICE = "exe-mem";
|
|
334
489
|
ACCOUNT = "master-key";
|
|
490
|
+
linuxSecretAvailability = null;
|
|
335
491
|
ENCRYPTED_PREFIX = "enc:";
|
|
336
492
|
}
|
|
337
493
|
});
|
|
@@ -479,6 +635,10 @@ async function loadConfig() {
|
|
|
479
635
|
if (config.dbPath.startsWith("~")) {
|
|
480
636
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
481
637
|
}
|
|
638
|
+
const envDbPath = path2.join(dir, "memories.db");
|
|
639
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync3(config.dbPath) && existsSync3(envDbPath)) {
|
|
640
|
+
config.dbPath = envDbPath;
|
|
641
|
+
}
|
|
482
642
|
return config;
|
|
483
643
|
} catch {
|
|
484
644
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
@@ -1370,7 +1530,7 @@ import net from "net";
|
|
|
1370
1530
|
import os5 from "os";
|
|
1371
1531
|
import { spawn } from "child_process";
|
|
1372
1532
|
import { randomUUID } from "crypto";
|
|
1373
|
-
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
1533
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync as statSync2 } from "fs";
|
|
1374
1534
|
import path6 from "path";
|
|
1375
1535
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1376
1536
|
function handleData(chunk) {
|
|
@@ -1520,7 +1680,7 @@ function acquireSpawnLock() {
|
|
|
1520
1680
|
return true;
|
|
1521
1681
|
} catch {
|
|
1522
1682
|
try {
|
|
1523
|
-
const stat =
|
|
1683
|
+
const stat = statSync2(SPAWN_LOCK_PATH);
|
|
1524
1684
|
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
1525
1685
|
try {
|
|
1526
1686
|
unlinkSync2(SPAWN_LOCK_PATH);
|
|
@@ -1681,7 +1841,7 @@ function killAndRespawnDaemon() {
|
|
|
1681
1841
|
}
|
|
1682
1842
|
function isDaemonTooYoung() {
|
|
1683
1843
|
try {
|
|
1684
|
-
const stat =
|
|
1844
|
+
const stat = statSync2(PID_PATH);
|
|
1685
1845
|
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
1686
1846
|
} catch {
|
|
1687
1847
|
return false;
|
|
@@ -2092,6 +2252,9 @@ function getClient() {
|
|
|
2092
2252
|
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
2093
2253
|
return _daemonClient;
|
|
2094
2254
|
}
|
|
2255
|
+
if (!_resilientClient) {
|
|
2256
|
+
return _adapterClient;
|
|
2257
|
+
}
|
|
2095
2258
|
return _resilientClient;
|
|
2096
2259
|
}
|
|
2097
2260
|
async function initDaemonClient() {
|
|
@@ -3124,6 +3287,127 @@ async function ensureSchema() {
|
|
|
3124
3287
|
VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
|
|
3125
3288
|
END;
|
|
3126
3289
|
`);
|
|
3290
|
+
await client.executeMultiple(`
|
|
3291
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
3292
|
+
id TEXT PRIMARY KEY,
|
|
3293
|
+
agent_id TEXT NOT NULL,
|
|
3294
|
+
project_name TEXT,
|
|
3295
|
+
started_at TEXT NOT NULL,
|
|
3296
|
+
last_event_at TEXT NOT NULL,
|
|
3297
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
3298
|
+
properties TEXT DEFAULT '{}'
|
|
3299
|
+
);
|
|
3300
|
+
|
|
3301
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
|
|
3302
|
+
ON agent_sessions(agent_id, started_at);
|
|
3303
|
+
|
|
3304
|
+
CREATE TABLE IF NOT EXISTS agent_goals (
|
|
3305
|
+
id TEXT PRIMARY KEY,
|
|
3306
|
+
statement TEXT NOT NULL,
|
|
3307
|
+
owner_agent_id TEXT,
|
|
3308
|
+
project_name TEXT,
|
|
3309
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
3310
|
+
priority INTEGER NOT NULL DEFAULT 5,
|
|
3311
|
+
success_criteria TEXT,
|
|
3312
|
+
parent_goal_id TEXT,
|
|
3313
|
+
due_at TEXT,
|
|
3314
|
+
achieved_at TEXT,
|
|
3315
|
+
supersedes_id TEXT,
|
|
3316
|
+
created_at TEXT NOT NULL,
|
|
3317
|
+
updated_at TEXT NOT NULL,
|
|
3318
|
+
source_memory_id TEXT
|
|
3319
|
+
);
|
|
3320
|
+
|
|
3321
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
|
|
3322
|
+
ON agent_goals(project_name, status, priority);
|
|
3323
|
+
|
|
3324
|
+
CREATE TABLE IF NOT EXISTS agent_events (
|
|
3325
|
+
id TEXT PRIMARY KEY,
|
|
3326
|
+
event_type TEXT NOT NULL,
|
|
3327
|
+
occurred_at TEXT NOT NULL,
|
|
3328
|
+
sequence_index INTEGER NOT NULL,
|
|
3329
|
+
actor_agent_id TEXT,
|
|
3330
|
+
agent_role TEXT,
|
|
3331
|
+
project_name TEXT,
|
|
3332
|
+
session_id TEXT,
|
|
3333
|
+
task_id TEXT,
|
|
3334
|
+
goal_id TEXT,
|
|
3335
|
+
parent_event_id TEXT,
|
|
3336
|
+
intention TEXT,
|
|
3337
|
+
outcome TEXT,
|
|
3338
|
+
evidence_memory_id TEXT,
|
|
3339
|
+
impact TEXT,
|
|
3340
|
+
payload TEXT DEFAULT '{}',
|
|
3341
|
+
created_at TEXT NOT NULL
|
|
3342
|
+
);
|
|
3343
|
+
|
|
3344
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_time
|
|
3345
|
+
ON agent_events(occurred_at, sequence_index);
|
|
3346
|
+
|
|
3347
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
|
|
3348
|
+
ON agent_events(session_id, sequence_index);
|
|
3349
|
+
|
|
3350
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
|
|
3351
|
+
ON agent_events(goal_id, occurred_at);
|
|
3352
|
+
|
|
3353
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_memory
|
|
3354
|
+
ON agent_events(evidence_memory_id);
|
|
3355
|
+
|
|
3356
|
+
CREATE TABLE IF NOT EXISTS agent_goal_links (
|
|
3357
|
+
id TEXT PRIMARY KEY,
|
|
3358
|
+
goal_id TEXT NOT NULL,
|
|
3359
|
+
link_type TEXT NOT NULL,
|
|
3360
|
+
target_id TEXT NOT NULL,
|
|
3361
|
+
target_type TEXT NOT NULL,
|
|
3362
|
+
created_at TEXT NOT NULL
|
|
3363
|
+
);
|
|
3364
|
+
|
|
3365
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
|
|
3366
|
+
ON agent_goal_links(goal_id, target_type);
|
|
3367
|
+
|
|
3368
|
+
CREATE TABLE IF NOT EXISTS agent_semantic_labels (
|
|
3369
|
+
id TEXT PRIMARY KEY,
|
|
3370
|
+
source_memory_id TEXT NOT NULL,
|
|
3371
|
+
event_id TEXT,
|
|
3372
|
+
labeler TEXT NOT NULL,
|
|
3373
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
3374
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
3375
|
+
labels TEXT NOT NULL,
|
|
3376
|
+
created_at TEXT NOT NULL,
|
|
3377
|
+
updated_at TEXT NOT NULL
|
|
3378
|
+
);
|
|
3379
|
+
|
|
3380
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
|
|
3381
|
+
ON agent_semantic_labels(source_memory_id, labeler);
|
|
3382
|
+
|
|
3383
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
|
|
3384
|
+
ON agent_semantic_labels(event_id);
|
|
3385
|
+
|
|
3386
|
+
CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
|
|
3387
|
+
id TEXT PRIMARY KEY,
|
|
3388
|
+
project_name TEXT,
|
|
3389
|
+
session_id TEXT,
|
|
3390
|
+
window_start_at TEXT NOT NULL,
|
|
3391
|
+
window_end_at TEXT NOT NULL,
|
|
3392
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
3393
|
+
goal_count INTEGER NOT NULL DEFAULT 0,
|
|
3394
|
+
success_count INTEGER NOT NULL DEFAULT 0,
|
|
3395
|
+
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
3396
|
+
risk_count INTEGER NOT NULL DEFAULT 0,
|
|
3397
|
+
summary TEXT NOT NULL,
|
|
3398
|
+
learnings TEXT NOT NULL DEFAULT '[]',
|
|
3399
|
+
next_actions TEXT NOT NULL DEFAULT '[]',
|
|
3400
|
+
evidence_event_ids TEXT NOT NULL DEFAULT '[]',
|
|
3401
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
3402
|
+
created_at TEXT NOT NULL
|
|
3403
|
+
);
|
|
3404
|
+
|
|
3405
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
|
|
3406
|
+
ON agent_reflection_checkpoints(project_name, window_end_at);
|
|
3407
|
+
|
|
3408
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
|
|
3409
|
+
ON agent_reflection_checkpoints(session_id, window_end_at);
|
|
3410
|
+
`);
|
|
3127
3411
|
try {
|
|
3128
3412
|
await client.execute({
|
|
3129
3413
|
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
@@ -3611,8 +3895,8 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
3611
3895
|
}
|
|
3612
3896
|
function getCacheAgeMs() {
|
|
3613
3897
|
try {
|
|
3614
|
-
const { statSync:
|
|
3615
|
-
const s =
|
|
3898
|
+
const { statSync: statSync6 } = __require("fs");
|
|
3899
|
+
const s = statSync6(CACHE_PATH);
|
|
3616
3900
|
return Date.now() - s.mtimeMs;
|
|
3617
3901
|
} catch {
|
|
3618
3902
|
return Infinity;
|
|
@@ -4053,7 +4337,7 @@ __export(db_backup_exports, {
|
|
|
4053
4337
|
listBackups: () => listBackups,
|
|
4054
4338
|
rotateBackups: () => rotateBackups
|
|
4055
4339
|
});
|
|
4056
|
-
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync4, statSync as
|
|
4340
|
+
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync4, statSync as statSync3 } from "fs";
|
|
4057
4341
|
import path9 from "path";
|
|
4058
4342
|
function findActiveDb() {
|
|
4059
4343
|
for (const name of DB_NAMES) {
|
|
@@ -4097,7 +4381,7 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
|
4097
4381
|
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
4098
4382
|
const filePath = path9.join(BACKUP_DIR, file);
|
|
4099
4383
|
try {
|
|
4100
|
-
const stat =
|
|
4384
|
+
const stat = statSync3(filePath);
|
|
4101
4385
|
if (stat.mtimeMs < cutoff) {
|
|
4102
4386
|
unlinkSync4(filePath);
|
|
4103
4387
|
deleted++;
|
|
@@ -4115,7 +4399,7 @@ function listBackups() {
|
|
|
4115
4399
|
const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
4116
4400
|
return files.map((name) => {
|
|
4117
4401
|
const p = path9.join(BACKUP_DIR, name);
|
|
4118
|
-
const stat =
|
|
4402
|
+
const stat = statSync3(p);
|
|
4119
4403
|
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
4120
4404
|
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
4121
4405
|
} catch {
|
|
@@ -4148,10 +4432,10 @@ var init_db_backup = __esm({
|
|
|
4148
4432
|
// src/lib/cloud-sync.ts
|
|
4149
4433
|
var cloud_sync_exports = {};
|
|
4150
4434
|
__export(cloud_sync_exports, {
|
|
4151
|
-
|
|
4435
|
+
CLOUD_REUPLOAD_REQUIRED_MESSAGE: () => CLOUD_REUPLOAD_REQUIRED_MESSAGE,
|
|
4152
4436
|
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
4153
4437
|
buildRosterBlob: () => buildRosterBlob,
|
|
4154
|
-
|
|
4438
|
+
clearCloudReuploadRequired: () => clearCloudReuploadRequired,
|
|
4155
4439
|
cloudPull: () => cloudPull,
|
|
4156
4440
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
4157
4441
|
cloudPullBlob: () => cloudPullBlob,
|
|
@@ -4170,14 +4454,16 @@ __export(cloud_sync_exports, {
|
|
|
4170
4454
|
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
4171
4455
|
cloudPushRoster: () => cloudPushRoster,
|
|
4172
4456
|
cloudPushTasks: () => cloudPushTasks,
|
|
4457
|
+
cloudResetMemoryBlobs: () => cloudResetMemoryBlobs,
|
|
4173
4458
|
cloudSync: () => cloudSync,
|
|
4174
|
-
|
|
4459
|
+
getCloudReuploadRequired: () => getCloudReuploadRequired,
|
|
4460
|
+
markCloudReuploadRequired: () => markCloudReuploadRequired,
|
|
4175
4461
|
mergeConfig: () => mergeConfig,
|
|
4176
4462
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
4177
4463
|
pushToPostgres: () => pushToPostgres,
|
|
4178
4464
|
recordRosterDeletion: () => recordRosterDeletion
|
|
4179
4465
|
});
|
|
4180
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2, statSync as
|
|
4466
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync4 } from "fs";
|
|
4181
4467
|
import crypto4 from "crypto";
|
|
4182
4468
|
import path10 from "path";
|
|
4183
4469
|
import { homedir as homedir2 } from "os";
|
|
@@ -4224,18 +4510,36 @@ function loadPgClient() {
|
|
|
4224
4510
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
4225
4511
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4226
4512
|
if (explicitPath) {
|
|
4227
|
-
const
|
|
4228
|
-
const
|
|
4229
|
-
if (!
|
|
4230
|
-
return new
|
|
4513
|
+
const mod = await import(pathToFileURL3(explicitPath).href);
|
|
4514
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4515
|
+
if (!Ctor) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4516
|
+
return new Ctor();
|
|
4231
4517
|
}
|
|
4232
4518
|
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(homedir2(), "exe-db");
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4519
|
+
const packagePath = path10.join(exeDbRoot, "package.json");
|
|
4520
|
+
if (existsSync10(packagePath)) {
|
|
4521
|
+
const req = createRequire3(packagePath);
|
|
4522
|
+
const entry = req.resolve("@prisma/client");
|
|
4523
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
4524
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4525
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
4526
|
+
return new Ctor();
|
|
4527
|
+
}
|
|
4528
|
+
const { Pool } = await import("pg");
|
|
4529
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
4530
|
+
return {
|
|
4531
|
+
async $queryRawUnsafe(query, ...values) {
|
|
4532
|
+
const result = await pool.query(query, values);
|
|
4533
|
+
return result.rows;
|
|
4534
|
+
},
|
|
4535
|
+
async $executeRawUnsafe(query, ...values) {
|
|
4536
|
+
const result = await pool.query(query, values);
|
|
4537
|
+
return result.rowCount ?? 0;
|
|
4538
|
+
},
|
|
4539
|
+
async $disconnect() {
|
|
4540
|
+
await pool.end();
|
|
4541
|
+
}
|
|
4542
|
+
};
|
|
4239
4543
|
})().catch(() => {
|
|
4240
4544
|
_pgFailed = true;
|
|
4241
4545
|
_pgPromise = null;
|
|
@@ -4256,7 +4560,7 @@ async function pushToPostgres(records) {
|
|
|
4256
4560
|
let inserted = 0;
|
|
4257
4561
|
for (const rec of records) {
|
|
4258
4562
|
try {
|
|
4259
|
-
await prisma.$executeRawUnsafe(
|
|
4563
|
+
const changed = await prisma.$executeRawUnsafe(
|
|
4260
4564
|
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
4261
4565
|
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
4262
4566
|
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
@@ -4265,7 +4569,7 @@ async function pushToPostgres(records) {
|
|
|
4265
4569
|
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
4266
4570
|
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
4267
4571
|
);
|
|
4268
|
-
inserted
|
|
4572
|
+
inserted += Number(changed ?? 0);
|
|
4269
4573
|
} catch {
|
|
4270
4574
|
}
|
|
4271
4575
|
}
|
|
@@ -4370,6 +4674,23 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
4370
4674
|
return false;
|
|
4371
4675
|
}
|
|
4372
4676
|
}
|
|
4677
|
+
async function cloudResetMemoryBlobs(config) {
|
|
4678
|
+
assertSecureEndpoint(config.endpoint);
|
|
4679
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/reset-memory`, {
|
|
4680
|
+
method: "POST",
|
|
4681
|
+
headers: {
|
|
4682
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
4683
|
+
"Content-Type": "application/json",
|
|
4684
|
+
"X-Device-Id": loadDeviceId()
|
|
4685
|
+
},
|
|
4686
|
+
body: JSON.stringify({ confirm: "LOCAL DB IS SOURCE OF TRUTH" })
|
|
4687
|
+
});
|
|
4688
|
+
if (!resp.ok) {
|
|
4689
|
+
throw new Error(`cloud reset failed: HTTP ${resp.status}`);
|
|
4690
|
+
}
|
|
4691
|
+
const data = await resp.json();
|
|
4692
|
+
return { deleted: Number(data.deleted ?? 0), freedBytes: Number(data.freed_bytes ?? 0) };
|
|
4693
|
+
}
|
|
4373
4694
|
async function cloudPull(sinceVersion, config) {
|
|
4374
4695
|
assertSecureEndpoint(config.endpoint);
|
|
4375
4696
|
try {
|
|
@@ -4389,40 +4710,61 @@ async function cloudPull(sinceVersion, config) {
|
|
|
4389
4710
|
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
4390
4711
|
const data = await response.json();
|
|
4391
4712
|
const allRecords = [];
|
|
4392
|
-
|
|
4713
|
+
let maxReadableVersion = sinceVersion;
|
|
4714
|
+
let skippedBlobs = 0;
|
|
4715
|
+
for (const { version, blob } of data.blobs ?? []) {
|
|
4393
4716
|
try {
|
|
4394
4717
|
const compressed = decryptSyncBlob(blob);
|
|
4395
4718
|
const json = decompress(compressed).toString("utf8");
|
|
4396
4719
|
const records = JSON.parse(json);
|
|
4397
4720
|
allRecords.push(...records);
|
|
4721
|
+
const recordMax = records.reduce((max, rec) => {
|
|
4722
|
+
const v = Number(rec.version ?? 0);
|
|
4723
|
+
return Number.isFinite(v) ? Math.max(max, v) : max;
|
|
4724
|
+
}, 0);
|
|
4725
|
+
const blobVersion = Number(version ?? 0);
|
|
4726
|
+
maxReadableVersion = Math.max(
|
|
4727
|
+
maxReadableVersion,
|
|
4728
|
+
Number.isFinite(blobVersion) ? blobVersion : 0,
|
|
4729
|
+
recordMax
|
|
4730
|
+
);
|
|
4398
4731
|
} catch {
|
|
4732
|
+
skippedBlobs++;
|
|
4399
4733
|
continue;
|
|
4400
4734
|
}
|
|
4401
4735
|
}
|
|
4402
|
-
|
|
4736
|
+
if (skippedBlobs > 0) {
|
|
4737
|
+
logError(`[cloud-sync] PULL skipped ${skippedBlobs} undecryptable blob(s); pull cursor advanced only to last readable version ${maxReadableVersion}`);
|
|
4738
|
+
}
|
|
4739
|
+
return { records: allRecords, maxVersion: maxReadableVersion };
|
|
4403
4740
|
} catch (err) {
|
|
4404
4741
|
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
4405
4742
|
return { records: [], maxVersion: sinceVersion };
|
|
4406
4743
|
}
|
|
4407
4744
|
}
|
|
4408
|
-
async function
|
|
4745
|
+
async function getCloudReuploadRequired(client = getClient()) {
|
|
4409
4746
|
try {
|
|
4410
4747
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
4411
|
-
const
|
|
4412
|
-
return String(
|
|
4748
|
+
const result = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('cloud_reupload_required', 'cloud_relink_required')");
|
|
4749
|
+
return result.rows.some((row) => String(row.value ?? "") === "1");
|
|
4413
4750
|
} catch {
|
|
4414
4751
|
return false;
|
|
4415
4752
|
}
|
|
4416
4753
|
}
|
|
4417
|
-
async function
|
|
4754
|
+
async function clearCloudReuploadRequired(client = getClient()) {
|
|
4418
4755
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
4756
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '0')");
|
|
4419
4757
|
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
|
|
4420
4758
|
await client.execute({
|
|
4421
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('
|
|
4759
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reuploaded_at', ?)",
|
|
4422
4760
|
args: [(/* @__PURE__ */ new Date()).toISOString()]
|
|
4423
4761
|
});
|
|
4424
4762
|
await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
|
|
4425
4763
|
}
|
|
4764
|
+
async function markCloudReuploadRequired(client = getClient()) {
|
|
4765
|
+
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
4766
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
|
|
4767
|
+
}
|
|
4426
4768
|
async function cloudSync(config) {
|
|
4427
4769
|
if (!isSyncCryptoInitialized()) {
|
|
4428
4770
|
try {
|
|
@@ -4444,10 +4786,10 @@ async function cloudSync(config) {
|
|
|
4444
4786
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
4445
4787
|
}
|
|
4446
4788
|
try {
|
|
4447
|
-
if (await
|
|
4789
|
+
if (await getCloudReuploadRequired(client)) throw new Error(CLOUD_REUPLOAD_REQUIRED_MESSAGE);
|
|
4448
4790
|
} catch (err) {
|
|
4449
4791
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4450
|
-
if (msg.includes("
|
|
4792
|
+
if (msg === CLOUD_REUPLOAD_REQUIRED_MESSAGE || msg.includes("key rotation")) throw err;
|
|
4451
4793
|
}
|
|
4452
4794
|
try {
|
|
4453
4795
|
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -4705,7 +5047,7 @@ async function cloudSync(config) {
|
|
|
4705
5047
|
const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
|
|
4706
5048
|
const latestBackup = getLatestBackup2();
|
|
4707
5049
|
if (latestBackup) {
|
|
4708
|
-
const backupSize =
|
|
5050
|
+
const backupSize = statSync4(latestBackup).size;
|
|
4709
5051
|
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
4710
5052
|
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
4711
5053
|
const backupData = readFileSync7(latestBackup);
|
|
@@ -5376,7 +5718,7 @@ async function cloudPullDocuments(config) {
|
|
|
5376
5718
|
}
|
|
5377
5719
|
return { pulled };
|
|
5378
5720
|
}
|
|
5379
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed,
|
|
5721
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_REUPLOAD_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
|
|
5380
5722
|
var init_cloud_sync = __esm({
|
|
5381
5723
|
"src/lib/cloud-sync.ts"() {
|
|
5382
5724
|
"use strict";
|
|
@@ -5395,7 +5737,7 @@ var init_cloud_sync = __esm({
|
|
|
5395
5737
|
LOCK_STALE_MS = 3e4;
|
|
5396
5738
|
_pgPromise = null;
|
|
5397
5739
|
_pgFailed = false;
|
|
5398
|
-
|
|
5740
|
+
CLOUD_REUPLOAD_REQUIRED_MESSAGE = "Cloud sync is blocked because this device rotated its memory encryption key. Run `exe-os cloud reupload` first to re-upload the cloud backup with the new key.";
|
|
5399
5741
|
ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
|
|
5400
5742
|
}
|
|
5401
5743
|
});
|
|
@@ -5665,7 +6007,7 @@ __export(shard_manager_exports, {
|
|
|
5665
6007
|
shardExists: () => shardExists
|
|
5666
6008
|
});
|
|
5667
6009
|
import path11 from "path";
|
|
5668
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3, renameSync as renameSync3, statSync as
|
|
6010
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3, renameSync as renameSync3, statSync as statSync5 } from "fs";
|
|
5669
6011
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5670
6012
|
function initShardManager(encryptionKey) {
|
|
5671
6013
|
_encryptionKey = encryptionKey;
|
|
@@ -5729,7 +6071,7 @@ async function auditShardHealth(options = {}) {
|
|
|
5729
6071
|
const shards = [];
|
|
5730
6072
|
for (const name of names) {
|
|
5731
6073
|
const dbPath = path11.join(SHARDS_DIR, `${name}.db`);
|
|
5732
|
-
const stat =
|
|
6074
|
+
const stat = statSync5(dbPath);
|
|
5733
6075
|
const item = {
|
|
5734
6076
|
name,
|
|
5735
6077
|
path: dbPath,
|
|
@@ -5982,7 +6324,7 @@ async function getReadyShardClient(projectName) {
|
|
|
5982
6324
|
_shardLastAccess.delete(safeName);
|
|
5983
6325
|
const dbPath = path11.join(SHARDS_DIR, `${safeName}.db`);
|
|
5984
6326
|
if (existsSync11(dbPath)) {
|
|
5985
|
-
const stat =
|
|
6327
|
+
const stat = statSync5(dbPath);
|
|
5986
6328
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5987
6329
|
const archivedPath = path11.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
5988
6330
|
renameSync3(dbPath, archivedPath);
|
|
@@ -6489,6 +6831,274 @@ var init_memory_cards = __esm({
|
|
|
6489
6831
|
}
|
|
6490
6832
|
});
|
|
6491
6833
|
|
|
6834
|
+
// src/lib/agentic-ontology.ts
|
|
6835
|
+
var agentic_ontology_exports = {};
|
|
6836
|
+
__export(agentic_ontology_exports, {
|
|
6837
|
+
clean: () => clean,
|
|
6838
|
+
extractGoalCandidates: () => extractGoalCandidates,
|
|
6839
|
+
inferIntention: () => inferIntention,
|
|
6840
|
+
inferOntologyEventType: () => inferOntologyEventType,
|
|
6841
|
+
inferOutcome: () => inferOutcome,
|
|
6842
|
+
inferSemanticLabel: () => inferSemanticLabel,
|
|
6843
|
+
insertOntologyForBatch: () => insertOntologyForBatch,
|
|
6844
|
+
insertOntologyForMemory: () => insertOntologyForMemory,
|
|
6845
|
+
ontologyPayload: () => ontologyPayload,
|
|
6846
|
+
stableId: () => stableId2
|
|
6847
|
+
});
|
|
6848
|
+
import { createHash as createHash3 } from "crypto";
|
|
6849
|
+
function stableId2(...parts) {
|
|
6850
|
+
return createHash3("sha256").update(parts.map((p) => String(p ?? "")).join("::")).digest("hex").slice(0, 32);
|
|
6851
|
+
}
|
|
6852
|
+
function clean(text, max = 240) {
|
|
6853
|
+
return text.replace(/\u0000/g, "").replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
|
|
6854
|
+
}
|
|
6855
|
+
function inferOntologyEventType(row) {
|
|
6856
|
+
const lower = row.raw_text.toLowerCase();
|
|
6857
|
+
if (row.has_error) return "error";
|
|
6858
|
+
if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published)\b/.test(lower)) return "milestone";
|
|
6859
|
+
if (/\b(blocked|failed|error|bug|regression|broken)\b/.test(lower)) return "problem";
|
|
6860
|
+
if (/\b(decided|decision|adr|we chose|approved|rejected)\b/.test(lower)) return "decision";
|
|
6861
|
+
if (/\b(goal|need to|we need|want to|trying to|objective)\b/.test(lower)) return "goal_signal";
|
|
6862
|
+
if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) return "tool_action";
|
|
6863
|
+
if (row.tool_name.startsWith("memory_card")) return "memory_card";
|
|
6864
|
+
return "memory_observation";
|
|
6865
|
+
}
|
|
6866
|
+
function inferIntention(row) {
|
|
6867
|
+
if (row.intent) return clean(row.intent, 220);
|
|
6868
|
+
const text = clean(row.raw_text, 1e3);
|
|
6869
|
+
const patterns = [
|
|
6870
|
+
/(?:we need to|need to|let'?s|i want to|we should|goal is to|objective is to|trying to)\s+([^.!?\n]{8,220})/i,
|
|
6871
|
+
/(?:so that|in order to)\s+([^.!?\n]{8,220})/i,
|
|
6872
|
+
/(?:task|plan):\s*([^.!?\n]{8,220})/i
|
|
6873
|
+
];
|
|
6874
|
+
for (const p of patterns) {
|
|
6875
|
+
const m = text.match(p);
|
|
6876
|
+
if (m?.[1]) return clean(m[1], 220);
|
|
6877
|
+
}
|
|
6878
|
+
if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) {
|
|
6879
|
+
return `${row.tool_name} during ${row.project_name}`;
|
|
6880
|
+
}
|
|
6881
|
+
return null;
|
|
6882
|
+
}
|
|
6883
|
+
function inferOutcome(row) {
|
|
6884
|
+
if (row.outcome) return clean(row.outcome, 220);
|
|
6885
|
+
if (row.has_error) return "error";
|
|
6886
|
+
const lower = row.raw_text.toLowerCase();
|
|
6887
|
+
if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published|passed)\b/.test(lower)) return "success_signal";
|
|
6888
|
+
if (/\b(blocked|failed|error|regression|broken|not working|could not)\b/.test(lower)) return "failure_signal";
|
|
6889
|
+
if (/\b(warning|risk|concern|caveat)\b/.test(lower)) return "risk_signal";
|
|
6890
|
+
return null;
|
|
6891
|
+
}
|
|
6892
|
+
function extractGoalCandidates(row) {
|
|
6893
|
+
const text = clean(row.raw_text, 1600);
|
|
6894
|
+
const patterns = [
|
|
6895
|
+
/(?:we need to|need to|i want to|we should|goal is to|objective is to|trying to|let'?s)\s+([^.!?\n]{12,220})/gi,
|
|
6896
|
+
/(?:success means|success criteria|so that)\s+([^.!?\n]{12,220})/gi
|
|
6897
|
+
];
|
|
6898
|
+
const out = [];
|
|
6899
|
+
for (const pattern of patterns) {
|
|
6900
|
+
for (const m of text.matchAll(pattern)) {
|
|
6901
|
+
const candidate = clean(m[1] ?? "", 220);
|
|
6902
|
+
if (candidate.length >= 12 && !out.some((x) => x.toLowerCase() === candidate.toLowerCase())) out.push(candidate);
|
|
6903
|
+
if (out.length >= 3) return out;
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
return out;
|
|
6907
|
+
}
|
|
6908
|
+
function uniq(values, max = 6) {
|
|
6909
|
+
const out = [];
|
|
6910
|
+
for (const value of values.map((v) => clean(v, 220)).filter(Boolean)) {
|
|
6911
|
+
if (!out.some((x) => x.toLowerCase() === value.toLowerCase())) out.push(value);
|
|
6912
|
+
if (out.length >= max) break;
|
|
6913
|
+
}
|
|
6914
|
+
return out;
|
|
6915
|
+
}
|
|
6916
|
+
function extractMatches(text, patterns, max = 5) {
|
|
6917
|
+
const out = [];
|
|
6918
|
+
for (const pattern of patterns) {
|
|
6919
|
+
for (const match of text.matchAll(pattern)) {
|
|
6920
|
+
const value = match[1] ?? match[0];
|
|
6921
|
+
if (value) out.push(value);
|
|
6922
|
+
if (out.length >= max) return uniq(out, max);
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
return uniq(out, max);
|
|
6926
|
+
}
|
|
6927
|
+
function inferSemanticLabel(row) {
|
|
6928
|
+
const text = clean(row.raw_text, 2400);
|
|
6929
|
+
const eventType = inferOntologyEventType(row);
|
|
6930
|
+
const intention = inferIntention(row);
|
|
6931
|
+
const outcome = inferOutcome(row);
|
|
6932
|
+
const goals = extractGoalCandidates(row);
|
|
6933
|
+
const milestones = extractMatches(text, [
|
|
6934
|
+
/\b(?:completed|finished|fixed|resolved|shipped|deployed|published|pushed|passed)\b([^.!?\n]{0,180})/gi,
|
|
6935
|
+
/(?:milestone|done):\s*([^.!?\n]{8,220})/gi
|
|
6936
|
+
]);
|
|
6937
|
+
const problems = extractMatches(text, [
|
|
6938
|
+
/\b(?:blocked by|failed because|bug|regression|broken|not working|error)\b([^.!?\n]{0,180})/gi,
|
|
6939
|
+
/(?:problem|issue|risk):\s*([^.!?\n]{8,220})/gi
|
|
6940
|
+
]);
|
|
6941
|
+
const decisions = extractMatches(text, [
|
|
6942
|
+
/(?:decided|decision|adr|we chose|approved|rejected)\s+([^.!?\n]{8,220})/gi
|
|
6943
|
+
]);
|
|
6944
|
+
const temporalAnchors = extractMatches(text, [
|
|
6945
|
+
/\b(\d{4}-\d{2}-\d{2}(?:[T ][0-9:.+-Z]+)?)\b/g,
|
|
6946
|
+
/\b(today|yesterday|tomorrow|this week|next week|last week|morning|afternoon|tonight)\b/gi
|
|
6947
|
+
], 8);
|
|
6948
|
+
const nextActions = extractMatches(text, [
|
|
6949
|
+
/(?:next|todo|follow[- ]?up|remaining|need to)\s*:?\s*([^.!?\n]{8,220})/gi
|
|
6950
|
+
]);
|
|
6951
|
+
const actors = uniq([
|
|
6952
|
+
row.agent_id,
|
|
6953
|
+
...extractMatches(text, [/\b(?:agent|employee|owner|assignee)[:= ]+([a-zA-Z][a-zA-Z0-9_-]{1,40})/gi], 5)
|
|
6954
|
+
], 6);
|
|
6955
|
+
const successSignals = milestones.length ? milestones : outcome === "success_signal" ? [clean(text, 180)] : [];
|
|
6956
|
+
const failureSignals = problems.length ? problems : outcome === "failure_signal" || row.has_error ? [clean(text, 180)] : [];
|
|
6957
|
+
const impact = successSignals.length && failureSignals.length ? "mixed" : failureSignals.length ? "negative" : successSignals.length ? "positive" : "neutral";
|
|
6958
|
+
const signalCount = goals.length + milestones.length + problems.length + decisions.length + nextActions.length;
|
|
6959
|
+
return {
|
|
6960
|
+
labeler: "deterministic",
|
|
6961
|
+
schemaVersion: 1,
|
|
6962
|
+
eventType,
|
|
6963
|
+
intention,
|
|
6964
|
+
outcome,
|
|
6965
|
+
impact,
|
|
6966
|
+
confidence: Math.min(0.95, 0.45 + signalCount * 0.08 + (intention ? 0.1 : 0) + (outcome ? 0.1 : 0)),
|
|
6967
|
+
goals,
|
|
6968
|
+
milestones,
|
|
6969
|
+
problems,
|
|
6970
|
+
decisions,
|
|
6971
|
+
actors,
|
|
6972
|
+
temporalAnchors,
|
|
6973
|
+
successSignals,
|
|
6974
|
+
failureSignals,
|
|
6975
|
+
nextActions,
|
|
6976
|
+
summary: clean(text, 280)
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
function ontologyPayload(row) {
|
|
6980
|
+
const semantic = inferSemanticLabel(row);
|
|
6981
|
+
return {
|
|
6982
|
+
tool_name: row.tool_name,
|
|
6983
|
+
memory_version: row.version ?? null,
|
|
6984
|
+
domain: row.domain ?? null,
|
|
6985
|
+
trajectory: row.trajectory ? safeJson(row.trajectory) : null,
|
|
6986
|
+
semantic
|
|
6987
|
+
};
|
|
6988
|
+
}
|
|
6989
|
+
function safeJson(value) {
|
|
6990
|
+
try {
|
|
6991
|
+
return JSON.parse(value);
|
|
6992
|
+
} catch {
|
|
6993
|
+
return value.slice(0, 1e3);
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
async function resolveClient(client) {
|
|
6997
|
+
if (client) return client;
|
|
6998
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
6999
|
+
return getClient2();
|
|
7000
|
+
}
|
|
7001
|
+
async function insertOntologyForMemory(row, client) {
|
|
7002
|
+
const db = await resolveClient(client);
|
|
7003
|
+
const occurredAt = row.timestamp;
|
|
7004
|
+
const sequence = Number(row.version ?? 0) || Math.floor(new Date(occurredAt).getTime() / 1e3);
|
|
7005
|
+
const eventType = inferOntologyEventType(row);
|
|
7006
|
+
const intention = inferIntention(row);
|
|
7007
|
+
const outcome = inferOutcome(row);
|
|
7008
|
+
const eventId = stableId2("event", row.id);
|
|
7009
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7010
|
+
await db.execute({
|
|
7011
|
+
sql: `INSERT INTO agent_sessions (id, agent_id, project_name, started_at, last_event_at, event_count, properties)
|
|
7012
|
+
VALUES (?, ?, ?, ?, ?, 1, ?)
|
|
7013
|
+
ON CONFLICT(id) DO UPDATE SET last_event_at = MAX(last_event_at, excluded.last_event_at),
|
|
7014
|
+
event_count = event_count + 1`,
|
|
7015
|
+
args: [row.session_id, row.agent_id, row.project_name, occurredAt, occurredAt, JSON.stringify({ agent_role: row.agent_role })]
|
|
7016
|
+
});
|
|
7017
|
+
await db.execute({
|
|
7018
|
+
sql: `INSERT OR IGNORE INTO agent_events
|
|
7019
|
+
(id, event_type, occurred_at, sequence_index, actor_agent_id, agent_role, project_name,
|
|
7020
|
+
session_id, task_id, goal_id, parent_event_id, intention, outcome, evidence_memory_id,
|
|
7021
|
+
impact, payload, created_at)
|
|
7022
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, ?)`,
|
|
7023
|
+
args: [
|
|
7024
|
+
eventId,
|
|
7025
|
+
eventType,
|
|
7026
|
+
occurredAt,
|
|
7027
|
+
sequence,
|
|
7028
|
+
row.agent_id,
|
|
7029
|
+
row.agent_role,
|
|
7030
|
+
row.project_name,
|
|
7031
|
+
row.session_id,
|
|
7032
|
+
row.task_id ?? null,
|
|
7033
|
+
intention,
|
|
7034
|
+
outcome,
|
|
7035
|
+
row.id,
|
|
7036
|
+
row.has_error ? "negative" : outcome === "success_signal" ? "positive" : "neutral",
|
|
7037
|
+
JSON.stringify(ontologyPayload(row)),
|
|
7038
|
+
now
|
|
7039
|
+
]
|
|
7040
|
+
});
|
|
7041
|
+
const semantic = inferSemanticLabel(row);
|
|
7042
|
+
await db.execute({
|
|
7043
|
+
sql: `INSERT INTO agent_semantic_labels
|
|
7044
|
+
(id, source_memory_id, event_id, labeler, schema_version, confidence, labels, created_at, updated_at)
|
|
7045
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7046
|
+
ON CONFLICT(id) DO UPDATE SET confidence = excluded.confidence,
|
|
7047
|
+
labels = excluded.labels, updated_at = excluded.updated_at`,
|
|
7048
|
+
args: [
|
|
7049
|
+
stableId2("semantic", row.id, semantic.labeler, semantic.schemaVersion),
|
|
7050
|
+
row.id,
|
|
7051
|
+
eventId,
|
|
7052
|
+
semantic.labeler,
|
|
7053
|
+
semantic.schemaVersion,
|
|
7054
|
+
semantic.confidence,
|
|
7055
|
+
JSON.stringify(semantic),
|
|
7056
|
+
now,
|
|
7057
|
+
now
|
|
7058
|
+
]
|
|
7059
|
+
});
|
|
7060
|
+
for (const statement of extractGoalCandidates(row)) {
|
|
7061
|
+
const goalId = stableId2("goal", row.project_name, statement.toLowerCase());
|
|
7062
|
+
await db.execute({
|
|
7063
|
+
sql: `INSERT INTO agent_goals
|
|
7064
|
+
(id, statement, owner_agent_id, project_name, status, priority, success_criteria,
|
|
7065
|
+
parent_goal_id, due_at, achieved_at, supersedes_id, created_at, updated_at, source_memory_id)
|
|
7066
|
+
VALUES (?, ?, ?, ?, 'open', 5, NULL, NULL, NULL, NULL, NULL, ?, ?, ?)
|
|
7067
|
+
ON CONFLICT(id) DO UPDATE SET updated_at = excluded.updated_at`,
|
|
7068
|
+
args: [goalId, statement, row.agent_id, row.project_name, now, now, row.id]
|
|
7069
|
+
});
|
|
7070
|
+
await db.execute({
|
|
7071
|
+
sql: `INSERT OR IGNORE INTO agent_goal_links
|
|
7072
|
+
(id, goal_id, link_type, target_id, target_type, created_at)
|
|
7073
|
+
VALUES (?, ?, 'evidence', ?, 'memory', ?)`,
|
|
7074
|
+
args: [stableId2("goal_link", goalId, row.id, "memory"), goalId, row.id, now]
|
|
7075
|
+
});
|
|
7076
|
+
await db.execute({
|
|
7077
|
+
sql: `INSERT OR IGNORE INTO agent_goal_links
|
|
7078
|
+
(id, goal_id, link_type, target_id, target_type, created_at)
|
|
7079
|
+
VALUES (?, ?, 'event', ?, 'event', ?)`,
|
|
7080
|
+
args: [stableId2("goal_link", goalId, eventId, "event"), goalId, eventId, now]
|
|
7081
|
+
});
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
async function insertOntologyForBatch(rows, client) {
|
|
7085
|
+
const db = await resolveClient(client);
|
|
7086
|
+
let count = 0;
|
|
7087
|
+
for (const row of rows) {
|
|
7088
|
+
try {
|
|
7089
|
+
await insertOntologyForMemory(row, db);
|
|
7090
|
+
count++;
|
|
7091
|
+
} catch {
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
return count;
|
|
7095
|
+
}
|
|
7096
|
+
var init_agentic_ontology = __esm({
|
|
7097
|
+
"src/lib/agentic-ontology.ts"() {
|
|
7098
|
+
"use strict";
|
|
7099
|
+
}
|
|
7100
|
+
});
|
|
7101
|
+
|
|
6492
7102
|
// src/lib/store.ts
|
|
6493
7103
|
var store_exports = {};
|
|
6494
7104
|
__export(store_exports, {
|
|
@@ -6832,6 +7442,11 @@ async function flushBatch() {
|
|
|
6832
7442
|
await insertMemoryCardsForBatch2(batch);
|
|
6833
7443
|
} catch {
|
|
6834
7444
|
}
|
|
7445
|
+
try {
|
|
7446
|
+
const { insertOntologyForBatch: insertOntologyForBatch2 } = await Promise.resolve().then(() => (init_agentic_ontology(), agentic_ontology_exports));
|
|
7447
|
+
await insertOntologyForBatch2(batch);
|
|
7448
|
+
} catch {
|
|
7449
|
+
}
|
|
6835
7450
|
schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
|
|
6836
7451
|
_pendingRecords.splice(0, batch.length);
|
|
6837
7452
|
try {
|
|
@@ -7201,8 +7816,8 @@ function ask(rl, question) {
|
|
|
7201
7816
|
rl.question(question, (answer) => resolve(answer.trim()));
|
|
7202
7817
|
});
|
|
7203
7818
|
}
|
|
7204
|
-
function
|
|
7205
|
-
return options.
|
|
7819
|
+
function shouldRunReuploadMutation(options) {
|
|
7820
|
+
return options.reuploadRequired && !options.dryRun && (options.force || options.interactive);
|
|
7206
7821
|
}
|
|
7207
7822
|
async function runCloudSyncOnce() {
|
|
7208
7823
|
const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
@@ -7240,12 +7855,12 @@ async function setupMode() {
|
|
|
7240
7855
|
console.log(" EXE CLOUD SETUP");
|
|
7241
7856
|
console.log(BAR);
|
|
7242
7857
|
console.log("");
|
|
7243
|
-
console.log("Step 1:
|
|
7858
|
+
console.log("Step 1: Memory encryption key");
|
|
7244
7859
|
const existingKey = await getMasterKey();
|
|
7245
7860
|
if (existingKey) {
|
|
7246
|
-
console.log(" \u2713
|
|
7861
|
+
console.log(" \u2713 This device already has its memory encryption key. Skipping.\n");
|
|
7247
7862
|
} else {
|
|
7248
|
-
const hasPhrase = await ask(rl, " Do you have a 24-word recovery phrase from another device? (y/n) ");
|
|
7863
|
+
const hasPhrase = await ask(rl, " Do you have a 24-word recovery phrase from another Exe OS device? (y/n) ");
|
|
7249
7864
|
if (hasPhrase.toLowerCase().startsWith("y")) {
|
|
7250
7865
|
const mnemonic = await ask(rl, " Paste your 24 words: ");
|
|
7251
7866
|
try {
|
|
@@ -7258,8 +7873,9 @@ async function setupMode() {
|
|
|
7258
7873
|
process.exit(1);
|
|
7259
7874
|
}
|
|
7260
7875
|
} else {
|
|
7261
|
-
console.log(" No recovery phrase
|
|
7262
|
-
console.log("
|
|
7876
|
+
console.log(" No recovery phrase yet.");
|
|
7877
|
+
console.log(" On your first device, run: exe-os setup");
|
|
7878
|
+
console.log(" Then reveal the phrase there with: exe-os cloud link --show-full\n");
|
|
7263
7879
|
rl.close();
|
|
7264
7880
|
return;
|
|
7265
7881
|
}
|
|
@@ -7287,9 +7903,9 @@ async function setupMode() {
|
|
|
7287
7903
|
}
|
|
7288
7904
|
}
|
|
7289
7905
|
} else {
|
|
7290
|
-
const apiKey = await ask(rl, " Paste your API key (exe_sk_...): ");
|
|
7906
|
+
const apiKey = await ask(rl, " Paste your Exe Cloud API key (exe_sk_...): ");
|
|
7291
7907
|
if (!apiKey) {
|
|
7292
|
-
console.log(" Skipped.
|
|
7908
|
+
console.log(" Skipped. You can configure cloud later with: exe-os cloud");
|
|
7293
7909
|
rl.close();
|
|
7294
7910
|
return;
|
|
7295
7911
|
}
|
|
@@ -7379,7 +7995,7 @@ async function linkMode() {
|
|
|
7379
7995
|
console.log(" EXE CLOUD \u2014 DEVICE SYNC INFO");
|
|
7380
7996
|
console.log(BAR);
|
|
7381
7997
|
console.log("");
|
|
7382
|
-
console.log("
|
|
7998
|
+
console.log("On the new device, run `exe-os cloud` and paste these when prompted:\n");
|
|
7383
7999
|
console.log(" 24-word recovery phrase:");
|
|
7384
8000
|
if (showFull) {
|
|
7385
8001
|
console.log(` ${mnemonic}
|
|
@@ -7412,6 +8028,38 @@ async function linkMode() {
|
|
|
7412
8028
|
async function statusMode() {
|
|
7413
8029
|
const key = await getMasterKey();
|
|
7414
8030
|
const config = await loadConfig();
|
|
8031
|
+
let reuploadRequired = false;
|
|
8032
|
+
let localMemoryVersion = 0;
|
|
8033
|
+
let lastPushedMemoryVersion = 0;
|
|
8034
|
+
let pendingMemoryCount = 0;
|
|
8035
|
+
let lastPullVersion = 0;
|
|
8036
|
+
let cloudReuploadedAt = null;
|
|
8037
|
+
try {
|
|
8038
|
+
const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
8039
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
8040
|
+
const { getCloudReuploadRequired: getCloudReuploadRequired2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
8041
|
+
await initStore2({ lightweight: true });
|
|
8042
|
+
const client = getClient2();
|
|
8043
|
+
reuploadRequired = await getCloudReuploadRequired2(client);
|
|
8044
|
+
const maxVersion = await client.execute("SELECT COALESCE(MAX(version), 0) as version FROM memories WHERE scope IS NULL OR scope != 'personal'");
|
|
8045
|
+
localMemoryVersion = Number(maxVersion.rows[0]?.version ?? 0);
|
|
8046
|
+
const meta = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('last_cloud_push_version', 'last_cloud_pull_version', 'cloud_reuploaded_at', 'cloud_relinked_at')");
|
|
8047
|
+
for (const row of meta.rows) {
|
|
8048
|
+
const keyName = String(row.key ?? "");
|
|
8049
|
+
const value = String(row.value ?? "");
|
|
8050
|
+
if (keyName === "last_cloud_push_version") lastPushedMemoryVersion = Number(value || 0);
|
|
8051
|
+
if (keyName === "last_cloud_pull_version") lastPullVersion = Number(value || 0);
|
|
8052
|
+
if (keyName === "cloud_reuploaded_at" || keyName === "cloud_relinked_at") cloudReuploadedAt = value;
|
|
8053
|
+
}
|
|
8054
|
+
const pending = await client.execute({
|
|
8055
|
+
sql: "SELECT COUNT(*) as cnt FROM memories WHERE (scope IS NULL OR scope != 'personal') AND version > ?",
|
|
8056
|
+
args: [lastPushedMemoryVersion]
|
|
8057
|
+
});
|
|
8058
|
+
pendingMemoryCount = Number(pending.rows[0]?.cnt ?? 0);
|
|
8059
|
+
} catch {
|
|
8060
|
+
} finally {
|
|
8061
|
+
await disposeCloudCliResources();
|
|
8062
|
+
}
|
|
7415
8063
|
console.log(BAR);
|
|
7416
8064
|
console.log(" EXE CLOUD STATUS");
|
|
7417
8065
|
console.log(BAR);
|
|
@@ -7424,8 +8072,26 @@ async function statusMode() {
|
|
|
7424
8072
|
}
|
|
7425
8073
|
if (config.cloud?.apiKey) {
|
|
7426
8074
|
const masked = config.cloud.apiKey.slice(0, 10) + "..." + config.cloud.apiKey.slice(-4);
|
|
7427
|
-
console.log(` Cloud
|
|
8075
|
+
console.log(` Cloud account: connected to Exe Cloud (${masked})`);
|
|
7428
8076
|
console.log(` Endpoint: ${config.cloud.endpoint}`);
|
|
8077
|
+
console.log(" Status check: read-only; no data was uploaded or downloaded");
|
|
8078
|
+
console.log("");
|
|
8079
|
+
if (reuploadRequired) {
|
|
8080
|
+
console.log(" Sync status: NOT SYNCING \u2014 blocked for safety");
|
|
8081
|
+
console.log(" Why: this device changed its memory encryption key");
|
|
8082
|
+
console.log(" Required fix: run `exe-os cloud reupload`");
|
|
8083
|
+
console.log(" After reupload: automatic background sync will resume");
|
|
8084
|
+
} else if (pendingMemoryCount > 0) {
|
|
8085
|
+
console.log(" Sync status: SYNC ENABLED \u2014 waiting for background sync");
|
|
8086
|
+
console.log(` Pending upload: ${pendingMemoryCount} local memor${pendingMemoryCount === 1 ? "y" : "ies"}`);
|
|
8087
|
+
console.log(" Manual option: run `exe-os cloud sync` to sync now");
|
|
8088
|
+
} else {
|
|
8089
|
+
console.log(" Sync status: SYNCED \u2014 no local memory changes waiting to upload");
|
|
8090
|
+
console.log(" Auto sync: enabled \u2014 daemon syncs in the background");
|
|
8091
|
+
}
|
|
8092
|
+
console.log("");
|
|
8093
|
+
console.log(` Memory cursor: local=${localMemoryVersion} last pushed=${lastPushedMemoryVersion} last pulled=${lastPullVersion}`);
|
|
8094
|
+
if (cloudReuploadedAt) console.log(` Last re-upload: ${cloudReuploadedAt}`);
|
|
7429
8095
|
} else if (config.turso?.url) {
|
|
7430
8096
|
console.log(` Cloud sync: \u2713 libSQL (legacy) (${config.turso.url})`);
|
|
7431
8097
|
} else {
|
|
@@ -7434,41 +8100,45 @@ async function statusMode() {
|
|
|
7434
8100
|
console.log("");
|
|
7435
8101
|
console.log(BAR);
|
|
7436
8102
|
}
|
|
7437
|
-
async function
|
|
8103
|
+
async function reuploadMode() {
|
|
7438
8104
|
const dryRun = process.argv.includes("--dry-run");
|
|
7439
8105
|
const force = process.argv.includes("--local-db-is-source-of-truth") || process.argv.includes("--yes");
|
|
7440
8106
|
const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
7441
8107
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
7442
|
-
const {
|
|
8108
|
+
const { getCloudReuploadRequired: getCloudReuploadRequired2, clearCloudReuploadRequired: clearCloudReuploadRequired2, markCloudReuploadRequired: markCloudReuploadRequired2, cloudResetMemoryBlobs: cloudResetMemoryBlobs2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
7443
8109
|
await initStore2({ lightweight: true });
|
|
7444
8110
|
try {
|
|
7445
8111
|
const client = getClient2();
|
|
7446
|
-
const required = await
|
|
8112
|
+
const required = await getCloudReuploadRequired2(client);
|
|
7447
8113
|
console.log(BAR);
|
|
7448
|
-
console.log(" EXE CLOUD
|
|
8114
|
+
console.log(" EXE CLOUD RE-UPLOAD AFTER KEY ROTATION");
|
|
7449
8115
|
console.log(BAR);
|
|
7450
8116
|
console.log("");
|
|
7451
8117
|
if (!required) {
|
|
7452
|
-
console.log("Cloud
|
|
8118
|
+
console.log("Cloud status: connected");
|
|
8119
|
+
console.log("Normal cloud sync is safe to run.");
|
|
7453
8120
|
return;
|
|
7454
8121
|
}
|
|
7455
|
-
console.log("Cloud
|
|
8122
|
+
console.log("Cloud status: needs cloud re-upload after key rotation");
|
|
7456
8123
|
console.log("");
|
|
7457
|
-
console.log("
|
|
8124
|
+
console.log("What this means:");
|
|
8125
|
+
console.log(" This device now uses a new memory encryption key.");
|
|
8126
|
+
console.log(" The cloud copy may still be encrypted with the old key.");
|
|
8127
|
+
console.log(" Normal cloud sync is paused until this device re-uploads the cloud backup.");
|
|
7458
8128
|
console.log("");
|
|
7459
|
-
console.log("
|
|
7460
|
-
console.log(" 1.
|
|
7461
|
-
console.log(" 2.
|
|
7462
|
-
console.log(" 3. Treat this
|
|
7463
|
-
console.log(" 4.
|
|
8129
|
+
console.log("Running `exe-os cloud reupload` will:");
|
|
8130
|
+
console.log(" 1. Open your local memory DB with the current key.");
|
|
8131
|
+
console.log(" 2. Ask you to confirm you saved the current 24-word recovery phrase.");
|
|
8132
|
+
console.log(" 3. Treat this device as the correct copy of your memory.");
|
|
8133
|
+
console.log(" 4. Re-upload your cloud backup encrypted with the current key.");
|
|
7464
8134
|
console.log("");
|
|
7465
8135
|
if (dryRun) {
|
|
7466
|
-
console.log("No changes made.");
|
|
7467
|
-
console.log("When ready, run: exe-os cloud
|
|
8136
|
+
console.log("Dry run only. No changes made.");
|
|
8137
|
+
console.log("When ready, run: exe-os cloud reupload");
|
|
7468
8138
|
return;
|
|
7469
8139
|
}
|
|
7470
|
-
if (!
|
|
7471
|
-
console.error("
|
|
8140
|
+
if (!shouldRunReuploadMutation({ dryRun, force, interactive: isInteractiveTerminal(), reuploadRequired: required })) {
|
|
8141
|
+
console.error("For safety, cloud re-upload must be run in your local Terminal: exe-os cloud reupload\nAutomation only: pass --local-db-is-source-of-truth if this device is definitely the correct copy.");
|
|
7472
8142
|
process.exitCode = 1;
|
|
7473
8143
|
return;
|
|
7474
8144
|
}
|
|
@@ -7481,7 +8151,7 @@ async function relinkMode() {
|
|
|
7481
8151
|
process.exitCode = 1;
|
|
7482
8152
|
return;
|
|
7483
8153
|
}
|
|
7484
|
-
const confirm = await ask(rl, 'Type "LOCAL DB IS SOURCE OF TRUTH" to
|
|
8154
|
+
const confirm = await ask(rl, 'Type "LOCAL DB IS SOURCE OF TRUTH" to re-upload cloud data: ');
|
|
7485
8155
|
if (confirm !== "LOCAL DB IS SOURCE OF TRUTH") {
|
|
7486
8156
|
console.log("Cancelled.");
|
|
7487
8157
|
process.exitCode = 1;
|
|
@@ -7491,16 +8161,31 @@ async function relinkMode() {
|
|
|
7491
8161
|
rl.close();
|
|
7492
8162
|
}
|
|
7493
8163
|
}
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
8164
|
+
console.log("\u2713 Local DB confirmed. Clearing old cloud memory blobs, then re-uploading with the current key...");
|
|
8165
|
+
try {
|
|
8166
|
+
const reset = await cloudResetMemoryBlobs2((await loadConfig()).cloud);
|
|
8167
|
+
console.log(`\u2713 Cleared ${reset.deleted} old cloud memory blob(s) before re-upload.`);
|
|
8168
|
+
} catch (err) {
|
|
8169
|
+
console.error(`Cloud reset failed before re-upload: ${err instanceof Error ? err.message : String(err)}`);
|
|
8170
|
+
console.error("Cloud sync remains paused. Try again after the endpoint is available.");
|
|
8171
|
+
process.exitCode = 1;
|
|
8172
|
+
return;
|
|
8173
|
+
}
|
|
8174
|
+
await clearCloudReuploadRequired2(client);
|
|
8175
|
+
let result;
|
|
8176
|
+
try {
|
|
8177
|
+
result = await runCloudSyncOnce();
|
|
8178
|
+
} catch (err) {
|
|
8179
|
+
await markCloudReuploadRequired2(client);
|
|
8180
|
+
throw err;
|
|
8181
|
+
}
|
|
7497
8182
|
console.log("");
|
|
7498
|
-
console.log("Cloud
|
|
7499
|
-
console.log(`
|
|
8183
|
+
console.log("Cloud re-upload complete. Normal cloud sync is safe again.");
|
|
8184
|
+
console.log(` Active memories: ${result.totalMemories} (\u2191 ${result.pushed} syncable rows pushed, \u2193 ${result.pulled} pulled)`);
|
|
7500
8185
|
console.log(` Behaviors: ${result.behaviors.pushed} total (\u2193 ${result.behaviors.pulled} pulled)`);
|
|
7501
8186
|
console.log(` Roster: ${result.roster.employees} employees, ${result.roster.identities} identities`);
|
|
7502
8187
|
console.log(` Tasks: ${result.tasks.pushed} total (\u2193 ${result.tasks.pulled} pulled)`);
|
|
7503
|
-
console.log("Other devices must
|
|
8188
|
+
console.log("Other devices must reconnect using the current 24-word recovery phrase.");
|
|
7504
8189
|
} finally {
|
|
7505
8190
|
await disposeCloudCliResources();
|
|
7506
8191
|
}
|
|
@@ -7510,21 +8195,26 @@ async function main() {
|
|
|
7510
8195
|
if (mode === "link" || mode === "export") {
|
|
7511
8196
|
await linkMode();
|
|
7512
8197
|
} else if (mode === "sync") {
|
|
7513
|
-
console.error("
|
|
7514
|
-
|
|
8198
|
+
console.error("Use `exe-os cloud sync` for data sync.");
|
|
8199
|
+
console.error("Use `exe-os cloud link --show-full` to reveal the recovery phrase in your local Terminal.");
|
|
8200
|
+
process.exitCode = 1;
|
|
7515
8201
|
} else if (mode === "status") {
|
|
7516
8202
|
await statusMode();
|
|
8203
|
+
} else if (mode === "reupload") {
|
|
8204
|
+
await reuploadMode();
|
|
7517
8205
|
} else if (mode === "relink") {
|
|
7518
|
-
|
|
8206
|
+
console.error("`exe-os cloud relink` has been removed. Use: exe-os cloud reupload");
|
|
8207
|
+
process.exitCode = 1;
|
|
7519
8208
|
} else if (!mode || mode === "setup") {
|
|
7520
8209
|
await setupMode();
|
|
7521
8210
|
} else {
|
|
7522
8211
|
console.error("Usage:");
|
|
7523
|
-
console.error(" exe-cloud
|
|
7524
|
-
console.error(" exe-cloud link
|
|
7525
|
-
console.error(" exe-cloud status
|
|
7526
|
-
console.error(" exe-cloud
|
|
7527
|
-
console.error(" exe-cloud
|
|
8212
|
+
console.error(" exe-os cloud \u2014 full device setup (key + cloud)");
|
|
8213
|
+
console.error(" exe-os cloud link \u2014 show recovery phrase + cloud info for a new device");
|
|
8214
|
+
console.error(" exe-os cloud status \u2014 show current cloud status");
|
|
8215
|
+
console.error(" exe-os cloud sync \u2014 run sync now (normally automatic)");
|
|
8216
|
+
console.error(" exe-os cloud reupload \u2014 re-upload cloud backup after key rotation");
|
|
8217
|
+
console.error(" exe-os cloud reupload --dry-run");
|
|
7528
8218
|
process.exit(1);
|
|
7529
8219
|
}
|
|
7530
8220
|
}
|
|
@@ -7536,5 +8226,5 @@ if (isMainModule(import.meta.url) && process.argv[1]?.includes("exe-cloud")) {
|
|
|
7536
8226
|
}
|
|
7537
8227
|
export {
|
|
7538
8228
|
main,
|
|
7539
|
-
|
|
8229
|
+
shouldRunReuploadMutation
|
|
7540
8230
|
};
|