@askexenow/exe-os 0.9.7 → 0.9.9
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/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -25,6 +25,44 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// src/lib/secure-files.ts
|
|
29
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
30
|
+
import { chmod, mkdir } from "fs/promises";
|
|
31
|
+
async function ensurePrivateDir(dirPath) {
|
|
32
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
33
|
+
try {
|
|
34
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function ensurePrivateDirSync(dirPath) {
|
|
39
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
40
|
+
try {
|
|
41
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function enforcePrivateFile(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function enforcePrivateFileSync(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
58
|
+
var init_secure_files = __esm({
|
|
59
|
+
"src/lib/secure-files.ts"() {
|
|
60
|
+
"use strict";
|
|
61
|
+
PRIVATE_DIR_MODE = 448;
|
|
62
|
+
PRIVATE_FILE_MODE = 384;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
28
66
|
// src/lib/config.ts
|
|
29
67
|
var config_exports = {};
|
|
30
68
|
__export(config_exports, {
|
|
@@ -41,8 +79,8 @@ __export(config_exports, {
|
|
|
41
79
|
migrateConfig: () => migrateConfig,
|
|
42
80
|
saveConfig: () => saveConfig
|
|
43
81
|
});
|
|
44
|
-
import { readFile, writeFile
|
|
45
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
82
|
+
import { readFile, writeFile } from "fs/promises";
|
|
83
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
46
84
|
import path2 from "path";
|
|
47
85
|
import os2 from "os";
|
|
48
86
|
function resolveDataDir() {
|
|
@@ -50,7 +88,7 @@ function resolveDataDir() {
|
|
|
50
88
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
51
89
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
52
90
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
53
|
-
if (!
|
|
91
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
54
92
|
try {
|
|
55
93
|
renameSync(legacyDir, newDir);
|
|
56
94
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -113,9 +151,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
113
151
|
}
|
|
114
152
|
async function loadConfig() {
|
|
115
153
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
116
|
-
await
|
|
154
|
+
await ensurePrivateDir(dir);
|
|
117
155
|
const configPath = path2.join(dir, "config.json");
|
|
118
|
-
if (!
|
|
156
|
+
if (!existsSync2(configPath)) {
|
|
119
157
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
120
158
|
}
|
|
121
159
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -128,6 +166,7 @@ async function loadConfig() {
|
|
|
128
166
|
`);
|
|
129
167
|
try {
|
|
130
168
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
169
|
+
await enforcePrivateFile(configPath);
|
|
131
170
|
} catch {
|
|
132
171
|
}
|
|
133
172
|
}
|
|
@@ -146,7 +185,7 @@ async function loadConfig() {
|
|
|
146
185
|
function loadConfigSync() {
|
|
147
186
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
148
187
|
const configPath = path2.join(dir, "config.json");
|
|
149
|
-
if (!
|
|
188
|
+
if (!existsSync2(configPath)) {
|
|
150
189
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
151
190
|
}
|
|
152
191
|
try {
|
|
@@ -164,12 +203,10 @@ function loadConfigSync() {
|
|
|
164
203
|
}
|
|
165
204
|
async function saveConfig(config2) {
|
|
166
205
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
167
|
-
await
|
|
206
|
+
await ensurePrivateDir(dir);
|
|
168
207
|
const configPath = path2.join(dir, "config.json");
|
|
169
208
|
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
170
|
-
|
|
171
|
-
await chmod(configPath, 384);
|
|
172
|
-
}
|
|
209
|
+
await enforcePrivateFile(configPath);
|
|
173
210
|
}
|
|
174
211
|
async function loadConfigFrom(configPath) {
|
|
175
212
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -189,6 +226,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
189
226
|
var init_config = __esm({
|
|
190
227
|
"src/lib/config.ts"() {
|
|
191
228
|
"use strict";
|
|
229
|
+
init_secure_files();
|
|
192
230
|
EXE_AI_DIR = resolveDataDir();
|
|
193
231
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
194
232
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -265,6 +303,120 @@ var init_config = __esm({
|
|
|
265
303
|
}
|
|
266
304
|
});
|
|
267
305
|
|
|
306
|
+
// src/lib/runtime-table.ts
|
|
307
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
308
|
+
var init_runtime_table = __esm({
|
|
309
|
+
"src/lib/runtime-table.ts"() {
|
|
310
|
+
"use strict";
|
|
311
|
+
RUNTIME_TABLE = {
|
|
312
|
+
codex: {
|
|
313
|
+
binary: "codex",
|
|
314
|
+
launchMode: "interactive",
|
|
315
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
316
|
+
inlineFlag: "--no-alt-screen",
|
|
317
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
318
|
+
defaultModel: "gpt-5.4"
|
|
319
|
+
},
|
|
320
|
+
opencode: {
|
|
321
|
+
binary: "opencode",
|
|
322
|
+
launchMode: "exec",
|
|
323
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
324
|
+
inlineFlag: "",
|
|
325
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
326
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
DEFAULT_RUNTIME = "claude";
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// src/lib/agent-config.ts
|
|
334
|
+
var agent_config_exports = {};
|
|
335
|
+
__export(agent_config_exports, {
|
|
336
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
337
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
338
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
339
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
340
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
341
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
342
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
343
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
344
|
+
setAgentRuntime: () => setAgentRuntime
|
|
345
|
+
});
|
|
346
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
347
|
+
import path3 from "path";
|
|
348
|
+
function loadAgentConfig() {
|
|
349
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
350
|
+
try {
|
|
351
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
352
|
+
} catch {
|
|
353
|
+
return {};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function saveAgentConfig(config2) {
|
|
357
|
+
const dir = path3.dirname(AGENT_CONFIG_PATH);
|
|
358
|
+
ensurePrivateDirSync(dir);
|
|
359
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
360
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
361
|
+
}
|
|
362
|
+
function getAgentRuntime(agentId) {
|
|
363
|
+
const config2 = loadAgentConfig();
|
|
364
|
+
const entry = config2[agentId];
|
|
365
|
+
if (entry) return entry;
|
|
366
|
+
const orgDefault = config2["default"];
|
|
367
|
+
if (orgDefault) return orgDefault;
|
|
368
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
369
|
+
}
|
|
370
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
371
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
372
|
+
if (!knownModels) {
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (!knownModels.includes(model)) {
|
|
379
|
+
return {
|
|
380
|
+
ok: false,
|
|
381
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const config2 = loadAgentConfig();
|
|
385
|
+
config2[agentId] = { runtime, model };
|
|
386
|
+
saveAgentConfig(config2);
|
|
387
|
+
return { ok: true };
|
|
388
|
+
}
|
|
389
|
+
function clearAgentRuntime(agentId) {
|
|
390
|
+
const config2 = loadAgentConfig();
|
|
391
|
+
delete config2[agentId];
|
|
392
|
+
saveAgentConfig(config2);
|
|
393
|
+
}
|
|
394
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
395
|
+
var init_agent_config = __esm({
|
|
396
|
+
"src/lib/agent-config.ts"() {
|
|
397
|
+
"use strict";
|
|
398
|
+
init_config();
|
|
399
|
+
init_runtime_table();
|
|
400
|
+
init_secure_files();
|
|
401
|
+
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
402
|
+
KNOWN_RUNTIMES = {
|
|
403
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
404
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
405
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
406
|
+
};
|
|
407
|
+
RUNTIME_LABELS = {
|
|
408
|
+
claude: "Claude Code (Anthropic)",
|
|
409
|
+
codex: "Codex (OpenAI)",
|
|
410
|
+
opencode: "OpenCode (open source)"
|
|
411
|
+
};
|
|
412
|
+
DEFAULT_MODELS = {
|
|
413
|
+
claude: "claude-opus-4",
|
|
414
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
415
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
268
420
|
// src/lib/employees.ts
|
|
269
421
|
var employees_exports = {};
|
|
270
422
|
__export(employees_exports, {
|
|
@@ -280,6 +432,7 @@ __export(employees_exports, {
|
|
|
280
432
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
281
433
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
282
434
|
hasRole: () => hasRole,
|
|
435
|
+
hireEmployee: () => hireEmployee,
|
|
283
436
|
isCoordinatorName: () => isCoordinatorName,
|
|
284
437
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
285
438
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -292,9 +445,9 @@ __export(employees_exports, {
|
|
|
292
445
|
validateEmployeeName: () => validateEmployeeName
|
|
293
446
|
});
|
|
294
447
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
295
|
-
import { existsSync as
|
|
448
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
296
449
|
import { execSync } from "child_process";
|
|
297
|
-
import
|
|
450
|
+
import path4 from "path";
|
|
298
451
|
import os3 from "os";
|
|
299
452
|
function normalizeRole(role) {
|
|
300
453
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -331,7 +484,7 @@ function validateEmployeeName(name) {
|
|
|
331
484
|
return { valid: true };
|
|
332
485
|
}
|
|
333
486
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
334
|
-
if (!
|
|
487
|
+
if (!existsSync4(employeesPath)) {
|
|
335
488
|
return [];
|
|
336
489
|
}
|
|
337
490
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -342,13 +495,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
342
495
|
}
|
|
343
496
|
}
|
|
344
497
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
345
|
-
await mkdir2(
|
|
498
|
+
await mkdir2(path4.dirname(employeesPath), { recursive: true });
|
|
346
499
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
347
500
|
}
|
|
348
501
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
349
|
-
if (!
|
|
502
|
+
if (!existsSync4(employeesPath)) return [];
|
|
350
503
|
try {
|
|
351
|
-
return JSON.parse(
|
|
504
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
352
505
|
} catch {
|
|
353
506
|
return [];
|
|
354
507
|
}
|
|
@@ -390,6 +543,52 @@ function addEmployee(employees, employee) {
|
|
|
390
543
|
}
|
|
391
544
|
return [...employees, normalized];
|
|
392
545
|
}
|
|
546
|
+
function appendToCoordinatorTeam(employee) {
|
|
547
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
548
|
+
if (!coordinator) return;
|
|
549
|
+
const idPath = path4.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
550
|
+
if (!existsSync4(idPath)) return;
|
|
551
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
552
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
553
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
554
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
555
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
556
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
557
|
+
const entry = `
|
|
558
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
559
|
+
`;
|
|
560
|
+
let updated;
|
|
561
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
562
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
563
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
564
|
+
} else {
|
|
565
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
566
|
+
}
|
|
567
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
568
|
+
}
|
|
569
|
+
function capitalize(s) {
|
|
570
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
571
|
+
}
|
|
572
|
+
async function hireEmployee(employee) {
|
|
573
|
+
const employees = await loadEmployees();
|
|
574
|
+
const updated = addEmployee(employees, employee);
|
|
575
|
+
await saveEmployees(updated);
|
|
576
|
+
try {
|
|
577
|
+
appendToCoordinatorTeam(employee);
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
582
|
+
const config2 = loadAgentConfig2();
|
|
583
|
+
const name = employee.name.toLowerCase();
|
|
584
|
+
if (!config2[name] && config2["default"]) {
|
|
585
|
+
config2[name] = { ...config2["default"] };
|
|
586
|
+
saveAgentConfig2(config2);
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
}
|
|
590
|
+
return updated;
|
|
591
|
+
}
|
|
393
592
|
async function normalizeRosterCase(rosterPath) {
|
|
394
593
|
const employees = await loadEmployees(rosterPath);
|
|
395
594
|
let changed = false;
|
|
@@ -399,14 +598,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
399
598
|
emp.name = emp.name.toLowerCase();
|
|
400
599
|
changed = true;
|
|
401
600
|
try {
|
|
402
|
-
const identityDir =
|
|
403
|
-
const oldPath =
|
|
404
|
-
const newPath =
|
|
405
|
-
if (
|
|
601
|
+
const identityDir = path4.join(os3.homedir(), ".exe-os", "identity");
|
|
602
|
+
const oldPath = path4.join(identityDir, `${oldName}.md`);
|
|
603
|
+
const newPath = path4.join(identityDir, `${emp.name}.md`);
|
|
604
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
406
605
|
renameSync2(oldPath, newPath);
|
|
407
|
-
} else if (
|
|
408
|
-
const content =
|
|
409
|
-
|
|
606
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
607
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
608
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
410
609
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
411
610
|
unlinkSync(oldPath);
|
|
412
611
|
}
|
|
@@ -436,7 +635,7 @@ function registerBinSymlinks(name) {
|
|
|
436
635
|
errors.push("Could not find 'exe-os' in PATH");
|
|
437
636
|
return { created, skipped, errors };
|
|
438
637
|
}
|
|
439
|
-
const binDir =
|
|
638
|
+
const binDir = path4.dirname(exeBinPath);
|
|
440
639
|
let target;
|
|
441
640
|
try {
|
|
442
641
|
target = readlinkSync(exeBinPath);
|
|
@@ -446,8 +645,8 @@ function registerBinSymlinks(name) {
|
|
|
446
645
|
}
|
|
447
646
|
for (const suffix of ["", "-opencode"]) {
|
|
448
647
|
const linkName = `${name}${suffix}`;
|
|
449
|
-
const linkPath =
|
|
450
|
-
if (
|
|
648
|
+
const linkPath = path4.join(binDir, linkName);
|
|
649
|
+
if (existsSync4(linkPath)) {
|
|
451
650
|
skipped.push(linkName);
|
|
452
651
|
continue;
|
|
453
652
|
}
|
|
@@ -460,15 +659,17 @@ function registerBinSymlinks(name) {
|
|
|
460
659
|
}
|
|
461
660
|
return { created, skipped, errors };
|
|
462
661
|
}
|
|
463
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
662
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
464
663
|
var init_employees = __esm({
|
|
465
664
|
"src/lib/employees.ts"() {
|
|
466
665
|
"use strict";
|
|
467
666
|
init_config();
|
|
468
|
-
EMPLOYEES_PATH =
|
|
667
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
469
668
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
470
669
|
COORDINATOR_ROLE = "COO";
|
|
471
670
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
671
|
+
IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
|
|
672
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
472
673
|
}
|
|
473
674
|
});
|
|
474
675
|
|
|
@@ -479,14 +680,14 @@ __export(session_registry_exports, {
|
|
|
479
680
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
480
681
|
registerSession: () => registerSession
|
|
481
682
|
});
|
|
482
|
-
import { readFileSync as
|
|
683
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
483
684
|
import { execSync as execSync2 } from "child_process";
|
|
484
|
-
import
|
|
685
|
+
import path5 from "path";
|
|
485
686
|
import os4 from "os";
|
|
486
687
|
function registerSession(entry) {
|
|
487
|
-
const dir =
|
|
488
|
-
if (!
|
|
489
|
-
|
|
688
|
+
const dir = path5.dirname(REGISTRY_PATH);
|
|
689
|
+
if (!existsSync5(dir)) {
|
|
690
|
+
mkdirSync2(dir, { recursive: true });
|
|
490
691
|
}
|
|
491
692
|
const sessions = listSessions();
|
|
492
693
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -495,11 +696,11 @@ function registerSession(entry) {
|
|
|
495
696
|
} else {
|
|
496
697
|
sessions.push(entry);
|
|
497
698
|
}
|
|
498
|
-
|
|
699
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
499
700
|
}
|
|
500
701
|
function listSessions() {
|
|
501
702
|
try {
|
|
502
|
-
const raw =
|
|
703
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
503
704
|
return JSON.parse(raw);
|
|
504
705
|
} catch {
|
|
505
706
|
return [];
|
|
@@ -520,7 +721,7 @@ function pruneStaleSessions() {
|
|
|
520
721
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
521
722
|
const pruned = sessions.length - alive.length;
|
|
522
723
|
if (pruned > 0) {
|
|
523
|
-
|
|
724
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
524
725
|
}
|
|
525
726
|
return pruned;
|
|
526
727
|
}
|
|
@@ -528,7 +729,7 @@ var REGISTRY_PATH;
|
|
|
528
729
|
var init_session_registry = __esm({
|
|
529
730
|
"src/lib/session-registry.ts"() {
|
|
530
731
|
"use strict";
|
|
531
|
-
REGISTRY_PATH =
|
|
732
|
+
REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
532
733
|
}
|
|
533
734
|
});
|
|
534
735
|
|
|
@@ -790,67 +991,6 @@ var init_provider_table = __esm({
|
|
|
790
991
|
}
|
|
791
992
|
});
|
|
792
993
|
|
|
793
|
-
// src/lib/runtime-table.ts
|
|
794
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
795
|
-
var init_runtime_table = __esm({
|
|
796
|
-
"src/lib/runtime-table.ts"() {
|
|
797
|
-
"use strict";
|
|
798
|
-
RUNTIME_TABLE = {
|
|
799
|
-
codex: {
|
|
800
|
-
binary: "codex",
|
|
801
|
-
launchMode: "interactive",
|
|
802
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
803
|
-
inlineFlag: "--no-alt-screen",
|
|
804
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
805
|
-
defaultModel: "gpt-5.4"
|
|
806
|
-
},
|
|
807
|
-
opencode: {
|
|
808
|
-
binary: "opencode",
|
|
809
|
-
launchMode: "exec",
|
|
810
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
811
|
-
inlineFlag: "",
|
|
812
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
813
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
DEFAULT_RUNTIME = "claude";
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
// src/lib/agent-config.ts
|
|
821
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
822
|
-
import path5 from "path";
|
|
823
|
-
function loadAgentConfig() {
|
|
824
|
-
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
825
|
-
try {
|
|
826
|
-
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
827
|
-
} catch {
|
|
828
|
-
return {};
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
function getAgentRuntime(agentId) {
|
|
832
|
-
const config2 = loadAgentConfig();
|
|
833
|
-
const entry = config2[agentId];
|
|
834
|
-
if (entry) return entry;
|
|
835
|
-
const orgDefault = config2["default"];
|
|
836
|
-
if (orgDefault) return orgDefault;
|
|
837
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
838
|
-
}
|
|
839
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
840
|
-
var init_agent_config = __esm({
|
|
841
|
-
"src/lib/agent-config.ts"() {
|
|
842
|
-
"use strict";
|
|
843
|
-
init_config();
|
|
844
|
-
init_runtime_table();
|
|
845
|
-
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
846
|
-
DEFAULT_MODELS = {
|
|
847
|
-
claude: "claude-opus-4",
|
|
848
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
849
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
|
|
854
994
|
// src/lib/intercom-queue.ts
|
|
855
995
|
var intercom_queue_exports = {};
|
|
856
996
|
__export(intercom_queue_exports, {
|
|
@@ -860,16 +1000,16 @@ __export(intercom_queue_exports, {
|
|
|
860
1000
|
queueIntercom: () => queueIntercom,
|
|
861
1001
|
readQueue: () => readQueue
|
|
862
1002
|
});
|
|
863
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as
|
|
1003
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
864
1004
|
import path6 from "path";
|
|
865
1005
|
import os5 from "os";
|
|
866
1006
|
function ensureDir() {
|
|
867
1007
|
const dir = path6.dirname(QUEUE_PATH);
|
|
868
|
-
if (!
|
|
1008
|
+
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
869
1009
|
}
|
|
870
1010
|
function readQueue() {
|
|
871
1011
|
try {
|
|
872
|
-
if (!
|
|
1012
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
873
1013
|
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
874
1014
|
} catch {
|
|
875
1015
|
return [];
|
|
@@ -1032,13 +1172,634 @@ var init_db_retry = __esm({
|
|
|
1032
1172
|
}
|
|
1033
1173
|
});
|
|
1034
1174
|
|
|
1175
|
+
// src/lib/database-adapter.ts
|
|
1176
|
+
import os6 from "os";
|
|
1177
|
+
import path7 from "path";
|
|
1178
|
+
import { createRequire } from "module";
|
|
1179
|
+
import { pathToFileURL } from "url";
|
|
1180
|
+
function quotedIdentifier(identifier) {
|
|
1181
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1182
|
+
}
|
|
1183
|
+
function unqualifiedTableName(name) {
|
|
1184
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
1185
|
+
const parts = raw.split(".");
|
|
1186
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
1187
|
+
}
|
|
1188
|
+
function stripTrailingSemicolon(sql) {
|
|
1189
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
1190
|
+
}
|
|
1191
|
+
function appendClause(sql, clause) {
|
|
1192
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
1193
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
1194
|
+
if (!returningMatch) {
|
|
1195
|
+
return `${trimmed}${clause}`;
|
|
1196
|
+
}
|
|
1197
|
+
const idx = returningMatch.index;
|
|
1198
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
1199
|
+
}
|
|
1200
|
+
function normalizeStatement(stmt) {
|
|
1201
|
+
if (typeof stmt === "string") {
|
|
1202
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
1203
|
+
}
|
|
1204
|
+
const sql = stmt.sql;
|
|
1205
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
1206
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
1207
|
+
}
|
|
1208
|
+
return { kind: "named", sql, args: stmt.args };
|
|
1209
|
+
}
|
|
1210
|
+
function rewriteBooleanLiterals(sql) {
|
|
1211
|
+
let out = sql;
|
|
1212
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1213
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
1214
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
1215
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
1216
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
1217
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
1218
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
1219
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
1220
|
+
}
|
|
1221
|
+
return out;
|
|
1222
|
+
}
|
|
1223
|
+
function rewriteInsertOrIgnore(sql) {
|
|
1224
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
1225
|
+
return sql;
|
|
1226
|
+
}
|
|
1227
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
1228
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
1229
|
+
}
|
|
1230
|
+
function rewriteInsertOrReplace(sql) {
|
|
1231
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
1232
|
+
if (!match) {
|
|
1233
|
+
return sql;
|
|
1234
|
+
}
|
|
1235
|
+
const rawTable = match[1];
|
|
1236
|
+
const rawColumns = match[2];
|
|
1237
|
+
const remainder = match[3];
|
|
1238
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
1239
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
1240
|
+
if (!conflictKeys?.length) {
|
|
1241
|
+
return sql;
|
|
1242
|
+
}
|
|
1243
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1244
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
1245
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
1246
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
1247
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
1248
|
+
}
|
|
1249
|
+
function rewriteSql(sql) {
|
|
1250
|
+
let out = sql;
|
|
1251
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
1252
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
1253
|
+
out = rewriteBooleanLiterals(out);
|
|
1254
|
+
out = rewriteInsertOrReplace(out);
|
|
1255
|
+
out = rewriteInsertOrIgnore(out);
|
|
1256
|
+
return stripTrailingSemicolon(out);
|
|
1257
|
+
}
|
|
1258
|
+
function toBoolean(value) {
|
|
1259
|
+
if (value === null || value === void 0) return value;
|
|
1260
|
+
if (typeof value === "boolean") return value;
|
|
1261
|
+
if (typeof value === "number") return value !== 0;
|
|
1262
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
1263
|
+
if (typeof value === "string") {
|
|
1264
|
+
const normalized = value.trim().toLowerCase();
|
|
1265
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
1266
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
1267
|
+
}
|
|
1268
|
+
return Boolean(value);
|
|
1269
|
+
}
|
|
1270
|
+
function countQuestionMarks(sql, end) {
|
|
1271
|
+
let count = 0;
|
|
1272
|
+
let inSingle = false;
|
|
1273
|
+
let inDouble = false;
|
|
1274
|
+
let inLineComment = false;
|
|
1275
|
+
let inBlockComment = false;
|
|
1276
|
+
for (let i = 0; i < end; i++) {
|
|
1277
|
+
const ch = sql[i];
|
|
1278
|
+
const next = sql[i + 1];
|
|
1279
|
+
if (inLineComment) {
|
|
1280
|
+
if (ch === "\n") inLineComment = false;
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
if (inBlockComment) {
|
|
1284
|
+
if (ch === "*" && next === "/") {
|
|
1285
|
+
inBlockComment = false;
|
|
1286
|
+
i += 1;
|
|
1287
|
+
}
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1291
|
+
inLineComment = true;
|
|
1292
|
+
i += 1;
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1296
|
+
inBlockComment = true;
|
|
1297
|
+
i += 1;
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1301
|
+
inSingle = !inSingle;
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1305
|
+
inDouble = !inDouble;
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1309
|
+
count += 1;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return count;
|
|
1313
|
+
}
|
|
1314
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
1315
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1316
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1317
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
1318
|
+
for (const match of sql.matchAll(pattern)) {
|
|
1319
|
+
const matchText = match[0];
|
|
1320
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1321
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return indexes;
|
|
1325
|
+
}
|
|
1326
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1327
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1328
|
+
if (!match) return;
|
|
1329
|
+
const rawTable = match[1];
|
|
1330
|
+
const rawColumns = match[2];
|
|
1331
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1332
|
+
if (!boolColumns?.size) return;
|
|
1333
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1334
|
+
for (const [index, column] of columns.entries()) {
|
|
1335
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1336
|
+
args[index] = toBoolean(args[index]);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1341
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1342
|
+
if (!match) return;
|
|
1343
|
+
const rawTable = match[1];
|
|
1344
|
+
const setClause = match[2];
|
|
1345
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1346
|
+
if (!boolColumns?.size) return;
|
|
1347
|
+
const assignments = setClause.split(",");
|
|
1348
|
+
let placeholderIndex = 0;
|
|
1349
|
+
for (const assignment of assignments) {
|
|
1350
|
+
if (!assignment.includes("?")) continue;
|
|
1351
|
+
placeholderIndex += 1;
|
|
1352
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1353
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1354
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function coerceBooleanArgs(sql, args) {
|
|
1359
|
+
const nextArgs = [...args];
|
|
1360
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1361
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1362
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1363
|
+
for (const index of placeholderIndexes) {
|
|
1364
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1365
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return nextArgs;
|
|
1369
|
+
}
|
|
1370
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1371
|
+
let out = "";
|
|
1372
|
+
let placeholder = 0;
|
|
1373
|
+
let inSingle = false;
|
|
1374
|
+
let inDouble = false;
|
|
1375
|
+
let inLineComment = false;
|
|
1376
|
+
let inBlockComment = false;
|
|
1377
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1378
|
+
const ch = sql[i];
|
|
1379
|
+
const next = sql[i + 1];
|
|
1380
|
+
if (inLineComment) {
|
|
1381
|
+
out += ch;
|
|
1382
|
+
if (ch === "\n") inLineComment = false;
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (inBlockComment) {
|
|
1386
|
+
out += ch;
|
|
1387
|
+
if (ch === "*" && next === "/") {
|
|
1388
|
+
out += next;
|
|
1389
|
+
inBlockComment = false;
|
|
1390
|
+
i += 1;
|
|
1391
|
+
}
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1395
|
+
out += ch + next;
|
|
1396
|
+
inLineComment = true;
|
|
1397
|
+
i += 1;
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1401
|
+
out += ch + next;
|
|
1402
|
+
inBlockComment = true;
|
|
1403
|
+
i += 1;
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1407
|
+
inSingle = !inSingle;
|
|
1408
|
+
out += ch;
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1412
|
+
inDouble = !inDouble;
|
|
1413
|
+
out += ch;
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1417
|
+
placeholder += 1;
|
|
1418
|
+
out += `$${placeholder}`;
|
|
1419
|
+
continue;
|
|
1420
|
+
}
|
|
1421
|
+
out += ch;
|
|
1422
|
+
}
|
|
1423
|
+
return out;
|
|
1424
|
+
}
|
|
1425
|
+
function translateStatementForPostgres(stmt) {
|
|
1426
|
+
const normalized = normalizeStatement(stmt);
|
|
1427
|
+
if (normalized.kind === "named") {
|
|
1428
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1429
|
+
}
|
|
1430
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1431
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1432
|
+
return {
|
|
1433
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1434
|
+
args: coercedArgs
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function shouldBypassPostgres(stmt) {
|
|
1438
|
+
const normalized = normalizeStatement(stmt);
|
|
1439
|
+
if (normalized.kind === "named") {
|
|
1440
|
+
return true;
|
|
1441
|
+
}
|
|
1442
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1443
|
+
}
|
|
1444
|
+
function shouldFallbackOnError(error) {
|
|
1445
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1446
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1447
|
+
}
|
|
1448
|
+
function isReadQuery(sql) {
|
|
1449
|
+
const trimmed = sql.trimStart();
|
|
1450
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1451
|
+
}
|
|
1452
|
+
function buildRow(row, columns) {
|
|
1453
|
+
const values = columns.map((column) => row[column]);
|
|
1454
|
+
return Object.assign(values, row);
|
|
1455
|
+
}
|
|
1456
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1457
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1458
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1459
|
+
return {
|
|
1460
|
+
columns,
|
|
1461
|
+
columnTypes: columns.map(() => ""),
|
|
1462
|
+
rows: resultRows,
|
|
1463
|
+
rowsAffected,
|
|
1464
|
+
lastInsertRowid: void 0,
|
|
1465
|
+
toJSON() {
|
|
1466
|
+
return {
|
|
1467
|
+
columns,
|
|
1468
|
+
columnTypes: columns.map(() => ""),
|
|
1469
|
+
rows,
|
|
1470
|
+
rowsAffected,
|
|
1471
|
+
lastInsertRowid: void 0
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
async function loadPrismaClient() {
|
|
1477
|
+
if (!prismaClientPromise) {
|
|
1478
|
+
prismaClientPromise = (async () => {
|
|
1479
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1480
|
+
if (explicitPath) {
|
|
1481
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1482
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1483
|
+
if (!PrismaClient2) {
|
|
1484
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1485
|
+
}
|
|
1486
|
+
return new PrismaClient2();
|
|
1487
|
+
}
|
|
1488
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
|
|
1489
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
1490
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1491
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1492
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1493
|
+
if (!PrismaClient) {
|
|
1494
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1495
|
+
}
|
|
1496
|
+
return new PrismaClient();
|
|
1497
|
+
})();
|
|
1498
|
+
}
|
|
1499
|
+
return prismaClientPromise;
|
|
1500
|
+
}
|
|
1501
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1502
|
+
if (!compatibilityBootstrapPromise) {
|
|
1503
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1504
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1505
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1506
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1507
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1508
|
+
relation
|
|
1509
|
+
);
|
|
1510
|
+
if (!rows[0]?.regclass) {
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
await prisma.$executeRawUnsafe(
|
|
1514
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
})();
|
|
1518
|
+
}
|
|
1519
|
+
return compatibilityBootstrapPromise;
|
|
1520
|
+
}
|
|
1521
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1522
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1523
|
+
if (isReadQuery(translated.sql)) {
|
|
1524
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1525
|
+
translated.sql,
|
|
1526
|
+
...translated.args
|
|
1527
|
+
);
|
|
1528
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1529
|
+
}
|
|
1530
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1531
|
+
return buildResultSet([], rowsAffected);
|
|
1532
|
+
}
|
|
1533
|
+
function splitSqlStatements(sql) {
|
|
1534
|
+
const parts = [];
|
|
1535
|
+
let current = "";
|
|
1536
|
+
let inSingle = false;
|
|
1537
|
+
let inDouble = false;
|
|
1538
|
+
let inLineComment = false;
|
|
1539
|
+
let inBlockComment = false;
|
|
1540
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1541
|
+
const ch = sql[i];
|
|
1542
|
+
const next = sql[i + 1];
|
|
1543
|
+
if (inLineComment) {
|
|
1544
|
+
current += ch;
|
|
1545
|
+
if (ch === "\n") inLineComment = false;
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
if (inBlockComment) {
|
|
1549
|
+
current += ch;
|
|
1550
|
+
if (ch === "*" && next === "/") {
|
|
1551
|
+
current += next;
|
|
1552
|
+
inBlockComment = false;
|
|
1553
|
+
i += 1;
|
|
1554
|
+
}
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1558
|
+
current += ch + next;
|
|
1559
|
+
inLineComment = true;
|
|
1560
|
+
i += 1;
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1564
|
+
current += ch + next;
|
|
1565
|
+
inBlockComment = true;
|
|
1566
|
+
i += 1;
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1570
|
+
inSingle = !inSingle;
|
|
1571
|
+
current += ch;
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1575
|
+
inDouble = !inDouble;
|
|
1576
|
+
current += ch;
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1580
|
+
if (current.trim()) {
|
|
1581
|
+
parts.push(current.trim());
|
|
1582
|
+
}
|
|
1583
|
+
current = "";
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
current += ch;
|
|
1587
|
+
}
|
|
1588
|
+
if (current.trim()) {
|
|
1589
|
+
parts.push(current.trim());
|
|
1590
|
+
}
|
|
1591
|
+
return parts;
|
|
1592
|
+
}
|
|
1593
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1594
|
+
const prisma = await loadPrismaClient();
|
|
1595
|
+
await ensureCompatibilityViews(prisma);
|
|
1596
|
+
let closed = false;
|
|
1597
|
+
let adapter;
|
|
1598
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1599
|
+
if (!fallbackClient) {
|
|
1600
|
+
if (error) throw error;
|
|
1601
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1602
|
+
}
|
|
1603
|
+
if (error) {
|
|
1604
|
+
process.stderr.write(
|
|
1605
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1606
|
+
`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
return fallbackClient.execute(stmt);
|
|
1610
|
+
};
|
|
1611
|
+
adapter = {
|
|
1612
|
+
async execute(stmt) {
|
|
1613
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1614
|
+
return fallbackExecute(stmt);
|
|
1615
|
+
}
|
|
1616
|
+
try {
|
|
1617
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
if (shouldFallbackOnError(error)) {
|
|
1620
|
+
return fallbackExecute(stmt, error);
|
|
1621
|
+
}
|
|
1622
|
+
throw error;
|
|
1623
|
+
}
|
|
1624
|
+
},
|
|
1625
|
+
async batch(stmts, mode) {
|
|
1626
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1627
|
+
if (!fallbackClient) {
|
|
1628
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1629
|
+
}
|
|
1630
|
+
return fallbackClient.batch(stmts, mode);
|
|
1631
|
+
}
|
|
1632
|
+
try {
|
|
1633
|
+
if (prisma.$transaction) {
|
|
1634
|
+
return await prisma.$transaction(async (tx) => {
|
|
1635
|
+
const results2 = [];
|
|
1636
|
+
for (const stmt of stmts) {
|
|
1637
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1638
|
+
}
|
|
1639
|
+
return results2;
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
const results = [];
|
|
1643
|
+
for (const stmt of stmts) {
|
|
1644
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1645
|
+
}
|
|
1646
|
+
return results;
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1649
|
+
process.stderr.write(
|
|
1650
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1651
|
+
`
|
|
1652
|
+
);
|
|
1653
|
+
return fallbackClient.batch(stmts, mode);
|
|
1654
|
+
}
|
|
1655
|
+
throw error;
|
|
1656
|
+
}
|
|
1657
|
+
},
|
|
1658
|
+
async migrate(stmts) {
|
|
1659
|
+
if (fallbackClient) {
|
|
1660
|
+
return fallbackClient.migrate(stmts);
|
|
1661
|
+
}
|
|
1662
|
+
return adapter.batch(stmts, "deferred");
|
|
1663
|
+
},
|
|
1664
|
+
async transaction(mode) {
|
|
1665
|
+
if (!fallbackClient) {
|
|
1666
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1667
|
+
}
|
|
1668
|
+
return fallbackClient.transaction(mode);
|
|
1669
|
+
},
|
|
1670
|
+
async executeMultiple(sql) {
|
|
1671
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1672
|
+
return fallbackClient.executeMultiple(sql);
|
|
1673
|
+
}
|
|
1674
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1675
|
+
await adapter.execute(statement);
|
|
1676
|
+
}
|
|
1677
|
+
},
|
|
1678
|
+
async sync() {
|
|
1679
|
+
if (fallbackClient) {
|
|
1680
|
+
return fallbackClient.sync();
|
|
1681
|
+
}
|
|
1682
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1683
|
+
},
|
|
1684
|
+
close() {
|
|
1685
|
+
closed = true;
|
|
1686
|
+
prismaClientPromise = null;
|
|
1687
|
+
compatibilityBootstrapPromise = null;
|
|
1688
|
+
void prisma.$disconnect?.();
|
|
1689
|
+
},
|
|
1690
|
+
get closed() {
|
|
1691
|
+
return closed;
|
|
1692
|
+
},
|
|
1693
|
+
get protocol() {
|
|
1694
|
+
return "prisma-postgres";
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
return adapter;
|
|
1698
|
+
}
|
|
1699
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1700
|
+
var init_database_adapter = __esm({
|
|
1701
|
+
"src/lib/database-adapter.ts"() {
|
|
1702
|
+
"use strict";
|
|
1703
|
+
VIEW_MAPPINGS = [
|
|
1704
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1705
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1706
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1707
|
+
{ view: "entities", source: "memory.entities" },
|
|
1708
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1709
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1710
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1711
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1712
|
+
{ view: "messages", source: "memory.messages" },
|
|
1713
|
+
{ view: "users", source: "wiki.users" },
|
|
1714
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1715
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1716
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1717
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1718
|
+
];
|
|
1719
|
+
UPSERT_KEYS = {
|
|
1720
|
+
memories: ["id"],
|
|
1721
|
+
tasks: ["id"],
|
|
1722
|
+
behaviors: ["id"],
|
|
1723
|
+
entities: ["id"],
|
|
1724
|
+
relationships: ["id"],
|
|
1725
|
+
entity_aliases: ["alias"],
|
|
1726
|
+
notifications: ["id"],
|
|
1727
|
+
messages: ["id"],
|
|
1728
|
+
users: ["id"],
|
|
1729
|
+
workspaces: ["id"],
|
|
1730
|
+
workspace_users: ["id"],
|
|
1731
|
+
documents: ["id"],
|
|
1732
|
+
chats: ["id"]
|
|
1733
|
+
};
|
|
1734
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1735
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1736
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1737
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1738
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1739
|
+
};
|
|
1740
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1741
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1742
|
+
);
|
|
1743
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1744
|
+
/\bPRAGMA\b/i,
|
|
1745
|
+
/\bsqlite_master\b/i,
|
|
1746
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1747
|
+
/\bMATCH\b/i,
|
|
1748
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1749
|
+
/\bjson_extract\s*\(/i,
|
|
1750
|
+
/\bjulianday\s*\(/i,
|
|
1751
|
+
/\bstrftime\s*\(/i,
|
|
1752
|
+
/\blast_insert_rowid\s*\(/i
|
|
1753
|
+
];
|
|
1754
|
+
prismaClientPromise = null;
|
|
1755
|
+
compatibilityBootstrapPromise = null;
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
// src/lib/daemon-auth.ts
|
|
1760
|
+
import crypto from "crypto";
|
|
1761
|
+
import path8 from "path";
|
|
1762
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1763
|
+
function normalizeToken(token) {
|
|
1764
|
+
if (!token) return null;
|
|
1765
|
+
const trimmed = token.trim();
|
|
1766
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1767
|
+
}
|
|
1768
|
+
function readDaemonToken() {
|
|
1769
|
+
try {
|
|
1770
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1771
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1772
|
+
} catch {
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function ensureDaemonToken(seed) {
|
|
1777
|
+
const existing = readDaemonToken();
|
|
1778
|
+
if (existing) return existing;
|
|
1779
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1780
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1781
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1782
|
+
`, "utf8");
|
|
1783
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1784
|
+
return token;
|
|
1785
|
+
}
|
|
1786
|
+
var DAEMON_TOKEN_PATH;
|
|
1787
|
+
var init_daemon_auth = __esm({
|
|
1788
|
+
"src/lib/daemon-auth.ts"() {
|
|
1789
|
+
"use strict";
|
|
1790
|
+
init_config();
|
|
1791
|
+
init_secure_files();
|
|
1792
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1035
1796
|
// src/lib/exe-daemon-client.ts
|
|
1036
1797
|
import net from "net";
|
|
1037
|
-
import
|
|
1798
|
+
import os7 from "os";
|
|
1038
1799
|
import { spawn } from "child_process";
|
|
1039
1800
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1040
|
-
import { existsSync as
|
|
1041
|
-
import
|
|
1801
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1802
|
+
import path9 from "path";
|
|
1042
1803
|
import { fileURLToPath } from "url";
|
|
1043
1804
|
function handleData(chunk) {
|
|
1044
1805
|
_buffer += chunk.toString();
|
|
@@ -1066,9 +1827,9 @@ function handleData(chunk) {
|
|
|
1066
1827
|
}
|
|
1067
1828
|
}
|
|
1068
1829
|
function cleanupStaleFiles() {
|
|
1069
|
-
if (
|
|
1830
|
+
if (existsSync8(PID_PATH)) {
|
|
1070
1831
|
try {
|
|
1071
|
-
const pid = parseInt(
|
|
1832
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1072
1833
|
if (pid > 0) {
|
|
1073
1834
|
try {
|
|
1074
1835
|
process.kill(pid, 0);
|
|
@@ -1089,17 +1850,17 @@ function cleanupStaleFiles() {
|
|
|
1089
1850
|
}
|
|
1090
1851
|
}
|
|
1091
1852
|
function findPackageRoot() {
|
|
1092
|
-
let dir =
|
|
1093
|
-
const { root } =
|
|
1853
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1854
|
+
const { root } = path9.parse(dir);
|
|
1094
1855
|
while (dir !== root) {
|
|
1095
|
-
if (
|
|
1096
|
-
dir =
|
|
1856
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1857
|
+
dir = path9.dirname(dir);
|
|
1097
1858
|
}
|
|
1098
1859
|
return null;
|
|
1099
1860
|
}
|
|
1100
1861
|
function spawnDaemon() {
|
|
1101
|
-
const freeGB =
|
|
1102
|
-
const totalGB =
|
|
1862
|
+
const freeGB = os7.freemem() / (1024 * 1024 * 1024);
|
|
1863
|
+
const totalGB = os7.totalmem() / (1024 * 1024 * 1024);
|
|
1103
1864
|
if (totalGB <= 8) {
|
|
1104
1865
|
process.stderr.write(
|
|
1105
1866
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1119,16 +1880,17 @@ function spawnDaemon() {
|
|
|
1119
1880
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1120
1881
|
return;
|
|
1121
1882
|
}
|
|
1122
|
-
const daemonPath =
|
|
1123
|
-
if (!
|
|
1883
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1884
|
+
if (!existsSync8(daemonPath)) {
|
|
1124
1885
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1125
1886
|
`);
|
|
1126
1887
|
return;
|
|
1127
1888
|
}
|
|
1128
1889
|
const resolvedPath = daemonPath;
|
|
1890
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1129
1891
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1130
1892
|
`);
|
|
1131
|
-
const logPath =
|
|
1893
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
1132
1894
|
let stderrFd = "ignore";
|
|
1133
1895
|
try {
|
|
1134
1896
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1146,7 +1908,8 @@ function spawnDaemon() {
|
|
|
1146
1908
|
TMUX_PANE: void 0,
|
|
1147
1909
|
// Prevents resolveExeSession() from scoping to one session
|
|
1148
1910
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1149
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1911
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1912
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1150
1913
|
}
|
|
1151
1914
|
});
|
|
1152
1915
|
child.unref();
|
|
@@ -1256,13 +2019,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1256
2019
|
return;
|
|
1257
2020
|
}
|
|
1258
2021
|
const id = randomUUID2();
|
|
2022
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1259
2023
|
const timer = setTimeout(() => {
|
|
1260
2024
|
_pending.delete(id);
|
|
1261
2025
|
resolve({ error: "Request timeout" });
|
|
1262
2026
|
}, timeoutMs);
|
|
1263
2027
|
_pending.set(id, { resolve, timer });
|
|
1264
2028
|
try {
|
|
1265
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2029
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1266
2030
|
} catch {
|
|
1267
2031
|
clearTimeout(timer);
|
|
1268
2032
|
_pending.delete(id);
|
|
@@ -1279,74 +2043,123 @@ async function pingDaemon() {
|
|
|
1279
2043
|
return null;
|
|
1280
2044
|
}
|
|
1281
2045
|
function killAndRespawnDaemon() {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
2046
|
+
if (!acquireSpawnLock()) {
|
|
2047
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2048
|
+
if (_socket) {
|
|
2049
|
+
_socket.destroy();
|
|
2050
|
+
_socket = null;
|
|
2051
|
+
}
|
|
2052
|
+
_connected = false;
|
|
2053
|
+
_buffer = "";
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
try {
|
|
2057
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2058
|
+
if (existsSync8(PID_PATH)) {
|
|
2059
|
+
try {
|
|
2060
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
2061
|
+
if (pid > 0) {
|
|
2062
|
+
try {
|
|
2063
|
+
process.kill(pid, "SIGKILL");
|
|
2064
|
+
} catch {
|
|
2065
|
+
}
|
|
1290
2066
|
}
|
|
2067
|
+
} catch {
|
|
1291
2068
|
}
|
|
2069
|
+
}
|
|
2070
|
+
if (_socket) {
|
|
2071
|
+
_socket.destroy();
|
|
2072
|
+
_socket = null;
|
|
2073
|
+
}
|
|
2074
|
+
_connected = false;
|
|
2075
|
+
_buffer = "";
|
|
2076
|
+
try {
|
|
2077
|
+
unlinkSync2(PID_PATH);
|
|
1292
2078
|
} catch {
|
|
1293
2079
|
}
|
|
2080
|
+
try {
|
|
2081
|
+
unlinkSync2(SOCKET_PATH);
|
|
2082
|
+
} catch {
|
|
2083
|
+
}
|
|
2084
|
+
spawnDaemon();
|
|
2085
|
+
} finally {
|
|
2086
|
+
releaseSpawnLock();
|
|
1294
2087
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
_socket = null;
|
|
1298
|
-
}
|
|
1299
|
-
_connected = false;
|
|
1300
|
-
_buffer = "";
|
|
1301
|
-
try {
|
|
1302
|
-
unlinkSync2(PID_PATH);
|
|
1303
|
-
} catch {
|
|
1304
|
-
}
|
|
2088
|
+
}
|
|
2089
|
+
function isDaemonTooYoung() {
|
|
1305
2090
|
try {
|
|
1306
|
-
|
|
2091
|
+
const stat = statSync(PID_PATH);
|
|
2092
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
1307
2093
|
} catch {
|
|
2094
|
+
return false;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
async function retryThenRestart(doRequest, label) {
|
|
2098
|
+
const result = await doRequest();
|
|
2099
|
+
if (!result.error) {
|
|
2100
|
+
_consecutiveFailures = 0;
|
|
2101
|
+
return result;
|
|
2102
|
+
}
|
|
2103
|
+
_consecutiveFailures++;
|
|
2104
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
2105
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
2106
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
2107
|
+
`);
|
|
2108
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2109
|
+
if (!_connected) {
|
|
2110
|
+
if (!await connectToSocket()) continue;
|
|
2111
|
+
}
|
|
2112
|
+
const retry = await doRequest();
|
|
2113
|
+
if (!retry.error) {
|
|
2114
|
+
_consecutiveFailures = 0;
|
|
2115
|
+
return retry;
|
|
2116
|
+
}
|
|
2117
|
+
_consecutiveFailures++;
|
|
2118
|
+
}
|
|
2119
|
+
if (isDaemonTooYoung()) {
|
|
2120
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
2121
|
+
`);
|
|
2122
|
+
return { error: result.error };
|
|
2123
|
+
}
|
|
2124
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
2125
|
+
`);
|
|
2126
|
+
killAndRespawnDaemon();
|
|
2127
|
+
const start = Date.now();
|
|
2128
|
+
let delay2 = 200;
|
|
2129
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2130
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2131
|
+
if (await connectToSocket()) break;
|
|
2132
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1308
2133
|
}
|
|
1309
|
-
|
|
2134
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
2135
|
+
const final = await doRequest();
|
|
2136
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
2137
|
+
return final;
|
|
1310
2138
|
}
|
|
1311
2139
|
async function embedViaClient(text, priority = "high") {
|
|
1312
2140
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
1313
2141
|
_requestCount++;
|
|
1314
2142
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
1315
2143
|
const health = await pingDaemon();
|
|
1316
|
-
if (!health) {
|
|
2144
|
+
if (!health && !isDaemonTooYoung()) {
|
|
1317
2145
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
1318
2146
|
`);
|
|
1319
2147
|
killAndRespawnDaemon();
|
|
1320
2148
|
const start = Date.now();
|
|
1321
|
-
let
|
|
2149
|
+
let d = 200;
|
|
1322
2150
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1323
|
-
await new Promise((r) => setTimeout(r,
|
|
2151
|
+
await new Promise((r) => setTimeout(r, d));
|
|
1324
2152
|
if (await connectToSocket()) break;
|
|
1325
|
-
|
|
2153
|
+
d = Math.min(d * 2, 3e3);
|
|
1326
2154
|
}
|
|
1327
2155
|
if (!_connected) return null;
|
|
1328
2156
|
}
|
|
1329
2157
|
}
|
|
1330
|
-
const result = await
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
killAndRespawnDaemon();
|
|
1336
|
-
const start = Date.now();
|
|
1337
|
-
let delay2 = 200;
|
|
1338
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1339
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
1340
|
-
if (await connectToSocket()) break;
|
|
1341
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1342
|
-
}
|
|
1343
|
-
if (!_connected) return null;
|
|
1344
|
-
const retry = await sendRequest([text], priority);
|
|
1345
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
1346
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
1347
|
-
`);
|
|
1348
|
-
}
|
|
1349
|
-
return null;
|
|
2158
|
+
const result = await retryThenRestart(
|
|
2159
|
+
() => sendRequest([text], priority),
|
|
2160
|
+
"Embed"
|
|
2161
|
+
);
|
|
2162
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
1350
2163
|
}
|
|
1351
2164
|
function disconnectClient() {
|
|
1352
2165
|
if (_socket) {
|
|
@@ -1364,22 +2177,28 @@ function disconnectClient() {
|
|
|
1364
2177
|
function isClientConnected() {
|
|
1365
2178
|
return _connected;
|
|
1366
2179
|
}
|
|
1367
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
2180
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
1368
2181
|
var init_exe_daemon_client = __esm({
|
|
1369
2182
|
"src/lib/exe-daemon-client.ts"() {
|
|
1370
2183
|
"use strict";
|
|
1371
2184
|
init_config();
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2185
|
+
init_daemon_auth();
|
|
2186
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
2187
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
2188
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1375
2189
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1376
2190
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1377
2191
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2192
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1378
2193
|
_socket = null;
|
|
1379
2194
|
_connected = false;
|
|
1380
2195
|
_buffer = "";
|
|
1381
2196
|
_requestCount = 0;
|
|
2197
|
+
_consecutiveFailures = 0;
|
|
1382
2198
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2199
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
2200
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
2201
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
1383
2202
|
_pending = /* @__PURE__ */ new Map();
|
|
1384
2203
|
MAX_BUFFER = 1e7;
|
|
1385
2204
|
}
|
|
@@ -1455,7 +2274,7 @@ __export(db_daemon_client_exports, {
|
|
|
1455
2274
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1456
2275
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1457
2276
|
});
|
|
1458
|
-
function
|
|
2277
|
+
function normalizeStatement2(stmt) {
|
|
1459
2278
|
if (typeof stmt === "string") {
|
|
1460
2279
|
return { sql: stmt, args: [] };
|
|
1461
2280
|
}
|
|
@@ -1479,7 +2298,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1479
2298
|
if (!_useDaemon || !isClientConnected()) {
|
|
1480
2299
|
return fallbackClient.execute(stmt);
|
|
1481
2300
|
}
|
|
1482
|
-
const { sql, args } =
|
|
2301
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1483
2302
|
const response = await sendDaemonRequest({
|
|
1484
2303
|
type: "db-execute",
|
|
1485
2304
|
sql,
|
|
@@ -1504,7 +2323,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1504
2323
|
if (!_useDaemon || !isClientConnected()) {
|
|
1505
2324
|
return fallbackClient.batch(stmts, mode);
|
|
1506
2325
|
}
|
|
1507
|
-
const statements = stmts.map(
|
|
2326
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1508
2327
|
const response = await sendDaemonRequest({
|
|
1509
2328
|
type: "db-batch",
|
|
1510
2329
|
statements,
|
|
@@ -1599,6 +2418,18 @@ __export(database_exports, {
|
|
|
1599
2418
|
});
|
|
1600
2419
|
import { createClient } from "@libsql/client";
|
|
1601
2420
|
async function initDatabase(config2) {
|
|
2421
|
+
if (_walCheckpointTimer) {
|
|
2422
|
+
clearInterval(_walCheckpointTimer);
|
|
2423
|
+
_walCheckpointTimer = null;
|
|
2424
|
+
}
|
|
2425
|
+
if (_daemonClient) {
|
|
2426
|
+
_daemonClient.close();
|
|
2427
|
+
_daemonClient = null;
|
|
2428
|
+
}
|
|
2429
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2430
|
+
_adapterClient.close();
|
|
2431
|
+
}
|
|
2432
|
+
_adapterClient = null;
|
|
1602
2433
|
if (_client) {
|
|
1603
2434
|
_client.close();
|
|
1604
2435
|
_client = null;
|
|
@@ -1612,6 +2443,7 @@ async function initDatabase(config2) {
|
|
|
1612
2443
|
}
|
|
1613
2444
|
_client = createClient(opts);
|
|
1614
2445
|
_resilientClient = wrapWithRetry(_client);
|
|
2446
|
+
_adapterClient = _resilientClient;
|
|
1615
2447
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1616
2448
|
});
|
|
1617
2449
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1622,14 +2454,20 @@ async function initDatabase(config2) {
|
|
|
1622
2454
|
});
|
|
1623
2455
|
}, 3e4);
|
|
1624
2456
|
_walCheckpointTimer.unref();
|
|
2457
|
+
if (process.env.DATABASE_URL) {
|
|
2458
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
2459
|
+
}
|
|
1625
2460
|
}
|
|
1626
2461
|
function isInitialized() {
|
|
1627
|
-
return _client !== null;
|
|
2462
|
+
return _adapterClient !== null || _client !== null;
|
|
1628
2463
|
}
|
|
1629
2464
|
function getClient() {
|
|
1630
|
-
if (!
|
|
2465
|
+
if (!_adapterClient) {
|
|
1631
2466
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1632
2467
|
}
|
|
2468
|
+
if (process.env.DATABASE_URL) {
|
|
2469
|
+
return _adapterClient;
|
|
2470
|
+
}
|
|
1633
2471
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1634
2472
|
return _resilientClient;
|
|
1635
2473
|
}
|
|
@@ -1639,6 +2477,7 @@ function getClient() {
|
|
|
1639
2477
|
return _resilientClient;
|
|
1640
2478
|
}
|
|
1641
2479
|
async function initDaemonClient() {
|
|
2480
|
+
if (process.env.DATABASE_URL) return;
|
|
1642
2481
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1643
2482
|
if (!_resilientClient) return;
|
|
1644
2483
|
try {
|
|
@@ -1935,6 +2774,7 @@ async function ensureSchema() {
|
|
|
1935
2774
|
project TEXT NOT NULL,
|
|
1936
2775
|
summary TEXT NOT NULL,
|
|
1937
2776
|
task_file TEXT,
|
|
2777
|
+
session_scope TEXT,
|
|
1938
2778
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1939
2779
|
created_at TEXT NOT NULL
|
|
1940
2780
|
);
|
|
@@ -1943,7 +2783,7 @@ async function ensureSchema() {
|
|
|
1943
2783
|
ON notifications(read);
|
|
1944
2784
|
|
|
1945
2785
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1946
|
-
ON notifications(agent_id);
|
|
2786
|
+
ON notifications(agent_id, session_scope);
|
|
1947
2787
|
|
|
1948
2788
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1949
2789
|
ON notifications(task_file);
|
|
@@ -1981,6 +2821,7 @@ async function ensureSchema() {
|
|
|
1981
2821
|
target_agent TEXT NOT NULL,
|
|
1982
2822
|
target_project TEXT,
|
|
1983
2823
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2824
|
+
session_scope TEXT,
|
|
1984
2825
|
content TEXT NOT NULL,
|
|
1985
2826
|
priority TEXT DEFAULT 'normal',
|
|
1986
2827
|
status TEXT DEFAULT 'pending',
|
|
@@ -1994,10 +2835,31 @@ async function ensureSchema() {
|
|
|
1994
2835
|
);
|
|
1995
2836
|
|
|
1996
2837
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1997
|
-
ON messages(target_agent, status);
|
|
2838
|
+
ON messages(target_agent, session_scope, status);
|
|
1998
2839
|
|
|
1999
2840
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2000
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2841
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2842
|
+
`);
|
|
2843
|
+
try {
|
|
2844
|
+
await client.execute({
|
|
2845
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2846
|
+
args: []
|
|
2847
|
+
});
|
|
2848
|
+
} catch {
|
|
2849
|
+
}
|
|
2850
|
+
try {
|
|
2851
|
+
await client.execute({
|
|
2852
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2853
|
+
args: []
|
|
2854
|
+
});
|
|
2855
|
+
} catch {
|
|
2856
|
+
}
|
|
2857
|
+
await client.executeMultiple(`
|
|
2858
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2859
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2860
|
+
|
|
2861
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2862
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2001
2863
|
`);
|
|
2002
2864
|
try {
|
|
2003
2865
|
await client.execute({
|
|
@@ -2581,46 +3443,66 @@ async function ensureSchema() {
|
|
|
2581
3443
|
} catch {
|
|
2582
3444
|
}
|
|
2583
3445
|
}
|
|
3446
|
+
try {
|
|
3447
|
+
await client.execute({
|
|
3448
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3449
|
+
args: []
|
|
3450
|
+
});
|
|
3451
|
+
} catch {
|
|
3452
|
+
}
|
|
2584
3453
|
}
|
|
2585
3454
|
async function disposeDatabase() {
|
|
3455
|
+
if (_walCheckpointTimer) {
|
|
3456
|
+
clearInterval(_walCheckpointTimer);
|
|
3457
|
+
_walCheckpointTimer = null;
|
|
3458
|
+
}
|
|
2586
3459
|
if (_daemonClient) {
|
|
2587
3460
|
_daemonClient.close();
|
|
2588
3461
|
_daemonClient = null;
|
|
2589
3462
|
}
|
|
3463
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
3464
|
+
_adapterClient.close();
|
|
3465
|
+
}
|
|
3466
|
+
_adapterClient = null;
|
|
2590
3467
|
if (_client) {
|
|
2591
3468
|
_client.close();
|
|
2592
3469
|
_client = null;
|
|
2593
3470
|
_resilientClient = null;
|
|
2594
3471
|
}
|
|
2595
3472
|
}
|
|
2596
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
3473
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2597
3474
|
var init_database = __esm({
|
|
2598
3475
|
"src/lib/database.ts"() {
|
|
2599
3476
|
"use strict";
|
|
2600
3477
|
init_db_retry();
|
|
2601
3478
|
init_employees();
|
|
3479
|
+
init_database_adapter();
|
|
2602
3480
|
_client = null;
|
|
2603
3481
|
_resilientClient = null;
|
|
2604
3482
|
_walCheckpointTimer = null;
|
|
2605
3483
|
_daemonClient = null;
|
|
3484
|
+
_adapterClient = null;
|
|
2606
3485
|
initTurso = initDatabase;
|
|
2607
3486
|
disposeTurso = disposeDatabase;
|
|
2608
3487
|
}
|
|
2609
3488
|
});
|
|
2610
3489
|
|
|
2611
3490
|
// src/lib/license.ts
|
|
2612
|
-
import { readFileSync as
|
|
3491
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2613
3492
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2614
|
-
import
|
|
3493
|
+
import { createRequire as createRequire2 } from "module";
|
|
3494
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3495
|
+
import os8 from "os";
|
|
3496
|
+
import path10 from "path";
|
|
2615
3497
|
import { jwtVerify, importSPKI } from "jose";
|
|
2616
3498
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2617
3499
|
var init_license = __esm({
|
|
2618
3500
|
"src/lib/license.ts"() {
|
|
2619
3501
|
"use strict";
|
|
2620
3502
|
init_config();
|
|
2621
|
-
LICENSE_PATH =
|
|
2622
|
-
CACHE_PATH =
|
|
2623
|
-
DEVICE_ID_PATH =
|
|
3503
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3504
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3505
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2624
3506
|
PLAN_LIMITS = {
|
|
2625
3507
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2626
3508
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2632,12 +3514,12 @@ var init_license = __esm({
|
|
|
2632
3514
|
});
|
|
2633
3515
|
|
|
2634
3516
|
// src/lib/plan-limits.ts
|
|
2635
|
-
import { readFileSync as
|
|
2636
|
-
import
|
|
3517
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3518
|
+
import path11 from "path";
|
|
2637
3519
|
function getLicenseSync() {
|
|
2638
3520
|
try {
|
|
2639
|
-
if (!
|
|
2640
|
-
const raw = JSON.parse(
|
|
3521
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3522
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2641
3523
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2642
3524
|
const parts = raw.token.split(".");
|
|
2643
3525
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2675,8 +3557,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2675
3557
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2676
3558
|
let count = 0;
|
|
2677
3559
|
try {
|
|
2678
|
-
if (
|
|
2679
|
-
const raw =
|
|
3560
|
+
if (existsSync10(filePath)) {
|
|
3561
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2680
3562
|
const employees = JSON.parse(raw);
|
|
2681
3563
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2682
3564
|
}
|
|
@@ -2705,29 +3587,69 @@ var init_plan_limits = __esm({
|
|
|
2705
3587
|
this.name = "PlanLimitError";
|
|
2706
3588
|
}
|
|
2707
3589
|
};
|
|
2708
|
-
CACHE_PATH2 =
|
|
3590
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
|
|
3594
|
+
// src/lib/task-scope.ts
|
|
3595
|
+
var task_scope_exports = {};
|
|
3596
|
+
__export(task_scope_exports, {
|
|
3597
|
+
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
3598
|
+
sessionScopeFilter: () => sessionScopeFilter,
|
|
3599
|
+
strictSessionScopeFilter: () => strictSessionScopeFilter
|
|
3600
|
+
});
|
|
3601
|
+
function getCurrentSessionScope() {
|
|
3602
|
+
try {
|
|
3603
|
+
return resolveExeSession();
|
|
3604
|
+
} catch {
|
|
3605
|
+
return null;
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3609
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3610
|
+
if (!scope) return { sql: "", args: [] };
|
|
3611
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3612
|
+
return {
|
|
3613
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3614
|
+
args: [scope]
|
|
3615
|
+
};
|
|
3616
|
+
}
|
|
3617
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
3618
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3619
|
+
if (!scope) return { sql: "", args: [] };
|
|
3620
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3621
|
+
return {
|
|
3622
|
+
sql: ` AND ${col} = ?`,
|
|
3623
|
+
args: [scope]
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
var init_task_scope = __esm({
|
|
3627
|
+
"src/lib/task-scope.ts"() {
|
|
3628
|
+
"use strict";
|
|
3629
|
+
init_tmux_routing();
|
|
2709
3630
|
}
|
|
2710
3631
|
});
|
|
2711
3632
|
|
|
2712
3633
|
// src/lib/notifications.ts
|
|
2713
|
-
import
|
|
2714
|
-
import
|
|
2715
|
-
import
|
|
3634
|
+
import crypto2 from "crypto";
|
|
3635
|
+
import path12 from "path";
|
|
3636
|
+
import os9 from "os";
|
|
2716
3637
|
import {
|
|
2717
|
-
readFileSync as
|
|
3638
|
+
readFileSync as readFileSync10,
|
|
2718
3639
|
readdirSync,
|
|
2719
3640
|
unlinkSync as unlinkSync3,
|
|
2720
|
-
existsSync as
|
|
3641
|
+
existsSync as existsSync11,
|
|
2721
3642
|
rmdirSync
|
|
2722
3643
|
} from "fs";
|
|
2723
3644
|
async function writeNotification(notification) {
|
|
2724
3645
|
try {
|
|
2725
3646
|
const client = getClient();
|
|
2726
|
-
const id =
|
|
3647
|
+
const id = crypto2.randomUUID();
|
|
2727
3648
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3649
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2728
3650
|
await client.execute({
|
|
2729
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2730
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3651
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3652
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2731
3653
|
args: [
|
|
2732
3654
|
id,
|
|
2733
3655
|
notification.agentId,
|
|
@@ -2736,6 +3658,7 @@ async function writeNotification(notification) {
|
|
|
2736
3658
|
notification.project,
|
|
2737
3659
|
notification.summary,
|
|
2738
3660
|
notification.taskFile ?? null,
|
|
3661
|
+
sessionScope,
|
|
2739
3662
|
now
|
|
2740
3663
|
]
|
|
2741
3664
|
});
|
|
@@ -2744,12 +3667,14 @@ async function writeNotification(notification) {
|
|
|
2744
3667
|
`);
|
|
2745
3668
|
}
|
|
2746
3669
|
}
|
|
2747
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3670
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2748
3671
|
try {
|
|
2749
3672
|
const client = getClient();
|
|
3673
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2750
3674
|
await client.execute({
|
|
2751
|
-
sql:
|
|
2752
|
-
|
|
3675
|
+
sql: `UPDATE notifications SET read = 1
|
|
3676
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3677
|
+
args: [taskFile, ...scope.args]
|
|
2753
3678
|
});
|
|
2754
3679
|
} catch {
|
|
2755
3680
|
}
|
|
@@ -2758,11 +3683,12 @@ var init_notifications = __esm({
|
|
|
2758
3683
|
"src/lib/notifications.ts"() {
|
|
2759
3684
|
"use strict";
|
|
2760
3685
|
init_database();
|
|
3686
|
+
init_task_scope();
|
|
2761
3687
|
}
|
|
2762
3688
|
});
|
|
2763
3689
|
|
|
2764
3690
|
// src/lib/session-kill-telemetry.ts
|
|
2765
|
-
import
|
|
3691
|
+
import crypto3 from "crypto";
|
|
2766
3692
|
async function recordSessionKill(input) {
|
|
2767
3693
|
try {
|
|
2768
3694
|
const client = getClient();
|
|
@@ -2772,7 +3698,7 @@ async function recordSessionKill(input) {
|
|
|
2772
3698
|
ticks_idle, estimated_tokens_saved)
|
|
2773
3699
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2774
3700
|
args: [
|
|
2775
|
-
|
|
3701
|
+
crypto3.randomUUID(),
|
|
2776
3702
|
input.sessionName,
|
|
2777
3703
|
input.agentId,
|
|
2778
3704
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2795,35 +3721,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
2795
3721
|
}
|
|
2796
3722
|
});
|
|
2797
3723
|
|
|
2798
|
-
// src/lib/task-scope.ts
|
|
2799
|
-
var task_scope_exports = {};
|
|
2800
|
-
__export(task_scope_exports, {
|
|
2801
|
-
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
2802
|
-
sessionScopeFilter: () => sessionScopeFilter
|
|
2803
|
-
});
|
|
2804
|
-
function getCurrentSessionScope() {
|
|
2805
|
-
try {
|
|
2806
|
-
return resolveExeSession();
|
|
2807
|
-
} catch {
|
|
2808
|
-
return null;
|
|
2809
|
-
}
|
|
2810
|
-
}
|
|
2811
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
2812
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2813
|
-
if (!scope) return { sql: "", args: [] };
|
|
2814
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2815
|
-
return {
|
|
2816
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
2817
|
-
args: [scope]
|
|
2818
|
-
};
|
|
2819
|
-
}
|
|
2820
|
-
var init_task_scope = __esm({
|
|
2821
|
-
"src/lib/task-scope.ts"() {
|
|
2822
|
-
"use strict";
|
|
2823
|
-
init_tmux_routing();
|
|
2824
|
-
}
|
|
2825
|
-
});
|
|
2826
|
-
|
|
2827
3724
|
// src/lib/state-bus.ts
|
|
2828
3725
|
var StateBus, orgBus;
|
|
2829
3726
|
var init_state_bus = __esm({
|
|
@@ -2880,12 +3777,12 @@ var init_state_bus = __esm({
|
|
|
2880
3777
|
});
|
|
2881
3778
|
|
|
2882
3779
|
// src/lib/tasks-crud.ts
|
|
2883
|
-
import
|
|
2884
|
-
import
|
|
2885
|
-
import
|
|
3780
|
+
import crypto4 from "crypto";
|
|
3781
|
+
import path13 from "path";
|
|
3782
|
+
import os10 from "os";
|
|
2886
3783
|
import { execSync as execSync5 } from "child_process";
|
|
2887
3784
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2888
|
-
import { existsSync as
|
|
3785
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
2889
3786
|
async function writeCheckpoint(input) {
|
|
2890
3787
|
const client = getClient();
|
|
2891
3788
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3001,7 +3898,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3001
3898
|
}
|
|
3002
3899
|
async function createTaskCore(input) {
|
|
3003
3900
|
const client = getClient();
|
|
3004
|
-
const id =
|
|
3901
|
+
const id = crypto4.randomUUID();
|
|
3005
3902
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3006
3903
|
const slug = slugify(input.title);
|
|
3007
3904
|
let earlySessionScope = null;
|
|
@@ -3060,8 +3957,8 @@ ${laneWarning}` : laneWarning;
|
|
|
3060
3957
|
}
|
|
3061
3958
|
if (input.baseDir) {
|
|
3062
3959
|
try {
|
|
3063
|
-
await mkdir3(
|
|
3064
|
-
await mkdir3(
|
|
3960
|
+
await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3961
|
+
await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3065
3962
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3066
3963
|
await ensureGitignoreExe(input.baseDir);
|
|
3067
3964
|
} catch {
|
|
@@ -3097,13 +3994,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3097
3994
|
});
|
|
3098
3995
|
if (input.baseDir) {
|
|
3099
3996
|
try {
|
|
3100
|
-
const EXE_OS_DIR =
|
|
3101
|
-
const mdPath =
|
|
3102
|
-
const mdDir =
|
|
3103
|
-
if (!
|
|
3997
|
+
const EXE_OS_DIR = path13.join(os10.homedir(), ".exe-os");
|
|
3998
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
3999
|
+
const mdDir = path13.dirname(mdPath);
|
|
4000
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3104
4001
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3105
4002
|
const mdContent = `# ${input.title}
|
|
3106
4003
|
|
|
4004
|
+
## MANDATORY: When done
|
|
4005
|
+
|
|
4006
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4007
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4008
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4009
|
+
|
|
3107
4010
|
**ID:** ${id}
|
|
3108
4011
|
**Status:** ${initialStatus}
|
|
3109
4012
|
**Priority:** ${input.priority}
|
|
@@ -3117,12 +4020,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3117
4020
|
## Context
|
|
3118
4021
|
|
|
3119
4022
|
${input.context}
|
|
3120
|
-
|
|
3121
|
-
## MANDATORY: When done
|
|
3122
|
-
|
|
3123
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3124
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3125
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3126
4023
|
`;
|
|
3127
4024
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3128
4025
|
} catch (err) {
|
|
@@ -3371,7 +4268,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3371
4268
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3372
4269
|
} catch {
|
|
3373
4270
|
}
|
|
3374
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4271
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3375
4272
|
try {
|
|
3376
4273
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3377
4274
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3400,9 +4297,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3400
4297
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3401
4298
|
}
|
|
3402
4299
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3403
|
-
const archPath =
|
|
4300
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3404
4301
|
try {
|
|
3405
|
-
if (
|
|
4302
|
+
if (existsSync12(archPath)) return;
|
|
3406
4303
|
const template = [
|
|
3407
4304
|
`# ${projectName} \u2014 System Architecture`,
|
|
3408
4305
|
"",
|
|
@@ -3435,10 +4332,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3435
4332
|
}
|
|
3436
4333
|
}
|
|
3437
4334
|
async function ensureGitignoreExe(baseDir) {
|
|
3438
|
-
const gitignorePath =
|
|
4335
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
3439
4336
|
try {
|
|
3440
|
-
if (
|
|
3441
|
-
const content =
|
|
4337
|
+
if (existsSync12(gitignorePath)) {
|
|
4338
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3442
4339
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3443
4340
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3444
4341
|
} else {
|
|
@@ -3469,58 +4366,42 @@ var init_tasks_crud = __esm({
|
|
|
3469
4366
|
});
|
|
3470
4367
|
|
|
3471
4368
|
// src/lib/tasks-review.ts
|
|
3472
|
-
import
|
|
3473
|
-
import { existsSync as
|
|
4369
|
+
import path14 from "path";
|
|
4370
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3474
4371
|
async function countPendingReviews(sessionScope) {
|
|
3475
4372
|
const client = getClient();
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
args: [sessionScope]
|
|
3480
|
-
});
|
|
3481
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3482
|
-
}
|
|
4373
|
+
const scope = strictSessionScopeFilter(
|
|
4374
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4375
|
+
);
|
|
3483
4376
|
const result = await client.execute({
|
|
3484
|
-
sql:
|
|
3485
|
-
|
|
4377
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4378
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4379
|
+
args: [...scope.args]
|
|
3486
4380
|
});
|
|
3487
4381
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3488
4382
|
}
|
|
3489
4383
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3490
4384
|
const client = getClient();
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3495
|
-
AND session_scope = ?`,
|
|
3496
|
-
args: [sinceIso, sessionScope]
|
|
3497
|
-
});
|
|
3498
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3499
|
-
}
|
|
4385
|
+
const scope = strictSessionScopeFilter(
|
|
4386
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4387
|
+
);
|
|
3500
4388
|
const result = await client.execute({
|
|
3501
4389
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3502
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3503
|
-
args: [sinceIso]
|
|
4390
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4391
|
+
args: [sinceIso, ...scope.args]
|
|
3504
4392
|
});
|
|
3505
4393
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3506
4394
|
}
|
|
3507
4395
|
async function listPendingReviews(limit, sessionScope) {
|
|
3508
4396
|
const client = getClient();
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
WHERE status = 'needs_review'
|
|
3513
|
-
AND session_scope = ?
|
|
3514
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3515
|
-
args: [sessionScope, limit]
|
|
3516
|
-
});
|
|
3517
|
-
return result2.rows;
|
|
3518
|
-
}
|
|
4397
|
+
const scope = strictSessionScopeFilter(
|
|
4398
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4399
|
+
);
|
|
3519
4400
|
const result = await client.execute({
|
|
3520
4401
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3521
|
-
WHERE status = 'needs_review'
|
|
4402
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3522
4403
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3523
|
-
args: [limit]
|
|
4404
|
+
args: [...scope.args, limit]
|
|
3524
4405
|
});
|
|
3525
4406
|
return result.rows;
|
|
3526
4407
|
}
|
|
@@ -3532,7 +4413,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3532
4413
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3533
4414
|
AND assigned_by = 'system'
|
|
3534
4415
|
AND title LIKE 'Review:%'
|
|
3535
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4416
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3536
4417
|
args: [now]
|
|
3537
4418
|
});
|
|
3538
4419
|
const r1b = await client.execute({
|
|
@@ -3651,11 +4532,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3651
4532
|
);
|
|
3652
4533
|
}
|
|
3653
4534
|
try {
|
|
3654
|
-
const cacheDir =
|
|
3655
|
-
if (
|
|
4535
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4536
|
+
if (existsSync13(cacheDir)) {
|
|
3656
4537
|
for (const f of readdirSync2(cacheDir)) {
|
|
3657
4538
|
if (f.startsWith("review-notified-")) {
|
|
3658
|
-
unlinkSync4(
|
|
4539
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
3659
4540
|
}
|
|
3660
4541
|
}
|
|
3661
4542
|
}
|
|
@@ -3672,11 +4553,12 @@ var init_tasks_review = __esm({
|
|
|
3672
4553
|
init_tmux_routing();
|
|
3673
4554
|
init_session_key();
|
|
3674
4555
|
init_state_bus();
|
|
4556
|
+
init_task_scope();
|
|
3675
4557
|
}
|
|
3676
4558
|
});
|
|
3677
4559
|
|
|
3678
4560
|
// src/lib/tasks-chain.ts
|
|
3679
|
-
import
|
|
4561
|
+
import path15 from "path";
|
|
3680
4562
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3681
4563
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3682
4564
|
const client = getClient();
|
|
@@ -3693,7 +4575,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3693
4575
|
});
|
|
3694
4576
|
for (const ur of unblockedRows.rows) {
|
|
3695
4577
|
try {
|
|
3696
|
-
const ubFile =
|
|
4578
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
3697
4579
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3698
4580
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3699
4581
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3728,7 +4610,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3728
4610
|
const scScope = sessionScopeFilter();
|
|
3729
4611
|
const remaining = await client.execute({
|
|
3730
4612
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3731
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4613
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3732
4614
|
args: [parentTaskId, ...scScope.args]
|
|
3733
4615
|
});
|
|
3734
4616
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3762,7 +4644,7 @@ var init_tasks_chain = __esm({
|
|
|
3762
4644
|
|
|
3763
4645
|
// src/lib/project-name.ts
|
|
3764
4646
|
import { execSync as execSync6 } from "child_process";
|
|
3765
|
-
import
|
|
4647
|
+
import path16 from "path";
|
|
3766
4648
|
function getProjectName(cwd) {
|
|
3767
4649
|
const dir = cwd ?? process.cwd();
|
|
3768
4650
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3775,7 +4657,7 @@ function getProjectName(cwd) {
|
|
|
3775
4657
|
timeout: 2e3,
|
|
3776
4658
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3777
4659
|
}).trim();
|
|
3778
|
-
repoRoot =
|
|
4660
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
3779
4661
|
} catch {
|
|
3780
4662
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3781
4663
|
cwd: dir,
|
|
@@ -3784,11 +4666,11 @@ function getProjectName(cwd) {
|
|
|
3784
4666
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3785
4667
|
}).trim();
|
|
3786
4668
|
}
|
|
3787
|
-
_cached2 =
|
|
4669
|
+
_cached2 = path16.basename(repoRoot);
|
|
3788
4670
|
_cachedCwd = dir;
|
|
3789
4671
|
return _cached2;
|
|
3790
4672
|
} catch {
|
|
3791
|
-
_cached2 =
|
|
4673
|
+
_cached2 = path16.basename(dir);
|
|
3792
4674
|
_cachedCwd = dir;
|
|
3793
4675
|
return _cached2;
|
|
3794
4676
|
}
|
|
@@ -3938,10 +4820,10 @@ __export(behaviors_exports, {
|
|
|
3938
4820
|
listBehaviorsByDomain: () => listBehaviorsByDomain,
|
|
3939
4821
|
storeBehavior: () => storeBehavior
|
|
3940
4822
|
});
|
|
3941
|
-
import
|
|
4823
|
+
import crypto5 from "crypto";
|
|
3942
4824
|
async function storeBehavior(opts) {
|
|
3943
4825
|
const client = getClient();
|
|
3944
|
-
const id =
|
|
4826
|
+
const id = crypto5.randomUUID();
|
|
3945
4827
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3946
4828
|
await client.execute({
|
|
3947
4829
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4025,7 +4907,7 @@ __export(skill_learning_exports, {
|
|
|
4025
4907
|
storeTrajectory: () => storeTrajectory,
|
|
4026
4908
|
sweepTrajectories: () => sweepTrajectories
|
|
4027
4909
|
});
|
|
4028
|
-
import
|
|
4910
|
+
import crypto6 from "crypto";
|
|
4029
4911
|
async function extractTrajectory(taskId, agentId) {
|
|
4030
4912
|
const client = getClient();
|
|
4031
4913
|
const result = await client.execute({
|
|
@@ -4054,11 +4936,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4054
4936
|
return signature;
|
|
4055
4937
|
}
|
|
4056
4938
|
function hashSignature(signature) {
|
|
4057
|
-
return
|
|
4939
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4058
4940
|
}
|
|
4059
4941
|
async function storeTrajectory(opts) {
|
|
4060
4942
|
const client = getClient();
|
|
4061
|
-
const id =
|
|
4943
|
+
const id = crypto6.randomUUID();
|
|
4062
4944
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4063
4945
|
const signatureHash = hashSignature(opts.signature);
|
|
4064
4946
|
await client.execute({
|
|
@@ -4323,8 +5205,8 @@ __export(tasks_exports, {
|
|
|
4323
5205
|
updateTaskStatus: () => updateTaskStatus,
|
|
4324
5206
|
writeCheckpoint: () => writeCheckpoint
|
|
4325
5207
|
});
|
|
4326
|
-
import
|
|
4327
|
-
import { writeFileSync as
|
|
5208
|
+
import path17 from "path";
|
|
5209
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4328
5210
|
async function createTask(input) {
|
|
4329
5211
|
const result = await createTaskCore(input);
|
|
4330
5212
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4343,12 +5225,12 @@ async function updateTask(input) {
|
|
|
4343
5225
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4344
5226
|
try {
|
|
4345
5227
|
const agent = String(row.assigned_to);
|
|
4346
|
-
const cacheDir =
|
|
4347
|
-
const cachePath =
|
|
5228
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
5229
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
4348
5230
|
if (input.status === "in_progress") {
|
|
4349
5231
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4350
|
-
|
|
4351
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5232
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5233
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4352
5234
|
try {
|
|
4353
5235
|
unlinkSync5(cachePath);
|
|
4354
5236
|
} catch {
|
|
@@ -4356,10 +5238,10 @@ async function updateTask(input) {
|
|
|
4356
5238
|
}
|
|
4357
5239
|
} catch {
|
|
4358
5240
|
}
|
|
4359
|
-
if (input.status === "done") {
|
|
5241
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4360
5242
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4361
5243
|
}
|
|
4362
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5244
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4363
5245
|
try {
|
|
4364
5246
|
const client = getClient();
|
|
4365
5247
|
const taskTitle = String(row.title);
|
|
@@ -4375,7 +5257,7 @@ async function updateTask(input) {
|
|
|
4375
5257
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4376
5258
|
try {
|
|
4377
5259
|
const draftClient = getClient();
|
|
4378
|
-
if (input.status === "done") {
|
|
5260
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4379
5261
|
await draftClient.execute({
|
|
4380
5262
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4381
5263
|
args: [assignedAgent]
|
|
@@ -4392,7 +5274,7 @@ async function updateTask(input) {
|
|
|
4392
5274
|
try {
|
|
4393
5275
|
const client = getClient();
|
|
4394
5276
|
const cascaded = await client.execute({
|
|
4395
|
-
sql: `UPDATE tasks SET status = '
|
|
5277
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4396
5278
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4397
5279
|
args: [now, taskId]
|
|
4398
5280
|
});
|
|
@@ -4405,14 +5287,14 @@ async function updateTask(input) {
|
|
|
4405
5287
|
} catch {
|
|
4406
5288
|
}
|
|
4407
5289
|
}
|
|
4408
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
5290
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4409
5291
|
if (isTerminal) {
|
|
4410
5292
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4411
5293
|
if (!isCoordinator) {
|
|
4412
5294
|
notifyTaskDone();
|
|
4413
5295
|
}
|
|
4414
5296
|
await markTaskNotificationsRead(taskFile);
|
|
4415
|
-
if (input.status === "done") {
|
|
5297
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4416
5298
|
try {
|
|
4417
5299
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4418
5300
|
} catch {
|
|
@@ -4432,7 +5314,7 @@ async function updateTask(input) {
|
|
|
4432
5314
|
}
|
|
4433
5315
|
}
|
|
4434
5316
|
}
|
|
4435
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5317
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4436
5318
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4437
5319
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4438
5320
|
taskId,
|
|
@@ -4804,6 +5686,7 @@ __export(tmux_routing_exports, {
|
|
|
4804
5686
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4805
5687
|
isExeSession: () => isExeSession,
|
|
4806
5688
|
isSessionBusy: () => isSessionBusy,
|
|
5689
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4807
5690
|
notifyParentExe: () => notifyParentExe,
|
|
4808
5691
|
parseParentExe: () => parseParentExe,
|
|
4809
5692
|
registerParentExe: () => registerParentExe,
|
|
@@ -4814,13 +5697,13 @@ __export(tmux_routing_exports, {
|
|
|
4814
5697
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4815
5698
|
});
|
|
4816
5699
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
4817
|
-
import { readFileSync as
|
|
4818
|
-
import
|
|
4819
|
-
import
|
|
5700
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5701
|
+
import path18 from "path";
|
|
5702
|
+
import os11 from "os";
|
|
4820
5703
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4821
5704
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
4822
5705
|
function spawnLockPath(sessionName) {
|
|
4823
|
-
return
|
|
5706
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4824
5707
|
}
|
|
4825
5708
|
function isProcessAlive(pid) {
|
|
4826
5709
|
try {
|
|
@@ -4831,13 +5714,13 @@ function isProcessAlive(pid) {
|
|
|
4831
5714
|
}
|
|
4832
5715
|
}
|
|
4833
5716
|
function acquireSpawnLock2(sessionName) {
|
|
4834
|
-
if (!
|
|
5717
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4835
5718
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4836
5719
|
}
|
|
4837
5720
|
const lockFile = spawnLockPath(sessionName);
|
|
4838
|
-
if (
|
|
5721
|
+
if (existsSync14(lockFile)) {
|
|
4839
5722
|
try {
|
|
4840
|
-
const lock = JSON.parse(
|
|
5723
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
4841
5724
|
const age = Date.now() - lock.timestamp;
|
|
4842
5725
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4843
5726
|
return false;
|
|
@@ -4845,7 +5728,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
4845
5728
|
} catch {
|
|
4846
5729
|
}
|
|
4847
5730
|
}
|
|
4848
|
-
|
|
5731
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4849
5732
|
return true;
|
|
4850
5733
|
}
|
|
4851
5734
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -4857,13 +5740,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
4857
5740
|
function resolveBehaviorsExporterScript() {
|
|
4858
5741
|
try {
|
|
4859
5742
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4860
|
-
const scriptPath =
|
|
4861
|
-
|
|
5743
|
+
const scriptPath = path18.join(
|
|
5744
|
+
path18.dirname(thisFile),
|
|
4862
5745
|
"..",
|
|
4863
5746
|
"bin",
|
|
4864
5747
|
"exe-export-behaviors.js"
|
|
4865
5748
|
);
|
|
4866
|
-
return
|
|
5749
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
4867
5750
|
} catch {
|
|
4868
5751
|
return null;
|
|
4869
5752
|
}
|
|
@@ -4929,12 +5812,12 @@ function extractRootExe(name) {
|
|
|
4929
5812
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4930
5813
|
}
|
|
4931
5814
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4932
|
-
if (!
|
|
5815
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
4933
5816
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4934
5817
|
}
|
|
4935
5818
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4936
|
-
const filePath =
|
|
4937
|
-
|
|
5819
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5820
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
4938
5821
|
parentExe: rootExe,
|
|
4939
5822
|
dispatchedBy: dispatchedBy || rootExe,
|
|
4940
5823
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4942,7 +5825,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4942
5825
|
}
|
|
4943
5826
|
function getParentExe(sessionKey) {
|
|
4944
5827
|
try {
|
|
4945
|
-
const data = JSON.parse(
|
|
5828
|
+
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4946
5829
|
return data.parentExe || null;
|
|
4947
5830
|
} catch {
|
|
4948
5831
|
return null;
|
|
@@ -4950,8 +5833,8 @@ function getParentExe(sessionKey) {
|
|
|
4950
5833
|
}
|
|
4951
5834
|
function getDispatchedBy(sessionKey) {
|
|
4952
5835
|
try {
|
|
4953
|
-
const data = JSON.parse(
|
|
4954
|
-
|
|
5836
|
+
const data = JSON.parse(readFileSync12(
|
|
5837
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4955
5838
|
"utf8"
|
|
4956
5839
|
));
|
|
4957
5840
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5021,8 +5904,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5021
5904
|
}
|
|
5022
5905
|
function readDebounceState() {
|
|
5023
5906
|
try {
|
|
5024
|
-
if (!
|
|
5025
|
-
const raw = JSON.parse(
|
|
5907
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5908
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5026
5909
|
const state = {};
|
|
5027
5910
|
for (const [key, val] of Object.entries(raw)) {
|
|
5028
5911
|
if (typeof val === "number") {
|
|
@@ -5038,8 +5921,8 @@ function readDebounceState() {
|
|
|
5038
5921
|
}
|
|
5039
5922
|
function writeDebounceState(state) {
|
|
5040
5923
|
try {
|
|
5041
|
-
if (!
|
|
5042
|
-
|
|
5924
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5925
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5043
5926
|
} catch {
|
|
5044
5927
|
}
|
|
5045
5928
|
}
|
|
@@ -5137,8 +6020,8 @@ function sendIntercom(targetSession) {
|
|
|
5137
6020
|
try {
|
|
5138
6021
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5139
6022
|
const agent = baseAgentName(rawAgent);
|
|
5140
|
-
const markerPath =
|
|
5141
|
-
if (
|
|
6023
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6024
|
+
if (existsSync14(markerPath)) {
|
|
5142
6025
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5143
6026
|
return "debounced";
|
|
5144
6027
|
}
|
|
@@ -5147,8 +6030,8 @@ function sendIntercom(targetSession) {
|
|
|
5147
6030
|
try {
|
|
5148
6031
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5149
6032
|
const agent = baseAgentName(rawAgent);
|
|
5150
|
-
const taskDir =
|
|
5151
|
-
if (
|
|
6033
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
6034
|
+
if (existsSync14(taskDir)) {
|
|
5152
6035
|
const files = readdirSync3(taskDir).filter(
|
|
5153
6036
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5154
6037
|
);
|
|
@@ -5208,6 +6091,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5208
6091
|
}
|
|
5209
6092
|
return true;
|
|
5210
6093
|
}
|
|
6094
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
6095
|
+
const transport = getTransport();
|
|
6096
|
+
try {
|
|
6097
|
+
const sessions = transport.listSessions();
|
|
6098
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
6099
|
+
execSync7(
|
|
6100
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
6101
|
+
{ timeout: 3e3 }
|
|
6102
|
+
);
|
|
6103
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
6104
|
+
return true;
|
|
6105
|
+
} catch {
|
|
6106
|
+
return false;
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
5211
6109
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5212
6110
|
if (isCoordinatorName(employeeName)) {
|
|
5213
6111
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5281,26 +6179,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5281
6179
|
const transport = getTransport();
|
|
5282
6180
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5283
6181
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5284
|
-
const logDir =
|
|
5285
|
-
const logFile =
|
|
5286
|
-
if (!
|
|
6182
|
+
const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
|
|
6183
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
6184
|
+
if (!existsSync14(logDir)) {
|
|
5287
6185
|
mkdirSync6(logDir, { recursive: true });
|
|
5288
6186
|
}
|
|
5289
6187
|
transport.kill(sessionName);
|
|
5290
6188
|
let cleanupSuffix = "";
|
|
5291
6189
|
try {
|
|
5292
6190
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5293
|
-
const cleanupScript =
|
|
5294
|
-
if (
|
|
6191
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
6192
|
+
if (existsSync14(cleanupScript)) {
|
|
5295
6193
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5296
6194
|
}
|
|
5297
6195
|
} catch {
|
|
5298
6196
|
}
|
|
5299
6197
|
try {
|
|
5300
|
-
const claudeJsonPath =
|
|
6198
|
+
const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
|
|
5301
6199
|
let claudeJson = {};
|
|
5302
6200
|
try {
|
|
5303
|
-
claudeJson = JSON.parse(
|
|
6201
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
5304
6202
|
} catch {
|
|
5305
6203
|
}
|
|
5306
6204
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5308,17 +6206,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5308
6206
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5309
6207
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5310
6208
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5311
|
-
|
|
6209
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5312
6210
|
} catch {
|
|
5313
6211
|
}
|
|
5314
6212
|
try {
|
|
5315
|
-
const settingsDir =
|
|
6213
|
+
const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
|
|
5316
6214
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5317
|
-
const projSettingsDir =
|
|
5318
|
-
const settingsPath =
|
|
6215
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
6216
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
5319
6217
|
let settings = {};
|
|
5320
6218
|
try {
|
|
5321
|
-
settings = JSON.parse(
|
|
6219
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
5322
6220
|
} catch {
|
|
5323
6221
|
}
|
|
5324
6222
|
const perms = settings.permissions ?? {};
|
|
@@ -5347,7 +6245,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5347
6245
|
perms.allow = allow;
|
|
5348
6246
|
settings.permissions = perms;
|
|
5349
6247
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5350
|
-
|
|
6248
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5351
6249
|
}
|
|
5352
6250
|
} catch {
|
|
5353
6251
|
}
|
|
@@ -5362,8 +6260,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5362
6260
|
let behaviorsFlag = "";
|
|
5363
6261
|
let legacyFallbackWarned = false;
|
|
5364
6262
|
if (!useExeAgent && !useBinSymlink) {
|
|
5365
|
-
const identityPath =
|
|
5366
|
-
|
|
6263
|
+
const identityPath = path18.join(
|
|
6264
|
+
os11.homedir(),
|
|
5367
6265
|
".exe-os",
|
|
5368
6266
|
"identity",
|
|
5369
6267
|
`${employeeName}.md`
|
|
@@ -5372,13 +6270,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5372
6270
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5373
6271
|
if (hasAgentFlag) {
|
|
5374
6272
|
identityFlag = ` --agent ${employeeName}`;
|
|
5375
|
-
} else if (
|
|
6273
|
+
} else if (existsSync14(identityPath)) {
|
|
5376
6274
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5377
6275
|
legacyFallbackWarned = true;
|
|
5378
6276
|
}
|
|
5379
6277
|
const behaviorsFile = exportBehaviorsSync(
|
|
5380
6278
|
employeeName,
|
|
5381
|
-
|
|
6279
|
+
path18.basename(spawnCwd),
|
|
5382
6280
|
sessionName
|
|
5383
6281
|
);
|
|
5384
6282
|
if (behaviorsFile) {
|
|
@@ -5393,16 +6291,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5393
6291
|
}
|
|
5394
6292
|
let sessionContextFlag = "";
|
|
5395
6293
|
try {
|
|
5396
|
-
const ctxDir =
|
|
6294
|
+
const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
5397
6295
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5398
|
-
const ctxFile =
|
|
6296
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5399
6297
|
const ctxContent = [
|
|
5400
6298
|
`## Session Context`,
|
|
5401
6299
|
`You are running in tmux session: ${sessionName}.`,
|
|
5402
6300
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5403
6301
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5404
6302
|
].join("\n");
|
|
5405
|
-
|
|
6303
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5406
6304
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5407
6305
|
} catch {
|
|
5408
6306
|
}
|
|
@@ -5479,8 +6377,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5479
6377
|
transport.pipeLog(sessionName, logFile);
|
|
5480
6378
|
try {
|
|
5481
6379
|
const mySession = getMySession();
|
|
5482
|
-
const dispatchInfo =
|
|
5483
|
-
|
|
6380
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6381
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5484
6382
|
dispatchedBy: mySession,
|
|
5485
6383
|
rootExe: exeSession,
|
|
5486
6384
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5554,15 +6452,15 @@ var init_tmux_routing = __esm({
|
|
|
5554
6452
|
init_intercom_queue();
|
|
5555
6453
|
init_plan_limits();
|
|
5556
6454
|
init_employees();
|
|
5557
|
-
SPAWN_LOCK_DIR =
|
|
5558
|
-
SESSION_CACHE =
|
|
6455
|
+
SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
6456
|
+
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
5559
6457
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5560
6458
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5561
6459
|
VERIFY_PANE_LINES = 200;
|
|
5562
6460
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5563
6461
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5564
|
-
INTERCOM_LOG2 =
|
|
5565
|
-
DEBOUNCE_FILE =
|
|
6462
|
+
INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
6463
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5566
6464
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5567
6465
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5568
6466
|
}
|
|
@@ -5579,14 +6477,14 @@ var init_memory = __esm({
|
|
|
5579
6477
|
|
|
5580
6478
|
// src/lib/keychain.ts
|
|
5581
6479
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5582
|
-
import { existsSync as
|
|
5583
|
-
import
|
|
5584
|
-
import
|
|
6480
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6481
|
+
import path19 from "path";
|
|
6482
|
+
import os12 from "os";
|
|
5585
6483
|
function getKeyDir() {
|
|
5586
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6484
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
|
|
5587
6485
|
}
|
|
5588
6486
|
function getKeyPath() {
|
|
5589
|
-
return
|
|
6487
|
+
return path19.join(getKeyDir(), "master.key");
|
|
5590
6488
|
}
|
|
5591
6489
|
async function tryKeytar() {
|
|
5592
6490
|
try {
|
|
@@ -5607,9 +6505,9 @@ async function getMasterKey() {
|
|
|
5607
6505
|
}
|
|
5608
6506
|
}
|
|
5609
6507
|
const keyPath = getKeyPath();
|
|
5610
|
-
if (!
|
|
6508
|
+
if (!existsSync15(keyPath)) {
|
|
5611
6509
|
process.stderr.write(
|
|
5612
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
6510
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5613
6511
|
`
|
|
5614
6512
|
);
|
|
5615
6513
|
return null;
|
|
@@ -5639,6 +6537,7 @@ var shard_manager_exports = {};
|
|
|
5639
6537
|
__export(shard_manager_exports, {
|
|
5640
6538
|
disposeShards: () => disposeShards,
|
|
5641
6539
|
ensureShardSchema: () => ensureShardSchema,
|
|
6540
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5642
6541
|
getReadyShardClient: () => getReadyShardClient,
|
|
5643
6542
|
getShardClient: () => getShardClient,
|
|
5644
6543
|
getShardsDir: () => getShardsDir,
|
|
@@ -5647,15 +6546,18 @@ __export(shard_manager_exports, {
|
|
|
5647
6546
|
listShards: () => listShards,
|
|
5648
6547
|
shardExists: () => shardExists
|
|
5649
6548
|
});
|
|
5650
|
-
import
|
|
5651
|
-
import { existsSync as
|
|
6549
|
+
import path20 from "path";
|
|
6550
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5652
6551
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5653
6552
|
function initShardManager(encryptionKey) {
|
|
5654
6553
|
_encryptionKey = encryptionKey;
|
|
5655
|
-
if (!
|
|
6554
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5656
6555
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5657
6556
|
}
|
|
5658
6557
|
_shardingEnabled = true;
|
|
6558
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6559
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6560
|
+
_evictionTimer.unref();
|
|
5659
6561
|
}
|
|
5660
6562
|
function isShardingEnabled() {
|
|
5661
6563
|
return _shardingEnabled;
|
|
@@ -5672,21 +6574,28 @@ function getShardClient(projectName) {
|
|
|
5672
6574
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5673
6575
|
}
|
|
5674
6576
|
const cached = _shards.get(safeName);
|
|
5675
|
-
if (cached)
|
|
5676
|
-
|
|
6577
|
+
if (cached) {
|
|
6578
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6579
|
+
return cached;
|
|
6580
|
+
}
|
|
6581
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6582
|
+
evictLRU();
|
|
6583
|
+
}
|
|
6584
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
5677
6585
|
const client = createClient2({
|
|
5678
6586
|
url: `file:${dbPath}`,
|
|
5679
6587
|
encryptionKey: _encryptionKey
|
|
5680
6588
|
});
|
|
5681
6589
|
_shards.set(safeName, client);
|
|
6590
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5682
6591
|
return client;
|
|
5683
6592
|
}
|
|
5684
6593
|
function shardExists(projectName) {
|
|
5685
6594
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5686
|
-
return
|
|
6595
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
5687
6596
|
}
|
|
5688
6597
|
function listShards() {
|
|
5689
|
-
if (!
|
|
6598
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5690
6599
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5691
6600
|
}
|
|
5692
6601
|
async function ensureShardSchema(client) {
|
|
@@ -5738,6 +6647,8 @@ async function ensureShardSchema(client) {
|
|
|
5738
6647
|
for (const col of [
|
|
5739
6648
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5740
6649
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6650
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6651
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5741
6652
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5742
6653
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5743
6654
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5760,7 +6671,23 @@ async function ensureShardSchema(client) {
|
|
|
5760
6671
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
5761
6672
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
5762
6673
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
5763
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
6674
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
6675
|
+
// Metadata enrichment columns (must match database.ts)
|
|
6676
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
6677
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
6678
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
6679
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
6680
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
6681
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
6682
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
6683
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
6684
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
6685
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
6686
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
6687
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
6688
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
6689
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
6690
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
5764
6691
|
]) {
|
|
5765
6692
|
try {
|
|
5766
6693
|
await client.execute(col);
|
|
@@ -5859,21 +6786,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5859
6786
|
await ensureShardSchema(client);
|
|
5860
6787
|
return client;
|
|
5861
6788
|
}
|
|
6789
|
+
function evictLRU() {
|
|
6790
|
+
let oldest = null;
|
|
6791
|
+
let oldestTime = Infinity;
|
|
6792
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6793
|
+
if (time < oldestTime) {
|
|
6794
|
+
oldestTime = time;
|
|
6795
|
+
oldest = name;
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
if (oldest) {
|
|
6799
|
+
const client = _shards.get(oldest);
|
|
6800
|
+
if (client) {
|
|
6801
|
+
client.close();
|
|
6802
|
+
}
|
|
6803
|
+
_shards.delete(oldest);
|
|
6804
|
+
_shardLastAccess.delete(oldest);
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
function evictIdleShards() {
|
|
6808
|
+
const now = Date.now();
|
|
6809
|
+
const toEvict = [];
|
|
6810
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6811
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6812
|
+
toEvict.push(name);
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
for (const name of toEvict) {
|
|
6816
|
+
const client = _shards.get(name);
|
|
6817
|
+
if (client) {
|
|
6818
|
+
client.close();
|
|
6819
|
+
}
|
|
6820
|
+
_shards.delete(name);
|
|
6821
|
+
_shardLastAccess.delete(name);
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
6824
|
+
function getOpenShardCount() {
|
|
6825
|
+
return _shards.size;
|
|
6826
|
+
}
|
|
5862
6827
|
function disposeShards() {
|
|
6828
|
+
if (_evictionTimer) {
|
|
6829
|
+
clearInterval(_evictionTimer);
|
|
6830
|
+
_evictionTimer = null;
|
|
6831
|
+
}
|
|
5863
6832
|
for (const [, client] of _shards) {
|
|
5864
6833
|
client.close();
|
|
5865
6834
|
}
|
|
5866
6835
|
_shards.clear();
|
|
6836
|
+
_shardLastAccess.clear();
|
|
5867
6837
|
_shardingEnabled = false;
|
|
5868
6838
|
_encryptionKey = null;
|
|
5869
6839
|
}
|
|
5870
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6840
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5871
6841
|
var init_shard_manager = __esm({
|
|
5872
6842
|
"src/lib/shard-manager.ts"() {
|
|
5873
6843
|
"use strict";
|
|
5874
6844
|
init_config();
|
|
5875
|
-
SHARDS_DIR =
|
|
6845
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6846
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6847
|
+
MAX_OPEN_SHARDS = 10;
|
|
6848
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5876
6849
|
_shards = /* @__PURE__ */ new Map();
|
|
6850
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6851
|
+
_evictionTimer = null;
|
|
5877
6852
|
_encryptionKey = null;
|
|
5878
6853
|
_shardingEnabled = false;
|
|
5879
6854
|
}
|
|
@@ -6669,8 +7644,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
6669
7644
|
try {
|
|
6670
7645
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
6671
7646
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
6672
|
-
const { readFileSync:
|
|
6673
|
-
const source =
|
|
7647
|
+
const { readFileSync: readFileSync15 } = __require("fs");
|
|
7648
|
+
const source = readFileSync15(filePath, "utf8");
|
|
6674
7649
|
const lines = source.split("\n");
|
|
6675
7650
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
6676
7651
|
let matchLine = -1;
|
|
@@ -6736,9 +7711,9 @@ function extractBash(input, response) {
|
|
|
6736
7711
|
}
|
|
6737
7712
|
function extractGrep(input, response) {
|
|
6738
7713
|
const pattern = String(input.pattern ?? "");
|
|
6739
|
-
const
|
|
7714
|
+
const path23 = input.path ? String(input.path) : "";
|
|
6740
7715
|
const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
|
|
6741
|
-
return `Searched for "${pattern}"${
|
|
7716
|
+
return `Searched for "${pattern}"${path23 ? ` in ${path23}` : ""}
|
|
6742
7717
|
${output.slice(0, MAX_OUTPUT)}`;
|
|
6743
7718
|
}
|
|
6744
7719
|
function extractGlob(input, response) {
|
|
@@ -6841,7 +7816,7 @@ __export(error_detector_exports, {
|
|
|
6841
7816
|
errorFingerprint: () => errorFingerprint,
|
|
6842
7817
|
isExeOsError: () => isExeOsError
|
|
6843
7818
|
});
|
|
6844
|
-
import
|
|
7819
|
+
import crypto7 from "crypto";
|
|
6845
7820
|
function isRealStderr(stderr) {
|
|
6846
7821
|
const lines = stderr.trim().split("\n");
|
|
6847
7822
|
const meaningful = lines.filter(
|
|
@@ -6912,7 +7887,7 @@ function classifyError(errorText) {
|
|
|
6912
7887
|
}
|
|
6913
7888
|
function errorFingerprint(toolName, errorText) {
|
|
6914
7889
|
const normalized = errorText.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z/g, "TIMESTAMP").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "UUID").replace(/\/Users\/[^\s]+/g, "PATH").replace(/:\d+:\d+/g, ":LINE:COL").slice(0, 200);
|
|
6915
|
-
return
|
|
7890
|
+
return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
|
|
6916
7891
|
}
|
|
6917
7892
|
var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
|
|
6918
7893
|
var init_error_detector = __esm({
|
|
@@ -7276,10 +8251,10 @@ async function disposeEmbedder() {
|
|
|
7276
8251
|
async function embedDirect(text) {
|
|
7277
8252
|
const llamaCpp = await import("node-llama-cpp");
|
|
7278
8253
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
7279
|
-
const { existsSync:
|
|
7280
|
-
const
|
|
7281
|
-
const modelPath =
|
|
7282
|
-
if (!
|
|
8254
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
8255
|
+
const path23 = await import("path");
|
|
8256
|
+
const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
8257
|
+
if (!existsSync18(modelPath)) {
|
|
7283
8258
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
7284
8259
|
}
|
|
7285
8260
|
const llama = await llamaCpp.getLlama();
|
|
@@ -7316,8 +8291,8 @@ __export(wiki_client_exports, {
|
|
|
7316
8291
|
listDocuments: () => listDocuments,
|
|
7317
8292
|
listWorkspaces: () => listWorkspaces
|
|
7318
8293
|
});
|
|
7319
|
-
async function wikiFetch(config2,
|
|
7320
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
8294
|
+
async function wikiFetch(config2, path23, method = "GET", body) {
|
|
8295
|
+
const url = `${config2.baseUrl}/api/v1${path23}`;
|
|
7321
8296
|
const headers = {
|
|
7322
8297
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
7323
8298
|
"Content-Type": "application/json"
|
|
@@ -7350,7 +8325,7 @@ async function wikiFetch(config2, path21, method = "GET", body) {
|
|
|
7350
8325
|
}
|
|
7351
8326
|
}
|
|
7352
8327
|
if (!response.ok) {
|
|
7353
|
-
throw new Error(`Wiki API ${method} ${
|
|
8328
|
+
throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
|
|
7354
8329
|
}
|
|
7355
8330
|
return response.json();
|
|
7356
8331
|
} finally {
|
|
@@ -7643,13 +8618,13 @@ __export(graph_rag_exports, {
|
|
|
7643
8618
|
resolveAlias: () => resolveAlias,
|
|
7644
8619
|
storeExtraction: () => storeExtraction
|
|
7645
8620
|
});
|
|
7646
|
-
import
|
|
8621
|
+
import crypto8 from "crypto";
|
|
7647
8622
|
function normalizeEntityName(name) {
|
|
7648
8623
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
7649
8624
|
}
|
|
7650
8625
|
function entityId(name, type) {
|
|
7651
8626
|
const normalized = normalizeEntityName(name);
|
|
7652
|
-
return
|
|
8627
|
+
return crypto8.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
7653
8628
|
}
|
|
7654
8629
|
async function resolveAlias(client, name) {
|
|
7655
8630
|
const normalized = normalizeEntityName(name);
|
|
@@ -7899,7 +8874,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
7899
8874
|
const targetAlias = await resolveAlias(client, r.target);
|
|
7900
8875
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
7901
8876
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
7902
|
-
const relId =
|
|
8877
|
+
const relId = crypto8.randomUUID().slice(0, 16);
|
|
7903
8878
|
try {
|
|
7904
8879
|
await client.execute({
|
|
7905
8880
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -7962,7 +8937,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
7962
8937
|
}
|
|
7963
8938
|
}
|
|
7964
8939
|
for (const h of extraction.hyperedges) {
|
|
7965
|
-
const hId =
|
|
8940
|
+
const hId = crypto8.randomUUID().slice(0, 16);
|
|
7966
8941
|
try {
|
|
7967
8942
|
await client.execute({
|
|
7968
8943
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -8026,7 +9001,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
8026
9001
|
totalEntities += stored.entitiesStored;
|
|
8027
9002
|
totalRelationships += stored.relationshipsStored;
|
|
8028
9003
|
}
|
|
8029
|
-
const contentHash =
|
|
9004
|
+
const contentHash = crypto8.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
8030
9005
|
await client.execute({
|
|
8031
9006
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
8032
9007
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -8371,13 +9346,13 @@ __export(whatsapp_accounts_exports, {
|
|
|
8371
9346
|
getDefaultAccount: () => getDefaultAccount,
|
|
8372
9347
|
loadAccounts: () => loadAccounts
|
|
8373
9348
|
});
|
|
8374
|
-
import { readFileSync as
|
|
9349
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
8375
9350
|
import { join as join2 } from "path";
|
|
8376
9351
|
import { homedir as homedir2 } from "os";
|
|
8377
9352
|
function loadAccounts() {
|
|
8378
9353
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
8379
9354
|
try {
|
|
8380
|
-
const raw =
|
|
9355
|
+
const raw = readFileSync13(CONFIG_PATH2, "utf8");
|
|
8381
9356
|
const parsed = JSON.parse(raw);
|
|
8382
9357
|
if (!Array.isArray(parsed)) {
|
|
8383
9358
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -8433,10 +9408,10 @@ __export(messaging_exports, {
|
|
|
8433
9408
|
sendMessage: () => sendMessage,
|
|
8434
9409
|
setWsClientSend: () => setWsClientSend
|
|
8435
9410
|
});
|
|
8436
|
-
import
|
|
9411
|
+
import crypto10 from "crypto";
|
|
8437
9412
|
function generateUlid() {
|
|
8438
9413
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
8439
|
-
const random =
|
|
9414
|
+
const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
|
|
8440
9415
|
return (timestamp + random).toUpperCase();
|
|
8441
9416
|
}
|
|
8442
9417
|
function rowToMessage(row) {
|
|
@@ -8447,6 +9422,7 @@ function rowToMessage(row) {
|
|
|
8447
9422
|
targetAgent: row.target_agent,
|
|
8448
9423
|
targetProject: row.target_project ?? null,
|
|
8449
9424
|
targetDevice: row.target_device,
|
|
9425
|
+
sessionScope: row.session_scope ?? null,
|
|
8450
9426
|
content: row.content,
|
|
8451
9427
|
priority: row.priority ?? "normal",
|
|
8452
9428
|
status: row.status ?? "pending",
|
|
@@ -8464,15 +9440,17 @@ async function sendMessage(input) {
|
|
|
8464
9440
|
const id = generateUlid();
|
|
8465
9441
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8466
9442
|
const targetDevice = input.targetDevice ?? "local";
|
|
9443
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
8467
9444
|
await client.execute({
|
|
8468
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
8469
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
9445
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
9446
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
8470
9447
|
args: [
|
|
8471
9448
|
id,
|
|
8472
9449
|
input.fromAgent,
|
|
8473
9450
|
input.targetAgent,
|
|
8474
9451
|
input.targetProject ?? null,
|
|
8475
9452
|
targetDevice,
|
|
9453
|
+
sessionScope,
|
|
8476
9454
|
input.content,
|
|
8477
9455
|
input.priority ?? "normal",
|
|
8478
9456
|
now
|
|
@@ -8486,9 +9464,10 @@ async function sendMessage(input) {
|
|
|
8486
9464
|
}
|
|
8487
9465
|
} catch {
|
|
8488
9466
|
}
|
|
9467
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
8489
9468
|
const result = await client.execute({
|
|
8490
|
-
sql:
|
|
8491
|
-
args: [id]
|
|
9469
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
9470
|
+
args: [id, ...sentScope.args]
|
|
8492
9471
|
});
|
|
8493
9472
|
return rowToMessage(result.rows[0]);
|
|
8494
9473
|
}
|
|
@@ -8512,6 +9491,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
8512
9491
|
fromAgent: msg.fromAgent,
|
|
8513
9492
|
targetAgent: msg.targetAgent,
|
|
8514
9493
|
targetProject: msg.targetProject,
|
|
9494
|
+
sessionScope: msg.sessionScope,
|
|
8515
9495
|
content: msg.content,
|
|
8516
9496
|
priority: msg.priority,
|
|
8517
9497
|
createdAt: msg.createdAt
|
|
@@ -8555,7 +9535,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
8555
9535
|
} catch {
|
|
8556
9536
|
const newRetryCount = msg.retryCount + 1;
|
|
8557
9537
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
8558
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
9538
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
8559
9539
|
} else {
|
|
8560
9540
|
await client.execute({
|
|
8561
9541
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -8565,85 +9545,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
8565
9545
|
return false;
|
|
8566
9546
|
}
|
|
8567
9547
|
}
|
|
8568
|
-
async function getPendingMessages(targetAgent) {
|
|
9548
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
8569
9549
|
const client = getClient();
|
|
9550
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8570
9551
|
const result = await client.execute({
|
|
8571
9552
|
sql: `SELECT * FROM messages
|
|
8572
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
9553
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
8573
9554
|
ORDER BY id`,
|
|
8574
|
-
args: [targetAgent]
|
|
9555
|
+
args: [targetAgent, ...scope.args]
|
|
8575
9556
|
});
|
|
8576
9557
|
return result.rows.map((row) => rowToMessage(row));
|
|
8577
9558
|
}
|
|
8578
|
-
async function markRead(messageId) {
|
|
9559
|
+
async function markRead(messageId, sessionScope) {
|
|
8579
9560
|
const client = getClient();
|
|
9561
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8580
9562
|
await client.execute({
|
|
8581
|
-
sql:
|
|
8582
|
-
|
|
9563
|
+
sql: `UPDATE messages SET status = 'read'
|
|
9564
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
9565
|
+
args: [messageId, ...scope.args]
|
|
8583
9566
|
});
|
|
8584
9567
|
}
|
|
8585
|
-
async function markAcknowledged(messageId) {
|
|
9568
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
8586
9569
|
const client = getClient();
|
|
9570
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8587
9571
|
await client.execute({
|
|
8588
|
-
sql:
|
|
8589
|
-
|
|
9572
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
9573
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
9574
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
8590
9575
|
});
|
|
8591
9576
|
}
|
|
8592
|
-
async function markProcessed(messageId) {
|
|
9577
|
+
async function markProcessed(messageId, sessionScope) {
|
|
8593
9578
|
const client = getClient();
|
|
9579
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8594
9580
|
await client.execute({
|
|
8595
|
-
sql:
|
|
8596
|
-
|
|
9581
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
9582
|
+
WHERE id = ?${scope.sql}`,
|
|
9583
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
8597
9584
|
});
|
|
8598
9585
|
}
|
|
8599
|
-
async function getMessageStatus(messageId) {
|
|
9586
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
8600
9587
|
const client = getClient();
|
|
9588
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8601
9589
|
const result = await client.execute({
|
|
8602
|
-
sql:
|
|
8603
|
-
args: [messageId]
|
|
9590
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
9591
|
+
args: [messageId, ...scope.args]
|
|
8604
9592
|
});
|
|
8605
9593
|
return result.rows[0]?.status ?? null;
|
|
8606
9594
|
}
|
|
8607
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
9595
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
8608
9596
|
const client = getClient();
|
|
9597
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8609
9598
|
const result = await client.execute({
|
|
8610
9599
|
sql: `SELECT * FROM messages
|
|
8611
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
9600
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
8612
9601
|
ORDER BY id`,
|
|
8613
|
-
args: [targetAgent]
|
|
9602
|
+
args: [targetAgent, ...scope.args]
|
|
8614
9603
|
});
|
|
8615
9604
|
return result.rows.map((row) => rowToMessage(row));
|
|
8616
9605
|
}
|
|
8617
|
-
async function getReadMessages(targetAgent) {
|
|
9606
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
8618
9607
|
const client = getClient();
|
|
9608
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8619
9609
|
const result = await client.execute({
|
|
8620
|
-
sql:
|
|
8621
|
-
|
|
9610
|
+
sql: `SELECT * FROM messages
|
|
9611
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
9612
|
+
ORDER BY id`,
|
|
9613
|
+
args: [targetAgent, ...scope.args]
|
|
8622
9614
|
});
|
|
8623
9615
|
return result.rows.map((row) => rowToMessage(row));
|
|
8624
9616
|
}
|
|
8625
|
-
async function markFailed(messageId, reason) {
|
|
9617
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
8626
9618
|
const client = getClient();
|
|
9619
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8627
9620
|
await client.execute({
|
|
8628
|
-
sql:
|
|
8629
|
-
|
|
9621
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
9622
|
+
WHERE id = ?${scope.sql}`,
|
|
9623
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
8630
9624
|
});
|
|
8631
9625
|
}
|
|
8632
|
-
async function getFailedMessages() {
|
|
9626
|
+
async function getFailedMessages(sessionScope) {
|
|
8633
9627
|
const client = getClient();
|
|
9628
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8634
9629
|
const result = await client.execute({
|
|
8635
|
-
sql:
|
|
8636
|
-
args: []
|
|
9630
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
9631
|
+
args: [...scope.args]
|
|
8637
9632
|
});
|
|
8638
9633
|
return result.rows.map((row) => rowToMessage(row));
|
|
8639
9634
|
}
|
|
8640
|
-
async function retryPendingMessages() {
|
|
9635
|
+
async function retryPendingMessages(sessionScope) {
|
|
8641
9636
|
const client = getClient();
|
|
9637
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
8642
9638
|
const result = await client.execute({
|
|
8643
9639
|
sql: `SELECT * FROM messages
|
|
8644
|
-
WHERE status = 'pending' AND retry_count <
|
|
9640
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
8645
9641
|
ORDER BY id`,
|
|
8646
|
-
args: [MAX_RETRIES3]
|
|
9642
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
8647
9643
|
});
|
|
8648
9644
|
let delivered = 0;
|
|
8649
9645
|
for (const row of result.rows) {
|
|
@@ -8662,6 +9658,7 @@ var init_messaging = __esm({
|
|
|
8662
9658
|
"use strict";
|
|
8663
9659
|
init_database();
|
|
8664
9660
|
init_tmux_routing();
|
|
9661
|
+
init_task_scope();
|
|
8665
9662
|
MAX_RETRIES3 = 10;
|
|
8666
9663
|
_wsClientSend = null;
|
|
8667
9664
|
}
|
|
@@ -11142,11 +12139,11 @@ init_crm_bridge();
|
|
|
11142
12139
|
|
|
11143
12140
|
// src/lib/pipeline-router.ts
|
|
11144
12141
|
init_database();
|
|
11145
|
-
import
|
|
12142
|
+
import crypto9 from "crypto";
|
|
11146
12143
|
async function sinkConversationStore(msg, agentResponse, agentName) {
|
|
11147
12144
|
try {
|
|
11148
12145
|
const client = getClient();
|
|
11149
|
-
const id =
|
|
12146
|
+
const id = crypto9.randomUUID();
|
|
11150
12147
|
const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
|
|
11151
12148
|
await client.execute({
|
|
11152
12149
|
sql: `INSERT INTO conversations
|
|
@@ -11196,7 +12193,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
|
|
|
11196
12193
|
].filter(Boolean).join("\n");
|
|
11197
12194
|
const vector = await embed2(rawText);
|
|
11198
12195
|
await writeMemory2({
|
|
11199
|
-
id:
|
|
12196
|
+
id: crypto9.randomUUID(),
|
|
11200
12197
|
agent_id: agentName ?? "gateway",
|
|
11201
12198
|
agent_role: "gateway",
|
|
11202
12199
|
session_id: `gateway-${msg.platform}`,
|
|
@@ -13877,12 +14874,12 @@ var SlackAdapter = class {
|
|
|
13877
14874
|
// src/gateway/adapters/imessage.ts
|
|
13878
14875
|
import { execFile } from "child_process";
|
|
13879
14876
|
import { promisify } from "util";
|
|
13880
|
-
import
|
|
13881
|
-
import
|
|
14877
|
+
import os13 from "os";
|
|
14878
|
+
import path21 from "path";
|
|
13882
14879
|
var execFileAsync = promisify(execFile);
|
|
13883
14880
|
var POLL_INTERVAL_MS = 5e3;
|
|
13884
|
-
var MESSAGES_DB_PATH =
|
|
13885
|
-
process.env.HOME ??
|
|
14881
|
+
var MESSAGES_DB_PATH = path21.join(
|
|
14882
|
+
process.env.HOME ?? os13.homedir(),
|
|
13886
14883
|
"Library/Messages/chat.db"
|
|
13887
14884
|
);
|
|
13888
14885
|
var IMessageAdapter = class {
|
|
@@ -14725,11 +15722,11 @@ async function ensureCRMContact(info) {
|
|
|
14725
15722
|
}
|
|
14726
15723
|
|
|
14727
15724
|
// src/automation/trigger-engine.ts
|
|
14728
|
-
import { readFileSync as
|
|
15725
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
14729
15726
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
14730
|
-
import
|
|
14731
|
-
import
|
|
14732
|
-
var TRIGGERS_PATH =
|
|
15727
|
+
import path22 from "path";
|
|
15728
|
+
import os14 from "os";
|
|
15729
|
+
var TRIGGERS_PATH = path22.join(os14.homedir(), ".exe-os", "triggers.json");
|
|
14733
15730
|
var GRAPH_API_VERSION = "v21.0";
|
|
14734
15731
|
function substituteTemplate(template, record) {
|
|
14735
15732
|
return template.replace(
|
|
@@ -14783,9 +15780,9 @@ function evaluateConditions(conditions, record) {
|
|
|
14783
15780
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
14784
15781
|
}
|
|
14785
15782
|
function loadTriggers(project) {
|
|
14786
|
-
if (!
|
|
15783
|
+
if (!existsSync17(TRIGGERS_PATH)) return [];
|
|
14787
15784
|
try {
|
|
14788
|
-
const raw =
|
|
15785
|
+
const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
|
|
14789
15786
|
const all = JSON.parse(raw);
|
|
14790
15787
|
if (!Array.isArray(all)) return [];
|
|
14791
15788
|
if (project) {
|