@askexenow/exe-os 0.9.65 → 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} +1532 -2173
- package/dist/bin/backfill-conversations.js +528 -20
- package/dist/bin/backfill-responses.js +528 -20
- package/dist/bin/backfill-vectors.js +255 -20
- package/dist/bin/bulk-sync-postgres.js +4876 -0
- package/dist/bin/cleanup-stale-review-tasks.js +529 -21
- package/dist/bin/cli.js +3471 -1491
- package/dist/bin/exe-agent-config.js +4 -0
- package/dist/bin/exe-agent.js +16 -0
- package/dist/bin/exe-assign.js +528 -20
- package/dist/bin/exe-boot.js +492 -54
- package/dist/bin/exe-call.js +16 -0
- package/dist/bin/exe-cloud.js +7415 -518
- package/dist/bin/exe-dispatch.js +540 -22
- package/dist/bin/exe-doctor.js +3404 -1225
- package/dist/bin/exe-export-behaviors.js +542 -24
- package/dist/bin/exe-forget.js +529 -21
- package/dist/bin/exe-gateway.js +595 -25
- package/dist/bin/exe-heartbeat.js +541 -24
- package/dist/bin/exe-kill.js +529 -21
- package/dist/bin/exe-launch-agent.js +2334 -1067
- package/dist/bin/exe-new-employee.js +324 -166
- package/dist/bin/exe-pending-messages.js +529 -21
- package/dist/bin/exe-pending-notifications.js +529 -21
- package/dist/bin/exe-pending-reviews.js +529 -21
- package/dist/bin/exe-rename.js +529 -21
- package/dist/bin/exe-review.js +529 -21
- package/dist/bin/exe-search.js +542 -24
- package/dist/bin/exe-session-cleanup.js +540 -22
- package/dist/bin/exe-settings.js +14 -0
- package/dist/bin/exe-start-codex.js +817 -144
- package/dist/bin/exe-start-opencode.js +776 -80
- package/dist/bin/exe-status.js +529 -21
- package/dist/bin/exe-team.js +529 -21
- package/dist/bin/git-sweep.js +540 -22
- package/dist/bin/graph-backfill.js +580 -21
- package/dist/bin/graph-export.js +529 -21
- package/dist/bin/graph-layer-benchmark.js +109 -0
- package/dist/bin/install.js +420 -289
- package/dist/bin/intercom-check.js +540 -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 +540 -22
- package/dist/bin/setup.js +790 -206
- package/dist/bin/shard-migrate.js +528 -20
- package/dist/bin/update.js +4 -0
- package/dist/gateway/index.js +593 -23
- package/dist/hooks/bug-report-worker.js +651 -64
- package/dist/hooks/codex-stop-task-finalizer.js +540 -22
- package/dist/hooks/commit-complete.js +540 -22
- package/dist/hooks/error-recall.js +542 -24
- package/dist/hooks/exe-heartbeat-hook.js +4 -0
- package/dist/hooks/ingest-worker.js +4 -0
- package/dist/hooks/ingest.js +539 -22
- package/dist/hooks/instructions-loaded.js +529 -21
- package/dist/hooks/notification.js +529 -21
- package/dist/hooks/post-compact.js +529 -21
- package/dist/hooks/post-tool-combined.js +543 -25
- package/dist/hooks/pre-compact.js +772 -127
- package/dist/hooks/pre-tool-use.js +529 -21
- package/dist/hooks/prompt-submit.js +543 -25
- package/dist/hooks/session-end.js +673 -140
- package/dist/hooks/session-start.js +662 -26
- package/dist/hooks/stop.js +540 -23
- package/dist/hooks/subagent-stop.js +529 -21
- package/dist/hooks/summary-worker.js +571 -126
- package/dist/index.js +593 -23
- package/dist/lib/agent-config.js +4 -0
- package/dist/lib/cloud-sync.js +408 -47
- package/dist/lib/config.js +25 -1
- package/dist/lib/consolidation.js +5 -1
- package/dist/lib/database.js +128 -0
- package/dist/lib/db-daemon-client.js +4 -0
- package/dist/lib/db.js +128 -0
- package/dist/lib/device-registry.js +128 -0
- package/dist/lib/embedder.js +25 -1
- package/dist/lib/employee-templates.js +16 -0
- package/dist/lib/employees.js +4 -0
- package/dist/lib/exe-daemon-client.js +4 -0
- package/dist/lib/exe-daemon.js +3158 -930
- package/dist/lib/hybrid-search.js +542 -24
- package/dist/lib/identity.js +7 -0
- package/dist/lib/keychain.js +178 -22
- package/dist/lib/license.js +4 -0
- package/dist/lib/messaging.js +7 -0
- package/dist/lib/reminders.js +7 -0
- package/dist/lib/schedules.js +255 -20
- package/dist/lib/skill-learning.js +28 -1
- package/dist/lib/status-brief.js +39 -0
- package/dist/lib/store.js +528 -20
- package/dist/lib/task-router.js +4 -0
- package/dist/lib/tasks.js +28 -1
- package/dist/lib/tmux-routing.js +28 -1
- package/dist/lib/token-spend.js +7 -0
- package/dist/mcp/server.js +2739 -813
- package/dist/mcp/tools/complete-reminder.js +7 -0
- package/dist/mcp/tools/create-reminder.js +7 -0
- package/dist/mcp/tools/create-task.js +28 -1
- package/dist/mcp/tools/deactivate-behavior.js +7 -0
- package/dist/mcp/tools/list-reminders.js +7 -0
- package/dist/mcp/tools/list-tasks.js +7 -0
- package/dist/mcp/tools/send-message.js +7 -0
- package/dist/mcp/tools/update-task.js +28 -1
- package/dist/runtime/index.js +540 -22
- package/dist/tui/App.js +618 -29
- package/package.json +9 -5
- package/src/commands/exe/cloud.md +11 -8
- package/stack.release.json +3 -3
- package/src/commands/exe/link.md +0 -17
|
@@ -15,334 +15,77 @@ var __export = (target, all) => {
|
|
|
15
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
// src/
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
importMnemonic: () => importMnemonic,
|
|
25
|
-
setMasterKey: () => setMasterKey
|
|
26
|
-
});
|
|
27
|
-
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
28
|
-
import { existsSync } from "fs";
|
|
29
|
-
import { execSync } from "child_process";
|
|
30
|
-
import path from "path";
|
|
31
|
-
import os from "os";
|
|
32
|
-
function getKeyDir() {
|
|
33
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
34
|
-
}
|
|
35
|
-
function getKeyPath() {
|
|
36
|
-
return path.join(getKeyDir(), "master.key");
|
|
37
|
-
}
|
|
38
|
-
function macKeychainGet() {
|
|
39
|
-
if (process.platform !== "darwin") return null;
|
|
40
|
-
try {
|
|
41
|
-
return execSync(
|
|
42
|
-
`security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
43
|
-
{ encoding: "utf-8", timeout: 5e3 }
|
|
44
|
-
).trim();
|
|
45
|
-
} catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function macKeychainSet(value) {
|
|
50
|
-
if (process.platform !== "darwin") return false;
|
|
51
|
-
try {
|
|
52
|
-
try {
|
|
53
|
-
execSync(
|
|
54
|
-
`security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
55
|
-
{ timeout: 5e3 }
|
|
56
|
-
);
|
|
57
|
-
} catch {
|
|
58
|
-
}
|
|
59
|
-
execSync(
|
|
60
|
-
`security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
|
|
61
|
-
{ timeout: 5e3 }
|
|
62
|
-
);
|
|
63
|
-
return true;
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function macKeychainDelete() {
|
|
69
|
-
if (process.platform !== "darwin") return false;
|
|
70
|
-
try {
|
|
71
|
-
execSync(
|
|
72
|
-
`security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
73
|
-
{ timeout: 5e3 }
|
|
74
|
-
);
|
|
75
|
-
return true;
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function linuxSecretGet() {
|
|
81
|
-
if (process.platform !== "linux") return null;
|
|
82
|
-
try {
|
|
83
|
-
return execSync(
|
|
84
|
-
`secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
|
|
85
|
-
{ encoding: "utf-8", timeout: 5e3 }
|
|
86
|
-
).trim();
|
|
87
|
-
} catch {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function linuxSecretSet(value) {
|
|
92
|
-
if (process.platform !== "linux") return false;
|
|
93
|
-
try {
|
|
94
|
-
execSync(
|
|
95
|
-
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
|
|
96
|
-
{ timeout: 5e3 }
|
|
97
|
-
);
|
|
98
|
-
return true;
|
|
99
|
-
} catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function linuxSecretDelete() {
|
|
104
|
-
if (process.platform !== "linux") return false;
|
|
105
|
-
try {
|
|
106
|
-
execSync(
|
|
107
|
-
`secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
|
|
108
|
-
{ timeout: 5e3 }
|
|
109
|
-
);
|
|
110
|
-
return true;
|
|
111
|
-
} catch {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async function tryKeytar() {
|
|
116
|
-
try {
|
|
117
|
-
return await import("keytar");
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function deriveMachineKey() {
|
|
123
|
-
try {
|
|
124
|
-
const crypto4 = __require("crypto");
|
|
125
|
-
const material = [
|
|
126
|
-
os.hostname(),
|
|
127
|
-
os.userInfo().username,
|
|
128
|
-
os.arch(),
|
|
129
|
-
os.platform(),
|
|
130
|
-
// Machine ID on Linux (stable across reboots)
|
|
131
|
-
process.platform === "linux" ? readMachineId() : ""
|
|
132
|
-
].join("|");
|
|
133
|
-
return crypto4.createHash("sha256").update(material).digest();
|
|
134
|
-
} catch {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function readMachineId() {
|
|
139
|
-
try {
|
|
140
|
-
const { readFileSync: readFileSync8 } = __require("fs");
|
|
141
|
-
return readFileSync8("/etc/machine-id", "utf-8").trim();
|
|
142
|
-
} catch {
|
|
143
|
-
return "";
|
|
18
|
+
// src/types/memory.ts
|
|
19
|
+
var EMBEDDING_DIM;
|
|
20
|
+
var init_memory = __esm({
|
|
21
|
+
"src/types/memory.ts"() {
|
|
22
|
+
"use strict";
|
|
23
|
+
EMBEDDING_DIM = 1024;
|
|
144
24
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const authTag = cipher.getAuthTag().toString("base64");
|
|
153
|
-
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
154
|
-
}
|
|
155
|
-
function decryptWithMachineKey(encrypted, machineKey) {
|
|
156
|
-
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
157
|
-
try {
|
|
158
|
-
const crypto4 = __require("crypto");
|
|
159
|
-
const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
|
|
160
|
-
if (parts.length !== 3) return null;
|
|
161
|
-
const [ivB64, tagB64, cipherB64] = parts;
|
|
162
|
-
const iv = Buffer.from(ivB64, "base64");
|
|
163
|
-
const authTag = Buffer.from(tagB64, "base64");
|
|
164
|
-
const decipher = crypto4.createDecipheriv("aes-256-gcm", machineKey, iv);
|
|
165
|
-
decipher.setAuthTag(authTag);
|
|
166
|
-
let decrypted = decipher.update(cipherB64, "base64", "utf-8");
|
|
167
|
-
decrypted += decipher.final("utf-8");
|
|
168
|
-
return decrypted;
|
|
169
|
-
} catch {
|
|
170
|
-
return null;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// src/lib/db-retry.ts
|
|
28
|
+
function isBusyError(err) {
|
|
29
|
+
if (err instanceof Error) {
|
|
30
|
+
const msg = err.message.toLowerCase();
|
|
31
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
171
32
|
}
|
|
33
|
+
return false;
|
|
172
34
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
await mkdir(dir, { recursive: true });
|
|
176
|
-
const keyPath = getKeyPath();
|
|
177
|
-
const machineKey = deriveMachineKey();
|
|
178
|
-
if (machineKey) {
|
|
179
|
-
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
180
|
-
await writeFile(keyPath, encrypted + "\n", "utf-8");
|
|
181
|
-
await chmod(keyPath, 384);
|
|
182
|
-
return "encrypted";
|
|
183
|
-
}
|
|
184
|
-
await writeFile(keyPath, b64 + "\n", "utf-8");
|
|
185
|
-
await chmod(keyPath, 384);
|
|
186
|
-
return "plaintext";
|
|
35
|
+
function delay(ms) {
|
|
36
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
187
37
|
}
|
|
188
|
-
async function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return Buffer.from(nativeValue, "base64");
|
|
192
|
-
}
|
|
193
|
-
const keytar = await tryKeytar();
|
|
194
|
-
if (keytar) {
|
|
38
|
+
async function retryOnBusy(fn, label) {
|
|
39
|
+
let lastError;
|
|
40
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
195
41
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
return Buffer.from(keytarValue, "base64");
|
|
203
|
-
}
|
|
204
|
-
} catch {
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
const keyPath = getKeyPath();
|
|
208
|
-
if (!existsSync(keyPath)) {
|
|
209
|
-
process.stderr.write(
|
|
210
|
-
`[keychain] Key not found at ${keyPath} (HOME=${os.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
211
|
-
`
|
|
212
|
-
);
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
const content = (await readFile(keyPath, "utf-8")).trim();
|
|
217
|
-
let b64Value;
|
|
218
|
-
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
219
|
-
const machineKey = deriveMachineKey();
|
|
220
|
-
if (!machineKey) {
|
|
221
|
-
process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
225
|
-
if (!decrypted) {
|
|
226
|
-
process.stderr.write(
|
|
227
|
-
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
|
|
228
|
-
);
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
b64Value = decrypted;
|
|
232
|
-
} else {
|
|
233
|
-
b64Value = content;
|
|
234
|
-
}
|
|
235
|
-
const key = Buffer.from(b64Value, "base64");
|
|
236
|
-
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
237
|
-
if (migrated) {
|
|
238
|
-
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
239
|
-
try {
|
|
240
|
-
await unlink(keyPath);
|
|
241
|
-
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
242
|
-
} catch {
|
|
243
|
-
}
|
|
244
|
-
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
245
|
-
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
246
|
-
if (fallback === "encrypted") {
|
|
247
|
-
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
248
|
-
} else {
|
|
249
|
-
process.stderr.write(
|
|
250
|
-
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
251
|
-
);
|
|
42
|
+
return await fn();
|
|
43
|
+
} catch (err) {
|
|
44
|
+
lastError = err;
|
|
45
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
46
|
+
throw err;
|
|
252
47
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
48
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
49
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
258
52
|
`
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
async function setMasterKey(key) {
|
|
264
|
-
const b64 = key.toString("base64");
|
|
265
|
-
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
const keytar = await tryKeytar();
|
|
269
|
-
if (keytar) {
|
|
270
|
-
try {
|
|
271
|
-
await keytar.setPassword(SERVICE, ACCOUNT, b64);
|
|
272
|
-
return;
|
|
273
|
-
} catch {
|
|
53
|
+
);
|
|
54
|
+
await delay(backoff + jitter);
|
|
274
55
|
}
|
|
275
56
|
}
|
|
276
|
-
|
|
277
|
-
if (fallback === "encrypted") {
|
|
278
|
-
process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
|
|
279
|
-
} else {
|
|
280
|
-
process.stderr.write(
|
|
281
|
-
"[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
|
|
282
|
-
);
|
|
283
|
-
}
|
|
57
|
+
throw lastError;
|
|
284
58
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
59
|
+
function wrapWithRetry(client) {
|
|
60
|
+
return new Proxy(client, {
|
|
61
|
+
get(target, prop, receiver) {
|
|
62
|
+
if (prop === "execute") {
|
|
63
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
64
|
+
}
|
|
65
|
+
if (prop === "batch") {
|
|
66
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
67
|
+
}
|
|
68
|
+
return Reflect.get(target, prop, receiver);
|
|
293
69
|
}
|
|
294
|
-
}
|
|
295
|
-
const keyPath = getKeyPath();
|
|
296
|
-
if (existsSync(keyPath)) {
|
|
297
|
-
await unlink(keyPath);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
async function loadBip39() {
|
|
301
|
-
try {
|
|
302
|
-
return await import("bip39");
|
|
303
|
-
} catch {
|
|
304
|
-
throw new Error(
|
|
305
|
-
"bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
async function exportMnemonic(key) {
|
|
310
|
-
if (key.length !== 32) {
|
|
311
|
-
throw new Error(`Key must be 32 bytes, got ${key.length}`);
|
|
312
|
-
}
|
|
313
|
-
const { entropyToMnemonic } = await loadBip39();
|
|
314
|
-
return entropyToMnemonic(key.toString("hex"));
|
|
315
|
-
}
|
|
316
|
-
async function importMnemonic(mnemonic) {
|
|
317
|
-
const trimmed = mnemonic.trim();
|
|
318
|
-
const words = trimmed.split(/\s+/);
|
|
319
|
-
if (words.length !== 24) {
|
|
320
|
-
throw new Error(`Expected 24 words, got ${words.length}`);
|
|
321
|
-
}
|
|
322
|
-
const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
|
|
323
|
-
if (!validateMnemonic(trimmed)) {
|
|
324
|
-
throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
|
|
325
|
-
}
|
|
326
|
-
const entropy = mnemonicToEntropy(trimmed);
|
|
327
|
-
return Buffer.from(entropy, "hex");
|
|
70
|
+
});
|
|
328
71
|
}
|
|
329
|
-
var
|
|
330
|
-
var
|
|
331
|
-
"src/lib/
|
|
72
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
73
|
+
var init_db_retry = __esm({
|
|
74
|
+
"src/lib/db-retry.ts"() {
|
|
332
75
|
"use strict";
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
76
|
+
MAX_RETRIES = 5;
|
|
77
|
+
BASE_DELAY_MS = 250;
|
|
78
|
+
MAX_JITTER_MS = 400;
|
|
336
79
|
}
|
|
337
80
|
});
|
|
338
81
|
|
|
339
82
|
// src/lib/secure-files.ts
|
|
340
|
-
import { chmodSync, existsSync
|
|
341
|
-
import { chmod
|
|
83
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
84
|
+
import { chmod, mkdir } from "fs/promises";
|
|
342
85
|
async function ensurePrivateDir(dirPath) {
|
|
343
|
-
await
|
|
86
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
344
87
|
try {
|
|
345
|
-
await
|
|
88
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
346
89
|
} catch {
|
|
347
90
|
}
|
|
348
91
|
}
|
|
@@ -355,13 +98,13 @@ function ensurePrivateDirSync(dirPath) {
|
|
|
355
98
|
}
|
|
356
99
|
async function enforcePrivateFile(filePath) {
|
|
357
100
|
try {
|
|
358
|
-
await
|
|
101
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
359
102
|
} catch {
|
|
360
103
|
}
|
|
361
104
|
}
|
|
362
105
|
function enforcePrivateFileSync(filePath) {
|
|
363
106
|
try {
|
|
364
|
-
if (
|
|
107
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
365
108
|
} catch {
|
|
366
109
|
}
|
|
367
110
|
}
|
|
@@ -375,31 +118,16 @@ var init_secure_files = __esm({
|
|
|
375
118
|
});
|
|
376
119
|
|
|
377
120
|
// src/lib/config.ts
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
383
|
-
DB_PATH: () => DB_PATH,
|
|
384
|
-
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
385
|
-
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
386
|
-
MODELS_DIR: () => MODELS_DIR,
|
|
387
|
-
loadConfig: () => loadConfig,
|
|
388
|
-
loadConfigFrom: () => loadConfigFrom,
|
|
389
|
-
loadConfigSync: () => loadConfigSync,
|
|
390
|
-
migrateConfig: () => migrateConfig,
|
|
391
|
-
saveConfig: () => saveConfig
|
|
392
|
-
});
|
|
393
|
-
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
394
|
-
import { readFileSync, existsSync as existsSync3, renameSync } from "fs";
|
|
395
|
-
import path2 from "path";
|
|
396
|
-
import os2 from "os";
|
|
121
|
+
import { readFile, writeFile } from "fs/promises";
|
|
122
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
123
|
+
import path from "path";
|
|
124
|
+
import os from "os";
|
|
397
125
|
function resolveDataDir() {
|
|
398
126
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
399
127
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
400
|
-
const newDir =
|
|
401
|
-
const legacyDir =
|
|
402
|
-
if (!
|
|
128
|
+
const newDir = path.join(os.homedir(), ".exe-os");
|
|
129
|
+
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
130
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
403
131
|
try {
|
|
404
132
|
renameSync(legacyDir, newDir);
|
|
405
133
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -460,14 +188,19 @@ function normalizeAutoUpdate(raw) {
|
|
|
460
188
|
const userAU = raw.autoUpdate ?? {};
|
|
461
189
|
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
462
190
|
}
|
|
191
|
+
function normalizeOrchestration(raw) {
|
|
192
|
+
const defaultOrg = DEFAULT_CONFIG.orchestration;
|
|
193
|
+
const userOrg = raw.orchestration ?? {};
|
|
194
|
+
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
195
|
+
}
|
|
463
196
|
async function loadConfig() {
|
|
464
197
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
465
198
|
await ensurePrivateDir(dir);
|
|
466
|
-
const configPath =
|
|
467
|
-
if (!
|
|
468
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
199
|
+
const configPath = path.join(dir, "config.json");
|
|
200
|
+
if (!existsSync2(configPath)) {
|
|
201
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
469
202
|
}
|
|
470
|
-
const raw = await
|
|
203
|
+
const raw = await readFile(configPath, "utf-8");
|
|
471
204
|
try {
|
|
472
205
|
let parsed = JSON.parse(raw);
|
|
473
206
|
parsed = migrateLegacyConfig(parsed);
|
|
@@ -476,7 +209,7 @@ async function loadConfig() {
|
|
|
476
209
|
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
477
210
|
`);
|
|
478
211
|
try {
|
|
479
|
-
await
|
|
212
|
+
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
480
213
|
await enforcePrivateFile(configPath);
|
|
481
214
|
} catch {
|
|
482
215
|
}
|
|
@@ -484,53 +217,18 @@ async function loadConfig() {
|
|
|
484
217
|
normalizeScalingRoadmap(migratedCfg);
|
|
485
218
|
normalizeSessionLifecycle(migratedCfg);
|
|
486
219
|
normalizeAutoUpdate(migratedCfg);
|
|
487
|
-
|
|
220
|
+
normalizeOrchestration(migratedCfg);
|
|
221
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
488
222
|
if (config.dbPath.startsWith("~")) {
|
|
489
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
223
|
+
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
224
|
+
}
|
|
225
|
+
const envDbPath = path.join(dir, "memories.db");
|
|
226
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
|
|
227
|
+
config.dbPath = envDbPath;
|
|
490
228
|
}
|
|
491
229
|
return config;
|
|
492
230
|
} catch {
|
|
493
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
function loadConfigSync() {
|
|
497
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
498
|
-
const configPath = path2.join(dir, "config.json");
|
|
499
|
-
if (!existsSync3(configPath)) {
|
|
500
|
-
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
501
|
-
}
|
|
502
|
-
try {
|
|
503
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
504
|
-
let parsed = JSON.parse(raw);
|
|
505
|
-
parsed = migrateLegacyConfig(parsed);
|
|
506
|
-
const { config: migratedCfg } = migrateConfig(parsed);
|
|
507
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
508
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
509
|
-
normalizeAutoUpdate(migratedCfg);
|
|
510
|
-
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
511
|
-
} catch {
|
|
512
|
-
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async function saveConfig(config) {
|
|
516
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
517
|
-
await ensurePrivateDir(dir);
|
|
518
|
-
const configPath = path2.join(dir, "config.json");
|
|
519
|
-
await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
520
|
-
await enforcePrivateFile(configPath);
|
|
521
|
-
}
|
|
522
|
-
async function loadConfigFrom(configPath) {
|
|
523
|
-
const raw = await readFile2(configPath, "utf-8");
|
|
524
|
-
try {
|
|
525
|
-
let parsed = JSON.parse(raw);
|
|
526
|
-
parsed = migrateLegacyConfig(parsed);
|
|
527
|
-
const { config: migratedCfg } = migrateConfig(parsed);
|
|
528
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
529
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
530
|
-
normalizeAutoUpdate(migratedCfg);
|
|
531
|
-
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
532
|
-
} catch {
|
|
533
|
-
return { ...DEFAULT_CONFIG };
|
|
231
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
534
232
|
}
|
|
535
233
|
}
|
|
536
234
|
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
@@ -539,10 +237,10 @@ var init_config = __esm({
|
|
|
539
237
|
"use strict";
|
|
540
238
|
init_secure_files();
|
|
541
239
|
EXE_AI_DIR = resolveDataDir();
|
|
542
|
-
DB_PATH =
|
|
543
|
-
MODELS_DIR =
|
|
544
|
-
CONFIG_PATH =
|
|
545
|
-
LEGACY_LANCE_PATH =
|
|
240
|
+
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
241
|
+
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
242
|
+
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
243
|
+
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
546
244
|
CURRENT_CONFIG_VERSION = 1;
|
|
547
245
|
DEFAULT_CONFIG = {
|
|
548
246
|
config_version: CURRENT_CONFIG_VERSION,
|
|
@@ -599,6 +297,10 @@ var init_config = __esm({
|
|
|
599
297
|
checkOnBoot: true,
|
|
600
298
|
autoInstall: false,
|
|
601
299
|
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
300
|
+
},
|
|
301
|
+
orchestration: {
|
|
302
|
+
phase: "phase_1_coo",
|
|
303
|
+
phaseSetBy: "default"
|
|
602
304
|
}
|
|
603
305
|
};
|
|
604
306
|
CONFIG_MIGRATIONS = [
|
|
@@ -614,126 +316,12 @@ var init_config = __esm({
|
|
|
614
316
|
}
|
|
615
317
|
});
|
|
616
318
|
|
|
617
|
-
// src/lib/crypto.ts
|
|
618
|
-
var crypto_exports = {};
|
|
619
|
-
__export(crypto_exports, {
|
|
620
|
-
decryptSyncBlob: () => decryptSyncBlob,
|
|
621
|
-
encryptSyncBlob: () => encryptSyncBlob,
|
|
622
|
-
initSyncCrypto: () => initSyncCrypto,
|
|
623
|
-
isSyncCryptoInitialized: () => isSyncCryptoInitialized
|
|
624
|
-
});
|
|
625
|
-
import crypto from "crypto";
|
|
626
|
-
function initSyncCrypto(masterKey) {
|
|
627
|
-
if (masterKey.length !== 32) {
|
|
628
|
-
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
629
|
-
}
|
|
630
|
-
_syncKey = Buffer.from(
|
|
631
|
-
crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
function isSyncCryptoInitialized() {
|
|
635
|
-
return _syncKey !== null;
|
|
636
|
-
}
|
|
637
|
-
function requireSyncKey() {
|
|
638
|
-
if (!_syncKey) {
|
|
639
|
-
throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
|
|
640
|
-
}
|
|
641
|
-
return _syncKey;
|
|
642
|
-
}
|
|
643
|
-
function encryptSyncBlob(data) {
|
|
644
|
-
const key = requireSyncKey();
|
|
645
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
646
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
647
|
-
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
648
|
-
const tag = cipher.getAuthTag();
|
|
649
|
-
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
650
|
-
}
|
|
651
|
-
function decryptSyncBlob(ciphertext) {
|
|
652
|
-
const key = requireSyncKey();
|
|
653
|
-
const combined = Buffer.from(ciphertext, "base64");
|
|
654
|
-
if (combined.length < IV_LENGTH + TAG_LENGTH) {
|
|
655
|
-
throw new Error("Sync blob too short to contain IV + tag");
|
|
656
|
-
}
|
|
657
|
-
const iv = combined.subarray(0, IV_LENGTH);
|
|
658
|
-
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
659
|
-
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
660
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
661
|
-
decipher.setAuthTag(tag);
|
|
662
|
-
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
663
|
-
}
|
|
664
|
-
var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
|
|
665
|
-
var init_crypto = __esm({
|
|
666
|
-
"src/lib/crypto.ts"() {
|
|
667
|
-
"use strict";
|
|
668
|
-
ALGORITHM = "aes-256-gcm";
|
|
669
|
-
IV_LENGTH = 12;
|
|
670
|
-
TAG_LENGTH = 16;
|
|
671
|
-
SYNC_HKDF_INFO = "exe-mem-sync-v2";
|
|
672
|
-
_syncKey = null;
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
// src/lib/db-retry.ts
|
|
677
|
-
function isBusyError(err) {
|
|
678
|
-
if (err instanceof Error) {
|
|
679
|
-
const msg = err.message.toLowerCase();
|
|
680
|
-
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
681
|
-
}
|
|
682
|
-
return false;
|
|
683
|
-
}
|
|
684
|
-
function delay(ms) {
|
|
685
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
686
|
-
}
|
|
687
|
-
async function retryOnBusy(fn, label) {
|
|
688
|
-
let lastError;
|
|
689
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
690
|
-
try {
|
|
691
|
-
return await fn();
|
|
692
|
-
} catch (err) {
|
|
693
|
-
lastError = err;
|
|
694
|
-
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
695
|
-
throw err;
|
|
696
|
-
}
|
|
697
|
-
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
698
|
-
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
699
|
-
process.stderr.write(
|
|
700
|
-
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
701
|
-
`
|
|
702
|
-
);
|
|
703
|
-
await delay(backoff + jitter);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
throw lastError;
|
|
707
|
-
}
|
|
708
|
-
function wrapWithRetry(client) {
|
|
709
|
-
return new Proxy(client, {
|
|
710
|
-
get(target, prop, receiver) {
|
|
711
|
-
if (prop === "execute") {
|
|
712
|
-
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
713
|
-
}
|
|
714
|
-
if (prop === "batch") {
|
|
715
|
-
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
716
|
-
}
|
|
717
|
-
return Reflect.get(target, prop, receiver);
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
722
|
-
var init_db_retry = __esm({
|
|
723
|
-
"src/lib/db-retry.ts"() {
|
|
724
|
-
"use strict";
|
|
725
|
-
MAX_RETRIES = 5;
|
|
726
|
-
BASE_DELAY_MS = 250;
|
|
727
|
-
MAX_JITTER_MS = 400;
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
|
|
731
319
|
// src/lib/employees.ts
|
|
732
|
-
import { readFile as
|
|
733
|
-
import { existsSync as
|
|
734
|
-
import { execSync
|
|
735
|
-
import
|
|
736
|
-
import
|
|
320
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
321
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
322
|
+
import { execSync } from "child_process";
|
|
323
|
+
import path2 from "path";
|
|
324
|
+
import os2 from "os";
|
|
737
325
|
function normalizeRole(role) {
|
|
738
326
|
return (role ?? "").trim().toLowerCase();
|
|
739
327
|
}
|
|
@@ -746,84 +334,29 @@ function getCoordinatorEmployee(employees) {
|
|
|
746
334
|
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
747
335
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
748
336
|
}
|
|
749
|
-
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
750
|
-
if (!existsSync4(employeesPath)) {
|
|
751
|
-
return [];
|
|
752
|
-
}
|
|
753
|
-
const raw = await readFile3(employeesPath, "utf-8");
|
|
754
|
-
try {
|
|
755
|
-
return JSON.parse(raw);
|
|
756
|
-
} catch {
|
|
757
|
-
return [];
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
761
|
-
await mkdir3(path3.dirname(employeesPath), { recursive: true });
|
|
762
|
-
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
763
|
-
}
|
|
764
337
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
765
|
-
if (!
|
|
338
|
+
if (!existsSync3(employeesPath)) return [];
|
|
766
339
|
try {
|
|
767
340
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
768
341
|
} catch {
|
|
769
342
|
return [];
|
|
770
343
|
}
|
|
771
344
|
}
|
|
772
|
-
function findExeBin() {
|
|
773
|
-
try {
|
|
774
|
-
return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
775
|
-
} catch {
|
|
776
|
-
return null;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
function registerBinSymlinks(name) {
|
|
780
|
-
const created = [];
|
|
781
|
-
const skipped = [];
|
|
782
|
-
const errors = [];
|
|
783
|
-
const exeBinPath = findExeBin();
|
|
784
|
-
if (!exeBinPath) {
|
|
785
|
-
errors.push("Could not find 'exe-os' in PATH");
|
|
786
|
-
return { created, skipped, errors };
|
|
787
|
-
}
|
|
788
|
-
const binDir = path3.dirname(exeBinPath);
|
|
789
|
-
let target;
|
|
790
|
-
try {
|
|
791
|
-
target = readlinkSync(exeBinPath);
|
|
792
|
-
} catch {
|
|
793
|
-
errors.push("Could not read 'exe' symlink");
|
|
794
|
-
return { created, skipped, errors };
|
|
795
|
-
}
|
|
796
|
-
for (const suffix of ["", "-opencode"]) {
|
|
797
|
-
const linkName = `${name}${suffix}`;
|
|
798
|
-
const linkPath = path3.join(binDir, linkName);
|
|
799
|
-
if (existsSync4(linkPath)) {
|
|
800
|
-
skipped.push(linkName);
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
try {
|
|
804
|
-
symlinkSync(target, linkPath);
|
|
805
|
-
created.push(linkName);
|
|
806
|
-
} catch (err) {
|
|
807
|
-
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return { created, skipped, errors };
|
|
811
|
-
}
|
|
812
345
|
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
813
346
|
var init_employees = __esm({
|
|
814
347
|
"src/lib/employees.ts"() {
|
|
815
348
|
"use strict";
|
|
816
349
|
init_config();
|
|
817
|
-
EMPLOYEES_PATH =
|
|
350
|
+
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
818
351
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
819
352
|
COORDINATOR_ROLE = "COO";
|
|
820
|
-
IDENTITY_DIR =
|
|
353
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
821
354
|
}
|
|
822
355
|
});
|
|
823
356
|
|
|
824
357
|
// src/lib/database-adapter.ts
|
|
825
|
-
import
|
|
826
|
-
import
|
|
358
|
+
import os3 from "os";
|
|
359
|
+
import path3 from "path";
|
|
827
360
|
import { createRequire } from "module";
|
|
828
361
|
import { pathToFileURL } from "url";
|
|
829
362
|
function quotedIdentifier(identifier) {
|
|
@@ -1134,8 +667,8 @@ async function loadPrismaClient() {
|
|
|
1134
667
|
}
|
|
1135
668
|
return new PrismaClient2();
|
|
1136
669
|
}
|
|
1137
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
1138
|
-
const requireFromExeDb = createRequire(
|
|
670
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
671
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
1139
672
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1140
673
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1141
674
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1405,19 +938,10 @@ var init_database_adapter = __esm({
|
|
|
1405
938
|
}
|
|
1406
939
|
});
|
|
1407
940
|
|
|
1408
|
-
// src/types/memory.ts
|
|
1409
|
-
var EMBEDDING_DIM;
|
|
1410
|
-
var init_memory = __esm({
|
|
1411
|
-
"src/types/memory.ts"() {
|
|
1412
|
-
"use strict";
|
|
1413
|
-
EMBEDDING_DIM = 1024;
|
|
1414
|
-
}
|
|
1415
|
-
});
|
|
1416
|
-
|
|
1417
941
|
// src/lib/daemon-auth.ts
|
|
1418
|
-
import
|
|
1419
|
-
import
|
|
1420
|
-
import { existsSync as
|
|
942
|
+
import crypto from "crypto";
|
|
943
|
+
import path4 from "path";
|
|
944
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1421
945
|
function normalizeToken(token) {
|
|
1422
946
|
if (!token) return null;
|
|
1423
947
|
const trimmed = token.trim();
|
|
@@ -1425,7 +949,7 @@ function normalizeToken(token) {
|
|
|
1425
949
|
}
|
|
1426
950
|
function readDaemonToken() {
|
|
1427
951
|
try {
|
|
1428
|
-
if (!
|
|
952
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
1429
953
|
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
1430
954
|
} catch {
|
|
1431
955
|
return null;
|
|
@@ -1434,7 +958,7 @@ function readDaemonToken() {
|
|
|
1434
958
|
function ensureDaemonToken(seed) {
|
|
1435
959
|
const existing = readDaemonToken();
|
|
1436
960
|
if (existing) return existing;
|
|
1437
|
-
const token = normalizeToken(seed) ??
|
|
961
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1438
962
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1439
963
|
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
1440
964
|
`, "utf8");
|
|
@@ -1447,18 +971,18 @@ var init_daemon_auth = __esm({
|
|
|
1447
971
|
"use strict";
|
|
1448
972
|
init_config();
|
|
1449
973
|
init_secure_files();
|
|
1450
|
-
DAEMON_TOKEN_PATH =
|
|
974
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
1451
975
|
}
|
|
1452
976
|
});
|
|
1453
977
|
|
|
1454
978
|
// src/lib/exe-daemon-client.ts
|
|
1455
979
|
import net from "net";
|
|
1456
|
-
import
|
|
980
|
+
import os4 from "os";
|
|
1457
981
|
import { spawn } from "child_process";
|
|
1458
982
|
import { randomUUID } from "crypto";
|
|
1459
|
-
import { existsSync as
|
|
1460
|
-
import
|
|
1461
|
-
import { fileURLToPath
|
|
983
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
984
|
+
import path5 from "path";
|
|
985
|
+
import { fileURLToPath } from "url";
|
|
1462
986
|
function handleData(chunk) {
|
|
1463
987
|
_buffer += chunk.toString();
|
|
1464
988
|
if (_buffer.length > MAX_BUFFER) {
|
|
@@ -1485,7 +1009,7 @@ function handleData(chunk) {
|
|
|
1485
1009
|
}
|
|
1486
1010
|
}
|
|
1487
1011
|
function cleanupStaleFiles() {
|
|
1488
|
-
if (
|
|
1012
|
+
if (existsSync5(PID_PATH)) {
|
|
1489
1013
|
try {
|
|
1490
1014
|
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
1491
1015
|
if (pid > 0) {
|
|
@@ -1508,11 +1032,11 @@ function cleanupStaleFiles() {
|
|
|
1508
1032
|
}
|
|
1509
1033
|
}
|
|
1510
1034
|
function findPackageRoot() {
|
|
1511
|
-
let dir =
|
|
1512
|
-
const { root } =
|
|
1035
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1036
|
+
const { root } = path5.parse(dir);
|
|
1513
1037
|
while (dir !== root) {
|
|
1514
|
-
if (
|
|
1515
|
-
dir =
|
|
1038
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
1039
|
+
dir = path5.dirname(dir);
|
|
1516
1040
|
}
|
|
1517
1041
|
return null;
|
|
1518
1042
|
}
|
|
@@ -1532,14 +1056,14 @@ function getAvailableMemoryGB() {
|
|
|
1532
1056
|
const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
|
|
1533
1057
|
return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
|
|
1534
1058
|
} catch {
|
|
1535
|
-
return
|
|
1059
|
+
return os4.freemem() / (1024 * 1024 * 1024);
|
|
1536
1060
|
}
|
|
1537
1061
|
}
|
|
1538
|
-
return
|
|
1062
|
+
return os4.freemem() / (1024 * 1024 * 1024);
|
|
1539
1063
|
}
|
|
1540
1064
|
function spawnDaemon() {
|
|
1541
1065
|
const freeGB = getAvailableMemoryGB();
|
|
1542
|
-
const totalGB =
|
|
1066
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
1543
1067
|
if (totalGB <= 8) {
|
|
1544
1068
|
process.stderr.write(
|
|
1545
1069
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1559,8 +1083,8 @@ function spawnDaemon() {
|
|
|
1559
1083
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1560
1084
|
return;
|
|
1561
1085
|
}
|
|
1562
|
-
const daemonPath =
|
|
1563
|
-
if (!
|
|
1086
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1087
|
+
if (!existsSync5(daemonPath)) {
|
|
1564
1088
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1565
1089
|
`);
|
|
1566
1090
|
return;
|
|
@@ -1569,7 +1093,7 @@ function spawnDaemon() {
|
|
|
1569
1093
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1570
1094
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1571
1095
|
`);
|
|
1572
|
-
const logPath =
|
|
1096
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
1573
1097
|
let stderrFd = "ignore";
|
|
1574
1098
|
try {
|
|
1575
1099
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1719,9 +1243,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1719
1243
|
"use strict";
|
|
1720
1244
|
init_config();
|
|
1721
1245
|
init_daemon_auth();
|
|
1722
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1723
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1724
|
-
SPAWN_LOCK_PATH =
|
|
1246
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1247
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1248
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1725
1249
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1726
1250
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1727
1251
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -2009,6 +1533,9 @@ function getClient() {
|
|
|
2009
1533
|
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
2010
1534
|
return _daemonClient;
|
|
2011
1535
|
}
|
|
1536
|
+
if (!_resilientClient) {
|
|
1537
|
+
return _adapterClient;
|
|
1538
|
+
}
|
|
2012
1539
|
return _resilientClient;
|
|
2013
1540
|
}
|
|
2014
1541
|
async function initDaemonClient() {
|
|
@@ -3041,6 +2568,127 @@ async function ensureSchema() {
|
|
|
3041
2568
|
VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
|
|
3042
2569
|
END;
|
|
3043
2570
|
`);
|
|
2571
|
+
await client.executeMultiple(`
|
|
2572
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
2573
|
+
id TEXT PRIMARY KEY,
|
|
2574
|
+
agent_id TEXT NOT NULL,
|
|
2575
|
+
project_name TEXT,
|
|
2576
|
+
started_at TEXT NOT NULL,
|
|
2577
|
+
last_event_at TEXT NOT NULL,
|
|
2578
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
2579
|
+
properties TEXT DEFAULT '{}'
|
|
2580
|
+
);
|
|
2581
|
+
|
|
2582
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
|
|
2583
|
+
ON agent_sessions(agent_id, started_at);
|
|
2584
|
+
|
|
2585
|
+
CREATE TABLE IF NOT EXISTS agent_goals (
|
|
2586
|
+
id TEXT PRIMARY KEY,
|
|
2587
|
+
statement TEXT NOT NULL,
|
|
2588
|
+
owner_agent_id TEXT,
|
|
2589
|
+
project_name TEXT,
|
|
2590
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
2591
|
+
priority INTEGER NOT NULL DEFAULT 5,
|
|
2592
|
+
success_criteria TEXT,
|
|
2593
|
+
parent_goal_id TEXT,
|
|
2594
|
+
due_at TEXT,
|
|
2595
|
+
achieved_at TEXT,
|
|
2596
|
+
supersedes_id TEXT,
|
|
2597
|
+
created_at TEXT NOT NULL,
|
|
2598
|
+
updated_at TEXT NOT NULL,
|
|
2599
|
+
source_memory_id TEXT
|
|
2600
|
+
);
|
|
2601
|
+
|
|
2602
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
|
|
2603
|
+
ON agent_goals(project_name, status, priority);
|
|
2604
|
+
|
|
2605
|
+
CREATE TABLE IF NOT EXISTS agent_events (
|
|
2606
|
+
id TEXT PRIMARY KEY,
|
|
2607
|
+
event_type TEXT NOT NULL,
|
|
2608
|
+
occurred_at TEXT NOT NULL,
|
|
2609
|
+
sequence_index INTEGER NOT NULL,
|
|
2610
|
+
actor_agent_id TEXT,
|
|
2611
|
+
agent_role TEXT,
|
|
2612
|
+
project_name TEXT,
|
|
2613
|
+
session_id TEXT,
|
|
2614
|
+
task_id TEXT,
|
|
2615
|
+
goal_id TEXT,
|
|
2616
|
+
parent_event_id TEXT,
|
|
2617
|
+
intention TEXT,
|
|
2618
|
+
outcome TEXT,
|
|
2619
|
+
evidence_memory_id TEXT,
|
|
2620
|
+
impact TEXT,
|
|
2621
|
+
payload TEXT DEFAULT '{}',
|
|
2622
|
+
created_at TEXT NOT NULL
|
|
2623
|
+
);
|
|
2624
|
+
|
|
2625
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_time
|
|
2626
|
+
ON agent_events(occurred_at, sequence_index);
|
|
2627
|
+
|
|
2628
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
|
|
2629
|
+
ON agent_events(session_id, sequence_index);
|
|
2630
|
+
|
|
2631
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
|
|
2632
|
+
ON agent_events(goal_id, occurred_at);
|
|
2633
|
+
|
|
2634
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_memory
|
|
2635
|
+
ON agent_events(evidence_memory_id);
|
|
2636
|
+
|
|
2637
|
+
CREATE TABLE IF NOT EXISTS agent_goal_links (
|
|
2638
|
+
id TEXT PRIMARY KEY,
|
|
2639
|
+
goal_id TEXT NOT NULL,
|
|
2640
|
+
link_type TEXT NOT NULL,
|
|
2641
|
+
target_id TEXT NOT NULL,
|
|
2642
|
+
target_type TEXT NOT NULL,
|
|
2643
|
+
created_at TEXT NOT NULL
|
|
2644
|
+
);
|
|
2645
|
+
|
|
2646
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
|
|
2647
|
+
ON agent_goal_links(goal_id, target_type);
|
|
2648
|
+
|
|
2649
|
+
CREATE TABLE IF NOT EXISTS agent_semantic_labels (
|
|
2650
|
+
id TEXT PRIMARY KEY,
|
|
2651
|
+
source_memory_id TEXT NOT NULL,
|
|
2652
|
+
event_id TEXT,
|
|
2653
|
+
labeler TEXT NOT NULL,
|
|
2654
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
2655
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
2656
|
+
labels TEXT NOT NULL,
|
|
2657
|
+
created_at TEXT NOT NULL,
|
|
2658
|
+
updated_at TEXT NOT NULL
|
|
2659
|
+
);
|
|
2660
|
+
|
|
2661
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
|
|
2662
|
+
ON agent_semantic_labels(source_memory_id, labeler);
|
|
2663
|
+
|
|
2664
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
|
|
2665
|
+
ON agent_semantic_labels(event_id);
|
|
2666
|
+
|
|
2667
|
+
CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
|
|
2668
|
+
id TEXT PRIMARY KEY,
|
|
2669
|
+
project_name TEXT,
|
|
2670
|
+
session_id TEXT,
|
|
2671
|
+
window_start_at TEXT NOT NULL,
|
|
2672
|
+
window_end_at TEXT NOT NULL,
|
|
2673
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
2674
|
+
goal_count INTEGER NOT NULL DEFAULT 0,
|
|
2675
|
+
success_count INTEGER NOT NULL DEFAULT 0,
|
|
2676
|
+
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
2677
|
+
risk_count INTEGER NOT NULL DEFAULT 0,
|
|
2678
|
+
summary TEXT NOT NULL,
|
|
2679
|
+
learnings TEXT NOT NULL DEFAULT '[]',
|
|
2680
|
+
next_actions TEXT NOT NULL DEFAULT '[]',
|
|
2681
|
+
evidence_event_ids TEXT NOT NULL DEFAULT '[]',
|
|
2682
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
2683
|
+
created_at TEXT NOT NULL
|
|
2684
|
+
);
|
|
2685
|
+
|
|
2686
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
|
|
2687
|
+
ON agent_reflection_checkpoints(project_name, window_end_at);
|
|
2688
|
+
|
|
2689
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
|
|
2690
|
+
ON agent_reflection_checkpoints(session_id, window_end_at);
|
|
2691
|
+
`);
|
|
3044
2692
|
try {
|
|
3045
2693
|
await client.execute({
|
|
3046
2694
|
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
@@ -3188,1719 +2836,1430 @@ var init_database = __esm({
|
|
|
3188
2836
|
}
|
|
3189
2837
|
});
|
|
3190
2838
|
|
|
3191
|
-
// src/lib/
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
2839
|
+
// src/lib/shard-manager.ts
|
|
2840
|
+
var shard_manager_exports = {};
|
|
2841
|
+
__export(shard_manager_exports, {
|
|
2842
|
+
auditShardHealth: () => auditShardHealth,
|
|
2843
|
+
disposeShards: () => disposeShards,
|
|
2844
|
+
ensureShardSchema: () => ensureShardSchema,
|
|
2845
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2846
|
+
getReadyShardClient: () => getReadyShardClient,
|
|
2847
|
+
getShardClient: () => getShardClient,
|
|
2848
|
+
getShardsDir: () => getShardsDir,
|
|
2849
|
+
initShardManager: () => initShardManager,
|
|
2850
|
+
isShardingEnabled: () => isShardingEnabled,
|
|
2851
|
+
listShards: () => listShards,
|
|
2852
|
+
shardExists: () => shardExists
|
|
2853
|
+
});
|
|
2854
|
+
import path7 from "path";
|
|
2855
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync3 } from "fs";
|
|
2856
|
+
import { createClient as createClient2 } from "@libsql/client";
|
|
2857
|
+
function initShardManager(encryptionKey) {
|
|
2858
|
+
_encryptionKey = encryptionKey;
|
|
2859
|
+
if (!existsSync7(SHARDS_DIR)) {
|
|
2860
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2861
|
+
}
|
|
2862
|
+
_shardingEnabled = true;
|
|
2863
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2864
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2865
|
+
_evictionTimer.unref();
|
|
2866
|
+
}
|
|
2867
|
+
function isShardingEnabled() {
|
|
2868
|
+
return _shardingEnabled;
|
|
2869
|
+
}
|
|
2870
|
+
function getShardsDir() {
|
|
2871
|
+
return SHARDS_DIR;
|
|
2872
|
+
}
|
|
2873
|
+
function getShardClient(projectName) {
|
|
2874
|
+
if (!_encryptionKey) {
|
|
2875
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
2876
|
+
}
|
|
2877
|
+
const safeName = safeShardName(projectName);
|
|
2878
|
+
if (!safeName || safeName === "unknown") {
|
|
2879
|
+
throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
|
|
2880
|
+
}
|
|
2881
|
+
const cached = _shards.get(safeName);
|
|
2882
|
+
if (cached) {
|
|
2883
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2884
|
+
return cached;
|
|
2885
|
+
}
|
|
2886
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2887
|
+
evictLRU();
|
|
2888
|
+
}
|
|
2889
|
+
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
2890
|
+
const client = createClient2({
|
|
2891
|
+
url: `file:${dbPath}`,
|
|
2892
|
+
encryptionKey: _encryptionKey
|
|
3199
2893
|
});
|
|
2894
|
+
_shards.set(safeName, client);
|
|
2895
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2896
|
+
return client;
|
|
3200
2897
|
}
|
|
3201
|
-
function
|
|
3202
|
-
|
|
3203
|
-
return
|
|
2898
|
+
function shardExists(projectName) {
|
|
2899
|
+
const safeName = safeShardName(projectName);
|
|
2900
|
+
return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
|
|
2901
|
+
}
|
|
2902
|
+
function safeShardName(projectName) {
|
|
2903
|
+
return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2904
|
+
}
|
|
2905
|
+
function listShards() {
|
|
2906
|
+
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2907
|
+
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2908
|
+
}
|
|
2909
|
+
async function auditShardHealth(options = {}) {
|
|
2910
|
+
if (!_encryptionKey) {
|
|
2911
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
2912
|
+
}
|
|
2913
|
+
const repair = options.repair === true;
|
|
2914
|
+
const dryRun = options.dryRun === true;
|
|
2915
|
+
const names = listShards();
|
|
2916
|
+
const shards = [];
|
|
2917
|
+
for (const name of names) {
|
|
2918
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
2919
|
+
const stat = statSync3(dbPath);
|
|
2920
|
+
const item = {
|
|
2921
|
+
name,
|
|
2922
|
+
path: dbPath,
|
|
2923
|
+
ok: false,
|
|
2924
|
+
unreadable: false,
|
|
2925
|
+
error: null,
|
|
2926
|
+
size: stat.size,
|
|
2927
|
+
mtime: stat.mtime.toISOString(),
|
|
2928
|
+
memoryCount: null
|
|
2929
|
+
};
|
|
2930
|
+
const client = createClient2({
|
|
2931
|
+
url: `file:${dbPath}`,
|
|
2932
|
+
encryptionKey: _encryptionKey
|
|
2933
|
+
});
|
|
2934
|
+
try {
|
|
2935
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
2936
|
+
const hasMemories = await client.execute(
|
|
2937
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
2938
|
+
);
|
|
2939
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
2940
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
2941
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
2942
|
+
}
|
|
2943
|
+
item.ok = true;
|
|
2944
|
+
} catch (err) {
|
|
2945
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2946
|
+
item.error = message;
|
|
2947
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
2948
|
+
if (item.unreadable && repair && !dryRun) {
|
|
2949
|
+
client.close();
|
|
2950
|
+
_shards.delete(name);
|
|
2951
|
+
_shardLastAccess.delete(name);
|
|
2952
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2953
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
2954
|
+
renameSync3(dbPath, archivedPath);
|
|
2955
|
+
item.archivedPath = archivedPath;
|
|
2956
|
+
}
|
|
2957
|
+
} finally {
|
|
2958
|
+
try {
|
|
2959
|
+
client.close();
|
|
2960
|
+
} catch {
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
shards.push(item);
|
|
2964
|
+
}
|
|
2965
|
+
return {
|
|
2966
|
+
total: shards.length,
|
|
2967
|
+
ok: shards.filter((s) => s.ok).length,
|
|
2968
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
2969
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
2970
|
+
shards
|
|
2971
|
+
};
|
|
3204
2972
|
}
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
2973
|
+
async function ensureShardSchema(client) {
|
|
2974
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
2975
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
2976
|
+
try {
|
|
2977
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
2978
|
+
} catch {
|
|
3208
2979
|
}
|
|
3209
|
-
|
|
2980
|
+
await client.executeMultiple(`
|
|
2981
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
2982
|
+
id TEXT PRIMARY KEY,
|
|
2983
|
+
agent_id TEXT NOT NULL,
|
|
2984
|
+
agent_role TEXT NOT NULL,
|
|
2985
|
+
session_id TEXT NOT NULL,
|
|
2986
|
+
timestamp TEXT NOT NULL,
|
|
2987
|
+
tool_name TEXT NOT NULL,
|
|
2988
|
+
project_name TEXT NOT NULL,
|
|
2989
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
2990
|
+
raw_text TEXT NOT NULL,
|
|
2991
|
+
vector F32_BLOB(1024),
|
|
2992
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
2993
|
+
);
|
|
3210
2994
|
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
2995
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
|
|
2996
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
|
|
2997
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
|
|
2998
|
+
`);
|
|
2999
|
+
await client.executeMultiple(`
|
|
3000
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
3001
|
+
raw_text,
|
|
3002
|
+
content='memories',
|
|
3003
|
+
content_rowid='rowid'
|
|
3004
|
+
);
|
|
3005
|
+
|
|
3006
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
3007
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
3008
|
+
END;
|
|
3009
|
+
|
|
3010
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
3011
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
3012
|
+
END;
|
|
3013
|
+
|
|
3014
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
3015
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
3016
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
3017
|
+
END;
|
|
3018
|
+
`);
|
|
3019
|
+
for (const col of [
|
|
3020
|
+
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
3021
|
+
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3022
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3023
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
3024
|
+
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
3025
|
+
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
3026
|
+
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
3027
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
|
|
3028
|
+
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
3029
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
|
|
3030
|
+
"ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
|
|
3031
|
+
"ALTER TABLE memories ADD COLUMN last_accessed TEXT",
|
|
3032
|
+
// Wiki linkage columns (must match database.ts)
|
|
3033
|
+
"ALTER TABLE memories ADD COLUMN workspace_id TEXT",
|
|
3034
|
+
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
3035
|
+
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
3036
|
+
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
3037
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
3038
|
+
// Source provenance columns (must match database.ts)
|
|
3039
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
3040
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
3041
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
3042
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
3043
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
3044
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
3045
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
3046
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3047
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3048
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3049
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3050
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3051
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3052
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3053
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3054
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3055
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3056
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3057
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3058
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3059
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3060
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3061
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3062
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
|
|
3063
|
+
"ALTER TABLE memories ADD COLUMN deleted_at TEXT"
|
|
3064
|
+
]) {
|
|
3065
|
+
try {
|
|
3066
|
+
await client.execute(col);
|
|
3067
|
+
} catch {
|
|
3225
3068
|
}
|
|
3226
|
-
} catch {
|
|
3227
3069
|
}
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3070
|
+
for (const idx of [
|
|
3071
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
3072
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
|
|
3073
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
|
|
3074
|
+
]) {
|
|
3075
|
+
try {
|
|
3076
|
+
await client.execute(idx);
|
|
3077
|
+
} catch {
|
|
3232
3078
|
}
|
|
3079
|
+
}
|
|
3080
|
+
try {
|
|
3081
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
3233
3082
|
} catch {
|
|
3234
3083
|
}
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
init_config();
|
|
3245
|
-
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
3246
|
-
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
3247
|
-
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
3084
|
+
for (const idx of [
|
|
3085
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
|
|
3086
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
|
|
3087
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
|
|
3088
|
+
]) {
|
|
3089
|
+
try {
|
|
3090
|
+
await client.execute(idx);
|
|
3091
|
+
} catch {
|
|
3092
|
+
}
|
|
3248
3093
|
}
|
|
3249
|
-
|
|
3094
|
+
await client.executeMultiple(`
|
|
3095
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
3096
|
+
id TEXT PRIMARY KEY,
|
|
3097
|
+
name TEXT NOT NULL,
|
|
3098
|
+
type TEXT NOT NULL,
|
|
3099
|
+
first_seen TEXT NOT NULL,
|
|
3100
|
+
last_seen TEXT NOT NULL,
|
|
3101
|
+
properties TEXT DEFAULT '{}',
|
|
3102
|
+
UNIQUE(name, type)
|
|
3103
|
+
);
|
|
3250
3104
|
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3105
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
3106
|
+
id TEXT PRIMARY KEY,
|
|
3107
|
+
source_entity_id TEXT NOT NULL,
|
|
3108
|
+
target_entity_id TEXT NOT NULL,
|
|
3109
|
+
type TEXT NOT NULL,
|
|
3110
|
+
weight REAL DEFAULT 1.0,
|
|
3111
|
+
timestamp TEXT NOT NULL,
|
|
3112
|
+
properties TEXT DEFAULT '{}',
|
|
3113
|
+
UNIQUE(source_entity_id, target_entity_id, type)
|
|
3114
|
+
);
|
|
3115
|
+
|
|
3116
|
+
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
3117
|
+
entity_id TEXT NOT NULL,
|
|
3118
|
+
memory_id TEXT NOT NULL,
|
|
3119
|
+
PRIMARY KEY (entity_id, memory_id)
|
|
3120
|
+
);
|
|
3121
|
+
|
|
3122
|
+
CREATE TABLE IF NOT EXISTS relationship_memories (
|
|
3123
|
+
relationship_id TEXT NOT NULL,
|
|
3124
|
+
memory_id TEXT NOT NULL,
|
|
3125
|
+
PRIMARY KEY (relationship_id, memory_id)
|
|
3126
|
+
);
|
|
3127
|
+
|
|
3128
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
3129
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
3130
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
|
|
3131
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
|
|
3132
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
|
|
3133
|
+
|
|
3134
|
+
CREATE TABLE IF NOT EXISTS hyperedges (
|
|
3135
|
+
id TEXT PRIMARY KEY,
|
|
3136
|
+
label TEXT NOT NULL,
|
|
3137
|
+
relation TEXT NOT NULL,
|
|
3138
|
+
confidence REAL DEFAULT 1.0,
|
|
3139
|
+
timestamp TEXT NOT NULL
|
|
3140
|
+
);
|
|
3141
|
+
|
|
3142
|
+
CREATE TABLE IF NOT EXISTS hyperedge_nodes (
|
|
3143
|
+
hyperedge_id TEXT NOT NULL,
|
|
3144
|
+
entity_id TEXT NOT NULL,
|
|
3145
|
+
PRIMARY KEY (hyperedge_id, entity_id)
|
|
3146
|
+
);
|
|
3147
|
+
`);
|
|
3148
|
+
for (const col of [
|
|
3149
|
+
"ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
|
|
3150
|
+
"ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
|
|
3151
|
+
]) {
|
|
3284
3152
|
try {
|
|
3285
|
-
|
|
3286
|
-
Y.applyUpdate(doc, new Uint8Array(state));
|
|
3153
|
+
await client.execute(col);
|
|
3287
3154
|
} catch {
|
|
3288
|
-
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
3289
|
-
try {
|
|
3290
|
-
unlinkSync3(sp);
|
|
3291
|
-
} catch {
|
|
3292
|
-
}
|
|
3293
|
-
rebuildFromDb().catch((err) => {
|
|
3294
|
-
console.warn("[crdt-sync] rebuild from DB failed:", err);
|
|
3295
|
-
});
|
|
3296
3155
|
}
|
|
3297
3156
|
}
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
return Y.encodeStateAsUpdate(d, remoteStateVector);
|
|
3322
|
-
}
|
|
3323
|
-
function getStateVector() {
|
|
3324
|
-
const d = initCrdtDoc();
|
|
3325
|
-
return Y.encodeStateVector(d);
|
|
3326
|
-
}
|
|
3327
|
-
function importExistingMemories(memories) {
|
|
3328
|
-
const map = getMemoriesMap();
|
|
3329
|
-
const d = initCrdtDoc();
|
|
3330
|
-
let imported = 0;
|
|
3331
|
-
d.transact(() => {
|
|
3332
|
-
for (const mem of memories) {
|
|
3333
|
-
if (!mem.id) continue;
|
|
3334
|
-
if (map.has(mem.id)) continue;
|
|
3335
|
-
const entry = new Y.Map();
|
|
3336
|
-
entry.set("id", mem.id);
|
|
3337
|
-
entry.set("agent_id", mem.agent_id ?? null);
|
|
3338
|
-
entry.set("agent_role", mem.agent_role ?? null);
|
|
3339
|
-
entry.set("session_id", mem.session_id ?? null);
|
|
3340
|
-
entry.set("timestamp", mem.timestamp ?? null);
|
|
3341
|
-
entry.set("tool_name", mem.tool_name ?? null);
|
|
3342
|
-
entry.set("project_name", mem.project_name ?? null);
|
|
3343
|
-
entry.set("has_error", mem.has_error ?? 0);
|
|
3344
|
-
entry.set("raw_text", mem.raw_text ?? "");
|
|
3345
|
-
entry.set("version", mem.version ?? 0);
|
|
3346
|
-
entry.set("author_device_id", mem.author_device_id ?? null);
|
|
3347
|
-
entry.set("scope", mem.scope ?? "business");
|
|
3348
|
-
map.set(mem.id, entry);
|
|
3349
|
-
imported++;
|
|
3157
|
+
}
|
|
3158
|
+
async function getReadyShardClient(projectName) {
|
|
3159
|
+
const safeName = safeShardName(projectName);
|
|
3160
|
+
let client = getShardClient(projectName);
|
|
3161
|
+
try {
|
|
3162
|
+
await ensureShardSchema(client);
|
|
3163
|
+
return client;
|
|
3164
|
+
} catch (err) {
|
|
3165
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3166
|
+
if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
|
|
3167
|
+
client.close();
|
|
3168
|
+
_shards.delete(safeName);
|
|
3169
|
+
_shardLastAccess.delete(safeName);
|
|
3170
|
+
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
3171
|
+
if (existsSync7(dbPath)) {
|
|
3172
|
+
const stat = statSync3(dbPath);
|
|
3173
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3174
|
+
const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
3175
|
+
renameSync3(dbPath, archivedPath);
|
|
3176
|
+
process.stderr.write(
|
|
3177
|
+
`[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
|
|
3178
|
+
`
|
|
3179
|
+
);
|
|
3350
3180
|
}
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
let
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
entry.set("id", beh.id);
|
|
3364
|
-
entry.set("agent_id", beh.agent_id ?? null);
|
|
3365
|
-
entry.set("project_name", beh.project_name ?? null);
|
|
3366
|
-
entry.set("domain", beh.domain ?? null);
|
|
3367
|
-
entry.set("content", beh.content ?? null);
|
|
3368
|
-
entry.set("active", beh.active ?? 1);
|
|
3369
|
-
entry.set("priority", beh.priority ?? "p1");
|
|
3370
|
-
entry.set("created_at", beh.created_at ?? null);
|
|
3371
|
-
entry.set("updated_at", beh.updated_at ?? null);
|
|
3372
|
-
map.set(beh.id, entry);
|
|
3373
|
-
imported++;
|
|
3181
|
+
client = getShardClient(projectName);
|
|
3182
|
+
await ensureShardSchema(client);
|
|
3183
|
+
return client;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
function evictLRU() {
|
|
3187
|
+
let oldest = null;
|
|
3188
|
+
let oldestTime = Infinity;
|
|
3189
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3190
|
+
if (time < oldestTime) {
|
|
3191
|
+
oldestTime = time;
|
|
3192
|
+
oldest = name;
|
|
3374
3193
|
}
|
|
3194
|
+
}
|
|
3195
|
+
if (oldest) {
|
|
3196
|
+
const client = _shards.get(oldest);
|
|
3197
|
+
if (client) {
|
|
3198
|
+
client.close();
|
|
3199
|
+
}
|
|
3200
|
+
_shards.delete(oldest);
|
|
3201
|
+
_shardLastAccess.delete(oldest);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
function evictIdleShards() {
|
|
3205
|
+
const now = Date.now();
|
|
3206
|
+
const toEvict = [];
|
|
3207
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3208
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3209
|
+
toEvict.push(name);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
for (const name of toEvict) {
|
|
3213
|
+
const client = _shards.get(name);
|
|
3214
|
+
if (client) {
|
|
3215
|
+
client.close();
|
|
3216
|
+
}
|
|
3217
|
+
_shards.delete(name);
|
|
3218
|
+
_shardLastAccess.delete(name);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
function getOpenShardCount() {
|
|
3222
|
+
return _shards.size;
|
|
3223
|
+
}
|
|
3224
|
+
function disposeShards() {
|
|
3225
|
+
if (_evictionTimer) {
|
|
3226
|
+
clearInterval(_evictionTimer);
|
|
3227
|
+
_evictionTimer = null;
|
|
3228
|
+
}
|
|
3229
|
+
for (const [, client] of _shards) {
|
|
3230
|
+
client.close();
|
|
3231
|
+
}
|
|
3232
|
+
_shards.clear();
|
|
3233
|
+
_shardLastAccess.clear();
|
|
3234
|
+
_shardingEnabled = false;
|
|
3235
|
+
_encryptionKey = null;
|
|
3236
|
+
}
|
|
3237
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
3238
|
+
var init_shard_manager = __esm({
|
|
3239
|
+
"src/lib/shard-manager.ts"() {
|
|
3240
|
+
"use strict";
|
|
3241
|
+
init_config();
|
|
3242
|
+
SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
|
|
3243
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3244
|
+
MAX_OPEN_SHARDS = 10;
|
|
3245
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
3246
|
+
_shards = /* @__PURE__ */ new Map();
|
|
3247
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3248
|
+
_evictionTimer = null;
|
|
3249
|
+
_encryptionKey = null;
|
|
3250
|
+
_shardingEnabled = false;
|
|
3251
|
+
}
|
|
3252
|
+
});
|
|
3253
|
+
|
|
3254
|
+
// src/lib/platform-procedures.ts
|
|
3255
|
+
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
3256
|
+
var init_platform_procedures = __esm({
|
|
3257
|
+
"src/lib/platform-procedures.ts"() {
|
|
3258
|
+
"use strict";
|
|
3259
|
+
PLATFORM_PROCEDURES = [
|
|
3260
|
+
// --- Foundation: what is exe-os ---
|
|
3261
|
+
{
|
|
3262
|
+
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
3263
|
+
domain: "architecture",
|
|
3264
|
+
priority: "p0",
|
|
3265
|
+
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
|
|
3266
|
+
},
|
|
3267
|
+
{
|
|
3268
|
+
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
3269
|
+
domain: "architecture",
|
|
3270
|
+
priority: "p0",
|
|
3271
|
+
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code, Codex, or OpenCode. The founder picks their default tool at setup. The COO manages employees in tmux sessions. Each coordinator session is a separate window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. The tool is the shell, exe-os is the brain."
|
|
3272
|
+
},
|
|
3273
|
+
{
|
|
3274
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
3275
|
+
domain: "architecture",
|
|
3276
|
+
priority: "p0",
|
|
3277
|
+
content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
|
|
3278
|
+
},
|
|
3279
|
+
{
|
|
3280
|
+
title: "Runtime settings \u2014 COO can view and change tools per agent",
|
|
3281
|
+
domain: "workflow",
|
|
3282
|
+
priority: "p1",
|
|
3283
|
+
content: "exe-os supports three tools: Claude Code (Anthropic), Codex (OpenAI), and OpenCode (open source, 75+ providers). Each agent can use a different tool and model. COO uses set_agent_config MCP tool to view or change settings. Call with no args to show all agents. Call with agent_id + runtime + model to change. Users can also run `exe-os settings` from terminal for interactive arrow-key selection."
|
|
3284
|
+
},
|
|
3285
|
+
// --- Hierarchy and dispatch ---
|
|
3286
|
+
{
|
|
3287
|
+
title: "Chain of command \u2014 who talks to whom",
|
|
3288
|
+
domain: "workflow",
|
|
3289
|
+
priority: "p0",
|
|
3290
|
+
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
3291
|
+
},
|
|
3292
|
+
{
|
|
3293
|
+
title: "Customer orchestration maturity \u2014 recommend, never trap",
|
|
3294
|
+
domain: "workflow",
|
|
3295
|
+
priority: "p1",
|
|
3296
|
+
content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
|
|
3297
|
+
},
|
|
3298
|
+
{
|
|
3299
|
+
title: "Single dispatch path \u2014 create_task only",
|
|
3300
|
+
domain: "workflow",
|
|
3301
|
+
priority: "p0",
|
|
3302
|
+
content: "create_task is the ONLY way to dispatch work to another agent. No direct ensureEmployee calls, no manual tmux spawns, no send_message for actionable work. create_task \u2192 system auto-spawns \u2192 session correctly named. ONE PATH. No backdoors. No exceptions."
|
|
3303
|
+
},
|
|
3304
|
+
// --- Session isolation ---
|
|
3305
|
+
{
|
|
3306
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
3307
|
+
domain: "security",
|
|
3308
|
+
priority: "p0",
|
|
3309
|
+
content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
|
|
3310
|
+
},
|
|
3311
|
+
{
|
|
3312
|
+
title: "Session isolation \u2014 never touch another session's work",
|
|
3313
|
+
domain: "workflow",
|
|
3314
|
+
priority: "p0",
|
|
3315
|
+
content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
|
|
3316
|
+
},
|
|
3317
|
+
// --- Engineering: session scoping in code ---
|
|
3318
|
+
{
|
|
3319
|
+
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
3320
|
+
domain: "architecture",
|
|
3321
|
+
priority: "p0",
|
|
3322
|
+
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
|
|
3323
|
+
},
|
|
3324
|
+
// --- Hard constraints ---
|
|
3325
|
+
{
|
|
3326
|
+
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
3327
|
+
domain: "security",
|
|
3328
|
+
priority: "p0",
|
|
3329
|
+
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
3330
|
+
},
|
|
3331
|
+
{
|
|
3332
|
+
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
3333
|
+
domain: "support",
|
|
3334
|
+
priority: "p0",
|
|
3335
|
+
content: "When an agent encounters a suspected Exe OS bug, update breakage, MCP/tool failure, installer issue, memory/orchestration defect, or customer-local patch need, it MUST use create_bug_report. Do this before or alongside any local workaround so the report reaches AskExe support directly via the customer's license. Classify first: upstream_bug = reproducible exe-os/platform defect; customer_customization = identity, behavior, procedure, config, branding, workflow preference that belongs in customer-owned layers; emergency_hotfix = temporary local patch. For upstream bugs/emergency hotfixes include version, repro steps, expected/actual, files changed, workaround, and local diff summary. Avoid permanent platform-code patches unless founder approves; if a hotfix is unavoidable, document it in the bug report and re-check after npm update."
|
|
3336
|
+
},
|
|
3337
|
+
// --- Operations ---
|
|
3338
|
+
{
|
|
3339
|
+
title: "Managers must supervise deployed workers",
|
|
3340
|
+
domain: "workflow",
|
|
3341
|
+
priority: "p0",
|
|
3342
|
+
content: `Every manager (COO/CTO/CMO) who dispatches work to a worker MUST actively monitor them. Check tmux capture-pane every 10 minutes. Verify they're working, not stuck. If idle at prompt with in_progress task \u2192 send intercom. If stuck \u2192 unblock or escalate. "Standing by" without checking is negligence.`
|
|
3343
|
+
},
|
|
3344
|
+
{
|
|
3345
|
+
title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
|
|
3346
|
+
domain: "workflow",
|
|
3347
|
+
priority: "p0",
|
|
3348
|
+
content: "On every /exe boot, COO MUST check system health BEFORE other work: (1) daemon \u2014 is exed PID alive, (2) cloud sync \u2014 grep workers.log for recent cloud-sync errors, (3) memory count \u2014 total in DB, (4) sync delta \u2014 local vs cloud storage_bytes. Report as 4-line status table. If ANY check fails, surface to founder immediately. Do not proceed to tasks until health confirmed."
|
|
3349
|
+
},
|
|
3350
|
+
{
|
|
3351
|
+
title: "exe-build-adv mandatory for 3+ files",
|
|
3352
|
+
domain: "workflow",
|
|
3353
|
+
priority: "p0",
|
|
3354
|
+
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
3355
|
+
},
|
|
3356
|
+
{
|
|
3357
|
+
title: "Commit discipline \u2014 never leave verified work floating",
|
|
3358
|
+
domain: "workflow",
|
|
3359
|
+
priority: "p1",
|
|
3360
|
+
content: "After any code-change batch passes typecheck/tests/build, run git status, summarize changed files, and commit with a clear message before ending the session. If work must remain uncommitted for review/dogfood, explicitly say so, list the files, and state the blocker. Never imply work is complete while verified changes are still floating locally."
|
|
3361
|
+
},
|
|
3362
|
+
{
|
|
3363
|
+
title: "Desktop and TUI are the same product",
|
|
3364
|
+
domain: "architecture",
|
|
3365
|
+
priority: "p0",
|
|
3366
|
+
content: "Desktop and TUI are the SAME product in different renderers. Same data contracts, same interactions, same acceptance criteria. Desktop tab specs in ARCHITECTURE.md ARE the TUI specs. When building TUI, cross-reference Desktop spec. Different tab names, identical behavior. Never treat them as separate products."
|
|
3367
|
+
},
|
|
3368
|
+
// --- Orchestration golden path ---
|
|
3369
|
+
{
|
|
3370
|
+
title: "Task lifecycle \u2014 the golden path every agent follows",
|
|
3371
|
+
domain: "workflow",
|
|
3372
|
+
priority: "p0",
|
|
3373
|
+
content: "create_task is dispatch + delivery. Task lifecycle: open \u2192 in_progress (you start) \u2192 done (update_task when finished) \u2192 needs_review (reviewer nudged) \u2192 closed (COO only via close_task). DB is the reliable delivery \u2014 intercom is just a speedup nudge. If you finish a task, self-chain: check for next task immediately (step 7). Never wait for a nudge. Never say 'standing by.'"
|
|
3374
|
+
},
|
|
3375
|
+
{
|
|
3376
|
+
title: "Intercom is a speedup, not delivery \u2014 DB is the source of truth",
|
|
3377
|
+
domain: "architecture",
|
|
3378
|
+
priority: "p0",
|
|
3379
|
+
content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
|
|
3380
|
+
},
|
|
3381
|
+
// --- MCP is the ONLY data interface ---
|
|
3382
|
+
{
|
|
3383
|
+
title: "MCP disconnect \u2014 ask the user, never work around it",
|
|
3384
|
+
domain: "workflow",
|
|
3385
|
+
priority: "p0",
|
|
3386
|
+
content: "If MCP tools are unavailable, disconnected, or returning connection errors: STOP. Tell the user clearly: 'MCP server is disconnected. Please run /mcp to reconnect.' Do NOT attempt workarounds \u2014 no raw Node imports, no direct DB access, no CLI hacks, no daemon socket calls. MCP is the ONLY data interface. Working around it wastes time, hits bundling issues, and bypasses the contract boundary. Ask once, wait, proceed when reconnected."
|
|
3387
|
+
},
|
|
3388
|
+
// --- MCP Tool Catalog (Layer 0 — every agent knows what tools exist) ---
|
|
3389
|
+
{
|
|
3390
|
+
title: "MCP tools \u2014 memory and search",
|
|
3391
|
+
domain: "tool-use",
|
|
3392
|
+
priority: "p1",
|
|
3393
|
+
content: "recall_my_memory: search your own memories (semantic + FTS). ask_team_memory: search a colleague's memories by agent name. store_memory: persist a memory (decisions, summaries, context). commit_memory: high-importance memory that survives consolidation. search_everything: unified search across memories, tasks, entities, conversations. get_session_context: temporal window of memories around a timestamp. consolidate_memories: merge duplicate/related memories into insights. get_memory_cardinality: count memories per agent (health check)."
|
|
3394
|
+
},
|
|
3395
|
+
{
|
|
3396
|
+
title: "MCP tools \u2014 task orchestration",
|
|
3397
|
+
domain: "tool-use",
|
|
3398
|
+
priority: "p1",
|
|
3399
|
+
content: "create_task: dispatch work to an employee (auto-spawns session). The ONLY dispatch path. list_tasks: query tasks by status, assignee, project. get_task: fetch full task details by ID. update_task: change status (in_progress, done, blocked, cancelled) + add result summary. close_task: finalize a reviewed task (COO only). checkpoint_task: save progress state for crash recovery. resume_employee: re-spawn an employee session for an existing task."
|
|
3400
|
+
},
|
|
3401
|
+
{
|
|
3402
|
+
title: "MCP tools \u2014 knowledge graph (GraphRAG)",
|
|
3403
|
+
domain: "tool-use",
|
|
3404
|
+
priority: "p1",
|
|
3405
|
+
content: "query_relationships: find connections between entities in the knowledge graph. get_entity_neighbors: explore an entity's direct connections. get_hot_entities: find most-referenced entities (trending topics). get_graph_stats: graph health \u2014 entity/relationship counts, density. export_graph: export graph data for visualization. merge_entities: deduplicate entities (alias resolution). find_similar_trajectories: match tool-call patterns to past task solutions."
|
|
3406
|
+
},
|
|
3407
|
+
{
|
|
3408
|
+
title: "MCP tools \u2014 identity, behavior, and decisions",
|
|
3409
|
+
domain: "tool-use",
|
|
3410
|
+
priority: "p1",
|
|
3411
|
+
content: "get_identity: read an agent's exe.md (Layer 1 identity). update_identity: write an agent's exe.md. Identity > behavior \u2014 use for permanent rules. store_behavior: record a correction or pattern for an agent (Layer 2 expertise). list_behaviors: view an agent's active behaviors. deactivate_behavior: soft-delete a stale or conflicting behavior. store_decision: record an ADR (architectural decision record). get_decision: retrieve a past decision by query. create_bug_report: customer-facing bug/support intake; use whenever an Exe OS bug or emergency hotfix is encountered so the report reaches AskExe directly. Customers only get report access; internal list/get/triage support tools are AskExe-only."
|
|
3412
|
+
},
|
|
3413
|
+
{
|
|
3414
|
+
title: "MCP tools \u2014 communication and messaging",
|
|
3415
|
+
domain: "tool-use",
|
|
3416
|
+
priority: "p1",
|
|
3417
|
+
content: "send_message: send supplementary context to another agent (NOT for actionable work \u2014 use create_task). acknowledge_messages: mark messages as read. send_whatsapp: send WhatsApp message via gateway (customer-facing alerts). query_conversations: search ingested conversations across all channels (WhatsApp, email, etc.)."
|
|
3418
|
+
},
|
|
3419
|
+
{
|
|
3420
|
+
title: "MCP tools \u2014 wiki, documents, and content",
|
|
3421
|
+
domain: "tool-use",
|
|
3422
|
+
priority: "p1",
|
|
3423
|
+
content: "wiki: read/list wiki pages only. Direct wiki write tools are removed; wiki updates flow through raw-data ingestion/projection into the curated wiki store. Legacy aliases: list_wiki_pages/get_wiki_page. crm: read/list/get CRM records from exe-db. raw_data: read capped raw landing-pad events from exe-db with payload opt-in. ingest_document: import a file (PDF, MD, etc.) into memory as chunks. list_documents: browse ingested documents by workspace. purge_document: remove a document and its memory chunks. set_document_importance: adjust chunk importance scores. rerank_documents: re-score document relevance for a query."
|
|
3424
|
+
},
|
|
3425
|
+
{
|
|
3426
|
+
title: "MCP tools \u2014 system, operations, and admin",
|
|
3427
|
+
domain: "tool-use",
|
|
3428
|
+
priority: "p1",
|
|
3429
|
+
content: "get_agent_spend: token usage per agent/session (cost tracking). list_agent_sessions: view agent session history. get_session_kills: audit log of killed sessions. get_daemon_health: check exed daemon status. get_auto_wake_status: daemon auto-wake configuration. get_worker_gate: check worker deployment gates. run_memory_audit: health check \u2014 duplicates, null vectors, orphaned rows. run_consolidation: trigger sleep-time memory consolidation. cloud_sync: force a cloud sync cycle. backup_vps: trigger VPS backup."
|
|
3430
|
+
},
|
|
3431
|
+
{
|
|
3432
|
+
title: "MCP tools \u2014 config, licensing, and team",
|
|
3433
|
+
domain: "tool-use",
|
|
3434
|
+
priority: "p1",
|
|
3435
|
+
content: "set_agent_config: view/change per-agent runtime and model settings. list_employees: view the employee roster. add_person: add a person to the CRM contacts roster. list_people: browse CRM contacts. get_person: fetch contact details. get_license_status: check license validity. create_license: generate a new license key (admin). list_licenses: view all issued licenses. activate_license: activate a license on a device."
|
|
3436
|
+
},
|
|
3437
|
+
{
|
|
3438
|
+
title: "MCP tools \u2014 advanced (triggers, skills, orchestration)",
|
|
3439
|
+
domain: "tool-use",
|
|
3440
|
+
priority: "p1",
|
|
3441
|
+
content: "create_trigger: set up a scheduled recurring agent job (cron). list_triggers: view active triggers. load_skill: load a slash-command skill dynamically. apply_starter_pack: import a pre-built behavior + identity pack for a role. export_orchestration: export full org state (tasks, behaviors, identities) as portable JSON. import_orchestration: import org state into a new instance. deploy_client: deploy a customer client instance. query_company_brain: unified RAG query across all company knowledge. create_reminder: set a text reminder (shown in boot brief). list_reminders: view pending reminders. complete_reminder: mark a reminder done. company_procedure: manage customer-owned company procedures (Layer 0; actions: store, list, deactivate). Legacy aliases: global_procedure, store_global_procedure, list_global_procedures, deactivate_global_procedure."
|
|
3442
|
+
}
|
|
3443
|
+
];
|
|
3444
|
+
PLATFORM_PROCEDURE_TITLES = new Set(
|
|
3445
|
+
PLATFORM_PROCEDURES.map((p) => p.title)
|
|
3446
|
+
);
|
|
3447
|
+
}
|
|
3448
|
+
});
|
|
3449
|
+
|
|
3450
|
+
// src/lib/global-procedures.ts
|
|
3451
|
+
var global_procedures_exports = {};
|
|
3452
|
+
__export(global_procedures_exports, {
|
|
3453
|
+
deactivateGlobalProcedure: () => deactivateGlobalProcedure,
|
|
3454
|
+
getGlobalProceduresBlock: () => getGlobalProceduresBlock,
|
|
3455
|
+
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
3456
|
+
storeGlobalProcedure: () => storeGlobalProcedure
|
|
3457
|
+
});
|
|
3458
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3459
|
+
async function loadGlobalProcedures() {
|
|
3460
|
+
const client = getClient();
|
|
3461
|
+
const result = await client.execute({
|
|
3462
|
+
sql: "SELECT * FROM company_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
3463
|
+
args: []
|
|
3375
3464
|
});
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
id,
|
|
3384
|
-
agent_id: entry.get("agent_id"),
|
|
3385
|
-
agent_role: entry.get("agent_role"),
|
|
3386
|
-
session_id: entry.get("session_id"),
|
|
3387
|
-
timestamp: entry.get("timestamp"),
|
|
3388
|
-
tool_name: entry.get("tool_name"),
|
|
3389
|
-
project_name: entry.get("project_name"),
|
|
3390
|
-
has_error: entry.get("has_error"),
|
|
3391
|
-
raw_text: entry.get("raw_text"),
|
|
3392
|
-
version: entry.get("version"),
|
|
3393
|
-
author_device_id: entry.get("author_device_id"),
|
|
3394
|
-
scope: entry.get("scope")
|
|
3395
|
-
});
|
|
3396
|
-
});
|
|
3397
|
-
return records;
|
|
3398
|
-
}
|
|
3399
|
-
function readAllBehaviors() {
|
|
3400
|
-
const map = getBehaviorsMap();
|
|
3401
|
-
const records = [];
|
|
3402
|
-
map.forEach((entry, id) => {
|
|
3403
|
-
records.push({
|
|
3404
|
-
id,
|
|
3405
|
-
agent_id: entry.get("agent_id"),
|
|
3406
|
-
project_name: entry.get("project_name"),
|
|
3407
|
-
domain: entry.get("domain"),
|
|
3408
|
-
content: entry.get("content"),
|
|
3409
|
-
active: entry.get("active"),
|
|
3410
|
-
priority: entry.get("priority"),
|
|
3411
|
-
created_at: entry.get("created_at"),
|
|
3412
|
-
updated_at: entry.get("updated_at")
|
|
3413
|
-
});
|
|
3414
|
-
});
|
|
3415
|
-
return records;
|
|
3416
|
-
}
|
|
3417
|
-
function onUpdate(callback) {
|
|
3418
|
-
const d = initCrdtDoc();
|
|
3419
|
-
const handler = (update) => callback(update);
|
|
3420
|
-
d.on("update", handler);
|
|
3421
|
-
return () => d.off("update", handler);
|
|
3422
|
-
}
|
|
3423
|
-
function persistState() {
|
|
3424
|
-
if (!doc) return;
|
|
3425
|
-
try {
|
|
3426
|
-
const sp = getStatePath();
|
|
3427
|
-
const dir = path8.dirname(sp);
|
|
3428
|
-
if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
|
|
3429
|
-
const state = Y.encodeStateAsUpdate(doc);
|
|
3430
|
-
writeFileSync4(sp, Buffer.from(state));
|
|
3431
|
-
} catch {
|
|
3465
|
+
const allRows = result.rows;
|
|
3466
|
+
const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
|
|
3467
|
+
if (customerOnly.length > 0) {
|
|
3468
|
+
_customerCache = customerOnly.map((p) => `### ${p.title}
|
|
3469
|
+
${p.content}`).join("\n\n");
|
|
3470
|
+
} else {
|
|
3471
|
+
_customerCache = "";
|
|
3432
3472
|
}
|
|
3473
|
+
_cacheLoaded = true;
|
|
3474
|
+
return customerOnly;
|
|
3433
3475
|
}
|
|
3434
|
-
function
|
|
3435
|
-
|
|
3476
|
+
function getGlobalProceduresBlock() {
|
|
3477
|
+
const sections = [];
|
|
3478
|
+
if (_platformCache) sections.push(_platformCache);
|
|
3479
|
+
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
3480
|
+
if (sections.length === 0) return "";
|
|
3481
|
+
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
3482
|
+
|
|
3483
|
+
${sections.join("\n\n")}
|
|
3484
|
+
`;
|
|
3436
3485
|
}
|
|
3437
|
-
async function
|
|
3438
|
-
const
|
|
3439
|
-
const
|
|
3440
|
-
const
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
timestamp: row.timestamp,
|
|
3449
|
-
tool_name: row.tool_name,
|
|
3450
|
-
project_name: row.project_name,
|
|
3451
|
-
has_error: row.has_error,
|
|
3452
|
-
raw_text: row.raw_text,
|
|
3453
|
-
version: row.version,
|
|
3454
|
-
author_device_id: row.author_device_id,
|
|
3455
|
-
scope: row.scope
|
|
3456
|
-
}));
|
|
3457
|
-
const count = importExistingMemories(memories);
|
|
3458
|
-
persistState();
|
|
3459
|
-
return count;
|
|
3486
|
+
async function storeGlobalProcedure(input) {
|
|
3487
|
+
const id = randomUUID2();
|
|
3488
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3489
|
+
const client = getClient();
|
|
3490
|
+
await client.execute({
|
|
3491
|
+
sql: `INSERT INTO company_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
3492
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
3493
|
+
args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
|
|
3494
|
+
});
|
|
3495
|
+
await loadGlobalProcedures();
|
|
3496
|
+
return id;
|
|
3460
3497
|
}
|
|
3461
|
-
function
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3498
|
+
async function deactivateGlobalProcedure(id) {
|
|
3499
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3500
|
+
const client = getClient();
|
|
3501
|
+
const result = await client.execute({
|
|
3502
|
+
sql: "UPDATE company_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
3503
|
+
args: [now, id]
|
|
3504
|
+
});
|
|
3505
|
+
await loadGlobalProcedures();
|
|
3506
|
+
return result.rowsAffected > 0;
|
|
3466
3507
|
}
|
|
3467
|
-
var
|
|
3468
|
-
var
|
|
3469
|
-
"src/lib/
|
|
3508
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
3509
|
+
var init_global_procedures = __esm({
|
|
3510
|
+
"src/lib/global-procedures.ts"() {
|
|
3470
3511
|
"use strict";
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3512
|
+
init_database();
|
|
3513
|
+
init_platform_procedures();
|
|
3514
|
+
_customerCache = "";
|
|
3515
|
+
_cacheLoaded = false;
|
|
3516
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
3517
|
+
${p.content}`).join("\n\n");
|
|
3474
3518
|
}
|
|
3475
3519
|
});
|
|
3476
3520
|
|
|
3477
|
-
// src/lib/
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3521
|
+
// src/lib/agentic-ontology.ts
|
|
3522
|
+
import { createHash as createHash2 } from "crypto";
|
|
3523
|
+
function stableId(...parts) {
|
|
3524
|
+
return createHash2("sha256").update(parts.map((p) => String(p ?? "")).join("::")).digest("hex").slice(0, 32);
|
|
3525
|
+
}
|
|
3526
|
+
function clean(text, max = 240) {
|
|
3527
|
+
return text.replace(/\u0000/g, "").replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
|
|
3528
|
+
}
|
|
3529
|
+
function inferOntologyEventType(row) {
|
|
3530
|
+
const lower = row.raw_text.toLowerCase();
|
|
3531
|
+
if (row.has_error) return "error";
|
|
3532
|
+
if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published)\b/.test(lower)) return "milestone";
|
|
3533
|
+
if (/\b(blocked|failed|error|bug|regression|broken)\b/.test(lower)) return "problem";
|
|
3534
|
+
if (/\b(decided|decision|adr|we chose|approved|rejected)\b/.test(lower)) return "decision";
|
|
3535
|
+
if (/\b(goal|need to|we need|want to|trying to|objective)\b/.test(lower)) return "goal_signal";
|
|
3536
|
+
if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) return "tool_action";
|
|
3537
|
+
if (row.tool_name.startsWith("memory_card")) return "memory_card";
|
|
3538
|
+
return "memory_observation";
|
|
3539
|
+
}
|
|
3540
|
+
function inferIntention(row) {
|
|
3541
|
+
if (row.intent) return clean(row.intent, 220);
|
|
3542
|
+
const text = clean(row.raw_text, 1e3);
|
|
3543
|
+
const patterns = [
|
|
3544
|
+
/(?: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,
|
|
3545
|
+
/(?:so that|in order to)\s+([^.!?\n]{8,220})/i,
|
|
3546
|
+
/(?:task|plan):\s*([^.!?\n]{8,220})/i
|
|
3547
|
+
];
|
|
3548
|
+
for (const p of patterns) {
|
|
3549
|
+
const m = text.match(p);
|
|
3550
|
+
if (m?.[1]) return clean(m[1], 220);
|
|
3551
|
+
}
|
|
3552
|
+
if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) {
|
|
3553
|
+
return `${row.tool_name} during ${row.project_name}`;
|
|
3494
3554
|
}
|
|
3495
3555
|
return null;
|
|
3496
3556
|
}
|
|
3497
|
-
function
|
|
3498
|
-
|
|
3499
|
-
if (
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
copyFileSync(dbPath, backupPath);
|
|
3506
|
-
const walPath = dbPath + "-wal";
|
|
3507
|
-
if (existsSync9(walPath)) {
|
|
3508
|
-
try {
|
|
3509
|
-
copyFileSync(walPath, backupPath + "-wal");
|
|
3510
|
-
} catch {
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3513
|
-
const shmPath = dbPath + "-shm";
|
|
3514
|
-
if (existsSync9(shmPath)) {
|
|
3515
|
-
try {
|
|
3516
|
-
copyFileSync(shmPath, backupPath + "-shm");
|
|
3517
|
-
} catch {
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
return backupPath;
|
|
3557
|
+
function inferOutcome(row) {
|
|
3558
|
+
if (row.outcome) return clean(row.outcome, 220);
|
|
3559
|
+
if (row.has_error) return "error";
|
|
3560
|
+
const lower = row.raw_text.toLowerCase();
|
|
3561
|
+
if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published|passed)\b/.test(lower)) return "success_signal";
|
|
3562
|
+
if (/\b(blocked|failed|error|regression|broken|not working|could not)\b/.test(lower)) return "failure_signal";
|
|
3563
|
+
if (/\b(warning|risk|concern|caveat)\b/.test(lower)) return "risk_signal";
|
|
3564
|
+
return null;
|
|
3521
3565
|
}
|
|
3522
|
-
function
|
|
3523
|
-
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
unlinkSync4(filePath);
|
|
3535
|
-
deleted++;
|
|
3536
|
-
}
|
|
3537
|
-
} catch {
|
|
3538
|
-
}
|
|
3566
|
+
function extractGoalCandidates(row) {
|
|
3567
|
+
const text = clean(row.raw_text, 1600);
|
|
3568
|
+
const patterns = [
|
|
3569
|
+
/(?: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,
|
|
3570
|
+
/(?:success means|success criteria|so that)\s+([^.!?\n]{12,220})/gi
|
|
3571
|
+
];
|
|
3572
|
+
const out = [];
|
|
3573
|
+
for (const pattern of patterns) {
|
|
3574
|
+
for (const m of text.matchAll(pattern)) {
|
|
3575
|
+
const candidate = clean(m[1] ?? "", 220);
|
|
3576
|
+
if (candidate.length >= 12 && !out.some((x) => x.toLowerCase() === candidate.toLowerCase())) out.push(candidate);
|
|
3577
|
+
if (out.length >= 3) return out;
|
|
3539
3578
|
}
|
|
3540
|
-
} catch {
|
|
3541
3579
|
}
|
|
3542
|
-
return
|
|
3580
|
+
return out;
|
|
3543
3581
|
}
|
|
3544
|
-
function
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
const p = path9.join(BACKUP_DIR, name);
|
|
3550
|
-
const stat = statSync2(p);
|
|
3551
|
-
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
3552
|
-
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
3553
|
-
} catch {
|
|
3554
|
-
return [];
|
|
3582
|
+
function uniq(values, max = 6) {
|
|
3583
|
+
const out = [];
|
|
3584
|
+
for (const value of values.map((v) => clean(v, 220)).filter(Boolean)) {
|
|
3585
|
+
if (!out.some((x) => x.toLowerCase() === value.toLowerCase())) out.push(value);
|
|
3586
|
+
if (out.length >= max) break;
|
|
3555
3587
|
}
|
|
3588
|
+
return out;
|
|
3556
3589
|
}
|
|
3557
|
-
function
|
|
3558
|
-
const
|
|
3559
|
-
const
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
}
|
|
3566
|
-
|
|
3567
|
-
|
|
3590
|
+
function extractMatches(text, patterns, max = 5) {
|
|
3591
|
+
const out = [];
|
|
3592
|
+
for (const pattern of patterns) {
|
|
3593
|
+
for (const match of text.matchAll(pattern)) {
|
|
3594
|
+
const value = match[1] ?? match[0];
|
|
3595
|
+
if (value) out.push(value);
|
|
3596
|
+
if (out.length >= max) return uniq(out, max);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
return uniq(out, max);
|
|
3600
|
+
}
|
|
3601
|
+
function inferSemanticLabel(row) {
|
|
3602
|
+
const text = clean(row.raw_text, 2400);
|
|
3603
|
+
const eventType = inferOntologyEventType(row);
|
|
3604
|
+
const intention = inferIntention(row);
|
|
3605
|
+
const outcome = inferOutcome(row);
|
|
3606
|
+
const goals = extractGoalCandidates(row);
|
|
3607
|
+
const milestones = extractMatches(text, [
|
|
3608
|
+
/\b(?:completed|finished|fixed|resolved|shipped|deployed|published|pushed|passed)\b([^.!?\n]{0,180})/gi,
|
|
3609
|
+
/(?:milestone|done):\s*([^.!?\n]{8,220})/gi
|
|
3610
|
+
]);
|
|
3611
|
+
const problems = extractMatches(text, [
|
|
3612
|
+
/\b(?:blocked by|failed because|bug|regression|broken|not working|error)\b([^.!?\n]{0,180})/gi,
|
|
3613
|
+
/(?:problem|issue|risk):\s*([^.!?\n]{8,220})/gi
|
|
3614
|
+
]);
|
|
3615
|
+
const decisions = extractMatches(text, [
|
|
3616
|
+
/(?:decided|decision|adr|we chose|approved|rejected)\s+([^.!?\n]{8,220})/gi
|
|
3617
|
+
]);
|
|
3618
|
+
const temporalAnchors = extractMatches(text, [
|
|
3619
|
+
/\b(\d{4}-\d{2}-\d{2}(?:[T ][0-9:.+-Z]+)?)\b/g,
|
|
3620
|
+
/\b(today|yesterday|tomorrow|this week|next week|last week|morning|afternoon|tonight)\b/gi
|
|
3621
|
+
], 8);
|
|
3622
|
+
const nextActions = extractMatches(text, [
|
|
3623
|
+
/(?:next|todo|follow[- ]?up|remaining|need to)\s*:?\s*([^.!?\n]{8,220})/gi
|
|
3624
|
+
]);
|
|
3625
|
+
const actors = uniq([
|
|
3626
|
+
row.agent_id,
|
|
3627
|
+
...extractMatches(text, [/\b(?:agent|employee|owner|assignee)[:= ]+([a-zA-Z][a-zA-Z0-9_-]{1,40})/gi], 5)
|
|
3628
|
+
], 6);
|
|
3629
|
+
const successSignals = milestones.length ? milestones : outcome === "success_signal" ? [clean(text, 180)] : [];
|
|
3630
|
+
const failureSignals = problems.length ? problems : outcome === "failure_signal" || row.has_error ? [clean(text, 180)] : [];
|
|
3631
|
+
const impact = successSignals.length && failureSignals.length ? "mixed" : failureSignals.length ? "negative" : successSignals.length ? "positive" : "neutral";
|
|
3632
|
+
const signalCount = goals.length + milestones.length + problems.length + decisions.length + nextActions.length;
|
|
3633
|
+
return {
|
|
3634
|
+
labeler: "deterministic",
|
|
3635
|
+
schemaVersion: 1,
|
|
3636
|
+
eventType,
|
|
3637
|
+
intention,
|
|
3638
|
+
outcome,
|
|
3639
|
+
impact,
|
|
3640
|
+
confidence: Math.min(0.95, 0.45 + signalCount * 0.08 + (intention ? 0.1 : 0) + (outcome ? 0.1 : 0)),
|
|
3641
|
+
goals,
|
|
3642
|
+
milestones,
|
|
3643
|
+
problems,
|
|
3644
|
+
decisions,
|
|
3645
|
+
actors,
|
|
3646
|
+
temporalAnchors,
|
|
3647
|
+
successSignals,
|
|
3648
|
+
failureSignals,
|
|
3649
|
+
nextActions,
|
|
3650
|
+
summary: clean(text, 280)
|
|
3651
|
+
};
|
|
3568
3652
|
}
|
|
3569
|
-
var
|
|
3570
|
-
|
|
3571
|
-
"src/lib/db-backup.ts"() {
|
|
3653
|
+
var init_agentic_ontology = __esm({
|
|
3654
|
+
"src/lib/agentic-ontology.ts"() {
|
|
3572
3655
|
"use strict";
|
|
3573
|
-
init_config();
|
|
3574
|
-
BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
|
|
3575
|
-
DEFAULT_KEEP_DAYS = 3;
|
|
3576
|
-
DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
|
|
3577
3656
|
}
|
|
3578
3657
|
});
|
|
3579
3658
|
|
|
3580
|
-
// src/lib/
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
cloudPushBehaviors: () => cloudPushBehaviors,
|
|
3596
|
-
cloudPushBlob: () => cloudPushBlob,
|
|
3597
|
-
cloudPushConversations: () => cloudPushConversations,
|
|
3598
|
-
cloudPushDocuments: () => cloudPushDocuments,
|
|
3599
|
-
cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
|
|
3600
|
-
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
3601
|
-
cloudPushRoster: () => cloudPushRoster,
|
|
3602
|
-
cloudPushTasks: () => cloudPushTasks,
|
|
3603
|
-
cloudSync: () => cloudSync,
|
|
3604
|
-
mergeConfig: () => mergeConfig,
|
|
3605
|
-
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
3606
|
-
pushToPostgres: () => pushToPostgres,
|
|
3607
|
-
recordRosterDeletion: () => recordRosterDeletion
|
|
3608
|
-
});
|
|
3609
|
-
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 statSync3 } from "fs";
|
|
3610
|
-
import crypto3 from "crypto";
|
|
3611
|
-
import path10 from "path";
|
|
3612
|
-
import { homedir as homedir2 } from "os";
|
|
3613
|
-
function sqlSafe(v) {
|
|
3614
|
-
return v === void 0 ? null : v;
|
|
3615
|
-
}
|
|
3616
|
-
function logError(msg) {
|
|
3617
|
-
try {
|
|
3618
|
-
const logPath = path10.join(homedir2(), ".exe-os", "workers.log");
|
|
3619
|
-
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3620
|
-
`);
|
|
3621
|
-
} catch {
|
|
3622
|
-
}
|
|
3659
|
+
// src/lib/store.ts
|
|
3660
|
+
init_memory();
|
|
3661
|
+
init_database();
|
|
3662
|
+
|
|
3663
|
+
// src/lib/keychain.ts
|
|
3664
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3665
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
3666
|
+
import { execSync as execSync2 } from "child_process";
|
|
3667
|
+
import path6 from "path";
|
|
3668
|
+
import os5 from "os";
|
|
3669
|
+
var SERVICE = "exe-os";
|
|
3670
|
+
var LEGACY_SERVICE = "exe-mem";
|
|
3671
|
+
var ACCOUNT = "master-key";
|
|
3672
|
+
function getKeyDir() {
|
|
3673
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
|
|
3623
3674
|
}
|
|
3624
|
-
function
|
|
3625
|
-
return
|
|
3675
|
+
function getKeyPath() {
|
|
3676
|
+
return path6.join(getKeyDir(), "master.key");
|
|
3677
|
+
}
|
|
3678
|
+
function nativeKeychainAllowed() {
|
|
3679
|
+
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
3626
3680
|
}
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3681
|
+
var linuxSecretAvailability = null;
|
|
3682
|
+
function linuxSecretAvailable() {
|
|
3683
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3684
|
+
if (process.platform !== "linux") return false;
|
|
3685
|
+
if (linuxSecretAvailability !== null) return linuxSecretAvailability;
|
|
3632
3686
|
try {
|
|
3633
|
-
|
|
3634
|
-
const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
3635
|
-
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
3636
|
-
configEnabled = cfg.cloud?.syncToPostgres === true;
|
|
3637
|
-
}
|
|
3687
|
+
execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
|
|
3638
3688
|
} catch {
|
|
3689
|
+
linuxSecretAvailability = false;
|
|
3690
|
+
return false;
|
|
3639
3691
|
}
|
|
3640
|
-
const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
|
|
3641
|
-
if (!envEnabled && !configEnabled) {
|
|
3642
|
-
return null;
|
|
3643
|
-
}
|
|
3644
|
-
const url = process.env.DATABASE_URL || cloudPostgresUrl;
|
|
3645
|
-
if (!url) {
|
|
3646
|
-
_pgFailed = true;
|
|
3647
|
-
return null;
|
|
3648
|
-
}
|
|
3649
|
-
if (!_pgPromise) {
|
|
3650
|
-
_pgPromise = (async () => {
|
|
3651
|
-
if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
|
|
3652
|
-
const { createRequire: createRequire3 } = await import("module");
|
|
3653
|
-
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
3654
|
-
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3655
|
-
if (explicitPath) {
|
|
3656
|
-
const mod2 = await import(pathToFileURL3(explicitPath).href);
|
|
3657
|
-
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3658
|
-
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3659
|
-
return new Ctor2();
|
|
3660
|
-
}
|
|
3661
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(homedir2(), "exe-db");
|
|
3662
|
-
const req = createRequire3(path10.join(exeDbRoot, "package.json"));
|
|
3663
|
-
const entry = req.resolve("@prisma/client");
|
|
3664
|
-
const mod = await import(pathToFileURL3(entry).href);
|
|
3665
|
-
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3666
|
-
if (!Ctor) throw new Error("No PrismaClient");
|
|
3667
|
-
return new Ctor();
|
|
3668
|
-
})().catch(() => {
|
|
3669
|
-
_pgFailed = true;
|
|
3670
|
-
_pgPromise = null;
|
|
3671
|
-
throw new Error("pg_unavailable");
|
|
3672
|
-
});
|
|
3673
|
-
}
|
|
3674
|
-
return _pgPromise;
|
|
3675
|
-
}
|
|
3676
|
-
async function pushToPostgres(records) {
|
|
3677
|
-
const loader = loadPgClient();
|
|
3678
|
-
if (!loader) return 0;
|
|
3679
|
-
let prisma;
|
|
3680
3692
|
try {
|
|
3681
|
-
|
|
3693
|
+
execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
|
|
3694
|
+
linuxSecretAvailability = true;
|
|
3682
3695
|
} catch {
|
|
3683
|
-
|
|
3684
|
-
}
|
|
3685
|
-
let inserted = 0;
|
|
3686
|
-
for (const rec of records) {
|
|
3687
|
-
try {
|
|
3688
|
-
await prisma.$executeRawUnsafe(
|
|
3689
|
-
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
3690
|
-
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
3691
|
-
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
3692
|
-
String(rec.id ?? ""),
|
|
3693
|
-
JSON.stringify(rec),
|
|
3694
|
-
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
3695
|
-
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
3696
|
-
);
|
|
3697
|
-
inserted++;
|
|
3698
|
-
} catch {
|
|
3699
|
-
}
|
|
3696
|
+
linuxSecretAvailability = false;
|
|
3700
3697
|
}
|
|
3701
|
-
return
|
|
3698
|
+
return linuxSecretAvailability;
|
|
3702
3699
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3706
|
-
closeSync2(fd);
|
|
3707
|
-
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3708
|
-
} catch (err) {
|
|
3709
|
-
if (err.code === "EEXIST") {
|
|
3710
|
-
try {
|
|
3711
|
-
const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
3712
|
-
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3713
|
-
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3714
|
-
}
|
|
3715
|
-
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3716
|
-
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3717
|
-
closeSync2(fd);
|
|
3718
|
-
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3719
|
-
} catch (retryErr) {
|
|
3720
|
-
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
3721
|
-
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3722
|
-
}
|
|
3723
|
-
} else {
|
|
3724
|
-
throw err;
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3700
|
+
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3701
|
+
if (process.platform !== "linux") return false;
|
|
3727
3702
|
try {
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3703
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3704
|
+
const st = statSync2(keyPath);
|
|
3705
|
+
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3706
|
+
if (uid === 0) return true;
|
|
3707
|
+
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3708
|
+
return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
|
|
3709
|
+
} catch {
|
|
3710
|
+
return false;
|
|
3734
3711
|
}
|
|
3735
3712
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
continue;
|
|
3747
|
-
}
|
|
3748
|
-
return resp;
|
|
3749
|
-
} catch (err) {
|
|
3750
|
-
lastError = err;
|
|
3751
|
-
if (attempt === MAX_RETRIES2) throw err;
|
|
3752
|
-
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
3753
|
-
}
|
|
3713
|
+
function macKeychainGet(service = SERVICE) {
|
|
3714
|
+
if (!nativeKeychainAllowed()) return null;
|
|
3715
|
+
if (process.platform !== "darwin") return null;
|
|
3716
|
+
try {
|
|
3717
|
+
return execSync2(
|
|
3718
|
+
`security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
3719
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
3720
|
+
).trim();
|
|
3721
|
+
} catch {
|
|
3722
|
+
return null;
|
|
3754
3723
|
}
|
|
3755
|
-
throw lastError;
|
|
3756
3724
|
}
|
|
3757
|
-
function
|
|
3758
|
-
if (
|
|
3759
|
-
if (
|
|
3725
|
+
function macKeychainSet(value, service = SERVICE) {
|
|
3726
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3727
|
+
if (process.platform !== "darwin") return false;
|
|
3728
|
+
try {
|
|
3760
3729
|
try {
|
|
3761
|
-
|
|
3762
|
-
|
|
3730
|
+
execSync2(
|
|
3731
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3732
|
+
{ timeout: 5e3 }
|
|
3733
|
+
);
|
|
3763
3734
|
} catch {
|
|
3764
|
-
return;
|
|
3765
3735
|
}
|
|
3766
|
-
|
|
3767
|
-
`
|
|
3736
|
+
execSync2(
|
|
3737
|
+
`security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
|
|
3738
|
+
{ timeout: 5e3 }
|
|
3768
3739
|
);
|
|
3769
|
-
|
|
3770
|
-
}
|
|
3771
|
-
async function cloudPush(records, maxVersion, config) {
|
|
3772
|
-
if (records.length === 0) return true;
|
|
3773
|
-
assertSecureEndpoint(config.endpoint);
|
|
3774
|
-
try {
|
|
3775
|
-
const json = JSON.stringify(records);
|
|
3776
|
-
const compressed = compress(Buffer.from(json, "utf8"));
|
|
3777
|
-
const blob = encryptSyncBlob(compressed);
|
|
3778
|
-
const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
|
|
3779
|
-
method: "POST",
|
|
3780
|
-
headers: {
|
|
3781
|
-
Authorization: `Bearer ${config.apiKey}`,
|
|
3782
|
-
"Content-Type": "application/json",
|
|
3783
|
-
"X-Device-Id": loadDeviceId(),
|
|
3784
|
-
"X-Expected-Version": String(maxVersion)
|
|
3785
|
-
},
|
|
3786
|
-
body: JSON.stringify({ version: maxVersion, blob })
|
|
3787
|
-
});
|
|
3788
|
-
if (resp == null) {
|
|
3789
|
-
logError("[cloud-sync] PUSH FAILED: no response from server");
|
|
3790
|
-
return false;
|
|
3791
|
-
}
|
|
3792
|
-
if (resp.status === 409) {
|
|
3793
|
-
logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
|
|
3794
|
-
return false;
|
|
3795
|
-
}
|
|
3796
|
-
return resp.ok;
|
|
3797
|
-
} catch (err) {
|
|
3798
|
-
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
3740
|
+
return true;
|
|
3741
|
+
} catch {
|
|
3799
3742
|
return false;
|
|
3800
3743
|
}
|
|
3801
3744
|
}
|
|
3802
|
-
|
|
3803
|
-
|
|
3745
|
+
function macKeychainDelete(service = SERVICE) {
|
|
3746
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3747
|
+
if (process.platform !== "darwin") return false;
|
|
3804
3748
|
try {
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
body: JSON.stringify({ since_version: sinceVersion })
|
|
3813
|
-
});
|
|
3814
|
-
if (response == null) {
|
|
3815
|
-
logError("[cloud-sync] PULL FAILED: no response from server");
|
|
3816
|
-
return { records: [], maxVersion: sinceVersion };
|
|
3817
|
-
}
|
|
3818
|
-
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
3819
|
-
const data = await response.json();
|
|
3820
|
-
const allRecords = [];
|
|
3821
|
-
for (const { blob } of data.blobs ?? []) {
|
|
3822
|
-
try {
|
|
3823
|
-
const compressed = decryptSyncBlob(blob);
|
|
3824
|
-
const json = decompress(compressed).toString("utf8");
|
|
3825
|
-
const records = JSON.parse(json);
|
|
3826
|
-
allRecords.push(...records);
|
|
3827
|
-
} catch {
|
|
3828
|
-
continue;
|
|
3829
|
-
}
|
|
3830
|
-
}
|
|
3831
|
-
return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
|
|
3832
|
-
} catch (err) {
|
|
3833
|
-
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
3834
|
-
return { records: [], maxVersion: sinceVersion };
|
|
3749
|
+
execSync2(
|
|
3750
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3751
|
+
{ timeout: 5e3 }
|
|
3752
|
+
);
|
|
3753
|
+
return true;
|
|
3754
|
+
} catch {
|
|
3755
|
+
return false;
|
|
3835
3756
|
}
|
|
3836
3757
|
}
|
|
3837
|
-
|
|
3838
|
-
if (!
|
|
3839
|
-
try {
|
|
3840
|
-
const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
|
|
3841
|
-
const masterKey = await getMasterKey2();
|
|
3842
|
-
if (masterKey) {
|
|
3843
|
-
initSyncCrypto(masterKey);
|
|
3844
|
-
} else {
|
|
3845
|
-
throw new Error("No master key found");
|
|
3846
|
-
}
|
|
3847
|
-
} catch (err) {
|
|
3848
|
-
throw new Error(`[cloud-sync] Cannot initialize encryption: ${err instanceof Error ? err.message : String(err)}`);
|
|
3849
|
-
}
|
|
3850
|
-
}
|
|
3851
|
-
let client;
|
|
3758
|
+
function linuxSecretGet(service = SERVICE) {
|
|
3759
|
+
if (!linuxSecretAvailable()) return null;
|
|
3852
3760
|
try {
|
|
3853
|
-
|
|
3761
|
+
return execSync2(
|
|
3762
|
+
`secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3763
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
3764
|
+
).trim();
|
|
3854
3765
|
} catch {
|
|
3855
|
-
|
|
3856
|
-
}
|
|
3857
|
-
try {
|
|
3858
|
-
const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
|
|
3859
|
-
if (String(relink.rows[0]?.value ?? "") === "1") {
|
|
3860
|
-
throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
|
|
3861
|
-
}
|
|
3862
|
-
} catch (err) {
|
|
3863
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3864
|
-
if (msg.includes("Paused after key rotation")) throw err;
|
|
3766
|
+
return null;
|
|
3865
3767
|
}
|
|
3768
|
+
}
|
|
3769
|
+
function linuxSecretSet(value, service = SERVICE) {
|
|
3770
|
+
if (!linuxSecretAvailable()) return false;
|
|
3866
3771
|
try {
|
|
3867
|
-
|
|
3868
|
-
|
|
3772
|
+
execSync2(
|
|
3773
|
+
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3774
|
+
{ timeout: 5e3 }
|
|
3775
|
+
);
|
|
3776
|
+
return true;
|
|
3869
3777
|
} catch {
|
|
3778
|
+
return false;
|
|
3870
3779
|
}
|
|
3780
|
+
}
|
|
3781
|
+
function linuxSecretDelete(service = SERVICE) {
|
|
3782
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3783
|
+
if (process.platform !== "linux") return false;
|
|
3871
3784
|
try {
|
|
3872
|
-
|
|
3873
|
-
|
|
3785
|
+
execSync2(
|
|
3786
|
+
`secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3787
|
+
{ timeout: 5e3 }
|
|
3874
3788
|
);
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
const pullMeta = await client.execute(
|
|
3879
|
-
"SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
|
|
3880
|
-
);
|
|
3881
|
-
const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
|
|
3882
|
-
const pullResult = await cloudPull(lastPullVersion, config);
|
|
3883
|
-
let pulled = 0;
|
|
3884
|
-
if (pullResult.records.length > 0) {
|
|
3885
|
-
if (isCrdtSyncEnabled()) {
|
|
3886
|
-
const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
|
|
3887
|
-
initCrdtDoc2();
|
|
3888
|
-
importExistingMemories2(
|
|
3889
|
-
pullResult.records.map((rec) => ({
|
|
3890
|
-
id: String(rec.id ?? ""),
|
|
3891
|
-
agent_id: rec.agent_id,
|
|
3892
|
-
agent_role: rec.agent_role,
|
|
3893
|
-
session_id: rec.session_id,
|
|
3894
|
-
timestamp: rec.timestamp,
|
|
3895
|
-
tool_name: rec.tool_name,
|
|
3896
|
-
project_name: rec.project_name,
|
|
3897
|
-
has_error: rec.has_error ?? 0,
|
|
3898
|
-
raw_text: rec.raw_text ?? "",
|
|
3899
|
-
version: rec.version ?? 0,
|
|
3900
|
-
author_device_id: rec.author_device_id,
|
|
3901
|
-
scope: rec.scope ?? "business"
|
|
3902
|
-
}))
|
|
3903
|
-
);
|
|
3904
|
-
const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
|
|
3905
|
-
const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
|
|
3906
|
-
const stmts = merged.map((rec) => ({
|
|
3907
|
-
sql: `INSERT OR REPLACE INTO memories
|
|
3908
|
-
(id, agent_id, agent_role, session_id, timestamp,
|
|
3909
|
-
tool_name, project_name, has_error, raw_text, version,
|
|
3910
|
-
author_device_id, scope)
|
|
3911
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3912
|
-
args: [
|
|
3913
|
-
sqlSafe(rec.id),
|
|
3914
|
-
sqlSafe(rec.agent_id),
|
|
3915
|
-
sqlSafe(rec.agent_role),
|
|
3916
|
-
sqlSafe(rec.session_id),
|
|
3917
|
-
sqlSafe(rec.timestamp),
|
|
3918
|
-
sqlSafe(rec.tool_name),
|
|
3919
|
-
sqlSafe(rec.project_name),
|
|
3920
|
-
sqlSafe(rec.has_error ?? 0),
|
|
3921
|
-
sqlSafe(rec.raw_text ?? ""),
|
|
3922
|
-
sqlSafe(rec.version ?? 0),
|
|
3923
|
-
sqlSafe(rec.author_device_id),
|
|
3924
|
-
sqlSafe(rec.scope ?? "business")
|
|
3925
|
-
]
|
|
3926
|
-
}));
|
|
3927
|
-
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
3928
|
-
pulled = pullResult.records.length;
|
|
3929
|
-
} else {
|
|
3930
|
-
const stmts = pullResult.records.map((rec) => ({
|
|
3931
|
-
sql: `INSERT OR REPLACE INTO memories
|
|
3932
|
-
(id, agent_id, agent_role, session_id, timestamp,
|
|
3933
|
-
tool_name, project_name, has_error, raw_text, version,
|
|
3934
|
-
author_device_id, scope)
|
|
3935
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3936
|
-
args: [
|
|
3937
|
-
sqlSafe(rec.id),
|
|
3938
|
-
sqlSafe(rec.agent_id),
|
|
3939
|
-
sqlSafe(rec.agent_role),
|
|
3940
|
-
sqlSafe(rec.session_id),
|
|
3941
|
-
sqlSafe(rec.timestamp),
|
|
3942
|
-
sqlSafe(rec.tool_name),
|
|
3943
|
-
sqlSafe(rec.project_name),
|
|
3944
|
-
sqlSafe(rec.has_error ?? 0),
|
|
3945
|
-
sqlSafe(rec.raw_text ?? ""),
|
|
3946
|
-
sqlSafe(rec.version ?? 0),
|
|
3947
|
-
sqlSafe(rec.author_device_id),
|
|
3948
|
-
sqlSafe(rec.scope ?? "business")
|
|
3949
|
-
]
|
|
3950
|
-
}));
|
|
3951
|
-
await client.batch(stmts, "write");
|
|
3952
|
-
pulled = pullResult.records.length;
|
|
3953
|
-
}
|
|
3954
|
-
}
|
|
3955
|
-
if (pulled > 0) {
|
|
3956
|
-
try {
|
|
3957
|
-
await pushToPostgres(pullResult.records);
|
|
3958
|
-
} catch {
|
|
3959
|
-
}
|
|
3960
|
-
}
|
|
3961
|
-
if (pullResult.maxVersion > lastPullVersion) {
|
|
3962
|
-
await client.execute({
|
|
3963
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
|
|
3964
|
-
args: [String(pullResult.maxVersion)]
|
|
3965
|
-
});
|
|
3966
|
-
}
|
|
3967
|
-
const pushMeta = await client.execute(
|
|
3968
|
-
"SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
|
|
3969
|
-
);
|
|
3970
|
-
const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
|
|
3971
|
-
let pushed = 0;
|
|
3972
|
-
let batchCursor = lastPushVersion;
|
|
3973
|
-
while (true) {
|
|
3974
|
-
const recordsResult = await client.execute({
|
|
3975
|
-
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
3976
|
-
tool_name, project_name, has_error, raw_text, version,
|
|
3977
|
-
author_device_id, scope
|
|
3978
|
-
FROM memories
|
|
3979
|
-
WHERE version > ?
|
|
3980
|
-
AND (scope IS NULL OR scope != 'personal')
|
|
3981
|
-
ORDER BY version ASC
|
|
3982
|
-
LIMIT ?`,
|
|
3983
|
-
args: [batchCursor, PUSH_BATCH_SIZE]
|
|
3984
|
-
});
|
|
3985
|
-
if (recordsResult.rows.length === 0) break;
|
|
3986
|
-
const records = recordsResult.rows.map((row) => ({
|
|
3987
|
-
id: row.id,
|
|
3988
|
-
agent_id: row.agent_id,
|
|
3989
|
-
agent_role: row.agent_role,
|
|
3990
|
-
session_id: row.session_id,
|
|
3991
|
-
timestamp: row.timestamp,
|
|
3992
|
-
tool_name: row.tool_name,
|
|
3993
|
-
project_name: row.project_name,
|
|
3994
|
-
has_error: row.has_error,
|
|
3995
|
-
raw_text: row.raw_text,
|
|
3996
|
-
version: row.version,
|
|
3997
|
-
author_device_id: row.author_device_id,
|
|
3998
|
-
scope: row.scope
|
|
3999
|
-
}));
|
|
4000
|
-
const maxVersion = Number(records[records.length - 1].version);
|
|
4001
|
-
const pushOk = await cloudPush(records, maxVersion, config);
|
|
4002
|
-
if (!pushOk) break;
|
|
4003
|
-
try {
|
|
4004
|
-
await pushToPostgres(records);
|
|
4005
|
-
} catch {
|
|
4006
|
-
}
|
|
4007
|
-
await client.execute({
|
|
4008
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
4009
|
-
args: [String(maxVersion)]
|
|
4010
|
-
});
|
|
4011
|
-
pushed += records.length;
|
|
4012
|
-
batchCursor = maxVersion;
|
|
4013
|
-
if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
|
|
4014
|
-
}
|
|
4015
|
-
try {
|
|
4016
|
-
await cloudPushRoster(config);
|
|
4017
|
-
} catch (err) {
|
|
4018
|
-
logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4019
|
-
}
|
|
4020
|
-
try {
|
|
4021
|
-
await cloudPullRoster(config);
|
|
4022
|
-
} catch (err) {
|
|
4023
|
-
logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4024
|
-
}
|
|
4025
|
-
try {
|
|
4026
|
-
await cloudPushGlobalProcedures(config);
|
|
4027
|
-
} catch (err) {
|
|
4028
|
-
logError(`[cloud-sync] Company procedures push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4029
|
-
}
|
|
4030
|
-
try {
|
|
4031
|
-
await cloudPullGlobalProcedures(config);
|
|
4032
|
-
} catch (err) {
|
|
4033
|
-
logError(`[cloud-sync] Company procedures pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4034
|
-
}
|
|
4035
|
-
const countRows = async (sql) => {
|
|
4036
|
-
try {
|
|
4037
|
-
return Number((await client.execute(sql)).rows[0]?.cnt ?? 0);
|
|
4038
|
-
} catch {
|
|
4039
|
-
return 0;
|
|
4040
|
-
}
|
|
4041
|
-
};
|
|
4042
|
-
let behaviorsResult = { pushed: 0, pulled: 0 };
|
|
4043
|
-
try {
|
|
4044
|
-
await cloudPushBehaviors(config);
|
|
4045
|
-
behaviorsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM behaviors WHERE active = 1");
|
|
4046
|
-
} catch (err) {
|
|
4047
|
-
logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4048
|
-
}
|
|
4049
|
-
try {
|
|
4050
|
-
const pullResult2 = await cloudPullBehaviors(config);
|
|
4051
|
-
behaviorsResult.pulled = pullResult2.pulled;
|
|
4052
|
-
} catch (err) {
|
|
4053
|
-
logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4054
|
-
}
|
|
4055
|
-
let graphragResult = { pushed: 0, pulled: 0 };
|
|
4056
|
-
try {
|
|
4057
|
-
await cloudPushGraphRAG(config);
|
|
4058
|
-
graphragResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM entities");
|
|
4059
|
-
} catch (err) {
|
|
4060
|
-
logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4061
|
-
}
|
|
4062
|
-
try {
|
|
4063
|
-
const pullResult2 = await cloudPullGraphRAG(config);
|
|
4064
|
-
graphragResult.pulled = pullResult2.pulled;
|
|
4065
|
-
} catch (err) {
|
|
4066
|
-
logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4067
|
-
}
|
|
4068
|
-
let tasksResult = { pushed: 0, pulled: 0 };
|
|
4069
|
-
try {
|
|
4070
|
-
await cloudPushTasks(config);
|
|
4071
|
-
tasksResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM tasks");
|
|
4072
|
-
} catch (err) {
|
|
4073
|
-
logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4074
|
-
}
|
|
4075
|
-
try {
|
|
4076
|
-
const pullResult2 = await cloudPullTasks(config);
|
|
4077
|
-
tasksResult.pulled = pullResult2.pulled;
|
|
4078
|
-
} catch (err) {
|
|
4079
|
-
logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4080
|
-
}
|
|
4081
|
-
let conversationsResult = { pushed: 0, pulled: 0 };
|
|
4082
|
-
try {
|
|
4083
|
-
await cloudPushConversations(config);
|
|
4084
|
-
conversationsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM conversations");
|
|
4085
|
-
} catch (err) {
|
|
4086
|
-
logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4087
|
-
}
|
|
4088
|
-
try {
|
|
4089
|
-
const pullResult2 = await cloudPullConversations(config);
|
|
4090
|
-
conversationsResult.pulled = pullResult2.pulled;
|
|
4091
|
-
} catch (err) {
|
|
4092
|
-
logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4093
|
-
}
|
|
4094
|
-
let documentsResult = { pushed: 0, pulled: 0 };
|
|
4095
|
-
try {
|
|
4096
|
-
await cloudPushDocuments(config);
|
|
4097
|
-
documentsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM documents");
|
|
4098
|
-
} catch (err) {
|
|
4099
|
-
logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4100
|
-
}
|
|
4101
|
-
try {
|
|
4102
|
-
const pullResult2 = await cloudPullDocuments(config);
|
|
4103
|
-
documentsResult.pulled = pullResult2.pulled;
|
|
4104
|
-
} catch (err) {
|
|
4105
|
-
logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
3789
|
+
return true;
|
|
3790
|
+
} catch {
|
|
3791
|
+
return false;
|
|
4106
3792
|
}
|
|
4107
|
-
|
|
3793
|
+
}
|
|
3794
|
+
async function tryKeytar() {
|
|
3795
|
+
if (!nativeKeychainAllowed()) return null;
|
|
4108
3796
|
try {
|
|
4109
|
-
|
|
4110
|
-
rosterResult.employees = employees.length;
|
|
4111
|
-
const idDir = path10.join(EXE_AI_DIR, "identity");
|
|
4112
|
-
if (existsSync10(idDir)) {
|
|
4113
|
-
rosterResult.identities = readdirSync2(idDir).filter((f) => f.endsWith(".md")).length;
|
|
4114
|
-
}
|
|
3797
|
+
return await import("keytar");
|
|
4115
3798
|
} catch {
|
|
3799
|
+
return null;
|
|
4116
3800
|
}
|
|
4117
|
-
|
|
3801
|
+
}
|
|
3802
|
+
var ENCRYPTED_PREFIX = "enc:";
|
|
3803
|
+
function deriveMachineKey() {
|
|
4118
3804
|
try {
|
|
4119
|
-
const
|
|
4120
|
-
const
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
body: JSON.stringify({
|
|
4132
|
-
device_id: deviceId,
|
|
4133
|
-
filename: path10.basename(latestBackup),
|
|
4134
|
-
blob: encrypted,
|
|
4135
|
-
size: backupData.length
|
|
4136
|
-
})
|
|
4137
|
-
});
|
|
4138
|
-
if (backupRes && !backupRes.ok) {
|
|
4139
|
-
logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
}
|
|
4143
|
-
} catch (err) {
|
|
4144
|
-
logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3805
|
+
const crypto2 = __require("crypto");
|
|
3806
|
+
const material = [
|
|
3807
|
+
os5.hostname(),
|
|
3808
|
+
os5.userInfo().username,
|
|
3809
|
+
os5.arch(),
|
|
3810
|
+
os5.platform(),
|
|
3811
|
+
// Machine ID on Linux (stable across reboots)
|
|
3812
|
+
process.platform === "linux" ? readMachineId() : ""
|
|
3813
|
+
].join("|");
|
|
3814
|
+
return crypto2.createHash("sha256").update(material).digest();
|
|
3815
|
+
} catch {
|
|
3816
|
+
return null;
|
|
4145
3817
|
}
|
|
4146
|
-
return {
|
|
4147
|
-
pushed,
|
|
4148
|
-
pulled,
|
|
4149
|
-
totalMemories,
|
|
4150
|
-
behaviors: behaviorsResult,
|
|
4151
|
-
graphrag: graphragResult,
|
|
4152
|
-
tasks: tasksResult,
|
|
4153
|
-
conversations: conversationsResult,
|
|
4154
|
-
documents: documentsResult,
|
|
4155
|
-
roster: rosterResult
|
|
4156
|
-
};
|
|
4157
3818
|
}
|
|
4158
|
-
function
|
|
4159
|
-
let deletions = [];
|
|
3819
|
+
function readMachineId() {
|
|
4160
3820
|
try {
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
}
|
|
3821
|
+
const { readFileSync: readFileSync5 } = __require("fs");
|
|
3822
|
+
return readFileSync5("/etc/machine-id", "utf-8").trim();
|
|
4164
3823
|
} catch {
|
|
3824
|
+
return "";
|
|
4165
3825
|
}
|
|
4166
|
-
if (!deletions.includes(name)) deletions.push(name);
|
|
4167
|
-
writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
4168
3826
|
}
|
|
4169
|
-
function
|
|
3827
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3828
|
+
const crypto2 = __require("crypto");
|
|
3829
|
+
const iv = crypto2.randomBytes(12);
|
|
3830
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3831
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3832
|
+
encrypted += cipher.final("base64");
|
|
3833
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3834
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3835
|
+
}
|
|
3836
|
+
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3837
|
+
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
4170
3838
|
try {
|
|
4171
|
-
|
|
4172
|
-
const
|
|
4173
|
-
|
|
4174
|
-
|
|
3839
|
+
const crypto2 = __require("crypto");
|
|
3840
|
+
const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
|
|
3841
|
+
if (parts.length !== 3) return null;
|
|
3842
|
+
const [ivB64, tagB64, cipherB64] = parts;
|
|
3843
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
3844
|
+
const authTag = Buffer.from(tagB64, "base64");
|
|
3845
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
|
|
3846
|
+
decipher.setAuthTag(authTag);
|
|
3847
|
+
let decrypted = decipher.update(cipherB64, "base64", "utf-8");
|
|
3848
|
+
decrypted += decipher.final("utf-8");
|
|
3849
|
+
return decrypted;
|
|
4175
3850
|
} catch {
|
|
4176
|
-
return
|
|
3851
|
+
return null;
|
|
4177
3852
|
}
|
|
4178
3853
|
}
|
|
4179
|
-
function
|
|
4180
|
-
const
|
|
4181
|
-
|
|
4182
|
-
const
|
|
4183
|
-
|
|
4184
|
-
if (
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
3854
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3855
|
+
const dir = getKeyDir();
|
|
3856
|
+
await mkdir3(dir, { recursive: true });
|
|
3857
|
+
const keyPath = getKeyPath();
|
|
3858
|
+
const machineKey = deriveMachineKey();
|
|
3859
|
+
if (machineKey) {
|
|
3860
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3861
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3862
|
+
await chmod2(keyPath, 384);
|
|
3863
|
+
return "encrypted";
|
|
4189
3864
|
}
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
3865
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3866
|
+
await chmod2(keyPath, 384);
|
|
3867
|
+
return "plaintext";
|
|
3868
|
+
}
|
|
3869
|
+
async function getMasterKey() {
|
|
3870
|
+
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3871
|
+
if (!nativeValue) {
|
|
3872
|
+
const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
|
|
3873
|
+
if (legacyValue) {
|
|
3874
|
+
const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
|
|
3875
|
+
if (migrated) {
|
|
3876
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
3877
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
3878
|
+
process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
|
|
4196
3879
|
}
|
|
3880
|
+
nativeValue = legacyValue;
|
|
4197
3881
|
}
|
|
4198
3882
|
}
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
try {
|
|
4202
|
-
config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
4203
|
-
} catch {
|
|
4204
|
-
}
|
|
3883
|
+
if (nativeValue) {
|
|
3884
|
+
return Buffer.from(nativeValue, "base64");
|
|
4205
3885
|
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
if (existsSync10(agentConfigPath)) {
|
|
3886
|
+
const keytar = await tryKeytar();
|
|
3887
|
+
if (keytar) {
|
|
4209
3888
|
try {
|
|
4210
|
-
|
|
3889
|
+
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
3890
|
+
const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
|
|
3891
|
+
if (legacyKeytarValue) {
|
|
3892
|
+
const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
|
|
3893
|
+
if (migrated) {
|
|
3894
|
+
process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
|
|
3895
|
+
try {
|
|
3896
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
3897
|
+
} catch {
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
return Buffer.from(legacyKeytarValue, "base64");
|
|
3901
|
+
}
|
|
4211
3902
|
} catch {
|
|
4212
3903
|
}
|
|
4213
3904
|
}
|
|
4214
|
-
const
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
async function cloudPushRoster(config) {
|
|
4220
|
-
assertSecureEndpoint(config.endpoint);
|
|
4221
|
-
const blob = buildRosterBlob();
|
|
4222
|
-
if (blob.roster.length === 0) return true;
|
|
4223
|
-
try {
|
|
4224
|
-
const client = getClient();
|
|
4225
|
-
const meta = await client.execute(
|
|
4226
|
-
"SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
|
|
3905
|
+
const keyPath = getKeyPath();
|
|
3906
|
+
if (!existsSync6(keyPath)) {
|
|
3907
|
+
process.stderr.write(
|
|
3908
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3909
|
+
`
|
|
4227
3910
|
);
|
|
4228
|
-
|
|
4229
|
-
if (blob.version === lastVersion) return true;
|
|
4230
|
-
} catch {
|
|
3911
|
+
return null;
|
|
4231
3912
|
}
|
|
4232
3913
|
try {
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
3914
|
+
const content = (await readFile3(keyPath, "utf-8")).trim();
|
|
3915
|
+
let b64Value;
|
|
3916
|
+
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3917
|
+
const machineKey = deriveMachineKey();
|
|
3918
|
+
if (!machineKey) {
|
|
3919
|
+
process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
|
|
3920
|
+
return null;
|
|
3921
|
+
}
|
|
3922
|
+
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
3923
|
+
if (!decrypted) {
|
|
3924
|
+
process.stderr.write(
|
|
3925
|
+
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
|
|
3926
|
+
);
|
|
3927
|
+
return null;
|
|
3928
|
+
}
|
|
3929
|
+
b64Value = decrypted;
|
|
3930
|
+
} else {
|
|
3931
|
+
b64Value = content;
|
|
3932
|
+
}
|
|
3933
|
+
const key = Buffer.from(b64Value, "base64");
|
|
3934
|
+
if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
3935
|
+
return key;
|
|
3936
|
+
}
|
|
3937
|
+
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3938
|
+
if (migrated) {
|
|
3939
|
+
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
4246
3940
|
try {
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
|
|
4250
|
-
args: [String(blob.version)]
|
|
4251
|
-
});
|
|
3941
|
+
await unlink(keyPath);
|
|
3942
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
4252
3943
|
} catch {
|
|
4253
3944
|
}
|
|
4254
|
-
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
async function cloudPullRoster(config) {
|
|
4263
|
-
assertSecureEndpoint(config.endpoint);
|
|
4264
|
-
try {
|
|
4265
|
-
const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
|
|
4266
|
-
method: "GET",
|
|
4267
|
-
headers: {
|
|
4268
|
-
Authorization: `Bearer ${config.apiKey}`,
|
|
4269
|
-
"X-Device-Id": loadDeviceId()
|
|
3945
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3946
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3947
|
+
if (fallback === "encrypted") {
|
|
3948
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3949
|
+
} else {
|
|
3950
|
+
process.stderr.write(
|
|
3951
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3952
|
+
);
|
|
4270
3953
|
}
|
|
4271
|
-
}
|
|
4272
|
-
|
|
4273
|
-
const data = await resp.json();
|
|
4274
|
-
if (!data.blob) return { added: 0 };
|
|
4275
|
-
const compressed = decryptSyncBlob(data.blob);
|
|
4276
|
-
const json = decompress(compressed).toString("utf8");
|
|
4277
|
-
const remote = JSON.parse(json);
|
|
4278
|
-
return mergeRosterFromRemote(remote);
|
|
3954
|
+
}
|
|
3955
|
+
return key;
|
|
4279
3956
|
} catch (err) {
|
|
4280
|
-
process.stderr.write(
|
|
4281
|
-
`)
|
|
4282
|
-
|
|
3957
|
+
process.stderr.write(
|
|
3958
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
3959
|
+
`
|
|
3960
|
+
);
|
|
3961
|
+
return null;
|
|
4283
3962
|
}
|
|
4284
3963
|
}
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
enforcePrivateFileSync(cfgPath);
|
|
4299
|
-
}
|
|
4300
|
-
async function mergeRosterFromRemote(remote, paths) {
|
|
4301
|
-
return withRosterLock(async () => {
|
|
4302
|
-
const rosterPath = paths?.rosterPath ?? void 0;
|
|
4303
|
-
const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
|
|
4304
|
-
const localEmployees = await loadEmployees(rosterPath);
|
|
4305
|
-
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
4306
|
-
let added = 0;
|
|
4307
|
-
let identitiesUpdated = 0;
|
|
4308
|
-
for (const remoteEmp of remote.roster) {
|
|
4309
|
-
if (!localNames.has(remoteEmp.name)) {
|
|
4310
|
-
localEmployees.push(remoteEmp);
|
|
4311
|
-
localNames.add(remoteEmp.name);
|
|
4312
|
-
added++;
|
|
4313
|
-
try {
|
|
4314
|
-
registerBinSymlinks(remoteEmp.name);
|
|
4315
|
-
} catch {
|
|
4316
|
-
}
|
|
4317
|
-
}
|
|
4318
|
-
const lookupKey = `${remoteEmp.name}.md`;
|
|
4319
|
-
const matchedKey = Object.keys(remote.identities).find(
|
|
4320
|
-
(k) => k.toLowerCase() === lookupKey.toLowerCase()
|
|
4321
|
-
) ?? lookupKey;
|
|
4322
|
-
const remoteIdentity = remote.identities[matchedKey];
|
|
4323
|
-
if (remoteIdentity) {
|
|
4324
|
-
if (!existsSync10(identityDir)) mkdirSync5(identityDir, { recursive: true });
|
|
4325
|
-
const idPath = path10.join(identityDir, `${remoteEmp.name}.md`);
|
|
4326
|
-
let localIdentity = null;
|
|
3964
|
+
|
|
3965
|
+
// src/lib/store.ts
|
|
3966
|
+
init_config();
|
|
3967
|
+
|
|
3968
|
+
// src/lib/state-bus.ts
|
|
3969
|
+
var StateBus = class {
|
|
3970
|
+
handlers = /* @__PURE__ */ new Map();
|
|
3971
|
+
globalHandlers = /* @__PURE__ */ new Set();
|
|
3972
|
+
/** Emit an event to all subscribers */
|
|
3973
|
+
emit(event) {
|
|
3974
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
3975
|
+
if (typeHandlers) {
|
|
3976
|
+
for (const handler of typeHandlers) {
|
|
4327
3977
|
try {
|
|
4328
|
-
|
|
3978
|
+
handler(event);
|
|
4329
3979
|
} catch {
|
|
4330
3980
|
}
|
|
4331
|
-
if (localIdentity !== remoteIdentity) {
|
|
4332
|
-
writeFileSync5(idPath, remoteIdentity, "utf-8");
|
|
4333
|
-
identitiesUpdated++;
|
|
4334
|
-
}
|
|
4335
3981
|
}
|
|
4336
3982
|
}
|
|
4337
|
-
|
|
4338
|
-
if (remote.deletedNames && remote.deletedNames.length > 0) {
|
|
4339
|
-
const toRemove = new Set(remote.deletedNames);
|
|
4340
|
-
const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
|
|
4341
|
-
removed = localEmployees.length - filtered.length;
|
|
4342
|
-
if (removed > 0) {
|
|
4343
|
-
localEmployees.length = 0;
|
|
4344
|
-
localEmployees.push(...filtered);
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
|
-
if (added > 0 || removed > 0) {
|
|
4348
|
-
await saveEmployees(localEmployees, rosterPath);
|
|
4349
|
-
}
|
|
4350
|
-
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
3983
|
+
for (const handler of this.globalHandlers) {
|
|
4351
3984
|
try {
|
|
4352
|
-
|
|
3985
|
+
handler(event);
|
|
4353
3986
|
} catch {
|
|
4354
3987
|
}
|
|
4355
3988
|
}
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
try {
|
|
4362
|
-
local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
4363
|
-
} catch {
|
|
4364
|
-
}
|
|
4365
|
-
}
|
|
4366
|
-
const merged = { ...remote.agentConfig, ...local };
|
|
4367
|
-
ensurePrivateDirSync(path10.dirname(agentConfigPath));
|
|
4368
|
-
writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
4369
|
-
enforcePrivateFileSync(agentConfigPath);
|
|
4370
|
-
} catch {
|
|
4371
|
-
}
|
|
3989
|
+
}
|
|
3990
|
+
/** Subscribe to a specific event type */
|
|
3991
|
+
on(type, handler) {
|
|
3992
|
+
if (!this.handlers.has(type)) {
|
|
3993
|
+
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
4372
3994
|
}
|
|
4373
|
-
|
|
4374
|
-
}
|
|
3995
|
+
this.handlers.get(type).add(handler);
|
|
3996
|
+
}
|
|
3997
|
+
/** Subscribe to ALL events */
|
|
3998
|
+
onAny(handler) {
|
|
3999
|
+
this.globalHandlers.add(handler);
|
|
4000
|
+
}
|
|
4001
|
+
/** Unsubscribe from a specific event type */
|
|
4002
|
+
off(type, handler) {
|
|
4003
|
+
this.handlers.get(type)?.delete(handler);
|
|
4004
|
+
}
|
|
4005
|
+
/** Unsubscribe from ALL events */
|
|
4006
|
+
offAny(handler) {
|
|
4007
|
+
this.globalHandlers.delete(handler);
|
|
4008
|
+
}
|
|
4009
|
+
/** Remove all listeners */
|
|
4010
|
+
clear() {
|
|
4011
|
+
this.handlers.clear();
|
|
4012
|
+
this.globalHandlers.clear();
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
var orgBus = new StateBus();
|
|
4016
|
+
|
|
4017
|
+
// src/lib/memory-write-governor.ts
|
|
4018
|
+
import { createHash } from "crypto";
|
|
4019
|
+
|
|
4020
|
+
// src/lib/store.ts
|
|
4021
|
+
var INIT_MAX_RETRIES = 3;
|
|
4022
|
+
var INIT_RETRY_DELAY_MS = 1e3;
|
|
4023
|
+
function isBusyError2(err) {
|
|
4024
|
+
if (err instanceof Error) {
|
|
4025
|
+
const msg = err.message.toLowerCase();
|
|
4026
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
4027
|
+
}
|
|
4028
|
+
return false;
|
|
4375
4029
|
}
|
|
4376
|
-
async function
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4030
|
+
async function retryOnBusy2(fn, label) {
|
|
4031
|
+
for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
|
|
4032
|
+
try {
|
|
4033
|
+
return await fn();
|
|
4034
|
+
} catch (err) {
|
|
4035
|
+
if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
|
|
4036
|
+
process.stderr.write(
|
|
4037
|
+
`[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
|
|
4038
|
+
`
|
|
4039
|
+
);
|
|
4040
|
+
await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
throw new Error("unreachable");
|
|
4044
|
+
}
|
|
4045
|
+
var _pendingRecords = [];
|
|
4046
|
+
var _batchSize = 20;
|
|
4047
|
+
var _flushIntervalMs = 1e4;
|
|
4048
|
+
var _flushTimer = null;
|
|
4049
|
+
var _flushing = false;
|
|
4050
|
+
var _nextVersion = 1;
|
|
4051
|
+
async function initStore(options) {
|
|
4052
|
+
if (_flushTimer !== null) {
|
|
4053
|
+
clearInterval(_flushTimer);
|
|
4054
|
+
_flushTimer = null;
|
|
4055
|
+
}
|
|
4056
|
+
_pendingRecords = [];
|
|
4057
|
+
_flushing = false;
|
|
4058
|
+
_batchSize = options?.batchSize ?? 20;
|
|
4059
|
+
_flushIntervalMs = options?.flushIntervalMs ?? 1e4;
|
|
4060
|
+
let dbPath = options?.dbPath;
|
|
4061
|
+
if (!dbPath) {
|
|
4062
|
+
const config = await loadConfig();
|
|
4063
|
+
dbPath = config.dbPath;
|
|
4064
|
+
}
|
|
4065
|
+
let masterKey = options?.masterKey ?? null;
|
|
4066
|
+
if (!masterKey) {
|
|
4067
|
+
masterKey = await getMasterKey();
|
|
4068
|
+
if (!masterKey) {
|
|
4069
|
+
throw new Error(
|
|
4070
|
+
"No encryption key found. Run /exe-setup to generate one."
|
|
4071
|
+
);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
const hexKey = masterKey.toString("hex");
|
|
4075
|
+
await initTurso({
|
|
4076
|
+
dbPath,
|
|
4077
|
+
encryptionKey: hexKey
|
|
4078
|
+
});
|
|
4079
|
+
await retryOnBusy2(() => ensureSchema(), "ensureSchema");
|
|
4381
4080
|
try {
|
|
4382
|
-
const
|
|
4383
|
-
|
|
4384
|
-
sql: "SELECT value FROM sync_meta WHERE key = ?",
|
|
4385
|
-
args: [metaKey]
|
|
4386
|
-
});
|
|
4387
|
-
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
4388
|
-
if (version === lastVersion) return { ok: true };
|
|
4081
|
+
const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
4082
|
+
await initDaemonClient2();
|
|
4389
4083
|
} catch {
|
|
4390
4084
|
}
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
|
|
4408
|
-
args: [metaKey, String(version)]
|
|
4409
|
-
});
|
|
4410
|
-
} catch {
|
|
4411
|
-
}
|
|
4085
|
+
if (!options?.lightweight) {
|
|
4086
|
+
try {
|
|
4087
|
+
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
4088
|
+
initShardManager2(hexKey);
|
|
4089
|
+
} catch {
|
|
4090
|
+
}
|
|
4091
|
+
const client = getClient();
|
|
4092
|
+
const vResult = await retryOnBusy2(
|
|
4093
|
+
() => client.execute("SELECT MAX(version) as max_v FROM memories"),
|
|
4094
|
+
"version-query"
|
|
4095
|
+
);
|
|
4096
|
+
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
4097
|
+
try {
|
|
4098
|
+
const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
|
|
4099
|
+
await loadGlobalProcedures2();
|
|
4100
|
+
} catch {
|
|
4412
4101
|
}
|
|
4413
|
-
return { ok: resp.ok };
|
|
4414
|
-
} catch (err) {
|
|
4415
|
-
logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
4416
|
-
return { ok: false };
|
|
4417
4102
|
}
|
|
4418
4103
|
}
|
|
4419
|
-
|
|
4420
|
-
|
|
4104
|
+
|
|
4105
|
+
// src/bin/agentic-semantic-label.ts
|
|
4106
|
+
init_database();
|
|
4107
|
+
init_agentic_ontology();
|
|
4108
|
+
|
|
4109
|
+
// src/lib/agentic-llm-labeler.ts
|
|
4110
|
+
init_agentic_ontology();
|
|
4111
|
+
import OpenAI from "openai";
|
|
4112
|
+
function jsonFromText(text) {
|
|
4113
|
+
const trimmed = text.trim().replace(/^```json\s*/i, "").replace(/^```\s*/i, "").replace(/```$/i, "").trim();
|
|
4421
4114
|
try {
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
headers: {
|
|
4425
|
-
Authorization: `Bearer ${config.apiKey}`,
|
|
4426
|
-
"X-Device-Id": loadDeviceId()
|
|
4427
|
-
}
|
|
4428
|
-
});
|
|
4429
|
-
if (!resp.ok) return null;
|
|
4430
|
-
const data = await resp.json();
|
|
4431
|
-
if (!data.blob) return null;
|
|
4432
|
-
const compressed = decryptSyncBlob(data.blob);
|
|
4433
|
-
const json = decompress(compressed).toString("utf8");
|
|
4434
|
-
return JSON.parse(json);
|
|
4435
|
-
} catch (err) {
|
|
4436
|
-
logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
4115
|
+
return JSON.parse(trimmed);
|
|
4116
|
+
} catch {
|
|
4437
4117
|
return null;
|
|
4438
4118
|
}
|
|
4439
4119
|
}
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
const rows = result.rows;
|
|
4444
|
-
const { ok } = await cloudPushBlob(
|
|
4445
|
-
"/sync/push-global-procedures",
|
|
4446
|
-
rows,
|
|
4447
|
-
"last_company_procedures_push_version",
|
|
4448
|
-
config
|
|
4449
|
-
);
|
|
4450
|
-
return ok;
|
|
4451
|
-
}
|
|
4452
|
-
async function cloudPullGlobalProcedures(config) {
|
|
4453
|
-
const remoteProcs = await cloudPullBlob(
|
|
4454
|
-
"/sync/pull-global-procedures",
|
|
4455
|
-
config
|
|
4456
|
-
);
|
|
4457
|
-
if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
|
|
4458
|
-
const client = getClient();
|
|
4459
|
-
const stmts = remoteProcs.map((p) => ({
|
|
4460
|
-
sql: `INSERT INTO company_procedures
|
|
4461
|
-
(id, title, content, priority, domain, active, created_at, updated_at)
|
|
4462
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
4463
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
4464
|
-
title = excluded.title,
|
|
4465
|
-
content = excluded.content,
|
|
4466
|
-
priority = excluded.priority,
|
|
4467
|
-
domain = excluded.domain,
|
|
4468
|
-
active = excluded.active,
|
|
4469
|
-
updated_at = excluded.updated_at
|
|
4470
|
-
WHERE excluded.updated_at > company_procedures.updated_at`,
|
|
4471
|
-
args: [
|
|
4472
|
-
sqlSafe(p.id),
|
|
4473
|
-
sqlSafe(p.title),
|
|
4474
|
-
sqlSafe(p.content),
|
|
4475
|
-
sqlSafe(p.priority ?? "p0"),
|
|
4476
|
-
sqlSafe(p.domain),
|
|
4477
|
-
sqlSafe(p.active ?? 1),
|
|
4478
|
-
sqlSafe(p.created_at),
|
|
4479
|
-
sqlSafe(p.updated_at)
|
|
4480
|
-
]
|
|
4481
|
-
}));
|
|
4482
|
-
await client.batch(stmts, "write");
|
|
4483
|
-
return { pulled: remoteProcs.length };
|
|
4484
|
-
}
|
|
4485
|
-
async function cloudPushBehaviors(config) {
|
|
4486
|
-
const client = getClient();
|
|
4487
|
-
const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
|
|
4488
|
-
const rows = result.rows;
|
|
4489
|
-
const { ok } = await cloudPushBlob(
|
|
4490
|
-
"/sync/push-behaviors",
|
|
4491
|
-
rows,
|
|
4492
|
-
"last_behaviors_push_version",
|
|
4493
|
-
config
|
|
4494
|
-
);
|
|
4495
|
-
return ok;
|
|
4496
|
-
}
|
|
4497
|
-
async function cloudPullBehaviors(config) {
|
|
4498
|
-
const remoteBehaviors = await cloudPullBlob(
|
|
4499
|
-
"/sync/pull-behaviors",
|
|
4500
|
-
config
|
|
4501
|
-
);
|
|
4502
|
-
if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
|
|
4503
|
-
const client = getClient();
|
|
4504
|
-
let pulled = 0;
|
|
4505
|
-
for (const behavior of remoteBehaviors) {
|
|
4506
|
-
const existing = await client.execute({
|
|
4507
|
-
sql: `SELECT COUNT(*) as cnt FROM behaviors
|
|
4508
|
-
WHERE agent_id = ? AND content = ?`,
|
|
4509
|
-
args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
|
|
4510
|
-
});
|
|
4511
|
-
if (Number(existing.rows[0]?.cnt) > 0) continue;
|
|
4512
|
-
await client.execute({
|
|
4513
|
-
sql: `INSERT OR IGNORE INTO behaviors
|
|
4514
|
-
(id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
|
|
4515
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4516
|
-
args: [
|
|
4517
|
-
sqlSafe(behavior.id),
|
|
4518
|
-
sqlSafe(behavior.agent_id),
|
|
4519
|
-
sqlSafe(behavior.project_name),
|
|
4520
|
-
sqlSafe(behavior.domain),
|
|
4521
|
-
sqlSafe(behavior.content),
|
|
4522
|
-
sqlSafe(behavior.active ?? 1),
|
|
4523
|
-
sqlSafe(behavior.priority ?? "p1"),
|
|
4524
|
-
sqlSafe(behavior.created_at),
|
|
4525
|
-
sqlSafe(behavior.updated_at)
|
|
4526
|
-
]
|
|
4527
|
-
});
|
|
4528
|
-
pulled++;
|
|
4529
|
-
}
|
|
4530
|
-
return { pulled };
|
|
4120
|
+
function stringArray(value, max = 6) {
|
|
4121
|
+
if (!Array.isArray(value)) return [];
|
|
4122
|
+
return value.map(String).map((v) => clean(v, 220)).filter(Boolean).slice(0, max);
|
|
4531
4123
|
}
|
|
4532
|
-
|
|
4533
|
-
const
|
|
4534
|
-
const
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4124
|
+
function normalizeLlmLabel(raw, fallback) {
|
|
4125
|
+
const impact = ["positive", "negative", "neutral", "mixed"].includes(String(raw.impact)) ? String(raw.impact) : fallback.impact;
|
|
4126
|
+
const confidenceRaw = Number(raw.confidence ?? fallback.confidence);
|
|
4127
|
+
return {
|
|
4128
|
+
labeler: "llm",
|
|
4129
|
+
schemaVersion: 1,
|
|
4130
|
+
eventType: String(raw.eventType ?? fallback.eventType),
|
|
4131
|
+
intention: raw.intention == null ? fallback.intention : clean(String(raw.intention), 260),
|
|
4132
|
+
outcome: raw.outcome == null ? fallback.outcome : clean(String(raw.outcome), 260),
|
|
4133
|
+
impact,
|
|
4134
|
+
confidence: Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(1, confidenceRaw)) : fallback.confidence,
|
|
4135
|
+
goals: stringArray(raw.goals).length ? stringArray(raw.goals) : fallback.goals,
|
|
4136
|
+
milestones: stringArray(raw.milestones).length ? stringArray(raw.milestones) : fallback.milestones,
|
|
4137
|
+
problems: stringArray(raw.problems).length ? stringArray(raw.problems) : fallback.problems,
|
|
4138
|
+
decisions: stringArray(raw.decisions).length ? stringArray(raw.decisions) : fallback.decisions,
|
|
4139
|
+
actors: stringArray(raw.actors).length ? stringArray(raw.actors) : fallback.actors,
|
|
4140
|
+
temporalAnchors: stringArray(raw.temporalAnchors, 10).length ? stringArray(raw.temporalAnchors, 10) : fallback.temporalAnchors,
|
|
4141
|
+
successSignals: stringArray(raw.successSignals).length ? stringArray(raw.successSignals) : fallback.successSignals,
|
|
4142
|
+
failureSignals: stringArray(raw.failureSignals).length ? stringArray(raw.failureSignals) : fallback.failureSignals,
|
|
4143
|
+
nextActions: stringArray(raw.nextActions).length ? stringArray(raw.nextActions) : fallback.nextActions,
|
|
4144
|
+
summary: clean(String(raw.summary ?? fallback.summary), 360)
|
|
4551
4145
|
};
|
|
4552
|
-
const { ok } = await cloudPushBlob(
|
|
4553
|
-
"/sync/push-graphrag",
|
|
4554
|
-
[blob],
|
|
4555
|
-
"last_graphrag_push_version",
|
|
4556
|
-
config
|
|
4557
|
-
);
|
|
4558
|
-
return ok;
|
|
4559
|
-
}
|
|
4560
|
-
async function cloudPullGraphRAG(config) {
|
|
4561
|
-
const data = await cloudPullBlob(
|
|
4562
|
-
"/sync/pull-graphrag",
|
|
4563
|
-
config
|
|
4564
|
-
);
|
|
4565
|
-
if (!data || data.length === 0) return { pulled: 0 };
|
|
4566
|
-
const blob = data[0];
|
|
4567
|
-
const client = getClient();
|
|
4568
|
-
let pulled = 0;
|
|
4569
|
-
if (blob.entities.length > 0) {
|
|
4570
|
-
const stmts = blob.entities.map((e) => ({
|
|
4571
|
-
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
|
|
4572
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
4573
|
-
args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
|
|
4574
|
-
}));
|
|
4575
|
-
await client.batch(stmts, "write");
|
|
4576
|
-
pulled += stmts.length;
|
|
4577
|
-
}
|
|
4578
|
-
if (blob.relationships.length > 0) {
|
|
4579
|
-
const stmts = blob.relationships.map((r) => ({
|
|
4580
|
-
sql: `INSERT OR IGNORE INTO relationships
|
|
4581
|
-
(id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
|
|
4582
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4583
|
-
args: [
|
|
4584
|
-
sqlSafe(r.id),
|
|
4585
|
-
sqlSafe(r.source_entity_id),
|
|
4586
|
-
sqlSafe(r.target_entity_id),
|
|
4587
|
-
sqlSafe(r.type),
|
|
4588
|
-
sqlSafe(r.weight ?? 1),
|
|
4589
|
-
sqlSafe(r.timestamp),
|
|
4590
|
-
sqlSafe(r.properties ?? "{}"),
|
|
4591
|
-
sqlSafe(r.confidence ?? 1),
|
|
4592
|
-
sqlSafe(r.confidence_label ?? "extracted")
|
|
4593
|
-
]
|
|
4594
|
-
}));
|
|
4595
|
-
await client.batch(stmts, "write");
|
|
4596
|
-
pulled += stmts.length;
|
|
4597
|
-
}
|
|
4598
|
-
if (blob.entity_aliases.length > 0) {
|
|
4599
|
-
const stmts = blob.entity_aliases.map((a) => ({
|
|
4600
|
-
sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
|
|
4601
|
-
args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
|
|
4602
|
-
}));
|
|
4603
|
-
await client.batch(stmts, "write");
|
|
4604
|
-
pulled += stmts.length;
|
|
4605
|
-
}
|
|
4606
|
-
if (blob.entity_memories.length > 0) {
|
|
4607
|
-
const stmts = blob.entity_memories.map((em) => ({
|
|
4608
|
-
sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
|
|
4609
|
-
args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
|
|
4610
|
-
}));
|
|
4611
|
-
await client.batch(stmts, "write");
|
|
4612
|
-
pulled += stmts.length;
|
|
4613
|
-
}
|
|
4614
|
-
if (blob.relationship_memories.length > 0) {
|
|
4615
|
-
const stmts = blob.relationship_memories.map((rm) => ({
|
|
4616
|
-
sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
|
|
4617
|
-
args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
|
|
4618
|
-
}));
|
|
4619
|
-
await client.batch(stmts, "write");
|
|
4620
|
-
pulled += stmts.length;
|
|
4621
|
-
}
|
|
4622
|
-
if (blob.hyperedges.length > 0) {
|
|
4623
|
-
const stmts = blob.hyperedges.map((h) => ({
|
|
4624
|
-
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
4625
|
-
VALUES (?, ?, ?, ?, ?)`,
|
|
4626
|
-
args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
|
|
4627
|
-
}));
|
|
4628
|
-
await client.batch(stmts, "write");
|
|
4629
|
-
pulled += stmts.length;
|
|
4630
|
-
}
|
|
4631
|
-
if (blob.hyperedge_nodes.length > 0) {
|
|
4632
|
-
const stmts = blob.hyperedge_nodes.map((hn) => ({
|
|
4633
|
-
sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
|
|
4634
|
-
args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
|
|
4635
|
-
}));
|
|
4636
|
-
await client.batch(stmts, "write");
|
|
4637
|
-
pulled += stmts.length;
|
|
4638
|
-
}
|
|
4639
|
-
return { pulled };
|
|
4640
|
-
}
|
|
4641
|
-
async function cloudPushTasks(config) {
|
|
4642
|
-
const client = getClient();
|
|
4643
|
-
const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
|
|
4644
|
-
const rows = result.rows;
|
|
4645
|
-
const { ok } = await cloudPushBlob(
|
|
4646
|
-
"/sync/push-tasks",
|
|
4647
|
-
rows,
|
|
4648
|
-
"last_tasks_push_version",
|
|
4649
|
-
config
|
|
4650
|
-
);
|
|
4651
|
-
return ok;
|
|
4652
|
-
}
|
|
4653
|
-
async function cloudPullTasks(config) {
|
|
4654
|
-
const remoteTasks = await cloudPullBlob(
|
|
4655
|
-
"/sync/pull-tasks",
|
|
4656
|
-
config
|
|
4657
|
-
);
|
|
4658
|
-
if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
|
|
4659
|
-
const client = getClient();
|
|
4660
|
-
const stmts = remoteTasks.map((t) => ({
|
|
4661
|
-
sql: `INSERT OR IGNORE INTO tasks
|
|
4662
|
-
(id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
|
|
4663
|
-
blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
|
|
4664
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4665
|
-
args: [
|
|
4666
|
-
sqlSafe(t.id),
|
|
4667
|
-
sqlSafe(t.title),
|
|
4668
|
-
sqlSafe(t.assigned_to),
|
|
4669
|
-
sqlSafe(t.assigned_by),
|
|
4670
|
-
sqlSafe(t.project_name),
|
|
4671
|
-
sqlSafe(t.priority ?? "p1"),
|
|
4672
|
-
sqlSafe(t.status ?? "open"),
|
|
4673
|
-
sqlSafe(t.task_file),
|
|
4674
|
-
sqlSafe(t.created_at),
|
|
4675
|
-
sqlSafe(t.updated_at),
|
|
4676
|
-
sqlSafe(t.blocked_by),
|
|
4677
|
-
sqlSafe(t.parent_task_id),
|
|
4678
|
-
sqlSafe(t.budget_tokens),
|
|
4679
|
-
sqlSafe(t.budget_fallback_model),
|
|
4680
|
-
sqlSafe(t.tokens_used ?? 0),
|
|
4681
|
-
sqlSafe(t.tokens_warned_at)
|
|
4682
|
-
]
|
|
4683
|
-
}));
|
|
4684
|
-
await client.batch(stmts, "write");
|
|
4685
|
-
return { pulled: remoteTasks.length };
|
|
4686
|
-
}
|
|
4687
|
-
async function cloudPushConversations(config) {
|
|
4688
|
-
const client = getClient();
|
|
4689
|
-
const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
|
|
4690
|
-
const rows = result.rows;
|
|
4691
|
-
const { ok } = await cloudPushBlob(
|
|
4692
|
-
"/sync/push-conversations",
|
|
4693
|
-
rows,
|
|
4694
|
-
"last_conversations_push_version",
|
|
4695
|
-
config
|
|
4696
|
-
);
|
|
4697
|
-
return ok;
|
|
4698
|
-
}
|
|
4699
|
-
async function cloudPullConversations(config) {
|
|
4700
|
-
const remoteConvos = await cloudPullBlob(
|
|
4701
|
-
"/sync/pull-conversations",
|
|
4702
|
-
config
|
|
4703
|
-
);
|
|
4704
|
-
if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
|
|
4705
|
-
const client = getClient();
|
|
4706
|
-
const stmts = remoteConvos.map((c) => ({
|
|
4707
|
-
sql: `INSERT OR IGNORE INTO conversations
|
|
4708
|
-
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
4709
|
-
recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
|
|
4710
|
-
content_metadata, agent_response, agent_name, timestamp, ingested_at)
|
|
4711
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4712
|
-
args: [
|
|
4713
|
-
sqlSafe(c.id),
|
|
4714
|
-
sqlSafe(c.platform),
|
|
4715
|
-
sqlSafe(c.external_id),
|
|
4716
|
-
sqlSafe(c.sender_id),
|
|
4717
|
-
sqlSafe(c.sender_name),
|
|
4718
|
-
sqlSafe(c.sender_phone),
|
|
4719
|
-
sqlSafe(c.sender_email),
|
|
4720
|
-
sqlSafe(c.recipient_id),
|
|
4721
|
-
sqlSafe(c.channel_id),
|
|
4722
|
-
sqlSafe(c.thread_id),
|
|
4723
|
-
sqlSafe(c.reply_to_id),
|
|
4724
|
-
sqlSafe(c.content_text),
|
|
4725
|
-
sqlSafe(c.content_media),
|
|
4726
|
-
sqlSafe(c.content_metadata),
|
|
4727
|
-
sqlSafe(c.agent_response),
|
|
4728
|
-
sqlSafe(c.agent_name),
|
|
4729
|
-
sqlSafe(c.timestamp),
|
|
4730
|
-
sqlSafe(c.ingested_at)
|
|
4731
|
-
]
|
|
4732
|
-
}));
|
|
4733
|
-
await client.batch(stmts, "write");
|
|
4734
|
-
return { pulled: remoteConvos.length };
|
|
4735
4146
|
}
|
|
4736
|
-
async function
|
|
4737
|
-
const
|
|
4738
|
-
const
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
const
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
let pulled = 0;
|
|
4763
|
-
if (blob.workspaces.length > 0) {
|
|
4764
|
-
const stmts = blob.workspaces.map((w) => ({
|
|
4765
|
-
sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
|
|
4766
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
4767
|
-
args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
|
|
4768
|
-
}));
|
|
4769
|
-
await client.batch(stmts, "write");
|
|
4770
|
-
pulled += stmts.length;
|
|
4771
|
-
}
|
|
4772
|
-
if (blob.documents.length > 0) {
|
|
4773
|
-
const stmts = blob.documents.map((d) => ({
|
|
4774
|
-
sql: `INSERT OR IGNORE INTO documents
|
|
4775
|
-
(id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
|
|
4776
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4777
|
-
args: [
|
|
4778
|
-
sqlSafe(d.id),
|
|
4779
|
-
sqlSafe(d.workspace_id),
|
|
4780
|
-
sqlSafe(d.filename),
|
|
4781
|
-
sqlSafe(d.mime),
|
|
4782
|
-
sqlSafe(d.source_type),
|
|
4783
|
-
sqlSafe(d.user_id),
|
|
4784
|
-
sqlSafe(d.uploaded_at),
|
|
4785
|
-
sqlSafe(d.metadata)
|
|
4786
|
-
]
|
|
4787
|
-
}));
|
|
4788
|
-
await client.batch(stmts, "write");
|
|
4789
|
-
pulled += stmts.length;
|
|
4790
|
-
}
|
|
4791
|
-
return { pulled };
|
|
4792
|
-
}
|
|
4793
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
4794
|
-
var init_cloud_sync = __esm({
|
|
4795
|
-
"src/lib/cloud-sync.ts"() {
|
|
4796
|
-
"use strict";
|
|
4797
|
-
init_database();
|
|
4798
|
-
init_crypto();
|
|
4799
|
-
init_compress();
|
|
4800
|
-
init_license();
|
|
4801
|
-
init_config();
|
|
4802
|
-
init_crdt_sync();
|
|
4803
|
-
init_employees();
|
|
4804
|
-
init_secure_files();
|
|
4805
|
-
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4806
|
-
FETCH_TIMEOUT_MS = 3e4;
|
|
4807
|
-
PUSH_BATCH_SIZE = 5e3;
|
|
4808
|
-
ROSTER_LOCK_PATH = path10.join(EXE_AI_DIR, "roster-merge.lock");
|
|
4809
|
-
LOCK_STALE_MS = 3e4;
|
|
4810
|
-
_pgPromise = null;
|
|
4811
|
-
_pgFailed = false;
|
|
4812
|
-
ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
|
|
4813
|
-
}
|
|
4814
|
-
});
|
|
4147
|
+
async function labelMemoryWithLlm(row, opts = {}) {
|
|
4148
|
+
const fallback = inferSemanticLabel(row);
|
|
4149
|
+
const apiKey = opts.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.API_ROUTER_KEY;
|
|
4150
|
+
const baseURL = opts.baseUrl ?? process.env.OPENAI_BASE_URL ?? process.env.API_ROUTER_URL;
|
|
4151
|
+
const model = opts.model ?? process.env.EXE_AGENTIC_LABEL_MODEL;
|
|
4152
|
+
if (!apiKey || !model) return fallback;
|
|
4153
|
+
const client = new OpenAI({ apiKey, baseURL: baseURL || void 0 });
|
|
4154
|
+
const prompt = `Label this agent-memory event for agentic memory. Return ONLY valid JSON with keys:
|
|
4155
|
+
{
|
|
4156
|
+
"eventType": "tool_action|milestone|problem|error|decision|goal_signal|memory_observation|memory_card",
|
|
4157
|
+
"intention": string|null,
|
|
4158
|
+
"outcome": string|null,
|
|
4159
|
+
"impact": "positive|negative|neutral|mixed",
|
|
4160
|
+
"confidence": number between 0 and 1,
|
|
4161
|
+
"goals": string[],
|
|
4162
|
+
"milestones": string[],
|
|
4163
|
+
"problems": string[],
|
|
4164
|
+
"decisions": string[],
|
|
4165
|
+
"actors": string[],
|
|
4166
|
+
"temporalAnchors": string[],
|
|
4167
|
+
"successSignals": string[],
|
|
4168
|
+
"failureSignals": string[],
|
|
4169
|
+
"nextActions": string[],
|
|
4170
|
+
"summary": string
|
|
4171
|
+
}
|
|
4172
|
+
Focus on explicit goals, chronology, intended action, actual outcome, and whether the event moved the goal forward.
|
|
4815
4173
|
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
import { createInterface } from "readline";
|
|
4819
|
-
|
|
4820
|
-
// src/lib/is-main.ts
|
|
4821
|
-
import { realpathSync } from "fs";
|
|
4822
|
-
import { fileURLToPath } from "url";
|
|
4823
|
-
function isMainModule(importMetaUrl) {
|
|
4824
|
-
if (process.argv[1] == null) return false;
|
|
4825
|
-
if (process.argv[1].includes("mcp/server")) return false;
|
|
4174
|
+
Metadata: ${JSON.stringify({ agent_id: row.agent_id, role: row.agent_role, project: row.project_name, session: row.session_id, timestamp: row.timestamp, tool: row.tool_name, has_error: row.has_error })}
|
|
4175
|
+
Text: ${clean(row.raw_text, 5e3)}`;
|
|
4826
4176
|
try {
|
|
4827
|
-
const
|
|
4828
|
-
|
|
4829
|
-
|
|
4177
|
+
const response = await client.chat.completions.create({
|
|
4178
|
+
model,
|
|
4179
|
+
messages: [
|
|
4180
|
+
{ role: "system", content: "You are a precise ontology labeler. Output compact JSON only." },
|
|
4181
|
+
{ role: "user", content: prompt }
|
|
4182
|
+
],
|
|
4183
|
+
max_tokens: opts.maxTokens ?? 900,
|
|
4184
|
+
response_format: { type: "json_object" }
|
|
4185
|
+
});
|
|
4186
|
+
const raw = jsonFromText(response.choices[0]?.message?.content ?? "");
|
|
4187
|
+
return raw ? normalizeLlmLabel(raw, fallback) : fallback;
|
|
4830
4188
|
} catch {
|
|
4831
|
-
return
|
|
4189
|
+
return fallback;
|
|
4832
4190
|
}
|
|
4833
4191
|
}
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
}
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4192
|
+
async function resolveClient(client) {
|
|
4193
|
+
if (client) return client;
|
|
4194
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
4195
|
+
return getClient2();
|
|
4196
|
+
}
|
|
4197
|
+
async function upsertSemanticLabel(row, label, client) {
|
|
4198
|
+
const db = await resolveClient(client);
|
|
4199
|
+
const eventId = stableId("event", row.id);
|
|
4200
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4201
|
+
await db.execute({
|
|
4202
|
+
sql: `INSERT INTO agent_semantic_labels
|
|
4203
|
+
(id, source_memory_id, event_id, labeler, schema_version, confidence, labels, created_at, updated_at)
|
|
4204
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4205
|
+
ON CONFLICT(id) DO UPDATE SET confidence = excluded.confidence, labels = excluded.labels,
|
|
4206
|
+
updated_at = excluded.updated_at`,
|
|
4207
|
+
args: [
|
|
4208
|
+
stableId("semantic", row.id, label.labeler, label.schemaVersion),
|
|
4209
|
+
row.id,
|
|
4210
|
+
eventId,
|
|
4211
|
+
label.labeler,
|
|
4212
|
+
label.schemaVersion,
|
|
4213
|
+
label.confidence,
|
|
4214
|
+
JSON.stringify(label),
|
|
4215
|
+
now,
|
|
4216
|
+
now
|
|
4217
|
+
]
|
|
4218
|
+
});
|
|
4219
|
+
if (label.labeler === "llm") {
|
|
4220
|
+
await db.execute({
|
|
4221
|
+
sql: `UPDATE agent_events SET intention = COALESCE(?, intention), outcome = COALESCE(?, outcome),
|
|
4222
|
+
impact = COALESCE(?, impact)
|
|
4223
|
+
WHERE id = ?`,
|
|
4224
|
+
args: [label.intention, label.outcome, label.impact, eventId]
|
|
4867
4225
|
});
|
|
4868
|
-
try {
|
|
4869
|
-
const key = await importMnemonic(mnemonic);
|
|
4870
|
-
await setMasterKey(key);
|
|
4871
|
-
console.log("Master key imported and stored securely.");
|
|
4872
|
-
try {
|
|
4873
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4874
|
-
const { initSyncCrypto: initSyncCrypto2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
4875
|
-
const { cloudPullRoster: cloudPullRoster2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
4876
|
-
const config = await loadConfig2();
|
|
4877
|
-
if (config.cloud?.apiKey && config.cloud?.endpoint) {
|
|
4878
|
-
initSyncCrypto2(key);
|
|
4879
|
-
const result = await cloudPullRoster2({ apiKey: config.cloud.apiKey, endpoint: config.cloud.endpoint });
|
|
4880
|
-
if (result.added > 0) {
|
|
4881
|
-
console.log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
|
|
4882
|
-
}
|
|
4883
|
-
}
|
|
4884
|
-
} catch {
|
|
4885
|
-
}
|
|
4886
|
-
console.log("Run /exe-setup to configure sync and download the embedding model.");
|
|
4887
|
-
} catch (err) {
|
|
4888
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
4889
|
-
process.exit(1);
|
|
4890
|
-
}
|
|
4891
|
-
} else {
|
|
4892
|
-
console.error("Usage:");
|
|
4893
|
-
console.error(" exe-link export \u2014 show 24-word mnemonic for current key");
|
|
4894
|
-
console.error(" exe-link import \u2014 paste mnemonic to import key on new device");
|
|
4895
|
-
process.exit(1);
|
|
4896
4226
|
}
|
|
4897
4227
|
}
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4228
|
+
|
|
4229
|
+
// src/bin/agentic-semantic-label.ts
|
|
4230
|
+
function arg(name) {
|
|
4231
|
+
const i = process.argv.indexOf(name);
|
|
4232
|
+
return i >= 0 ? process.argv[i + 1] : void 0;
|
|
4233
|
+
}
|
|
4234
|
+
async function main() {
|
|
4235
|
+
const limit = Number(arg("--limit") ?? "100");
|
|
4236
|
+
const project = arg("--project");
|
|
4237
|
+
const llm = process.argv.includes("--llm");
|
|
4238
|
+
await initStore();
|
|
4239
|
+
const db = getClient();
|
|
4240
|
+
const where = project ? "WHERE project_name = ?" : "";
|
|
4241
|
+
const rows = await db.execute({
|
|
4242
|
+
sql: `SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text,
|
|
4243
|
+
version, task_id, intent, outcome, domain, trajectory
|
|
4244
|
+
FROM memories ${where}
|
|
4245
|
+
ORDER BY timestamp DESC
|
|
4246
|
+
LIMIT ?`,
|
|
4247
|
+
args: project ? [project, limit] : [limit]
|
|
4902
4248
|
});
|
|
4249
|
+
let count = 0;
|
|
4250
|
+
for (const row of rows.rows) {
|
|
4251
|
+
const label = llm ? await labelMemoryWithLlm(row) : inferSemanticLabel(row);
|
|
4252
|
+
await upsertSemanticLabel(row, label, db);
|
|
4253
|
+
count++;
|
|
4254
|
+
if (count % 25 === 0) process.stderr.write(`[agentic-semantic-label] labeled ${count}
|
|
4255
|
+
`);
|
|
4256
|
+
}
|
|
4257
|
+
process.stderr.write(`[agentic-semantic-label] Complete: ${count} memories labeled (${llm ? "llm-if-configured" : "deterministic"}).
|
|
4258
|
+
`);
|
|
4259
|
+
process.exit(0);
|
|
4903
4260
|
}
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4261
|
+
main().catch((err) => {
|
|
4262
|
+
process.stderr.write(`[agentic-semantic-label] FATAL: ${err instanceof Error ? err.message : String(err)}
|
|
4263
|
+
`);
|
|
4264
|
+
process.exit(1);
|
|
4265
|
+
});
|