@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
|
@@ -80,9 +80,47 @@ var init_db_retry = __esm({
|
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
// src/lib/secure-files.ts
|
|
84
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
85
|
+
import { chmod, mkdir } from "fs/promises";
|
|
86
|
+
async function ensurePrivateDir(dirPath) {
|
|
87
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
88
|
+
try {
|
|
89
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function ensurePrivateDirSync(dirPath) {
|
|
94
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
95
|
+
try {
|
|
96
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function enforcePrivateFile(filePath) {
|
|
101
|
+
try {
|
|
102
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function enforcePrivateFileSync(filePath) {
|
|
107
|
+
try {
|
|
108
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
113
|
+
var init_secure_files = __esm({
|
|
114
|
+
"src/lib/secure-files.ts"() {
|
|
115
|
+
"use strict";
|
|
116
|
+
PRIVATE_DIR_MODE = 448;
|
|
117
|
+
PRIVATE_FILE_MODE = 384;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
83
121
|
// src/lib/config.ts
|
|
84
|
-
import { readFile, writeFile
|
|
85
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
122
|
+
import { readFile, writeFile } from "fs/promises";
|
|
123
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
86
124
|
import path from "path";
|
|
87
125
|
import os from "os";
|
|
88
126
|
function resolveDataDir() {
|
|
@@ -90,7 +128,7 @@ function resolveDataDir() {
|
|
|
90
128
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
91
129
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
92
130
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
93
|
-
if (!
|
|
131
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
94
132
|
try {
|
|
95
133
|
renameSync(legacyDir, newDir);
|
|
96
134
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -153,9 +191,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
153
191
|
}
|
|
154
192
|
async function loadConfig() {
|
|
155
193
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
156
|
-
await
|
|
194
|
+
await ensurePrivateDir(dir);
|
|
157
195
|
const configPath = path.join(dir, "config.json");
|
|
158
|
-
if (!
|
|
196
|
+
if (!existsSync2(configPath)) {
|
|
159
197
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
160
198
|
}
|
|
161
199
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -168,6 +206,7 @@ async function loadConfig() {
|
|
|
168
206
|
`);
|
|
169
207
|
try {
|
|
170
208
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
209
|
+
await enforcePrivateFile(configPath);
|
|
171
210
|
} catch {
|
|
172
211
|
}
|
|
173
212
|
}
|
|
@@ -187,6 +226,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
187
226
|
var init_config = __esm({
|
|
188
227
|
"src/lib/config.ts"() {
|
|
189
228
|
"use strict";
|
|
229
|
+
init_secure_files();
|
|
190
230
|
EXE_AI_DIR = resolveDataDir();
|
|
191
231
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
192
232
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -263,6 +303,120 @@ var init_config = __esm({
|
|
|
263
303
|
}
|
|
264
304
|
});
|
|
265
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 path2 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(config) {
|
|
357
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
358
|
+
ensurePrivateDirSync(dir);
|
|
359
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
360
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
361
|
+
}
|
|
362
|
+
function getAgentRuntime(agentId) {
|
|
363
|
+
const config = loadAgentConfig();
|
|
364
|
+
const entry = config[agentId];
|
|
365
|
+
if (entry) return entry;
|
|
366
|
+
const orgDefault = config["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 config = loadAgentConfig();
|
|
385
|
+
config[agentId] = { runtime, model };
|
|
386
|
+
saveAgentConfig(config);
|
|
387
|
+
return { ok: true };
|
|
388
|
+
}
|
|
389
|
+
function clearAgentRuntime(agentId) {
|
|
390
|
+
const config = loadAgentConfig();
|
|
391
|
+
delete config[agentId];
|
|
392
|
+
saveAgentConfig(config);
|
|
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 = path2.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
|
+
|
|
266
420
|
// src/lib/employees.ts
|
|
267
421
|
var employees_exports = {};
|
|
268
422
|
__export(employees_exports, {
|
|
@@ -278,6 +432,7 @@ __export(employees_exports, {
|
|
|
278
432
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
279
433
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
280
434
|
hasRole: () => hasRole,
|
|
435
|
+
hireEmployee: () => hireEmployee,
|
|
281
436
|
isCoordinatorName: () => isCoordinatorName,
|
|
282
437
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
283
438
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -290,9 +445,9 @@ __export(employees_exports, {
|
|
|
290
445
|
validateEmployeeName: () => validateEmployeeName
|
|
291
446
|
});
|
|
292
447
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
293
|
-
import { existsSync as
|
|
448
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
294
449
|
import { execSync } from "child_process";
|
|
295
|
-
import
|
|
450
|
+
import path3 from "path";
|
|
296
451
|
import os2 from "os";
|
|
297
452
|
function normalizeRole(role) {
|
|
298
453
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -329,7 +484,7 @@ function validateEmployeeName(name) {
|
|
|
329
484
|
return { valid: true };
|
|
330
485
|
}
|
|
331
486
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
332
|
-
if (!
|
|
487
|
+
if (!existsSync4(employeesPath)) {
|
|
333
488
|
return [];
|
|
334
489
|
}
|
|
335
490
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -340,13 +495,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
340
495
|
}
|
|
341
496
|
}
|
|
342
497
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
343
|
-
await mkdir2(
|
|
498
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
344
499
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
345
500
|
}
|
|
346
501
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
347
|
-
if (!
|
|
502
|
+
if (!existsSync4(employeesPath)) return [];
|
|
348
503
|
try {
|
|
349
|
-
return JSON.parse(
|
|
504
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
350
505
|
} catch {
|
|
351
506
|
return [];
|
|
352
507
|
}
|
|
@@ -388,6 +543,52 @@ function addEmployee(employees, employee) {
|
|
|
388
543
|
}
|
|
389
544
|
return [...employees, normalized];
|
|
390
545
|
}
|
|
546
|
+
function appendToCoordinatorTeam(employee) {
|
|
547
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
548
|
+
if (!coordinator) return;
|
|
549
|
+
const idPath = path3.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 config = loadAgentConfig2();
|
|
583
|
+
const name = employee.name.toLowerCase();
|
|
584
|
+
if (!config[name] && config["default"]) {
|
|
585
|
+
config[name] = { ...config["default"] };
|
|
586
|
+
saveAgentConfig2(config);
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
}
|
|
590
|
+
return updated;
|
|
591
|
+
}
|
|
391
592
|
async function normalizeRosterCase(rosterPath) {
|
|
392
593
|
const employees = await loadEmployees(rosterPath);
|
|
393
594
|
let changed = false;
|
|
@@ -397,14 +598,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
397
598
|
emp.name = emp.name.toLowerCase();
|
|
398
599
|
changed = true;
|
|
399
600
|
try {
|
|
400
|
-
const identityDir =
|
|
401
|
-
const oldPath =
|
|
402
|
-
const newPath =
|
|
403
|
-
if (
|
|
601
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
602
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
603
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
604
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
404
605
|
renameSync2(oldPath, newPath);
|
|
405
|
-
} else if (
|
|
406
|
-
const content =
|
|
407
|
-
|
|
606
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
607
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
608
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
408
609
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
409
610
|
unlinkSync(oldPath);
|
|
410
611
|
}
|
|
@@ -434,7 +635,7 @@ function registerBinSymlinks(name) {
|
|
|
434
635
|
errors.push("Could not find 'exe-os' in PATH");
|
|
435
636
|
return { created, skipped, errors };
|
|
436
637
|
}
|
|
437
|
-
const binDir =
|
|
638
|
+
const binDir = path3.dirname(exeBinPath);
|
|
438
639
|
let target;
|
|
439
640
|
try {
|
|
440
641
|
target = readlinkSync(exeBinPath);
|
|
@@ -444,8 +645,8 @@ function registerBinSymlinks(name) {
|
|
|
444
645
|
}
|
|
445
646
|
for (const suffix of ["", "-opencode"]) {
|
|
446
647
|
const linkName = `${name}${suffix}`;
|
|
447
|
-
const linkPath =
|
|
448
|
-
if (
|
|
648
|
+
const linkPath = path3.join(binDir, linkName);
|
|
649
|
+
if (existsSync4(linkPath)) {
|
|
449
650
|
skipped.push(linkName);
|
|
450
651
|
continue;
|
|
451
652
|
}
|
|
@@ -458,21 +659,619 @@ function registerBinSymlinks(name) {
|
|
|
458
659
|
}
|
|
459
660
|
return { created, skipped, errors };
|
|
460
661
|
}
|
|
461
|
-
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;
|
|
462
663
|
var init_employees = __esm({
|
|
463
664
|
"src/lib/employees.ts"() {
|
|
464
665
|
"use strict";
|
|
465
666
|
init_config();
|
|
466
|
-
EMPLOYEES_PATH =
|
|
667
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
467
668
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
468
669
|
COORDINATOR_ROLE = "COO";
|
|
469
670
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
671
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
672
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// src/lib/database-adapter.ts
|
|
677
|
+
import os3 from "os";
|
|
678
|
+
import path4 from "path";
|
|
679
|
+
import { createRequire } from "module";
|
|
680
|
+
import { pathToFileURL } from "url";
|
|
681
|
+
function quotedIdentifier(identifier) {
|
|
682
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
683
|
+
}
|
|
684
|
+
function unqualifiedTableName(name) {
|
|
685
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
686
|
+
const parts = raw.split(".");
|
|
687
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
688
|
+
}
|
|
689
|
+
function stripTrailingSemicolon(sql) {
|
|
690
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
691
|
+
}
|
|
692
|
+
function appendClause(sql, clause) {
|
|
693
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
694
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
695
|
+
if (!returningMatch) {
|
|
696
|
+
return `${trimmed}${clause}`;
|
|
697
|
+
}
|
|
698
|
+
const idx = returningMatch.index;
|
|
699
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
700
|
+
}
|
|
701
|
+
function normalizeStatement(stmt) {
|
|
702
|
+
if (typeof stmt === "string") {
|
|
703
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
704
|
+
}
|
|
705
|
+
const sql = stmt.sql;
|
|
706
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
707
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
708
|
+
}
|
|
709
|
+
return { kind: "named", sql, args: stmt.args };
|
|
710
|
+
}
|
|
711
|
+
function rewriteBooleanLiterals(sql) {
|
|
712
|
+
let out = sql;
|
|
713
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
714
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
715
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
716
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
717
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
718
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
719
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
720
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
721
|
+
}
|
|
722
|
+
return out;
|
|
723
|
+
}
|
|
724
|
+
function rewriteInsertOrIgnore(sql) {
|
|
725
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
726
|
+
return sql;
|
|
727
|
+
}
|
|
728
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
729
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
730
|
+
}
|
|
731
|
+
function rewriteInsertOrReplace(sql) {
|
|
732
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
733
|
+
if (!match) {
|
|
734
|
+
return sql;
|
|
735
|
+
}
|
|
736
|
+
const rawTable = match[1];
|
|
737
|
+
const rawColumns = match[2];
|
|
738
|
+
const remainder = match[3];
|
|
739
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
740
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
741
|
+
if (!conflictKeys?.length) {
|
|
742
|
+
return sql;
|
|
743
|
+
}
|
|
744
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
745
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
746
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
747
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
748
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
749
|
+
}
|
|
750
|
+
function rewriteSql(sql) {
|
|
751
|
+
let out = sql;
|
|
752
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
753
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
754
|
+
out = rewriteBooleanLiterals(out);
|
|
755
|
+
out = rewriteInsertOrReplace(out);
|
|
756
|
+
out = rewriteInsertOrIgnore(out);
|
|
757
|
+
return stripTrailingSemicolon(out);
|
|
758
|
+
}
|
|
759
|
+
function toBoolean(value) {
|
|
760
|
+
if (value === null || value === void 0) return value;
|
|
761
|
+
if (typeof value === "boolean") return value;
|
|
762
|
+
if (typeof value === "number") return value !== 0;
|
|
763
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
764
|
+
if (typeof value === "string") {
|
|
765
|
+
const normalized = value.trim().toLowerCase();
|
|
766
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
767
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
768
|
+
}
|
|
769
|
+
return Boolean(value);
|
|
770
|
+
}
|
|
771
|
+
function countQuestionMarks(sql, end) {
|
|
772
|
+
let count = 0;
|
|
773
|
+
let inSingle = false;
|
|
774
|
+
let inDouble = false;
|
|
775
|
+
let inLineComment = false;
|
|
776
|
+
let inBlockComment = false;
|
|
777
|
+
for (let i = 0; i < end; i++) {
|
|
778
|
+
const ch = sql[i];
|
|
779
|
+
const next = sql[i + 1];
|
|
780
|
+
if (inLineComment) {
|
|
781
|
+
if (ch === "\n") inLineComment = false;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (inBlockComment) {
|
|
785
|
+
if (ch === "*" && next === "/") {
|
|
786
|
+
inBlockComment = false;
|
|
787
|
+
i += 1;
|
|
788
|
+
}
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
792
|
+
inLineComment = true;
|
|
793
|
+
i += 1;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
797
|
+
inBlockComment = true;
|
|
798
|
+
i += 1;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
802
|
+
inSingle = !inSingle;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
806
|
+
inDouble = !inDouble;
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
810
|
+
count += 1;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return count;
|
|
814
|
+
}
|
|
815
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
816
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
817
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
818
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
819
|
+
for (const match of sql.matchAll(pattern)) {
|
|
820
|
+
const matchText = match[0];
|
|
821
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
822
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return indexes;
|
|
826
|
+
}
|
|
827
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
828
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
829
|
+
if (!match) return;
|
|
830
|
+
const rawTable = match[1];
|
|
831
|
+
const rawColumns = match[2];
|
|
832
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
833
|
+
if (!boolColumns?.size) return;
|
|
834
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
835
|
+
for (const [index, column] of columns.entries()) {
|
|
836
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
837
|
+
args[index] = toBoolean(args[index]);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
842
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
843
|
+
if (!match) return;
|
|
844
|
+
const rawTable = match[1];
|
|
845
|
+
const setClause = match[2];
|
|
846
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
847
|
+
if (!boolColumns?.size) return;
|
|
848
|
+
const assignments = setClause.split(",");
|
|
849
|
+
let placeholderIndex = 0;
|
|
850
|
+
for (const assignment of assignments) {
|
|
851
|
+
if (!assignment.includes("?")) continue;
|
|
852
|
+
placeholderIndex += 1;
|
|
853
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
854
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
855
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function coerceBooleanArgs(sql, args) {
|
|
860
|
+
const nextArgs = [...args];
|
|
861
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
862
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
863
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
864
|
+
for (const index of placeholderIndexes) {
|
|
865
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
866
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return nextArgs;
|
|
870
|
+
}
|
|
871
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
872
|
+
let out = "";
|
|
873
|
+
let placeholder = 0;
|
|
874
|
+
let inSingle = false;
|
|
875
|
+
let inDouble = false;
|
|
876
|
+
let inLineComment = false;
|
|
877
|
+
let inBlockComment = false;
|
|
878
|
+
for (let i = 0; i < sql.length; i++) {
|
|
879
|
+
const ch = sql[i];
|
|
880
|
+
const next = sql[i + 1];
|
|
881
|
+
if (inLineComment) {
|
|
882
|
+
out += ch;
|
|
883
|
+
if (ch === "\n") inLineComment = false;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (inBlockComment) {
|
|
887
|
+
out += ch;
|
|
888
|
+
if (ch === "*" && next === "/") {
|
|
889
|
+
out += next;
|
|
890
|
+
inBlockComment = false;
|
|
891
|
+
i += 1;
|
|
892
|
+
}
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
896
|
+
out += ch + next;
|
|
897
|
+
inLineComment = true;
|
|
898
|
+
i += 1;
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
902
|
+
out += ch + next;
|
|
903
|
+
inBlockComment = true;
|
|
904
|
+
i += 1;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
908
|
+
inSingle = !inSingle;
|
|
909
|
+
out += ch;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
913
|
+
inDouble = !inDouble;
|
|
914
|
+
out += ch;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
918
|
+
placeholder += 1;
|
|
919
|
+
out += `$${placeholder}`;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
out += ch;
|
|
923
|
+
}
|
|
924
|
+
return out;
|
|
925
|
+
}
|
|
926
|
+
function translateStatementForPostgres(stmt) {
|
|
927
|
+
const normalized = normalizeStatement(stmt);
|
|
928
|
+
if (normalized.kind === "named") {
|
|
929
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
930
|
+
}
|
|
931
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
932
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
933
|
+
return {
|
|
934
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
935
|
+
args: coercedArgs
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function shouldBypassPostgres(stmt) {
|
|
939
|
+
const normalized = normalizeStatement(stmt);
|
|
940
|
+
if (normalized.kind === "named") {
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
944
|
+
}
|
|
945
|
+
function shouldFallbackOnError(error) {
|
|
946
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
947
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
948
|
+
}
|
|
949
|
+
function isReadQuery(sql) {
|
|
950
|
+
const trimmed = sql.trimStart();
|
|
951
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
952
|
+
}
|
|
953
|
+
function buildRow(row, columns) {
|
|
954
|
+
const values = columns.map((column) => row[column]);
|
|
955
|
+
return Object.assign(values, row);
|
|
956
|
+
}
|
|
957
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
958
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
959
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
960
|
+
return {
|
|
961
|
+
columns,
|
|
962
|
+
columnTypes: columns.map(() => ""),
|
|
963
|
+
rows: resultRows,
|
|
964
|
+
rowsAffected,
|
|
965
|
+
lastInsertRowid: void 0,
|
|
966
|
+
toJSON() {
|
|
967
|
+
return {
|
|
968
|
+
columns,
|
|
969
|
+
columnTypes: columns.map(() => ""),
|
|
970
|
+
rows,
|
|
971
|
+
rowsAffected,
|
|
972
|
+
lastInsertRowid: void 0
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
async function loadPrismaClient() {
|
|
978
|
+
if (!prismaClientPromise) {
|
|
979
|
+
prismaClientPromise = (async () => {
|
|
980
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
981
|
+
if (explicitPath) {
|
|
982
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
983
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
984
|
+
if (!PrismaClient2) {
|
|
985
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
986
|
+
}
|
|
987
|
+
return new PrismaClient2();
|
|
988
|
+
}
|
|
989
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
990
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
991
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
992
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
993
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
994
|
+
if (!PrismaClient) {
|
|
995
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
996
|
+
}
|
|
997
|
+
return new PrismaClient();
|
|
998
|
+
})();
|
|
999
|
+
}
|
|
1000
|
+
return prismaClientPromise;
|
|
1001
|
+
}
|
|
1002
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1003
|
+
if (!compatibilityBootstrapPromise) {
|
|
1004
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1005
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1006
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1007
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1008
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1009
|
+
relation
|
|
1010
|
+
);
|
|
1011
|
+
if (!rows[0]?.regclass) {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
await prisma.$executeRawUnsafe(
|
|
1015
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
})();
|
|
1019
|
+
}
|
|
1020
|
+
return compatibilityBootstrapPromise;
|
|
1021
|
+
}
|
|
1022
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1023
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1024
|
+
if (isReadQuery(translated.sql)) {
|
|
1025
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1026
|
+
translated.sql,
|
|
1027
|
+
...translated.args
|
|
1028
|
+
);
|
|
1029
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1030
|
+
}
|
|
1031
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1032
|
+
return buildResultSet([], rowsAffected);
|
|
1033
|
+
}
|
|
1034
|
+
function splitSqlStatements(sql) {
|
|
1035
|
+
const parts = [];
|
|
1036
|
+
let current = "";
|
|
1037
|
+
let inSingle = false;
|
|
1038
|
+
let inDouble = false;
|
|
1039
|
+
let inLineComment = false;
|
|
1040
|
+
let inBlockComment = false;
|
|
1041
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1042
|
+
const ch = sql[i];
|
|
1043
|
+
const next = sql[i + 1];
|
|
1044
|
+
if (inLineComment) {
|
|
1045
|
+
current += ch;
|
|
1046
|
+
if (ch === "\n") inLineComment = false;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (inBlockComment) {
|
|
1050
|
+
current += ch;
|
|
1051
|
+
if (ch === "*" && next === "/") {
|
|
1052
|
+
current += next;
|
|
1053
|
+
inBlockComment = false;
|
|
1054
|
+
i += 1;
|
|
1055
|
+
}
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1059
|
+
current += ch + next;
|
|
1060
|
+
inLineComment = true;
|
|
1061
|
+
i += 1;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1065
|
+
current += ch + next;
|
|
1066
|
+
inBlockComment = true;
|
|
1067
|
+
i += 1;
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1071
|
+
inSingle = !inSingle;
|
|
1072
|
+
current += ch;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1076
|
+
inDouble = !inDouble;
|
|
1077
|
+
current += ch;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1081
|
+
if (current.trim()) {
|
|
1082
|
+
parts.push(current.trim());
|
|
1083
|
+
}
|
|
1084
|
+
current = "";
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
current += ch;
|
|
1088
|
+
}
|
|
1089
|
+
if (current.trim()) {
|
|
1090
|
+
parts.push(current.trim());
|
|
1091
|
+
}
|
|
1092
|
+
return parts;
|
|
1093
|
+
}
|
|
1094
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1095
|
+
const prisma = await loadPrismaClient();
|
|
1096
|
+
await ensureCompatibilityViews(prisma);
|
|
1097
|
+
let closed = false;
|
|
1098
|
+
let adapter;
|
|
1099
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1100
|
+
if (!fallbackClient) {
|
|
1101
|
+
if (error) throw error;
|
|
1102
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1103
|
+
}
|
|
1104
|
+
if (error) {
|
|
1105
|
+
process.stderr.write(
|
|
1106
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1107
|
+
`
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
return fallbackClient.execute(stmt);
|
|
1111
|
+
};
|
|
1112
|
+
adapter = {
|
|
1113
|
+
async execute(stmt) {
|
|
1114
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1115
|
+
return fallbackExecute(stmt);
|
|
1116
|
+
}
|
|
1117
|
+
try {
|
|
1118
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
if (shouldFallbackOnError(error)) {
|
|
1121
|
+
return fallbackExecute(stmt, error);
|
|
1122
|
+
}
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
async batch(stmts, mode) {
|
|
1127
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1128
|
+
if (!fallbackClient) {
|
|
1129
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1130
|
+
}
|
|
1131
|
+
return fallbackClient.batch(stmts, mode);
|
|
1132
|
+
}
|
|
1133
|
+
try {
|
|
1134
|
+
if (prisma.$transaction) {
|
|
1135
|
+
return await prisma.$transaction(async (tx) => {
|
|
1136
|
+
const results2 = [];
|
|
1137
|
+
for (const stmt of stmts) {
|
|
1138
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1139
|
+
}
|
|
1140
|
+
return results2;
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
const results = [];
|
|
1144
|
+
for (const stmt of stmts) {
|
|
1145
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1146
|
+
}
|
|
1147
|
+
return results;
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1150
|
+
process.stderr.write(
|
|
1151
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1152
|
+
`
|
|
1153
|
+
);
|
|
1154
|
+
return fallbackClient.batch(stmts, mode);
|
|
1155
|
+
}
|
|
1156
|
+
throw error;
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
async migrate(stmts) {
|
|
1160
|
+
if (fallbackClient) {
|
|
1161
|
+
return fallbackClient.migrate(stmts);
|
|
1162
|
+
}
|
|
1163
|
+
return adapter.batch(stmts, "deferred");
|
|
1164
|
+
},
|
|
1165
|
+
async transaction(mode) {
|
|
1166
|
+
if (!fallbackClient) {
|
|
1167
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1168
|
+
}
|
|
1169
|
+
return fallbackClient.transaction(mode);
|
|
1170
|
+
},
|
|
1171
|
+
async executeMultiple(sql) {
|
|
1172
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1173
|
+
return fallbackClient.executeMultiple(sql);
|
|
1174
|
+
}
|
|
1175
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1176
|
+
await adapter.execute(statement);
|
|
1177
|
+
}
|
|
1178
|
+
},
|
|
1179
|
+
async sync() {
|
|
1180
|
+
if (fallbackClient) {
|
|
1181
|
+
return fallbackClient.sync();
|
|
1182
|
+
}
|
|
1183
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1184
|
+
},
|
|
1185
|
+
close() {
|
|
1186
|
+
closed = true;
|
|
1187
|
+
prismaClientPromise = null;
|
|
1188
|
+
compatibilityBootstrapPromise = null;
|
|
1189
|
+
void prisma.$disconnect?.();
|
|
1190
|
+
},
|
|
1191
|
+
get closed() {
|
|
1192
|
+
return closed;
|
|
1193
|
+
},
|
|
1194
|
+
get protocol() {
|
|
1195
|
+
return "prisma-postgres";
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
return adapter;
|
|
1199
|
+
}
|
|
1200
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1201
|
+
var init_database_adapter = __esm({
|
|
1202
|
+
"src/lib/database-adapter.ts"() {
|
|
1203
|
+
"use strict";
|
|
1204
|
+
VIEW_MAPPINGS = [
|
|
1205
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1206
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1207
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1208
|
+
{ view: "entities", source: "memory.entities" },
|
|
1209
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1210
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1211
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1212
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1213
|
+
{ view: "messages", source: "memory.messages" },
|
|
1214
|
+
{ view: "users", source: "wiki.users" },
|
|
1215
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1216
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1217
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1218
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1219
|
+
];
|
|
1220
|
+
UPSERT_KEYS = {
|
|
1221
|
+
memories: ["id"],
|
|
1222
|
+
tasks: ["id"],
|
|
1223
|
+
behaviors: ["id"],
|
|
1224
|
+
entities: ["id"],
|
|
1225
|
+
relationships: ["id"],
|
|
1226
|
+
entity_aliases: ["alias"],
|
|
1227
|
+
notifications: ["id"],
|
|
1228
|
+
messages: ["id"],
|
|
1229
|
+
users: ["id"],
|
|
1230
|
+
workspaces: ["id"],
|
|
1231
|
+
workspace_users: ["id"],
|
|
1232
|
+
documents: ["id"],
|
|
1233
|
+
chats: ["id"]
|
|
1234
|
+
};
|
|
1235
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1236
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1237
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1238
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1239
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1240
|
+
};
|
|
1241
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1242
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1243
|
+
);
|
|
1244
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1245
|
+
/\bPRAGMA\b/i,
|
|
1246
|
+
/\bsqlite_master\b/i,
|
|
1247
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1248
|
+
/\bMATCH\b/i,
|
|
1249
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1250
|
+
/\bjson_extract\s*\(/i,
|
|
1251
|
+
/\bjulianday\s*\(/i,
|
|
1252
|
+
/\bstrftime\s*\(/i,
|
|
1253
|
+
/\blast_insert_rowid\s*\(/i
|
|
1254
|
+
];
|
|
1255
|
+
prismaClientPromise = null;
|
|
1256
|
+
compatibilityBootstrapPromise = null;
|
|
470
1257
|
}
|
|
471
1258
|
});
|
|
472
1259
|
|
|
473
1260
|
// src/lib/database.ts
|
|
474
1261
|
import { createClient } from "@libsql/client";
|
|
475
1262
|
async function initDatabase(config) {
|
|
1263
|
+
if (_walCheckpointTimer) {
|
|
1264
|
+
clearInterval(_walCheckpointTimer);
|
|
1265
|
+
_walCheckpointTimer = null;
|
|
1266
|
+
}
|
|
1267
|
+
if (_daemonClient) {
|
|
1268
|
+
_daemonClient.close();
|
|
1269
|
+
_daemonClient = null;
|
|
1270
|
+
}
|
|
1271
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1272
|
+
_adapterClient.close();
|
|
1273
|
+
}
|
|
1274
|
+
_adapterClient = null;
|
|
476
1275
|
if (_client) {
|
|
477
1276
|
_client.close();
|
|
478
1277
|
_client = null;
|
|
@@ -486,6 +1285,7 @@ async function initDatabase(config) {
|
|
|
486
1285
|
}
|
|
487
1286
|
_client = createClient(opts);
|
|
488
1287
|
_resilientClient = wrapWithRetry(_client);
|
|
1288
|
+
_adapterClient = _resilientClient;
|
|
489
1289
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
490
1290
|
});
|
|
491
1291
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -496,11 +1296,17 @@ async function initDatabase(config) {
|
|
|
496
1296
|
});
|
|
497
1297
|
}, 3e4);
|
|
498
1298
|
_walCheckpointTimer.unref();
|
|
1299
|
+
if (process.env.DATABASE_URL) {
|
|
1300
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1301
|
+
}
|
|
499
1302
|
}
|
|
500
1303
|
function getClient() {
|
|
501
|
-
if (!
|
|
1304
|
+
if (!_adapterClient) {
|
|
502
1305
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
503
1306
|
}
|
|
1307
|
+
if (process.env.DATABASE_URL) {
|
|
1308
|
+
return _adapterClient;
|
|
1309
|
+
}
|
|
504
1310
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
505
1311
|
return _resilientClient;
|
|
506
1312
|
}
|
|
@@ -793,6 +1599,7 @@ async function ensureSchema() {
|
|
|
793
1599
|
project TEXT NOT NULL,
|
|
794
1600
|
summary TEXT NOT NULL,
|
|
795
1601
|
task_file TEXT,
|
|
1602
|
+
session_scope TEXT,
|
|
796
1603
|
read INTEGER NOT NULL DEFAULT 0,
|
|
797
1604
|
created_at TEXT NOT NULL
|
|
798
1605
|
);
|
|
@@ -801,7 +1608,7 @@ async function ensureSchema() {
|
|
|
801
1608
|
ON notifications(read);
|
|
802
1609
|
|
|
803
1610
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
804
|
-
ON notifications(agent_id);
|
|
1611
|
+
ON notifications(agent_id, session_scope);
|
|
805
1612
|
|
|
806
1613
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
807
1614
|
ON notifications(task_file);
|
|
@@ -839,6 +1646,7 @@ async function ensureSchema() {
|
|
|
839
1646
|
target_agent TEXT NOT NULL,
|
|
840
1647
|
target_project TEXT,
|
|
841
1648
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1649
|
+
session_scope TEXT,
|
|
842
1650
|
content TEXT NOT NULL,
|
|
843
1651
|
priority TEXT DEFAULT 'normal',
|
|
844
1652
|
status TEXT DEFAULT 'pending',
|
|
@@ -852,10 +1660,31 @@ async function ensureSchema() {
|
|
|
852
1660
|
);
|
|
853
1661
|
|
|
854
1662
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
855
|
-
ON messages(target_agent, status);
|
|
1663
|
+
ON messages(target_agent, session_scope, status);
|
|
856
1664
|
|
|
857
1665
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
858
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1666
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1667
|
+
`);
|
|
1668
|
+
try {
|
|
1669
|
+
await client.execute({
|
|
1670
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1671
|
+
args: []
|
|
1672
|
+
});
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
try {
|
|
1676
|
+
await client.execute({
|
|
1677
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1678
|
+
args: []
|
|
1679
|
+
});
|
|
1680
|
+
} catch {
|
|
1681
|
+
}
|
|
1682
|
+
await client.executeMultiple(`
|
|
1683
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1684
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1685
|
+
|
|
1686
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1687
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
859
1688
|
`);
|
|
860
1689
|
try {
|
|
861
1690
|
await client.execute({
|
|
@@ -1439,17 +2268,26 @@ async function ensureSchema() {
|
|
|
1439
2268
|
} catch {
|
|
1440
2269
|
}
|
|
1441
2270
|
}
|
|
2271
|
+
try {
|
|
2272
|
+
await client.execute({
|
|
2273
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2274
|
+
args: []
|
|
2275
|
+
});
|
|
2276
|
+
} catch {
|
|
2277
|
+
}
|
|
1442
2278
|
}
|
|
1443
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
2279
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1444
2280
|
var init_database = __esm({
|
|
1445
2281
|
"src/lib/database.ts"() {
|
|
1446
2282
|
"use strict";
|
|
1447
2283
|
init_db_retry();
|
|
1448
2284
|
init_employees();
|
|
2285
|
+
init_database_adapter();
|
|
1449
2286
|
_client = null;
|
|
1450
2287
|
_resilientClient = null;
|
|
1451
2288
|
_walCheckpointTimer = null;
|
|
1452
2289
|
_daemonClient = null;
|
|
2290
|
+
_adapterClient = null;
|
|
1453
2291
|
initTurso = initDatabase;
|
|
1454
2292
|
}
|
|
1455
2293
|
});
|
|
@@ -1514,6 +2352,7 @@ var shard_manager_exports = {};
|
|
|
1514
2352
|
__export(shard_manager_exports, {
|
|
1515
2353
|
disposeShards: () => disposeShards,
|
|
1516
2354
|
ensureShardSchema: () => ensureShardSchema,
|
|
2355
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1517
2356
|
getReadyShardClient: () => getReadyShardClient,
|
|
1518
2357
|
getShardClient: () => getShardClient,
|
|
1519
2358
|
getShardsDir: () => getShardsDir,
|
|
@@ -1522,15 +2361,18 @@ __export(shard_manager_exports, {
|
|
|
1522
2361
|
listShards: () => listShards,
|
|
1523
2362
|
shardExists: () => shardExists
|
|
1524
2363
|
});
|
|
1525
|
-
import
|
|
1526
|
-
import { existsSync as
|
|
2364
|
+
import path6 from "path";
|
|
2365
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1527
2366
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1528
2367
|
function initShardManager(encryptionKey) {
|
|
1529
2368
|
_encryptionKey = encryptionKey;
|
|
1530
|
-
if (!
|
|
1531
|
-
|
|
2369
|
+
if (!existsSync6(SHARDS_DIR)) {
|
|
2370
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1532
2371
|
}
|
|
1533
2372
|
_shardingEnabled = true;
|
|
2373
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2374
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2375
|
+
_evictionTimer.unref();
|
|
1534
2376
|
}
|
|
1535
2377
|
function isShardingEnabled() {
|
|
1536
2378
|
return _shardingEnabled;
|
|
@@ -1547,21 +2389,28 @@ function getShardClient(projectName) {
|
|
|
1547
2389
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1548
2390
|
}
|
|
1549
2391
|
const cached = _shards.get(safeName);
|
|
1550
|
-
if (cached)
|
|
1551
|
-
|
|
2392
|
+
if (cached) {
|
|
2393
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2394
|
+
return cached;
|
|
2395
|
+
}
|
|
2396
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2397
|
+
evictLRU();
|
|
2398
|
+
}
|
|
2399
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1552
2400
|
const client = createClient2({
|
|
1553
2401
|
url: `file:${dbPath}`,
|
|
1554
2402
|
encryptionKey: _encryptionKey
|
|
1555
2403
|
});
|
|
1556
2404
|
_shards.set(safeName, client);
|
|
2405
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1557
2406
|
return client;
|
|
1558
2407
|
}
|
|
1559
2408
|
function shardExists(projectName) {
|
|
1560
2409
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1561
|
-
return
|
|
2410
|
+
return existsSync6(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1562
2411
|
}
|
|
1563
2412
|
function listShards() {
|
|
1564
|
-
if (!
|
|
2413
|
+
if (!existsSync6(SHARDS_DIR)) return [];
|
|
1565
2414
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1566
2415
|
}
|
|
1567
2416
|
async function ensureShardSchema(client) {
|
|
@@ -1613,6 +2462,8 @@ async function ensureShardSchema(client) {
|
|
|
1613
2462
|
for (const col of [
|
|
1614
2463
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1615
2464
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2465
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2466
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1616
2467
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1617
2468
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1618
2469
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1635,7 +2486,23 @@ async function ensureShardSchema(client) {
|
|
|
1635
2486
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1636
2487
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1637
2488
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1638
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2489
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2490
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2491
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2492
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2493
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2494
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2495
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2496
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2497
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2498
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2499
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2500
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2501
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2502
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2503
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2504
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2505
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1639
2506
|
]) {
|
|
1640
2507
|
try {
|
|
1641
2508
|
await client.execute(col);
|
|
@@ -1734,21 +2601,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1734
2601
|
await ensureShardSchema(client);
|
|
1735
2602
|
return client;
|
|
1736
2603
|
}
|
|
2604
|
+
function evictLRU() {
|
|
2605
|
+
let oldest = null;
|
|
2606
|
+
let oldestTime = Infinity;
|
|
2607
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2608
|
+
if (time < oldestTime) {
|
|
2609
|
+
oldestTime = time;
|
|
2610
|
+
oldest = name;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
if (oldest) {
|
|
2614
|
+
const client = _shards.get(oldest);
|
|
2615
|
+
if (client) {
|
|
2616
|
+
client.close();
|
|
2617
|
+
}
|
|
2618
|
+
_shards.delete(oldest);
|
|
2619
|
+
_shardLastAccess.delete(oldest);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
function evictIdleShards() {
|
|
2623
|
+
const now = Date.now();
|
|
2624
|
+
const toEvict = [];
|
|
2625
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2626
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2627
|
+
toEvict.push(name);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
for (const name of toEvict) {
|
|
2631
|
+
const client = _shards.get(name);
|
|
2632
|
+
if (client) {
|
|
2633
|
+
client.close();
|
|
2634
|
+
}
|
|
2635
|
+
_shards.delete(name);
|
|
2636
|
+
_shardLastAccess.delete(name);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
function getOpenShardCount() {
|
|
2640
|
+
return _shards.size;
|
|
2641
|
+
}
|
|
1737
2642
|
function disposeShards() {
|
|
2643
|
+
if (_evictionTimer) {
|
|
2644
|
+
clearInterval(_evictionTimer);
|
|
2645
|
+
_evictionTimer = null;
|
|
2646
|
+
}
|
|
1738
2647
|
for (const [, client] of _shards) {
|
|
1739
2648
|
client.close();
|
|
1740
2649
|
}
|
|
1741
2650
|
_shards.clear();
|
|
2651
|
+
_shardLastAccess.clear();
|
|
1742
2652
|
_shardingEnabled = false;
|
|
1743
2653
|
_encryptionKey = null;
|
|
1744
2654
|
}
|
|
1745
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2655
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1746
2656
|
var init_shard_manager = __esm({
|
|
1747
2657
|
"src/lib/shard-manager.ts"() {
|
|
1748
2658
|
"use strict";
|
|
1749
2659
|
init_config();
|
|
1750
|
-
SHARDS_DIR =
|
|
2660
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
2661
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2662
|
+
MAX_OPEN_SHARDS = 10;
|
|
2663
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1751
2664
|
_shards = /* @__PURE__ */ new Map();
|
|
2665
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2666
|
+
_evictionTimer = null;
|
|
1752
2667
|
_encryptionKey = null;
|
|
1753
2668
|
_shardingEnabled = false;
|
|
1754
2669
|
}
|
|
@@ -1941,66 +2856,14 @@ ${p.content}`).join("\n\n");
|
|
|
1941
2856
|
}
|
|
1942
2857
|
});
|
|
1943
2858
|
|
|
1944
|
-
// src/lib/notifications.ts
|
|
1945
|
-
import crypto from "crypto";
|
|
1946
|
-
import path5 from "path";
|
|
1947
|
-
import os4 from "os";
|
|
1948
|
-
import {
|
|
1949
|
-
readFileSync as readFileSync3,
|
|
1950
|
-
readdirSync as readdirSync2,
|
|
1951
|
-
unlinkSync as unlinkSync2,
|
|
1952
|
-
existsSync as existsSync5,
|
|
1953
|
-
rmdirSync
|
|
1954
|
-
} from "fs";
|
|
1955
|
-
async function writeNotification(notification) {
|
|
1956
|
-
try {
|
|
1957
|
-
const client = getClient();
|
|
1958
|
-
const id = crypto.randomUUID();
|
|
1959
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1960
|
-
await client.execute({
|
|
1961
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
1962
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
1963
|
-
args: [
|
|
1964
|
-
id,
|
|
1965
|
-
notification.agentId,
|
|
1966
|
-
notification.agentRole,
|
|
1967
|
-
notification.event,
|
|
1968
|
-
notification.project,
|
|
1969
|
-
notification.summary,
|
|
1970
|
-
notification.taskFile ?? null,
|
|
1971
|
-
now
|
|
1972
|
-
]
|
|
1973
|
-
});
|
|
1974
|
-
} catch (err) {
|
|
1975
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
1976
|
-
`);
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
1980
|
-
try {
|
|
1981
|
-
const client = getClient();
|
|
1982
|
-
await client.execute({
|
|
1983
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
1984
|
-
args: [taskFile]
|
|
1985
|
-
});
|
|
1986
|
-
} catch {
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
var init_notifications = __esm({
|
|
1990
|
-
"src/lib/notifications.ts"() {
|
|
1991
|
-
"use strict";
|
|
1992
|
-
init_database();
|
|
1993
|
-
}
|
|
1994
|
-
});
|
|
1995
|
-
|
|
1996
2859
|
// src/lib/session-registry.ts
|
|
1997
|
-
import { readFileSync as readFileSync4, writeFileSync as
|
|
1998
|
-
import
|
|
2860
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
2861
|
+
import path7 from "path";
|
|
1999
2862
|
import os5 from "os";
|
|
2000
2863
|
function registerSession(entry) {
|
|
2001
|
-
const dir =
|
|
2002
|
-
if (!
|
|
2003
|
-
|
|
2864
|
+
const dir = path7.dirname(REGISTRY_PATH);
|
|
2865
|
+
if (!existsSync7(dir)) {
|
|
2866
|
+
mkdirSync3(dir, { recursive: true });
|
|
2004
2867
|
}
|
|
2005
2868
|
const sessions = listSessions();
|
|
2006
2869
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -2009,7 +2872,7 @@ function registerSession(entry) {
|
|
|
2009
2872
|
} else {
|
|
2010
2873
|
sessions.push(entry);
|
|
2011
2874
|
}
|
|
2012
|
-
|
|
2875
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
2013
2876
|
}
|
|
2014
2877
|
function listSessions() {
|
|
2015
2878
|
try {
|
|
@@ -2023,7 +2886,7 @@ var REGISTRY_PATH;
|
|
|
2023
2886
|
var init_session_registry = __esm({
|
|
2024
2887
|
"src/lib/session-registry.ts"() {
|
|
2025
2888
|
"use strict";
|
|
2026
|
-
REGISTRY_PATH =
|
|
2889
|
+
REGISTRY_PATH = path7.join(os5.homedir(), ".exe-os", "session-registry.json");
|
|
2027
2890
|
}
|
|
2028
2891
|
});
|
|
2029
2892
|
|
|
@@ -2275,67 +3138,6 @@ var init_provider_table = __esm({
|
|
|
2275
3138
|
}
|
|
2276
3139
|
});
|
|
2277
3140
|
|
|
2278
|
-
// src/lib/runtime-table.ts
|
|
2279
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
2280
|
-
var init_runtime_table = __esm({
|
|
2281
|
-
"src/lib/runtime-table.ts"() {
|
|
2282
|
-
"use strict";
|
|
2283
|
-
RUNTIME_TABLE = {
|
|
2284
|
-
codex: {
|
|
2285
|
-
binary: "codex",
|
|
2286
|
-
launchMode: "interactive",
|
|
2287
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
2288
|
-
inlineFlag: "--no-alt-screen",
|
|
2289
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
2290
|
-
defaultModel: "gpt-5.4"
|
|
2291
|
-
},
|
|
2292
|
-
opencode: {
|
|
2293
|
-
binary: "opencode",
|
|
2294
|
-
launchMode: "exec",
|
|
2295
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2296
|
-
inlineFlag: "",
|
|
2297
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
2298
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
2299
|
-
}
|
|
2300
|
-
};
|
|
2301
|
-
DEFAULT_RUNTIME = "claude";
|
|
2302
|
-
}
|
|
2303
|
-
});
|
|
2304
|
-
|
|
2305
|
-
// src/lib/agent-config.ts
|
|
2306
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
2307
|
-
import path7 from "path";
|
|
2308
|
-
function loadAgentConfig() {
|
|
2309
|
-
if (!existsSync7(AGENT_CONFIG_PATH)) return {};
|
|
2310
|
-
try {
|
|
2311
|
-
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
2312
|
-
} catch {
|
|
2313
|
-
return {};
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
function getAgentRuntime(agentId) {
|
|
2317
|
-
const config = loadAgentConfig();
|
|
2318
|
-
const entry = config[agentId];
|
|
2319
|
-
if (entry) return entry;
|
|
2320
|
-
const orgDefault = config["default"];
|
|
2321
|
-
if (orgDefault) return orgDefault;
|
|
2322
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
2323
|
-
}
|
|
2324
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
2325
|
-
var init_agent_config = __esm({
|
|
2326
|
-
"src/lib/agent-config.ts"() {
|
|
2327
|
-
"use strict";
|
|
2328
|
-
init_config();
|
|
2329
|
-
init_runtime_table();
|
|
2330
|
-
AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
|
|
2331
|
-
DEFAULT_MODELS = {
|
|
2332
|
-
claude: "claude-opus-4",
|
|
2333
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
2334
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
2335
|
-
};
|
|
2336
|
-
}
|
|
2337
|
-
});
|
|
2338
|
-
|
|
2339
3141
|
// src/lib/intercom-queue.ts
|
|
2340
3142
|
var intercom_queue_exports = {};
|
|
2341
3143
|
__export(intercom_queue_exports, {
|
|
@@ -2345,7 +3147,7 @@ __export(intercom_queue_exports, {
|
|
|
2345
3147
|
queueIntercom: () => queueIntercom,
|
|
2346
3148
|
readQueue: () => readQueue
|
|
2347
3149
|
});
|
|
2348
|
-
import { readFileSync as
|
|
3150
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
2349
3151
|
import path8 from "path";
|
|
2350
3152
|
import os6 from "os";
|
|
2351
3153
|
function ensureDir() {
|
|
@@ -2355,7 +3157,7 @@ function ensureDir() {
|
|
|
2355
3157
|
function readQueue() {
|
|
2356
3158
|
try {
|
|
2357
3159
|
if (!existsSync8(QUEUE_PATH)) return [];
|
|
2358
|
-
return JSON.parse(
|
|
3160
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
2359
3161
|
} catch {
|
|
2360
3162
|
return [];
|
|
2361
3163
|
}
|
|
@@ -2463,8 +3265,11 @@ var init_intercom_queue = __esm({
|
|
|
2463
3265
|
});
|
|
2464
3266
|
|
|
2465
3267
|
// src/lib/license.ts
|
|
2466
|
-
import { readFileSync as
|
|
3268
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
2467
3269
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3270
|
+
import { createRequire as createRequire2 } from "module";
|
|
3271
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3272
|
+
import os7 from "os";
|
|
2468
3273
|
import path9 from "path";
|
|
2469
3274
|
import { jwtVerify, importSPKI } from "jose";
|
|
2470
3275
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
@@ -2486,12 +3291,12 @@ var init_license = __esm({
|
|
|
2486
3291
|
});
|
|
2487
3292
|
|
|
2488
3293
|
// src/lib/plan-limits.ts
|
|
2489
|
-
import { readFileSync as
|
|
3294
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
2490
3295
|
import path10 from "path";
|
|
2491
3296
|
function getLicenseSync() {
|
|
2492
3297
|
try {
|
|
2493
3298
|
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
2494
|
-
const raw = JSON.parse(
|
|
3299
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
2495
3300
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2496
3301
|
const parts = raw.token.split(".");
|
|
2497
3302
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2530,7 +3335,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2530
3335
|
let count = 0;
|
|
2531
3336
|
try {
|
|
2532
3337
|
if (existsSync10(filePath)) {
|
|
2533
|
-
const raw =
|
|
3338
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
2534
3339
|
const employees = JSON.parse(raw);
|
|
2535
3340
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2536
3341
|
}
|
|
@@ -2564,7 +3369,7 @@ var init_plan_limits = __esm({
|
|
|
2564
3369
|
});
|
|
2565
3370
|
|
|
2566
3371
|
// src/lib/session-kill-telemetry.ts
|
|
2567
|
-
import
|
|
3372
|
+
import crypto from "crypto";
|
|
2568
3373
|
async function recordSessionKill(input) {
|
|
2569
3374
|
try {
|
|
2570
3375
|
const client = getClient();
|
|
@@ -2574,7 +3379,7 @@ async function recordSessionKill(input) {
|
|
|
2574
3379
|
ticks_idle, estimated_tokens_saved)
|
|
2575
3380
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2576
3381
|
args: [
|
|
2577
|
-
|
|
3382
|
+
crypto.randomUUID(),
|
|
2578
3383
|
input.sessionName,
|
|
2579
3384
|
input.agentId,
|
|
2580
3385
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2897,6 +3702,7 @@ __export(tmux_routing_exports, {
|
|
|
2897
3702
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
2898
3703
|
isExeSession: () => isExeSession,
|
|
2899
3704
|
isSessionBusy: () => isSessionBusy,
|
|
3705
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
2900
3706
|
notifyParentExe: () => notifyParentExe,
|
|
2901
3707
|
parseParentExe: () => parseParentExe,
|
|
2902
3708
|
registerParentExe: () => registerParentExe,
|
|
@@ -2907,11 +3713,11 @@ __export(tmux_routing_exports, {
|
|
|
2907
3713
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
2908
3714
|
});
|
|
2909
3715
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
2910
|
-
import { readFileSync as
|
|
3716
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync, readdirSync as readdirSync2 } from "fs";
|
|
2911
3717
|
import path11 from "path";
|
|
2912
|
-
import
|
|
3718
|
+
import os8 from "os";
|
|
2913
3719
|
import { fileURLToPath } from "url";
|
|
2914
|
-
import { unlinkSync as
|
|
3720
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
2915
3721
|
function spawnLockPath(sessionName) {
|
|
2916
3722
|
return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2917
3723
|
}
|
|
@@ -2930,7 +3736,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
2930
3736
|
const lockFile = spawnLockPath(sessionName);
|
|
2931
3737
|
if (existsSync11(lockFile)) {
|
|
2932
3738
|
try {
|
|
2933
|
-
const lock = JSON.parse(
|
|
3739
|
+
const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
|
|
2934
3740
|
const age = Date.now() - lock.timestamp;
|
|
2935
3741
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2936
3742
|
return false;
|
|
@@ -2943,7 +3749,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
2943
3749
|
}
|
|
2944
3750
|
function releaseSpawnLock(sessionName) {
|
|
2945
3751
|
try {
|
|
2946
|
-
|
|
3752
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
2947
3753
|
} catch {
|
|
2948
3754
|
}
|
|
2949
3755
|
}
|
|
@@ -3035,7 +3841,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3035
3841
|
}
|
|
3036
3842
|
function getParentExe(sessionKey) {
|
|
3037
3843
|
try {
|
|
3038
|
-
const data = JSON.parse(
|
|
3844
|
+
const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3039
3845
|
return data.parentExe || null;
|
|
3040
3846
|
} catch {
|
|
3041
3847
|
return null;
|
|
@@ -3043,7 +3849,7 @@ function getParentExe(sessionKey) {
|
|
|
3043
3849
|
}
|
|
3044
3850
|
function getDispatchedBy(sessionKey) {
|
|
3045
3851
|
try {
|
|
3046
|
-
const data = JSON.parse(
|
|
3852
|
+
const data = JSON.parse(readFileSync8(
|
|
3047
3853
|
path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3048
3854
|
"utf8"
|
|
3049
3855
|
));
|
|
@@ -3115,7 +3921,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3115
3921
|
function readDebounceState() {
|
|
3116
3922
|
try {
|
|
3117
3923
|
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
3118
|
-
const raw = JSON.parse(
|
|
3924
|
+
const raw = JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
|
|
3119
3925
|
const state = {};
|
|
3120
3926
|
for (const [key, val] of Object.entries(raw)) {
|
|
3121
3927
|
if (typeof val === "number") {
|
|
@@ -3242,7 +4048,7 @@ function sendIntercom(targetSession) {
|
|
|
3242
4048
|
const agent = baseAgentName(rawAgent);
|
|
3243
4049
|
const taskDir = path11.join(process.cwd(), "exe", agent);
|
|
3244
4050
|
if (existsSync11(taskDir)) {
|
|
3245
|
-
const files =
|
|
4051
|
+
const files = readdirSync2(taskDir).filter(
|
|
3246
4052
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
3247
4053
|
);
|
|
3248
4054
|
if (files.length === 0) {
|
|
@@ -3301,6 +4107,21 @@ function notifyParentExe(sessionKey) {
|
|
|
3301
4107
|
}
|
|
3302
4108
|
return true;
|
|
3303
4109
|
}
|
|
4110
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
4111
|
+
const transport = getTransport();
|
|
4112
|
+
try {
|
|
4113
|
+
const sessions = transport.listSessions();
|
|
4114
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
4115
|
+
execSync4(
|
|
4116
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
4117
|
+
{ timeout: 3e3 }
|
|
4118
|
+
);
|
|
4119
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
4120
|
+
return true;
|
|
4121
|
+
} catch {
|
|
4122
|
+
return false;
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
3304
4125
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3305
4126
|
if (isCoordinatorName(employeeName)) {
|
|
3306
4127
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -3374,7 +4195,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3374
4195
|
const transport = getTransport();
|
|
3375
4196
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3376
4197
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3377
|
-
const logDir = path11.join(
|
|
4198
|
+
const logDir = path11.join(os8.homedir(), ".exe-os", "session-logs");
|
|
3378
4199
|
const logFile = path11.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3379
4200
|
if (!existsSync11(logDir)) {
|
|
3380
4201
|
mkdirSync6(logDir, { recursive: true });
|
|
@@ -3390,10 +4211,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3390
4211
|
} catch {
|
|
3391
4212
|
}
|
|
3392
4213
|
try {
|
|
3393
|
-
const claudeJsonPath = path11.join(
|
|
4214
|
+
const claudeJsonPath = path11.join(os8.homedir(), ".claude.json");
|
|
3394
4215
|
let claudeJson = {};
|
|
3395
4216
|
try {
|
|
3396
|
-
claudeJson = JSON.parse(
|
|
4217
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
3397
4218
|
} catch {
|
|
3398
4219
|
}
|
|
3399
4220
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3405,13 +4226,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3405
4226
|
} catch {
|
|
3406
4227
|
}
|
|
3407
4228
|
try {
|
|
3408
|
-
const settingsDir = path11.join(
|
|
4229
|
+
const settingsDir = path11.join(os8.homedir(), ".claude", "projects");
|
|
3409
4230
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3410
4231
|
const projSettingsDir = path11.join(settingsDir, normalizedKey);
|
|
3411
4232
|
const settingsPath = path11.join(projSettingsDir, "settings.json");
|
|
3412
4233
|
let settings = {};
|
|
3413
4234
|
try {
|
|
3414
|
-
settings = JSON.parse(
|
|
4235
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
3415
4236
|
} catch {
|
|
3416
4237
|
}
|
|
3417
4238
|
const perms = settings.permissions ?? {};
|
|
@@ -3456,7 +4277,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3456
4277
|
let legacyFallbackWarned = false;
|
|
3457
4278
|
if (!useExeAgent && !useBinSymlink) {
|
|
3458
4279
|
const identityPath = path11.join(
|
|
3459
|
-
|
|
4280
|
+
os8.homedir(),
|
|
3460
4281
|
".exe-os",
|
|
3461
4282
|
"identity",
|
|
3462
4283
|
`${employeeName}.md`
|
|
@@ -3486,7 +4307,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3486
4307
|
}
|
|
3487
4308
|
let sessionContextFlag = "";
|
|
3488
4309
|
try {
|
|
3489
|
-
const ctxDir = path11.join(
|
|
4310
|
+
const ctxDir = path11.join(os8.homedir(), ".exe-os", "session-cache");
|
|
3490
4311
|
mkdirSync6(ctxDir, { recursive: true });
|
|
3491
4312
|
const ctxFile = path11.join(ctxDir, `session-context-${sessionName}.md`);
|
|
3492
4313
|
const ctxContent = [
|
|
@@ -3647,14 +4468,14 @@ var init_tmux_routing = __esm({
|
|
|
3647
4468
|
init_intercom_queue();
|
|
3648
4469
|
init_plan_limits();
|
|
3649
4470
|
init_employees();
|
|
3650
|
-
SPAWN_LOCK_DIR = path11.join(
|
|
3651
|
-
SESSION_CACHE = path11.join(
|
|
4471
|
+
SPAWN_LOCK_DIR = path11.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
4472
|
+
SESSION_CACHE = path11.join(os8.homedir(), ".exe-os", "session-cache");
|
|
3652
4473
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3653
4474
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3654
4475
|
VERIFY_PANE_LINES = 200;
|
|
3655
4476
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3656
4477
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
3657
|
-
INTERCOM_LOG2 = path11.join(
|
|
4478
|
+
INTERCOM_LOG2 = path11.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
3658
4479
|
DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3659
4480
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3660
4481
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
@@ -3678,6 +4499,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
3678
4499
|
args: [scope]
|
|
3679
4500
|
};
|
|
3680
4501
|
}
|
|
4502
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
4503
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
4504
|
+
if (!scope) return { sql: "", args: [] };
|
|
4505
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
4506
|
+
return {
|
|
4507
|
+
sql: ` AND ${col} = ?`,
|
|
4508
|
+
args: [scope]
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
3681
4511
|
var init_task_scope = __esm({
|
|
3682
4512
|
"src/lib/task-scope.ts"() {
|
|
3683
4513
|
"use strict";
|
|
@@ -3685,13 +4515,70 @@ var init_task_scope = __esm({
|
|
|
3685
4515
|
}
|
|
3686
4516
|
});
|
|
3687
4517
|
|
|
4518
|
+
// src/lib/notifications.ts
|
|
4519
|
+
import crypto2 from "crypto";
|
|
4520
|
+
import path12 from "path";
|
|
4521
|
+
import os9 from "os";
|
|
4522
|
+
import {
|
|
4523
|
+
readFileSync as readFileSync9,
|
|
4524
|
+
readdirSync as readdirSync3,
|
|
4525
|
+
unlinkSync as unlinkSync3,
|
|
4526
|
+
existsSync as existsSync12,
|
|
4527
|
+
rmdirSync
|
|
4528
|
+
} from "fs";
|
|
4529
|
+
async function writeNotification(notification) {
|
|
4530
|
+
try {
|
|
4531
|
+
const client = getClient();
|
|
4532
|
+
const id = crypto2.randomUUID();
|
|
4533
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4534
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
4535
|
+
await client.execute({
|
|
4536
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4537
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4538
|
+
args: [
|
|
4539
|
+
id,
|
|
4540
|
+
notification.agentId,
|
|
4541
|
+
notification.agentRole,
|
|
4542
|
+
notification.event,
|
|
4543
|
+
notification.project,
|
|
4544
|
+
notification.summary,
|
|
4545
|
+
notification.taskFile ?? null,
|
|
4546
|
+
sessionScope,
|
|
4547
|
+
now
|
|
4548
|
+
]
|
|
4549
|
+
});
|
|
4550
|
+
} catch (err) {
|
|
4551
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
4552
|
+
`);
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4556
|
+
try {
|
|
4557
|
+
const client = getClient();
|
|
4558
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4559
|
+
await client.execute({
|
|
4560
|
+
sql: `UPDATE notifications SET read = 1
|
|
4561
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
4562
|
+
args: [taskFile, ...scope.args]
|
|
4563
|
+
});
|
|
4564
|
+
} catch {
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
var init_notifications = __esm({
|
|
4568
|
+
"src/lib/notifications.ts"() {
|
|
4569
|
+
"use strict";
|
|
4570
|
+
init_database();
|
|
4571
|
+
init_task_scope();
|
|
4572
|
+
}
|
|
4573
|
+
});
|
|
4574
|
+
|
|
3688
4575
|
// src/lib/tasks-crud.ts
|
|
3689
4576
|
import crypto3 from "crypto";
|
|
3690
|
-
import
|
|
3691
|
-
import
|
|
4577
|
+
import path13 from "path";
|
|
4578
|
+
import os10 from "os";
|
|
3692
4579
|
import { execSync as execSync5 } from "child_process";
|
|
3693
4580
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3694
|
-
import { existsSync as
|
|
4581
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
3695
4582
|
async function writeCheckpoint(input) {
|
|
3696
4583
|
const client = getClient();
|
|
3697
4584
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3866,8 +4753,8 @@ ${laneWarning}` : laneWarning;
|
|
|
3866
4753
|
}
|
|
3867
4754
|
if (input.baseDir) {
|
|
3868
4755
|
try {
|
|
3869
|
-
await mkdir4(
|
|
3870
|
-
await mkdir4(
|
|
4756
|
+
await mkdir4(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4757
|
+
await mkdir4(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3871
4758
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3872
4759
|
await ensureGitignoreExe(input.baseDir);
|
|
3873
4760
|
} catch {
|
|
@@ -3903,13 +4790,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3903
4790
|
});
|
|
3904
4791
|
if (input.baseDir) {
|
|
3905
4792
|
try {
|
|
3906
|
-
const EXE_OS_DIR =
|
|
3907
|
-
const mdPath =
|
|
3908
|
-
const mdDir =
|
|
3909
|
-
if (!
|
|
4793
|
+
const EXE_OS_DIR = path13.join(os10.homedir(), ".exe-os");
|
|
4794
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
4795
|
+
const mdDir = path13.dirname(mdPath);
|
|
4796
|
+
if (!existsSync13(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
3910
4797
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3911
4798
|
const mdContent = `# ${input.title}
|
|
3912
4799
|
|
|
4800
|
+
## MANDATORY: When done
|
|
4801
|
+
|
|
4802
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4803
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4804
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4805
|
+
|
|
3913
4806
|
**ID:** ${id}
|
|
3914
4807
|
**Status:** ${initialStatus}
|
|
3915
4808
|
**Priority:** ${input.priority}
|
|
@@ -3923,12 +4816,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3923
4816
|
## Context
|
|
3924
4817
|
|
|
3925
4818
|
${input.context}
|
|
3926
|
-
|
|
3927
|
-
## MANDATORY: When done
|
|
3928
|
-
|
|
3929
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3930
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3931
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3932
4819
|
`;
|
|
3933
4820
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
3934
4821
|
} catch (err) {
|
|
@@ -4177,7 +5064,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4177
5064
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4178
5065
|
} catch {
|
|
4179
5066
|
}
|
|
4180
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5067
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4181
5068
|
try {
|
|
4182
5069
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4183
5070
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -4206,9 +5093,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
4206
5093
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
4207
5094
|
}
|
|
4208
5095
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
4209
|
-
const archPath =
|
|
5096
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
4210
5097
|
try {
|
|
4211
|
-
if (
|
|
5098
|
+
if (existsSync13(archPath)) return;
|
|
4212
5099
|
const template = [
|
|
4213
5100
|
`# ${projectName} \u2014 System Architecture`,
|
|
4214
5101
|
"",
|
|
@@ -4241,9 +5128,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
4241
5128
|
}
|
|
4242
5129
|
}
|
|
4243
5130
|
async function ensureGitignoreExe(baseDir) {
|
|
4244
|
-
const gitignorePath =
|
|
5131
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
4245
5132
|
try {
|
|
4246
|
-
if (
|
|
5133
|
+
if (existsSync13(gitignorePath)) {
|
|
4247
5134
|
const content = readFileSync10(gitignorePath, "utf-8");
|
|
4248
5135
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4249
5136
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -4275,58 +5162,42 @@ var init_tasks_crud = __esm({
|
|
|
4275
5162
|
});
|
|
4276
5163
|
|
|
4277
5164
|
// src/lib/tasks-review.ts
|
|
4278
|
-
import
|
|
4279
|
-
import { existsSync as
|
|
5165
|
+
import path14 from "path";
|
|
5166
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
4280
5167
|
async function countPendingReviews(sessionScope) {
|
|
4281
5168
|
const client = getClient();
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
args: [sessionScope]
|
|
4286
|
-
});
|
|
4287
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4288
|
-
}
|
|
5169
|
+
const scope = strictSessionScopeFilter(
|
|
5170
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5171
|
+
);
|
|
4289
5172
|
const result = await client.execute({
|
|
4290
|
-
sql:
|
|
4291
|
-
|
|
5173
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5174
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
5175
|
+
args: [...scope.args]
|
|
4292
5176
|
});
|
|
4293
5177
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4294
5178
|
}
|
|
4295
5179
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
4296
5180
|
const client = getClient();
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
4301
|
-
AND session_scope = ?`,
|
|
4302
|
-
args: [sinceIso, sessionScope]
|
|
4303
|
-
});
|
|
4304
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4305
|
-
}
|
|
5181
|
+
const scope = strictSessionScopeFilter(
|
|
5182
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5183
|
+
);
|
|
4306
5184
|
const result = await client.execute({
|
|
4307
5185
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4308
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
4309
|
-
args: [sinceIso]
|
|
5186
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
5187
|
+
args: [sinceIso, ...scope.args]
|
|
4310
5188
|
});
|
|
4311
5189
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4312
5190
|
}
|
|
4313
5191
|
async function listPendingReviews(limit, sessionScope) {
|
|
4314
5192
|
const client = getClient();
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
WHERE status = 'needs_review'
|
|
4319
|
-
AND session_scope = ?
|
|
4320
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
4321
|
-
args: [sessionScope, limit]
|
|
4322
|
-
});
|
|
4323
|
-
return result2.rows;
|
|
4324
|
-
}
|
|
5193
|
+
const scope = strictSessionScopeFilter(
|
|
5194
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5195
|
+
);
|
|
4325
5196
|
const result = await client.execute({
|
|
4326
5197
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
4327
|
-
WHERE status = 'needs_review'
|
|
5198
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
4328
5199
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
4329
|
-
args: [limit]
|
|
5200
|
+
args: [...scope.args, limit]
|
|
4330
5201
|
});
|
|
4331
5202
|
return result.rows;
|
|
4332
5203
|
}
|
|
@@ -4338,7 +5209,7 @@ async function cleanupOrphanedReviews() {
|
|
|
4338
5209
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
4339
5210
|
AND assigned_by = 'system'
|
|
4340
5211
|
AND title LIKE 'Review:%'
|
|
4341
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
5212
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
4342
5213
|
args: [now]
|
|
4343
5214
|
});
|
|
4344
5215
|
const r1b = await client.execute({
|
|
@@ -4457,11 +5328,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4457
5328
|
);
|
|
4458
5329
|
}
|
|
4459
5330
|
try {
|
|
4460
|
-
const cacheDir =
|
|
4461
|
-
if (
|
|
5331
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
5332
|
+
if (existsSync14(cacheDir)) {
|
|
4462
5333
|
for (const f of readdirSync4(cacheDir)) {
|
|
4463
5334
|
if (f.startsWith("review-notified-")) {
|
|
4464
|
-
unlinkSync4(
|
|
5335
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
4465
5336
|
}
|
|
4466
5337
|
}
|
|
4467
5338
|
}
|
|
@@ -4478,11 +5349,12 @@ var init_tasks_review = __esm({
|
|
|
4478
5349
|
init_tmux_routing();
|
|
4479
5350
|
init_session_key();
|
|
4480
5351
|
init_state_bus();
|
|
5352
|
+
init_task_scope();
|
|
4481
5353
|
}
|
|
4482
5354
|
});
|
|
4483
5355
|
|
|
4484
5356
|
// src/lib/tasks-chain.ts
|
|
4485
|
-
import
|
|
5357
|
+
import path15 from "path";
|
|
4486
5358
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
4487
5359
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
4488
5360
|
const client = getClient();
|
|
@@ -4499,7 +5371,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4499
5371
|
});
|
|
4500
5372
|
for (const ur of unblockedRows.rows) {
|
|
4501
5373
|
try {
|
|
4502
|
-
const ubFile =
|
|
5374
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
4503
5375
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
4504
5376
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4505
5377
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4534,7 +5406,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4534
5406
|
const scScope = sessionScopeFilter();
|
|
4535
5407
|
const remaining = await client.execute({
|
|
4536
5408
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4537
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
5409
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4538
5410
|
args: [parentTaskId, ...scScope.args]
|
|
4539
5411
|
});
|
|
4540
5412
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4568,7 +5440,7 @@ var init_tasks_chain = __esm({
|
|
|
4568
5440
|
|
|
4569
5441
|
// src/lib/project-name.ts
|
|
4570
5442
|
import { execSync as execSync6 } from "child_process";
|
|
4571
|
-
import
|
|
5443
|
+
import path16 from "path";
|
|
4572
5444
|
function getProjectName(cwd) {
|
|
4573
5445
|
const dir = cwd ?? process.cwd();
|
|
4574
5446
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4581,7 +5453,7 @@ function getProjectName(cwd) {
|
|
|
4581
5453
|
timeout: 2e3,
|
|
4582
5454
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4583
5455
|
}).trim();
|
|
4584
|
-
repoRoot =
|
|
5456
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
4585
5457
|
} catch {
|
|
4586
5458
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4587
5459
|
cwd: dir,
|
|
@@ -4590,11 +5462,11 @@ function getProjectName(cwd) {
|
|
|
4590
5462
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4591
5463
|
}).trim();
|
|
4592
5464
|
}
|
|
4593
|
-
_cached2 =
|
|
5465
|
+
_cached2 = path16.basename(repoRoot);
|
|
4594
5466
|
_cachedCwd = dir;
|
|
4595
5467
|
return _cached2;
|
|
4596
5468
|
} catch {
|
|
4597
|
-
_cached2 =
|
|
5469
|
+
_cached2 = path16.basename(dir);
|
|
4598
5470
|
_cachedCwd = dir;
|
|
4599
5471
|
return _cached2;
|
|
4600
5472
|
}
|
|
@@ -5067,7 +5939,7 @@ __export(tasks_exports, {
|
|
|
5067
5939
|
updateTaskStatus: () => updateTaskStatus,
|
|
5068
5940
|
writeCheckpoint: () => writeCheckpoint
|
|
5069
5941
|
});
|
|
5070
|
-
import
|
|
5942
|
+
import path17 from "path";
|
|
5071
5943
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
5072
5944
|
async function createTask(input) {
|
|
5073
5945
|
const result = await createTaskCore(input);
|
|
@@ -5087,12 +5959,12 @@ async function updateTask(input) {
|
|
|
5087
5959
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
5088
5960
|
try {
|
|
5089
5961
|
const agent = String(row.assigned_to);
|
|
5090
|
-
const cacheDir =
|
|
5091
|
-
const cachePath =
|
|
5962
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
5963
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
5092
5964
|
if (input.status === "in_progress") {
|
|
5093
5965
|
mkdirSync7(cacheDir, { recursive: true });
|
|
5094
5966
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5095
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5967
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5096
5968
|
try {
|
|
5097
5969
|
unlinkSync5(cachePath);
|
|
5098
5970
|
} catch {
|
|
@@ -5100,10 +5972,10 @@ async function updateTask(input) {
|
|
|
5100
5972
|
}
|
|
5101
5973
|
} catch {
|
|
5102
5974
|
}
|
|
5103
|
-
if (input.status === "done") {
|
|
5975
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5104
5976
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5105
5977
|
}
|
|
5106
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5978
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5107
5979
|
try {
|
|
5108
5980
|
const client = getClient();
|
|
5109
5981
|
const taskTitle = String(row.title);
|
|
@@ -5119,7 +5991,7 @@ async function updateTask(input) {
|
|
|
5119
5991
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5120
5992
|
try {
|
|
5121
5993
|
const draftClient = getClient();
|
|
5122
|
-
if (input.status === "done") {
|
|
5994
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5123
5995
|
await draftClient.execute({
|
|
5124
5996
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5125
5997
|
args: [assignedAgent]
|
|
@@ -5136,7 +6008,7 @@ async function updateTask(input) {
|
|
|
5136
6008
|
try {
|
|
5137
6009
|
const client = getClient();
|
|
5138
6010
|
const cascaded = await client.execute({
|
|
5139
|
-
sql: `UPDATE tasks SET status = '
|
|
6011
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5140
6012
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5141
6013
|
args: [now, taskId]
|
|
5142
6014
|
});
|
|
@@ -5149,14 +6021,14 @@ async function updateTask(input) {
|
|
|
5149
6021
|
} catch {
|
|
5150
6022
|
}
|
|
5151
6023
|
}
|
|
5152
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
6024
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5153
6025
|
if (isTerminal) {
|
|
5154
6026
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5155
6027
|
if (!isCoordinator) {
|
|
5156
6028
|
notifyTaskDone();
|
|
5157
6029
|
}
|
|
5158
6030
|
await markTaskNotificationsRead(taskFile);
|
|
5159
|
-
if (input.status === "done") {
|
|
6031
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5160
6032
|
try {
|
|
5161
6033
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5162
6034
|
} catch {
|
|
@@ -5176,7 +6048,7 @@ async function updateTask(input) {
|
|
|
5176
6048
|
}
|
|
5177
6049
|
}
|
|
5178
6050
|
}
|
|
5179
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6051
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5180
6052
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5181
6053
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5182
6054
|
taskId,
|
|
@@ -5254,16 +6126,16 @@ init_database();
|
|
|
5254
6126
|
|
|
5255
6127
|
// src/lib/keychain.ts
|
|
5256
6128
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
5257
|
-
import { existsSync as
|
|
5258
|
-
import
|
|
5259
|
-
import
|
|
6129
|
+
import { existsSync as existsSync5 } from "fs";
|
|
6130
|
+
import path5 from "path";
|
|
6131
|
+
import os4 from "os";
|
|
5260
6132
|
var SERVICE = "exe-mem";
|
|
5261
6133
|
var ACCOUNT = "master-key";
|
|
5262
6134
|
function getKeyDir() {
|
|
5263
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6135
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
|
|
5264
6136
|
}
|
|
5265
6137
|
function getKeyPath() {
|
|
5266
|
-
return
|
|
6138
|
+
return path5.join(getKeyDir(), "master.key");
|
|
5267
6139
|
}
|
|
5268
6140
|
async function tryKeytar() {
|
|
5269
6141
|
try {
|
|
@@ -5284,9 +6156,9 @@ async function getMasterKey() {
|
|
|
5284
6156
|
}
|
|
5285
6157
|
}
|
|
5286
6158
|
const keyPath = getKeyPath();
|
|
5287
|
-
if (!
|
|
6159
|
+
if (!existsSync5(keyPath)) {
|
|
5288
6160
|
process.stderr.write(
|
|
5289
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
6161
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5290
6162
|
`
|
|
5291
6163
|
);
|
|
5292
6164
|
return null;
|