@askexenow/exe-os 0.8.83 → 0.8.86
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +375 -152
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
|
@@ -59,34 +59,34 @@ var init_mcp_prefix = __esm({
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
// src/lib/project-name.ts
|
|
62
|
-
import { execSync
|
|
63
|
-
import
|
|
62
|
+
import { execSync } from "child_process";
|
|
63
|
+
import path from "path";
|
|
64
64
|
function getProjectName(cwd) {
|
|
65
65
|
const dir = cwd ?? process.cwd();
|
|
66
66
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
67
67
|
try {
|
|
68
68
|
let repoRoot;
|
|
69
69
|
try {
|
|
70
|
-
const gitCommonDir =
|
|
70
|
+
const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
|
|
71
71
|
cwd: dir,
|
|
72
72
|
encoding: "utf8",
|
|
73
73
|
timeout: 2e3,
|
|
74
74
|
stdio: ["pipe", "pipe", "pipe"]
|
|
75
75
|
}).trim();
|
|
76
|
-
repoRoot =
|
|
76
|
+
repoRoot = path.dirname(gitCommonDir);
|
|
77
77
|
} catch {
|
|
78
|
-
repoRoot =
|
|
78
|
+
repoRoot = execSync("git rev-parse --show-toplevel", {
|
|
79
79
|
cwd: dir,
|
|
80
80
|
encoding: "utf8",
|
|
81
81
|
timeout: 2e3,
|
|
82
82
|
stdio: ["pipe", "pipe", "pipe"]
|
|
83
83
|
}).trim();
|
|
84
84
|
}
|
|
85
|
-
_cached =
|
|
85
|
+
_cached = path.basename(repoRoot);
|
|
86
86
|
_cachedCwd = dir;
|
|
87
87
|
return _cached;
|
|
88
88
|
} catch {
|
|
89
|
-
_cached =
|
|
89
|
+
_cached = path.basename(dir);
|
|
90
90
|
_cachedCwd = dir;
|
|
91
91
|
return _cached;
|
|
92
92
|
}
|
|
@@ -181,15 +181,15 @@ __export(config_exports, {
|
|
|
181
181
|
saveConfig: () => saveConfig
|
|
182
182
|
});
|
|
183
183
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
184
|
-
import { readFileSync
|
|
185
|
-
import
|
|
184
|
+
import { readFileSync, existsSync, renameSync } from "fs";
|
|
185
|
+
import path2 from "path";
|
|
186
186
|
import os from "os";
|
|
187
187
|
function resolveDataDir() {
|
|
188
188
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
189
189
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
190
|
-
const newDir =
|
|
191
|
-
const legacyDir =
|
|
192
|
-
if (!
|
|
190
|
+
const newDir = path2.join(os.homedir(), ".exe-os");
|
|
191
|
+
const legacyDir = path2.join(os.homedir(), ".exe-mem");
|
|
192
|
+
if (!existsSync(newDir) && existsSync(legacyDir)) {
|
|
193
193
|
try {
|
|
194
194
|
renameSync(legacyDir, newDir);
|
|
195
195
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -253,9 +253,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
253
253
|
async function loadConfig() {
|
|
254
254
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
255
255
|
await mkdir(dir, { recursive: true });
|
|
256
|
-
const configPath =
|
|
257
|
-
if (!
|
|
258
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
256
|
+
const configPath = path2.join(dir, "config.json");
|
|
257
|
+
if (!existsSync(configPath)) {
|
|
258
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
259
259
|
}
|
|
260
260
|
const raw = await readFile(configPath, "utf-8");
|
|
261
261
|
try {
|
|
@@ -273,38 +273,38 @@ async function loadConfig() {
|
|
|
273
273
|
normalizeScalingRoadmap(migratedCfg);
|
|
274
274
|
normalizeSessionLifecycle(migratedCfg);
|
|
275
275
|
normalizeAutoUpdate(migratedCfg);
|
|
276
|
-
const config = { ...DEFAULT_CONFIG, dbPath:
|
|
276
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
277
277
|
if (config.dbPath.startsWith("~")) {
|
|
278
278
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
279
279
|
}
|
|
280
280
|
return config;
|
|
281
281
|
} catch {
|
|
282
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
282
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
function loadConfigSync() {
|
|
286
286
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
287
|
-
const configPath =
|
|
288
|
-
if (!
|
|
289
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
287
|
+
const configPath = path2.join(dir, "config.json");
|
|
288
|
+
if (!existsSync(configPath)) {
|
|
289
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
290
290
|
}
|
|
291
291
|
try {
|
|
292
|
-
const raw =
|
|
292
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
293
293
|
let parsed = JSON.parse(raw);
|
|
294
294
|
parsed = migrateLegacyConfig(parsed);
|
|
295
295
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
296
296
|
normalizeScalingRoadmap(migratedCfg);
|
|
297
297
|
normalizeSessionLifecycle(migratedCfg);
|
|
298
298
|
normalizeAutoUpdate(migratedCfg);
|
|
299
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
299
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
300
300
|
} catch {
|
|
301
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
301
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
async function saveConfig(config) {
|
|
305
305
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
306
306
|
await mkdir(dir, { recursive: true });
|
|
307
|
-
const configPath =
|
|
307
|
+
const configPath = path2.join(dir, "config.json");
|
|
308
308
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
309
309
|
if (config.cloud?.apiKey) {
|
|
310
310
|
await chmod(configPath, 384);
|
|
@@ -329,10 +329,10 @@ var init_config = __esm({
|
|
|
329
329
|
"src/lib/config.ts"() {
|
|
330
330
|
"use strict";
|
|
331
331
|
EXE_AI_DIR = resolveDataDir();
|
|
332
|
-
DB_PATH =
|
|
333
|
-
MODELS_DIR =
|
|
334
|
-
CONFIG_PATH =
|
|
335
|
-
LEGACY_LANCE_PATH =
|
|
332
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
333
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
334
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
335
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
336
336
|
CURRENT_CONFIG_VERSION = 1;
|
|
337
337
|
DEFAULT_CONFIG = {
|
|
338
338
|
config_version: CURRENT_CONFIG_VERSION,
|
|
@@ -406,9 +406,9 @@ var init_config = __esm({
|
|
|
406
406
|
|
|
407
407
|
// src/lib/employees.ts
|
|
408
408
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
409
|
-
import { existsSync as
|
|
410
|
-
import { execSync as
|
|
411
|
-
import
|
|
409
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
410
|
+
import { execSync as execSync2 } from "child_process";
|
|
411
|
+
import path3 from "path";
|
|
412
412
|
import os2 from "os";
|
|
413
413
|
function normalizeRole(role) {
|
|
414
414
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -430,9 +430,9 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
|
430
430
|
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
431
431
|
}
|
|
432
432
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
433
|
-
if (!
|
|
433
|
+
if (!existsSync2(employeesPath)) return [];
|
|
434
434
|
try {
|
|
435
|
-
return JSON.parse(
|
|
435
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
436
436
|
} catch {
|
|
437
437
|
return [];
|
|
438
438
|
}
|
|
@@ -451,395 +451,948 @@ var init_employees = __esm({
|
|
|
451
451
|
"src/lib/employees.ts"() {
|
|
452
452
|
"use strict";
|
|
453
453
|
init_config();
|
|
454
|
-
EMPLOYEES_PATH =
|
|
454
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
455
455
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
456
456
|
COORDINATOR_ROLE = "COO";
|
|
457
457
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
458
458
|
}
|
|
459
459
|
});
|
|
460
460
|
|
|
461
|
-
// src/lib/
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
import { createClient } from "@libsql/client";
|
|
474
|
-
async function initDatabase(config) {
|
|
475
|
-
if (_client) {
|
|
476
|
-
_client.close();
|
|
477
|
-
_client = null;
|
|
478
|
-
_resilientClient = null;
|
|
461
|
+
// src/lib/exe-daemon-client.ts
|
|
462
|
+
import net from "net";
|
|
463
|
+
import { spawn } from "child_process";
|
|
464
|
+
import { randomUUID } from "crypto";
|
|
465
|
+
import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
466
|
+
import path4 from "path";
|
|
467
|
+
import { fileURLToPath } from "url";
|
|
468
|
+
function handleData(chunk) {
|
|
469
|
+
_buffer += chunk.toString();
|
|
470
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
471
|
+
_buffer = "";
|
|
472
|
+
return;
|
|
479
473
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
474
|
+
let newlineIdx;
|
|
475
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
476
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
477
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
478
|
+
if (!line) continue;
|
|
479
|
+
try {
|
|
480
|
+
const response = JSON.parse(line);
|
|
481
|
+
const id = response.id;
|
|
482
|
+
if (!id) continue;
|
|
483
|
+
const entry = _pending.get(id);
|
|
484
|
+
if (entry) {
|
|
485
|
+
clearTimeout(entry.timer);
|
|
486
|
+
_pending.delete(id);
|
|
487
|
+
entry.resolve(response);
|
|
488
|
+
}
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
485
491
|
}
|
|
486
|
-
_client = createClient(opts);
|
|
487
|
-
_resilientClient = wrapWithRetry(_client);
|
|
488
|
-
}
|
|
489
|
-
function isInitialized() {
|
|
490
|
-
return _client !== null;
|
|
491
492
|
}
|
|
492
|
-
function
|
|
493
|
-
if (
|
|
494
|
-
|
|
493
|
+
function cleanupStaleFiles() {
|
|
494
|
+
if (existsSync3(PID_PATH)) {
|
|
495
|
+
try {
|
|
496
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
497
|
+
if (pid > 0) {
|
|
498
|
+
try {
|
|
499
|
+
process.kill(pid, 0);
|
|
500
|
+
return;
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} catch {
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
unlinkSync2(PID_PATH);
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
unlinkSync2(SOCKET_PATH);
|
|
512
|
+
} catch {
|
|
513
|
+
}
|
|
495
514
|
}
|
|
496
|
-
return _resilientClient;
|
|
497
515
|
}
|
|
498
|
-
function
|
|
499
|
-
|
|
500
|
-
|
|
516
|
+
function findPackageRoot() {
|
|
517
|
+
let dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
518
|
+
const { root } = path4.parse(dir);
|
|
519
|
+
while (dir !== root) {
|
|
520
|
+
if (existsSync3(path4.join(dir, "package.json"))) return dir;
|
|
521
|
+
dir = path4.dirname(dir);
|
|
501
522
|
}
|
|
502
|
-
return
|
|
523
|
+
return null;
|
|
503
524
|
}
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
525
|
+
function spawnDaemon() {
|
|
526
|
+
const pkgRoot = findPackageRoot();
|
|
527
|
+
if (!pkgRoot) {
|
|
528
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
532
|
+
if (!existsSync3(daemonPath)) {
|
|
533
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
534
|
+
`);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const resolvedPath = daemonPath;
|
|
538
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
539
|
+
`);
|
|
540
|
+
const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
|
|
541
|
+
let stderrFd = "ignore";
|
|
509
542
|
try {
|
|
510
|
-
|
|
543
|
+
stderrFd = openSync(logPath, "a");
|
|
511
544
|
} catch {
|
|
512
545
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
532
|
-
ON memories(timestamp);
|
|
533
|
-
|
|
534
|
-
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
535
|
-
ON memories(session_id);
|
|
536
|
-
|
|
537
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
538
|
-
ON memories(project_name);
|
|
539
|
-
|
|
540
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
541
|
-
ON memories(tool_name);
|
|
542
|
-
|
|
543
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
544
|
-
ON memories(version);
|
|
545
|
-
|
|
546
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
547
|
-
ON memories(agent_id, project_name);
|
|
548
|
-
`);
|
|
549
|
-
await client.executeMultiple(`
|
|
550
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
551
|
-
raw_text,
|
|
552
|
-
content='memories',
|
|
553
|
-
content_rowid='rowid'
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
557
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
558
|
-
END;
|
|
559
|
-
|
|
560
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
561
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
562
|
-
END;
|
|
563
|
-
|
|
564
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
565
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
566
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
567
|
-
END;
|
|
568
|
-
`);
|
|
569
|
-
await client.executeMultiple(`
|
|
570
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
571
|
-
key TEXT PRIMARY KEY,
|
|
572
|
-
value TEXT NOT NULL
|
|
573
|
-
);
|
|
574
|
-
`);
|
|
575
|
-
await client.executeMultiple(`
|
|
576
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
577
|
-
id TEXT PRIMARY KEY,
|
|
578
|
-
title TEXT NOT NULL,
|
|
579
|
-
assigned_to TEXT NOT NULL,
|
|
580
|
-
assigned_by TEXT NOT NULL,
|
|
581
|
-
project_name TEXT NOT NULL,
|
|
582
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
583
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
584
|
-
task_file TEXT,
|
|
585
|
-
created_at TEXT NOT NULL,
|
|
586
|
-
updated_at TEXT NOT NULL
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
590
|
-
ON tasks(assigned_to, status);
|
|
591
|
-
`);
|
|
592
|
-
await client.executeMultiple(`
|
|
593
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
594
|
-
id TEXT PRIMARY KEY,
|
|
595
|
-
agent_id TEXT NOT NULL,
|
|
596
|
-
project_name TEXT,
|
|
597
|
-
domain TEXT,
|
|
598
|
-
content TEXT NOT NULL,
|
|
599
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
600
|
-
created_at TEXT NOT NULL,
|
|
601
|
-
updated_at TEXT NOT NULL
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
605
|
-
ON behaviors(agent_id, active);
|
|
606
|
-
`);
|
|
607
|
-
try {
|
|
608
|
-
const coordinatorName = getCoordinatorName();
|
|
609
|
-
const existing = await client.execute({
|
|
610
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
611
|
-
args: [coordinatorName]
|
|
612
|
-
});
|
|
613
|
-
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
614
|
-
const seededAt = "2026-03-25T00:00:00Z";
|
|
615
|
-
for (const [domain, content] of [
|
|
616
|
-
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
617
|
-
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
618
|
-
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
619
|
-
]) {
|
|
620
|
-
await client.execute({
|
|
621
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
622
|
-
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
623
|
-
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
624
|
-
});
|
|
625
|
-
}
|
|
546
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
547
|
+
detached: true,
|
|
548
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
549
|
+
env: {
|
|
550
|
+
...process.env,
|
|
551
|
+
TMUX: void 0,
|
|
552
|
+
// Daemon is global — must not inherit session scope
|
|
553
|
+
TMUX_PANE: void 0,
|
|
554
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
555
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
556
|
+
EXE_DAEMON_PID: PID_PATH
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
child.unref();
|
|
560
|
+
if (typeof stderrFd === "number") {
|
|
561
|
+
try {
|
|
562
|
+
closeSync(stderrFd);
|
|
563
|
+
} catch {
|
|
626
564
|
}
|
|
627
|
-
} catch {
|
|
628
565
|
}
|
|
566
|
+
}
|
|
567
|
+
function acquireSpawnLock() {
|
|
629
568
|
try {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
});
|
|
634
|
-
} catch {
|
|
635
|
-
}
|
|
636
|
-
try {
|
|
637
|
-
await client.execute({
|
|
638
|
-
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
639
|
-
args: []
|
|
640
|
-
});
|
|
641
|
-
} catch {
|
|
642
|
-
}
|
|
643
|
-
try {
|
|
644
|
-
await client.execute({
|
|
645
|
-
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
646
|
-
args: []
|
|
647
|
-
});
|
|
648
|
-
} catch {
|
|
649
|
-
}
|
|
650
|
-
try {
|
|
651
|
-
await client.execute({
|
|
652
|
-
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
653
|
-
ON tasks(parent_task_id)
|
|
654
|
-
WHERE parent_task_id IS NOT NULL`,
|
|
655
|
-
args: []
|
|
656
|
-
});
|
|
569
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
570
|
+
closeSync(fd);
|
|
571
|
+
return true;
|
|
657
572
|
} catch {
|
|
573
|
+
try {
|
|
574
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
575
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
576
|
+
try {
|
|
577
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
582
|
+
closeSync(fd);
|
|
583
|
+
return true;
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
658
590
|
}
|
|
591
|
+
}
|
|
592
|
+
function releaseSpawnLock() {
|
|
659
593
|
try {
|
|
660
|
-
|
|
661
|
-
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
662
|
-
args: []
|
|
663
|
-
});
|
|
594
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
664
595
|
} catch {
|
|
665
596
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
597
|
+
}
|
|
598
|
+
function connectToSocket() {
|
|
599
|
+
return new Promise((resolve) => {
|
|
600
|
+
if (_socket && _connected) {
|
|
601
|
+
resolve(true);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
605
|
+
const connectTimeout = setTimeout(() => {
|
|
606
|
+
socket.destroy();
|
|
607
|
+
resolve(false);
|
|
608
|
+
}, 2e3);
|
|
609
|
+
socket.on("connect", () => {
|
|
610
|
+
clearTimeout(connectTimeout);
|
|
611
|
+
_socket = socket;
|
|
612
|
+
_connected = true;
|
|
613
|
+
_buffer = "";
|
|
614
|
+
socket.on("data", handleData);
|
|
615
|
+
socket.on("close", () => {
|
|
616
|
+
_connected = false;
|
|
617
|
+
_socket = null;
|
|
618
|
+
for (const [id, entry] of _pending) {
|
|
619
|
+
clearTimeout(entry.timer);
|
|
620
|
+
_pending.delete(id);
|
|
621
|
+
entry.resolve({ error: "Connection closed" });
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
socket.on("error", () => {
|
|
625
|
+
_connected = false;
|
|
626
|
+
_socket = null;
|
|
627
|
+
});
|
|
628
|
+
resolve(true);
|
|
670
629
|
});
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
await client.execute({
|
|
675
|
-
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
676
|
-
args: []
|
|
630
|
+
socket.on("error", () => {
|
|
631
|
+
clearTimeout(connectTimeout);
|
|
632
|
+
resolve(false);
|
|
677
633
|
});
|
|
678
|
-
}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
async function connectEmbedDaemon() {
|
|
637
|
+
if (_socket && _connected) return true;
|
|
638
|
+
if (await connectToSocket()) return true;
|
|
639
|
+
if (acquireSpawnLock()) {
|
|
640
|
+
try {
|
|
641
|
+
cleanupStaleFiles();
|
|
642
|
+
spawnDaemon();
|
|
643
|
+
} finally {
|
|
644
|
+
releaseSpawnLock();
|
|
645
|
+
}
|
|
679
646
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
647
|
+
const start = Date.now();
|
|
648
|
+
let delay2 = 100;
|
|
649
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
650
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
651
|
+
if (await connectToSocket()) return true;
|
|
652
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
686
653
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
function sendRequest(texts, priority) {
|
|
657
|
+
return sendDaemonRequest({ texts, priority });
|
|
658
|
+
}
|
|
659
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
660
|
+
return new Promise((resolve) => {
|
|
661
|
+
if (!_socket || !_connected) {
|
|
662
|
+
resolve({ error: "Not connected" });
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const id = randomUUID();
|
|
666
|
+
const timer = setTimeout(() => {
|
|
667
|
+
_pending.delete(id);
|
|
668
|
+
resolve({ error: "Request timeout" });
|
|
669
|
+
}, timeoutMs);
|
|
670
|
+
_pending.set(id, { resolve, timer });
|
|
671
|
+
try {
|
|
672
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
673
|
+
} catch {
|
|
674
|
+
clearTimeout(timer);
|
|
675
|
+
_pending.delete(id);
|
|
676
|
+
resolve({ error: "Write failed" });
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
async function pingDaemon() {
|
|
681
|
+
if (!_socket || !_connected) return null;
|
|
682
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
683
|
+
if (response.health) {
|
|
684
|
+
return response.health;
|
|
693
685
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
function killAndRespawnDaemon() {
|
|
689
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
690
|
+
if (existsSync3(PID_PATH)) {
|
|
691
|
+
try {
|
|
692
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
693
|
+
if (pid > 0) {
|
|
694
|
+
try {
|
|
695
|
+
process.kill(pid, "SIGKILL");
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} catch {
|
|
700
|
+
}
|
|
700
701
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
args: []
|
|
705
|
-
});
|
|
706
|
-
} catch {
|
|
702
|
+
if (_socket) {
|
|
703
|
+
_socket.destroy();
|
|
704
|
+
_socket = null;
|
|
707
705
|
}
|
|
706
|
+
_connected = false;
|
|
707
|
+
_buffer = "";
|
|
708
708
|
try {
|
|
709
|
-
|
|
710
|
-
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
711
|
-
args: []
|
|
712
|
-
});
|
|
709
|
+
unlinkSync2(PID_PATH);
|
|
713
710
|
} catch {
|
|
714
711
|
}
|
|
715
712
|
try {
|
|
716
|
-
|
|
717
|
-
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
718
|
-
args: []
|
|
719
|
-
});
|
|
713
|
+
unlinkSync2(SOCKET_PATH);
|
|
720
714
|
} catch {
|
|
721
715
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
716
|
+
spawnDaemon();
|
|
717
|
+
}
|
|
718
|
+
async function embedViaClient(text, priority = "high") {
|
|
719
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
720
|
+
_requestCount++;
|
|
721
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
722
|
+
const health = await pingDaemon();
|
|
723
|
+
if (!health) {
|
|
724
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
725
|
+
`);
|
|
726
|
+
killAndRespawnDaemon();
|
|
727
|
+
const start = Date.now();
|
|
728
|
+
let delay2 = 200;
|
|
729
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
730
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
731
|
+
if (await connectToSocket()) break;
|
|
732
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
733
|
+
}
|
|
734
|
+
if (!_connected) return null;
|
|
735
|
+
}
|
|
728
736
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
737
|
+
const result = await sendRequest([text], priority);
|
|
738
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
739
|
+
if (result.error) {
|
|
740
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
741
|
+
`);
|
|
742
|
+
killAndRespawnDaemon();
|
|
743
|
+
const start = Date.now();
|
|
744
|
+
let delay2 = 200;
|
|
745
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
746
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
747
|
+
if (await connectToSocket()) break;
|
|
748
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
749
|
+
}
|
|
750
|
+
if (!_connected) return null;
|
|
751
|
+
const retry = await sendRequest([text], priority);
|
|
752
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
753
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
754
|
+
`);
|
|
735
755
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
function disconnectClient() {
|
|
759
|
+
if (_socket) {
|
|
760
|
+
_socket.destroy();
|
|
761
|
+
_socket = null;
|
|
742
762
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
763
|
+
_connected = false;
|
|
764
|
+
_buffer = "";
|
|
765
|
+
for (const [id, entry] of _pending) {
|
|
766
|
+
clearTimeout(entry.timer);
|
|
767
|
+
_pending.delete(id);
|
|
768
|
+
entry.resolve({ error: "Client disconnected" });
|
|
749
769
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
770
|
+
}
|
|
771
|
+
function isClientConnected() {
|
|
772
|
+
return _connected;
|
|
773
|
+
}
|
|
774
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
775
|
+
var init_exe_daemon_client = __esm({
|
|
776
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
777
|
+
"use strict";
|
|
778
|
+
init_config();
|
|
779
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
|
|
780
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
|
|
781
|
+
SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
782
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
783
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
784
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
785
|
+
_socket = null;
|
|
786
|
+
_connected = false;
|
|
787
|
+
_buffer = "";
|
|
788
|
+
_requestCount = 0;
|
|
789
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
790
|
+
_pending = /* @__PURE__ */ new Map();
|
|
791
|
+
MAX_BUFFER = 1e7;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// src/lib/daemon-protocol.ts
|
|
796
|
+
function serializeValue(v) {
|
|
797
|
+
if (v === null || v === void 0) return null;
|
|
798
|
+
if (typeof v === "bigint") return Number(v);
|
|
799
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
800
|
+
if (v instanceof Uint8Array) {
|
|
801
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
802
|
+
}
|
|
803
|
+
if (ArrayBuffer.isView(v)) {
|
|
804
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
805
|
+
}
|
|
806
|
+
if (v instanceof ArrayBuffer) {
|
|
807
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
808
|
+
}
|
|
809
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
810
|
+
return String(v);
|
|
811
|
+
}
|
|
812
|
+
function deserializeValue(v) {
|
|
813
|
+
if (v === null) return null;
|
|
814
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
815
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
816
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
817
|
+
}
|
|
818
|
+
return v;
|
|
819
|
+
}
|
|
820
|
+
function deserializeResultSet(srs) {
|
|
821
|
+
const rows = srs.rows.map((obj) => {
|
|
822
|
+
const values = srs.columns.map(
|
|
823
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
756
824
|
);
|
|
825
|
+
const row = values;
|
|
826
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
827
|
+
const col = srs.columns[i];
|
|
828
|
+
if (col !== void 0) {
|
|
829
|
+
row[col] = values[i] ?? null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
Object.defineProperty(row, "length", {
|
|
833
|
+
value: values.length,
|
|
834
|
+
enumerable: false
|
|
835
|
+
});
|
|
836
|
+
return row;
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
columns: srs.columns,
|
|
840
|
+
columnTypes: srs.columnTypes ?? [],
|
|
841
|
+
rows,
|
|
842
|
+
rowsAffected: srs.rowsAffected,
|
|
843
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
844
|
+
toJSON: () => ({
|
|
845
|
+
columns: srs.columns,
|
|
846
|
+
columnTypes: srs.columnTypes ?? [],
|
|
847
|
+
rows: srs.rows,
|
|
848
|
+
rowsAffected: srs.rowsAffected,
|
|
849
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
850
|
+
})
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
var init_daemon_protocol = __esm({
|
|
854
|
+
"src/lib/daemon-protocol.ts"() {
|
|
855
|
+
"use strict";
|
|
856
|
+
}
|
|
857
|
+
});
|
|
757
858
|
|
|
758
|
-
|
|
759
|
-
|
|
859
|
+
// src/lib/db-daemon-client.ts
|
|
860
|
+
var db_daemon_client_exports = {};
|
|
861
|
+
__export(db_daemon_client_exports, {
|
|
862
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
863
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
864
|
+
});
|
|
865
|
+
function normalizeStatement(stmt) {
|
|
866
|
+
if (typeof stmt === "string") {
|
|
867
|
+
return { sql: stmt, args: [] };
|
|
868
|
+
}
|
|
869
|
+
const sql = stmt.sql;
|
|
870
|
+
let args = [];
|
|
871
|
+
if (Array.isArray(stmt.args)) {
|
|
872
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
873
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
874
|
+
const named = {};
|
|
875
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
876
|
+
named[key] = serializeValue(val);
|
|
877
|
+
}
|
|
878
|
+
return { sql, args: named };
|
|
879
|
+
}
|
|
880
|
+
return { sql, args };
|
|
881
|
+
}
|
|
882
|
+
function createDaemonDbClient(fallbackClient) {
|
|
883
|
+
let _useDaemon = false;
|
|
884
|
+
const client = {
|
|
885
|
+
async execute(stmt) {
|
|
886
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
887
|
+
return fallbackClient.execute(stmt);
|
|
888
|
+
}
|
|
889
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
890
|
+
const response = await sendDaemonRequest({
|
|
891
|
+
type: "db-execute",
|
|
892
|
+
sql,
|
|
893
|
+
args
|
|
894
|
+
});
|
|
895
|
+
if (response.error) {
|
|
896
|
+
const errMsg = String(response.error);
|
|
897
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
898
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
899
|
+
`);
|
|
900
|
+
return fallbackClient.execute(stmt);
|
|
901
|
+
}
|
|
902
|
+
throw new Error(errMsg);
|
|
903
|
+
}
|
|
904
|
+
if (response.db) {
|
|
905
|
+
return deserializeResultSet(response.db);
|
|
906
|
+
}
|
|
907
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
908
|
+
return fallbackClient.execute(stmt);
|
|
909
|
+
},
|
|
910
|
+
async batch(stmts, mode) {
|
|
911
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
912
|
+
return fallbackClient.batch(stmts, mode);
|
|
913
|
+
}
|
|
914
|
+
const statements = stmts.map(normalizeStatement);
|
|
915
|
+
const response = await sendDaemonRequest({
|
|
916
|
+
type: "db-batch",
|
|
917
|
+
statements,
|
|
918
|
+
mode: mode ?? "deferred"
|
|
919
|
+
});
|
|
920
|
+
if (response.error) {
|
|
921
|
+
const errMsg = String(response.error);
|
|
922
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
923
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
924
|
+
`);
|
|
925
|
+
return fallbackClient.batch(stmts, mode);
|
|
926
|
+
}
|
|
927
|
+
throw new Error(errMsg);
|
|
928
|
+
}
|
|
929
|
+
const batchResults = response["db-batch"];
|
|
930
|
+
if (batchResults) {
|
|
931
|
+
return batchResults.map(deserializeResultSet);
|
|
932
|
+
}
|
|
933
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
934
|
+
return fallbackClient.batch(stmts, mode);
|
|
935
|
+
},
|
|
936
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
937
|
+
async transaction(mode) {
|
|
938
|
+
return fallbackClient.transaction(mode);
|
|
939
|
+
},
|
|
940
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
941
|
+
async executeMultiple(sql) {
|
|
942
|
+
return fallbackClient.executeMultiple(sql);
|
|
943
|
+
},
|
|
944
|
+
// migrate — delegate to fallback
|
|
945
|
+
async migrate(stmts) {
|
|
946
|
+
return fallbackClient.migrate(stmts);
|
|
947
|
+
},
|
|
948
|
+
// Sync mode — delegate to fallback
|
|
949
|
+
sync() {
|
|
950
|
+
return fallbackClient.sync();
|
|
951
|
+
},
|
|
952
|
+
close() {
|
|
953
|
+
_useDaemon = false;
|
|
954
|
+
},
|
|
955
|
+
get closed() {
|
|
956
|
+
return fallbackClient.closed;
|
|
957
|
+
},
|
|
958
|
+
get protocol() {
|
|
959
|
+
return fallbackClient.protocol;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
return {
|
|
963
|
+
...client,
|
|
964
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
965
|
+
_enableDaemon() {
|
|
966
|
+
_useDaemon = true;
|
|
967
|
+
},
|
|
968
|
+
/** Check if daemon routing is active */
|
|
969
|
+
_isDaemonActive() {
|
|
970
|
+
return _useDaemon && isClientConnected();
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
975
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
976
|
+
const connected = await connectEmbedDaemon();
|
|
977
|
+
if (!connected) {
|
|
978
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
982
|
+
client._enableDaemon();
|
|
983
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
984
|
+
return client;
|
|
985
|
+
}
|
|
986
|
+
var init_db_daemon_client = __esm({
|
|
987
|
+
"src/lib/db-daemon-client.ts"() {
|
|
988
|
+
"use strict";
|
|
989
|
+
init_exe_daemon_client();
|
|
990
|
+
init_daemon_protocol();
|
|
991
|
+
}
|
|
992
|
+
});
|
|
760
993
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
994
|
+
// src/lib/database.ts
|
|
995
|
+
var database_exports = {};
|
|
996
|
+
__export(database_exports, {
|
|
997
|
+
disposeDatabase: () => disposeDatabase,
|
|
998
|
+
disposeTurso: () => disposeTurso,
|
|
999
|
+
ensureSchema: () => ensureSchema,
|
|
1000
|
+
getClient: () => getClient,
|
|
1001
|
+
getRawClient: () => getRawClient,
|
|
1002
|
+
initDaemonClient: () => initDaemonClient,
|
|
1003
|
+
initDatabase: () => initDatabase,
|
|
1004
|
+
initTurso: () => initTurso,
|
|
1005
|
+
isInitialized: () => isInitialized
|
|
1006
|
+
});
|
|
1007
|
+
import { createClient } from "@libsql/client";
|
|
1008
|
+
async function initDatabase(config) {
|
|
1009
|
+
if (_client) {
|
|
1010
|
+
_client.close();
|
|
1011
|
+
_client = null;
|
|
1012
|
+
_resilientClient = null;
|
|
1013
|
+
}
|
|
1014
|
+
const opts = {
|
|
1015
|
+
url: `file:${config.dbPath}`
|
|
1016
|
+
};
|
|
1017
|
+
if (config.encryptionKey) {
|
|
1018
|
+
opts.encryptionKey = config.encryptionKey;
|
|
1019
|
+
}
|
|
1020
|
+
_client = createClient(opts);
|
|
1021
|
+
_resilientClient = wrapWithRetry(_client);
|
|
1022
|
+
}
|
|
1023
|
+
function isInitialized() {
|
|
1024
|
+
return _client !== null;
|
|
1025
|
+
}
|
|
1026
|
+
function getClient() {
|
|
1027
|
+
if (!_resilientClient) {
|
|
1028
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1029
|
+
}
|
|
1030
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1031
|
+
return _resilientClient;
|
|
1032
|
+
}
|
|
1033
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1034
|
+
return _daemonClient;
|
|
1035
|
+
}
|
|
1036
|
+
return _resilientClient;
|
|
1037
|
+
}
|
|
1038
|
+
async function initDaemonClient() {
|
|
1039
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1040
|
+
if (!_resilientClient) return;
|
|
1041
|
+
try {
|
|
1042
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1043
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1044
|
+
} catch (err) {
|
|
1045
|
+
process.stderr.write(
|
|
1046
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1047
|
+
`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function getRawClient() {
|
|
1052
|
+
if (!_client) {
|
|
1053
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1054
|
+
}
|
|
1055
|
+
return _client;
|
|
1056
|
+
}
|
|
1057
|
+
async function ensureSchema() {
|
|
1058
|
+
const client = getRawClient();
|
|
1059
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
1060
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1061
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
1062
|
+
try {
|
|
1063
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1064
|
+
} catch {
|
|
1065
|
+
}
|
|
764
1066
|
await client.executeMultiple(`
|
|
765
|
-
CREATE TABLE IF NOT EXISTS
|
|
1067
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
766
1068
|
id TEXT PRIMARY KEY,
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1069
|
+
agent_id TEXT NOT NULL,
|
|
1070
|
+
agent_role TEXT NOT NULL,
|
|
1071
|
+
session_id TEXT NOT NULL,
|
|
1072
|
+
timestamp TEXT NOT NULL,
|
|
1073
|
+
tool_name TEXT NOT NULL,
|
|
1074
|
+
project_name TEXT NOT NULL,
|
|
1075
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1076
|
+
raw_text TEXT NOT NULL,
|
|
1077
|
+
vector F32_BLOB(1024),
|
|
1078
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
771
1079
|
);
|
|
1080
|
+
|
|
1081
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
1082
|
+
ON memories(agent_id);
|
|
1083
|
+
|
|
1084
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
1085
|
+
ON memories(timestamp);
|
|
1086
|
+
|
|
1087
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
1088
|
+
ON memories(session_id);
|
|
1089
|
+
|
|
1090
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
1091
|
+
ON memories(project_name);
|
|
1092
|
+
|
|
1093
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
1094
|
+
ON memories(tool_name);
|
|
1095
|
+
|
|
1096
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
1097
|
+
ON memories(version);
|
|
1098
|
+
|
|
1099
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
1100
|
+
ON memories(agent_id, project_name);
|
|
772
1101
|
`);
|
|
773
1102
|
await client.executeMultiple(`
|
|
774
|
-
CREATE TABLE IF NOT EXISTS
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
event TEXT NOT NULL,
|
|
779
|
-
project TEXT NOT NULL,
|
|
780
|
-
summary TEXT NOT NULL,
|
|
781
|
-
task_file TEXT,
|
|
782
|
-
read INTEGER NOT NULL DEFAULT 0,
|
|
783
|
-
created_at TEXT NOT NULL
|
|
1103
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1104
|
+
raw_text,
|
|
1105
|
+
content='memories',
|
|
1106
|
+
content_rowid='rowid'
|
|
784
1107
|
);
|
|
785
1108
|
|
|
786
|
-
CREATE
|
|
787
|
-
|
|
1109
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
1110
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1111
|
+
END;
|
|
788
1112
|
|
|
789
|
-
CREATE
|
|
790
|
-
|
|
1113
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
1114
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1115
|
+
END;
|
|
791
1116
|
|
|
792
|
-
CREATE
|
|
793
|
-
|
|
1117
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
1118
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1119
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1120
|
+
END;
|
|
794
1121
|
`);
|
|
795
1122
|
await client.executeMultiple(`
|
|
796
|
-
CREATE TABLE IF NOT EXISTS
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
description TEXT NOT NULL,
|
|
800
|
-
job_type TEXT NOT NULL DEFAULT 'report',
|
|
801
|
-
prompt TEXT,
|
|
802
|
-
assigned_to TEXT,
|
|
803
|
-
project_name TEXT,
|
|
804
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
805
|
-
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
806
|
-
created_at TEXT NOT NULL
|
|
1123
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
1124
|
+
key TEXT PRIMARY KEY,
|
|
1125
|
+
value TEXT NOT NULL
|
|
807
1126
|
);
|
|
808
1127
|
`);
|
|
809
1128
|
await client.executeMultiple(`
|
|
810
|
-
CREATE TABLE IF NOT EXISTS
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1129
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1130
|
+
id TEXT PRIMARY KEY,
|
|
1131
|
+
title TEXT NOT NULL,
|
|
1132
|
+
assigned_to TEXT NOT NULL,
|
|
1133
|
+
assigned_by TEXT NOT NULL,
|
|
1134
|
+
project_name TEXT NOT NULL,
|
|
1135
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
1136
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
1137
|
+
task_file TEXT,
|
|
1138
|
+
created_at TEXT NOT NULL,
|
|
1139
|
+
updated_at TEXT NOT NULL
|
|
818
1140
|
);
|
|
1141
|
+
|
|
1142
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
1143
|
+
ON tasks(assigned_to, status);
|
|
819
1144
|
`);
|
|
820
1145
|
await client.executeMultiple(`
|
|
821
|
-
CREATE TABLE IF NOT EXISTS
|
|
822
|
-
id
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
status TEXT DEFAULT 'pending',
|
|
831
|
-
server_seq INTEGER,
|
|
832
|
-
retry_count INTEGER DEFAULT 0,
|
|
833
|
-
created_at TEXT NOT NULL,
|
|
834
|
-
delivered_at TEXT,
|
|
835
|
-
processed_at TEXT,
|
|
836
|
-
failed_at TEXT,
|
|
837
|
-
failure_reason TEXT
|
|
1146
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1147
|
+
id TEXT PRIMARY KEY,
|
|
1148
|
+
agent_id TEXT NOT NULL,
|
|
1149
|
+
project_name TEXT,
|
|
1150
|
+
domain TEXT,
|
|
1151
|
+
content TEXT NOT NULL,
|
|
1152
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1153
|
+
created_at TEXT NOT NULL,
|
|
1154
|
+
updated_at TEXT NOT NULL
|
|
838
1155
|
);
|
|
839
1156
|
|
|
840
|
-
CREATE INDEX IF NOT EXISTS
|
|
841
|
-
ON
|
|
842
|
-
|
|
1157
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
1158
|
+
ON behaviors(agent_id, active);
|
|
1159
|
+
`);
|
|
1160
|
+
try {
|
|
1161
|
+
const coordinatorName = getCoordinatorName();
|
|
1162
|
+
const existing = await client.execute({
|
|
1163
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1164
|
+
args: [coordinatorName]
|
|
1165
|
+
});
|
|
1166
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1167
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1168
|
+
for (const [domain, content] of [
|
|
1169
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1170
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1171
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1172
|
+
]) {
|
|
1173
|
+
await client.execute({
|
|
1174
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1175
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1176
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
try {
|
|
1183
|
+
await client.execute({
|
|
1184
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1185
|
+
args: []
|
|
1186
|
+
});
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
try {
|
|
1190
|
+
await client.execute({
|
|
1191
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1192
|
+
args: []
|
|
1193
|
+
});
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
try {
|
|
1197
|
+
await client.execute({
|
|
1198
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
1199
|
+
args: []
|
|
1200
|
+
});
|
|
1201
|
+
} catch {
|
|
1202
|
+
}
|
|
1203
|
+
try {
|
|
1204
|
+
await client.execute({
|
|
1205
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1206
|
+
ON tasks(parent_task_id)
|
|
1207
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
1208
|
+
args: []
|
|
1209
|
+
});
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
try {
|
|
1213
|
+
await client.execute({
|
|
1214
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
1215
|
+
args: []
|
|
1216
|
+
});
|
|
1217
|
+
} catch {
|
|
1218
|
+
}
|
|
1219
|
+
try {
|
|
1220
|
+
await client.execute({
|
|
1221
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1222
|
+
args: []
|
|
1223
|
+
});
|
|
1224
|
+
} catch {
|
|
1225
|
+
}
|
|
1226
|
+
try {
|
|
1227
|
+
await client.execute({
|
|
1228
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
1229
|
+
args: []
|
|
1230
|
+
});
|
|
1231
|
+
} catch {
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
await client.execute({
|
|
1235
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1236
|
+
args: []
|
|
1237
|
+
});
|
|
1238
|
+
} catch {
|
|
1239
|
+
}
|
|
1240
|
+
try {
|
|
1241
|
+
await client.execute({
|
|
1242
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1243
|
+
args: []
|
|
1244
|
+
});
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
try {
|
|
1248
|
+
await client.execute({
|
|
1249
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
1250
|
+
args: []
|
|
1251
|
+
});
|
|
1252
|
+
} catch {
|
|
1253
|
+
}
|
|
1254
|
+
try {
|
|
1255
|
+
await client.execute({
|
|
1256
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1257
|
+
args: []
|
|
1258
|
+
});
|
|
1259
|
+
} catch {
|
|
1260
|
+
}
|
|
1261
|
+
try {
|
|
1262
|
+
await client.execute({
|
|
1263
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1264
|
+
args: []
|
|
1265
|
+
});
|
|
1266
|
+
} catch {
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
await client.execute({
|
|
1270
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1271
|
+
args: []
|
|
1272
|
+
});
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
try {
|
|
1276
|
+
await client.execute({
|
|
1277
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1278
|
+
args: []
|
|
1279
|
+
});
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
try {
|
|
1283
|
+
await client.execute({
|
|
1284
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1285
|
+
args: []
|
|
1286
|
+
});
|
|
1287
|
+
} catch {
|
|
1288
|
+
}
|
|
1289
|
+
try {
|
|
1290
|
+
await client.execute({
|
|
1291
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1292
|
+
args: []
|
|
1293
|
+
});
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
try {
|
|
1297
|
+
await client.execute({
|
|
1298
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
1299
|
+
args: []
|
|
1300
|
+
});
|
|
1301
|
+
} catch {
|
|
1302
|
+
}
|
|
1303
|
+
await client.executeMultiple(`
|
|
1304
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
1305
|
+
id TEXT PRIMARY KEY,
|
|
1306
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
1307
|
+
source_memory_id TEXT NOT NULL,
|
|
1308
|
+
created_at TEXT NOT NULL
|
|
1309
|
+
);
|
|
1310
|
+
|
|
1311
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
1312
|
+
ON consolidations(source_memory_id);
|
|
1313
|
+
|
|
1314
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
1315
|
+
ON consolidations(consolidated_memory_id);
|
|
1316
|
+
`);
|
|
1317
|
+
await client.executeMultiple(`
|
|
1318
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
1319
|
+
id TEXT PRIMARY KEY,
|
|
1320
|
+
text TEXT NOT NULL,
|
|
1321
|
+
created_at TEXT NOT NULL,
|
|
1322
|
+
due_date TEXT,
|
|
1323
|
+
completed_at TEXT
|
|
1324
|
+
);
|
|
1325
|
+
`);
|
|
1326
|
+
await client.executeMultiple(`
|
|
1327
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
1328
|
+
id TEXT PRIMARY KEY,
|
|
1329
|
+
agent_id TEXT NOT NULL,
|
|
1330
|
+
agent_role TEXT NOT NULL,
|
|
1331
|
+
event TEXT NOT NULL,
|
|
1332
|
+
project TEXT NOT NULL,
|
|
1333
|
+
summary TEXT NOT NULL,
|
|
1334
|
+
task_file TEXT,
|
|
1335
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
1336
|
+
created_at TEXT NOT NULL
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_read
|
|
1340
|
+
ON notifications(read);
|
|
1341
|
+
|
|
1342
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1343
|
+
ON notifications(agent_id);
|
|
1344
|
+
|
|
1345
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1346
|
+
ON notifications(task_file);
|
|
1347
|
+
`);
|
|
1348
|
+
await client.executeMultiple(`
|
|
1349
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
1350
|
+
id TEXT PRIMARY KEY,
|
|
1351
|
+
cron TEXT NOT NULL,
|
|
1352
|
+
description TEXT NOT NULL,
|
|
1353
|
+
job_type TEXT NOT NULL DEFAULT 'report',
|
|
1354
|
+
prompt TEXT,
|
|
1355
|
+
assigned_to TEXT,
|
|
1356
|
+
project_name TEXT,
|
|
1357
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1358
|
+
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
1359
|
+
created_at TEXT NOT NULL
|
|
1360
|
+
);
|
|
1361
|
+
`);
|
|
1362
|
+
await client.executeMultiple(`
|
|
1363
|
+
CREATE TABLE IF NOT EXISTS device_registry (
|
|
1364
|
+
device_id TEXT PRIMARY KEY,
|
|
1365
|
+
friendly_name TEXT NOT NULL,
|
|
1366
|
+
hostname TEXT NOT NULL,
|
|
1367
|
+
projects TEXT NOT NULL DEFAULT '[]',
|
|
1368
|
+
agents TEXT NOT NULL DEFAULT '[]',
|
|
1369
|
+
connected INTEGER DEFAULT 0,
|
|
1370
|
+
last_seen TEXT NOT NULL
|
|
1371
|
+
);
|
|
1372
|
+
`);
|
|
1373
|
+
await client.executeMultiple(`
|
|
1374
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
1375
|
+
id TEXT PRIMARY KEY,
|
|
1376
|
+
from_agent TEXT NOT NULL,
|
|
1377
|
+
from_device TEXT NOT NULL DEFAULT 'local',
|
|
1378
|
+
target_agent TEXT NOT NULL,
|
|
1379
|
+
target_project TEXT,
|
|
1380
|
+
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1381
|
+
content TEXT NOT NULL,
|
|
1382
|
+
priority TEXT DEFAULT 'normal',
|
|
1383
|
+
status TEXT DEFAULT 'pending',
|
|
1384
|
+
server_seq INTEGER,
|
|
1385
|
+
retry_count INTEGER DEFAULT 0,
|
|
1386
|
+
created_at TEXT NOT NULL,
|
|
1387
|
+
delivered_at TEXT,
|
|
1388
|
+
processed_at TEXT,
|
|
1389
|
+
failed_at TEXT,
|
|
1390
|
+
failure_reason TEXT
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1394
|
+
ON messages(target_agent, status);
|
|
1395
|
+
|
|
843
1396
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
844
1397
|
ON messages(target_agent, from_agent, server_seq);
|
|
845
1398
|
`);
|
|
@@ -981,6 +1534,12 @@ async function ensureSchema() {
|
|
|
981
1534
|
} catch {
|
|
982
1535
|
}
|
|
983
1536
|
}
|
|
1537
|
+
try {
|
|
1538
|
+
await client.execute(
|
|
1539
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1540
|
+
);
|
|
1541
|
+
} catch {
|
|
1542
|
+
}
|
|
984
1543
|
await client.executeMultiple(`
|
|
985
1544
|
CREATE TABLE IF NOT EXISTS entities (
|
|
986
1545
|
id TEXT PRIMARY KEY,
|
|
@@ -1033,7 +1592,30 @@ async function ensureSchema() {
|
|
|
1033
1592
|
entity_id TEXT NOT NULL,
|
|
1034
1593
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1035
1594
|
);
|
|
1595
|
+
|
|
1596
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1597
|
+
name,
|
|
1598
|
+
content=entities,
|
|
1599
|
+
content_rowid=rowid
|
|
1600
|
+
);
|
|
1601
|
+
|
|
1602
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1603
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1604
|
+
END;
|
|
1605
|
+
|
|
1606
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1607
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1608
|
+
END;
|
|
1609
|
+
|
|
1610
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1611
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1612
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1613
|
+
END;
|
|
1036
1614
|
`);
|
|
1615
|
+
try {
|
|
1616
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1617
|
+
} catch {
|
|
1618
|
+
}
|
|
1037
1619
|
await client.executeMultiple(`
|
|
1038
1620
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1039
1621
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1214,7 +1796,34 @@ async function ensureSchema() {
|
|
|
1214
1796
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1215
1797
|
ON conversations(channel_id);
|
|
1216
1798
|
`);
|
|
1217
|
-
|
|
1799
|
+
await client.executeMultiple(`
|
|
1800
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1801
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1802
|
+
agent_id TEXT NOT NULL,
|
|
1803
|
+
session_name TEXT,
|
|
1804
|
+
task_id TEXT,
|
|
1805
|
+
project_name TEXT,
|
|
1806
|
+
started_at TEXT NOT NULL
|
|
1807
|
+
);
|
|
1808
|
+
|
|
1809
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1810
|
+
ON session_agent_map(agent_id);
|
|
1811
|
+
`);
|
|
1812
|
+
try {
|
|
1813
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1814
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1815
|
+
await client.execute({
|
|
1816
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1817
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1818
|
+
FROM memories
|
|
1819
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1820
|
+
GROUP BY session_id, agent_id`,
|
|
1821
|
+
args: []
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
} catch {
|
|
1825
|
+
}
|
|
1826
|
+
try {
|
|
1218
1827
|
await client.execute({
|
|
1219
1828
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
1220
1829
|
args: []
|
|
@@ -1347,15 +1956,41 @@ async function ensureSchema() {
|
|
|
1347
1956
|
});
|
|
1348
1957
|
} catch {
|
|
1349
1958
|
}
|
|
1959
|
+
for (const col of [
|
|
1960
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1961
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1962
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1963
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1964
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1965
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1966
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1967
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1968
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1969
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1970
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1971
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1972
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1973
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1974
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1975
|
+
]) {
|
|
1976
|
+
try {
|
|
1977
|
+
await client.execute(col);
|
|
1978
|
+
} catch {
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1350
1981
|
}
|
|
1351
1982
|
async function disposeDatabase() {
|
|
1983
|
+
if (_daemonClient) {
|
|
1984
|
+
_daemonClient.close();
|
|
1985
|
+
_daemonClient = null;
|
|
1986
|
+
}
|
|
1352
1987
|
if (_client) {
|
|
1353
1988
|
_client.close();
|
|
1354
1989
|
_client = null;
|
|
1355
1990
|
_resilientClient = null;
|
|
1356
1991
|
}
|
|
1357
1992
|
}
|
|
1358
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1993
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1359
1994
|
var init_database = __esm({
|
|
1360
1995
|
"src/lib/database.ts"() {
|
|
1361
1996
|
"use strict";
|
|
@@ -1363,6 +1998,7 @@ var init_database = __esm({
|
|
|
1363
1998
|
init_employees();
|
|
1364
1999
|
_client = null;
|
|
1365
2000
|
_resilientClient = null;
|
|
2001
|
+
_daemonClient = null;
|
|
1366
2002
|
initTurso = initDatabase;
|
|
1367
2003
|
disposeTurso = disposeDatabase;
|
|
1368
2004
|
}
|
|
@@ -1437,7 +2073,7 @@ __export(shard_manager_exports, {
|
|
|
1437
2073
|
shardExists: () => shardExists
|
|
1438
2074
|
});
|
|
1439
2075
|
import path6 from "path";
|
|
1440
|
-
import { existsSync as existsSync5, mkdirSync, readdirSync
|
|
2076
|
+
import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
|
|
1441
2077
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1442
2078
|
function initShardManager(encryptionKey) {
|
|
1443
2079
|
_encryptionKey = encryptionKey;
|
|
@@ -1476,7 +2112,7 @@ function shardExists(projectName) {
|
|
|
1476
2112
|
}
|
|
1477
2113
|
function listShards() {
|
|
1478
2114
|
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1479
|
-
return
|
|
2115
|
+
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1480
2116
|
}
|
|
1481
2117
|
async function ensureShardSchema(client) {
|
|
1482
2118
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
@@ -1786,7 +2422,7 @@ __export(global_procedures_exports, {
|
|
|
1786
2422
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
1787
2423
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
1788
2424
|
});
|
|
1789
|
-
import { randomUUID } from "crypto";
|
|
2425
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1790
2426
|
async function loadGlobalProcedures() {
|
|
1791
2427
|
const client = getClient();
|
|
1792
2428
|
const result = await client.execute({
|
|
@@ -1810,768 +2446,423 @@ function getGlobalProceduresBlock() {
|
|
|
1810
2446
|
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
1811
2447
|
if (sections.length === 0) return "";
|
|
1812
2448
|
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1813
|
-
|
|
1814
|
-
${sections.join("\n\n")}
|
|
1815
|
-
`;
|
|
1816
|
-
}
|
|
1817
|
-
async function storeGlobalProcedure(input2) {
|
|
1818
|
-
const id = randomUUID();
|
|
1819
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1820
|
-
const client = getClient();
|
|
1821
|
-
await client.execute({
|
|
1822
|
-
sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
1823
|
-
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
1824
|
-
args: [id, input2.title, input2.content, input2.priority ?? "p0", input2.domain ?? null, now, now]
|
|
1825
|
-
});
|
|
1826
|
-
await loadGlobalProcedures();
|
|
1827
|
-
return id;
|
|
1828
|
-
}
|
|
1829
|
-
async function deactivateGlobalProcedure(id) {
|
|
1830
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1831
|
-
const client = getClient();
|
|
1832
|
-
const result = await client.execute({
|
|
1833
|
-
sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
1834
|
-
args: [now, id]
|
|
1835
|
-
});
|
|
1836
|
-
await loadGlobalProcedures();
|
|
1837
|
-
return result.rowsAffected > 0;
|
|
1838
|
-
}
|
|
1839
|
-
var _customerCache, _cacheLoaded, _platformCache;
|
|
1840
|
-
var init_global_procedures = __esm({
|
|
1841
|
-
"src/lib/global-procedures.ts"() {
|
|
1842
|
-
"use strict";
|
|
1843
|
-
init_database();
|
|
1844
|
-
init_platform_procedures();
|
|
1845
|
-
_customerCache = "";
|
|
1846
|
-
_cacheLoaded = false;
|
|
1847
|
-
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
1848
|
-
${p.content}`).join("\n\n");
|
|
1849
|
-
}
|
|
1850
|
-
});
|
|
1851
|
-
|
|
1852
|
-
// src/lib/notifications.ts
|
|
1853
|
-
import crypto2 from "crypto";
|
|
1854
|
-
import path7 from "path";
|
|
1855
|
-
import os4 from "os";
|
|
1856
|
-
import {
|
|
1857
|
-
readFileSync as readFileSync4,
|
|
1858
|
-
readdirSync as readdirSync3,
|
|
1859
|
-
unlinkSync as unlinkSync2,
|
|
1860
|
-
existsSync as existsSync6,
|
|
1861
|
-
rmdirSync
|
|
1862
|
-
} from "fs";
|
|
1863
|
-
async function writeNotification(notification) {
|
|
1864
|
-
try {
|
|
1865
|
-
const client = getClient();
|
|
1866
|
-
const id = crypto2.randomUUID();
|
|
1867
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1868
|
-
await client.execute({
|
|
1869
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
1870
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
1871
|
-
args: [
|
|
1872
|
-
id,
|
|
1873
|
-
notification.agentId,
|
|
1874
|
-
notification.agentRole,
|
|
1875
|
-
notification.event,
|
|
1876
|
-
notification.project,
|
|
1877
|
-
notification.summary,
|
|
1878
|
-
notification.taskFile ?? null,
|
|
1879
|
-
now
|
|
1880
|
-
]
|
|
1881
|
-
});
|
|
1882
|
-
} catch (err) {
|
|
1883
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
1884
|
-
`);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
1888
|
-
try {
|
|
1889
|
-
const client = getClient();
|
|
1890
|
-
await client.execute({
|
|
1891
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
1892
|
-
args: [taskFile]
|
|
1893
|
-
});
|
|
1894
|
-
} catch {
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
var init_notifications = __esm({
|
|
1898
|
-
"src/lib/notifications.ts"() {
|
|
1899
|
-
"use strict";
|
|
1900
|
-
init_database();
|
|
1901
|
-
}
|
|
1902
|
-
});
|
|
1903
|
-
|
|
1904
|
-
// src/lib/license.ts
|
|
1905
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1906
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1907
|
-
import path8 from "path";
|
|
1908
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
1909
|
-
async function fetchRetry(url, init) {
|
|
1910
|
-
try {
|
|
1911
|
-
return await fetch(url, init);
|
|
1912
|
-
} catch {
|
|
1913
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1914
|
-
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
function loadDeviceId() {
|
|
1918
|
-
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
1919
|
-
try {
|
|
1920
|
-
if (existsSync7(deviceJsonPath)) {
|
|
1921
|
-
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
1922
|
-
if (data.deviceId) return data.deviceId;
|
|
1923
|
-
}
|
|
1924
|
-
} catch {
|
|
1925
|
-
}
|
|
1926
|
-
try {
|
|
1927
|
-
if (existsSync7(DEVICE_ID_PATH)) {
|
|
1928
|
-
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
1929
|
-
if (id2) return id2;
|
|
1930
|
-
}
|
|
1931
|
-
} catch {
|
|
1932
|
-
}
|
|
1933
|
-
const id = randomUUID2();
|
|
1934
|
-
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
1935
|
-
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
1936
|
-
return id;
|
|
1937
|
-
}
|
|
1938
|
-
function loadLicense() {
|
|
1939
|
-
try {
|
|
1940
|
-
if (!existsSync7(LICENSE_PATH)) return null;
|
|
1941
|
-
return readFileSync5(LICENSE_PATH, "utf8").trim();
|
|
1942
|
-
} catch {
|
|
1943
|
-
return null;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
function saveLicense(apiKey) {
|
|
1947
|
-
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
1948
|
-
writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1949
|
-
}
|
|
1950
|
-
async function verifyLicenseJwt(token) {
|
|
1951
|
-
try {
|
|
1952
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1953
|
-
const { payload } = await jwtVerify(token, key, {
|
|
1954
|
-
algorithms: [LICENSE_JWT_ALG]
|
|
1955
|
-
});
|
|
1956
|
-
const plan = payload.plan ?? "free";
|
|
1957
|
-
const email = payload.sub ?? "";
|
|
1958
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1959
|
-
return {
|
|
1960
|
-
valid: true,
|
|
1961
|
-
plan,
|
|
1962
|
-
email,
|
|
1963
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1964
|
-
deviceLimit: limits.devices,
|
|
1965
|
-
employeeLimit: limits.employees,
|
|
1966
|
-
memoryLimit: limits.memories
|
|
1967
|
-
};
|
|
1968
|
-
} catch {
|
|
1969
|
-
return null;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
async function getCachedLicense() {
|
|
1973
|
-
try {
|
|
1974
|
-
if (!existsSync7(CACHE_PATH)) return null;
|
|
1975
|
-
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
1976
|
-
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1977
|
-
return await verifyLicenseJwt(raw.token);
|
|
1978
|
-
} catch {
|
|
1979
|
-
return null;
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
function readCachedToken() {
|
|
1983
|
-
try {
|
|
1984
|
-
if (!existsSync7(CACHE_PATH)) return null;
|
|
1985
|
-
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
1986
|
-
return typeof raw.token === "string" ? raw.token : null;
|
|
1987
|
-
} catch {
|
|
1988
|
-
return null;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
function getRawCachedPlan() {
|
|
1992
|
-
try {
|
|
1993
|
-
const token = readCachedToken();
|
|
1994
|
-
if (!token) return null;
|
|
1995
|
-
const parts = token.split(".");
|
|
1996
|
-
if (parts.length !== 3) return null;
|
|
1997
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1998
|
-
const plan = payload.plan ?? "free";
|
|
1999
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2000
|
-
process.stderr.write(
|
|
2001
|
-
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
2002
|
-
`
|
|
2003
|
-
);
|
|
2004
|
-
return {
|
|
2005
|
-
valid: true,
|
|
2006
|
-
plan,
|
|
2007
|
-
email: payload.sub ?? "",
|
|
2008
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2009
|
-
deviceLimit: limits.devices,
|
|
2010
|
-
employeeLimit: limits.employees,
|
|
2011
|
-
memoryLimit: limits.memories
|
|
2012
|
-
};
|
|
2013
|
-
} catch {
|
|
2014
|
-
return null;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
function cacheResponse(token) {
|
|
2018
|
-
try {
|
|
2019
|
-
writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2020
|
-
} catch {
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
async function validateLicense(apiKey, deviceId) {
|
|
2024
|
-
const did = deviceId ?? loadDeviceId();
|
|
2025
|
-
try {
|
|
2026
|
-
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2027
|
-
method: "POST",
|
|
2028
|
-
headers: { "Content-Type": "application/json" },
|
|
2029
|
-
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
2030
|
-
signal: AbortSignal.timeout(1e4)
|
|
2031
|
-
});
|
|
2032
|
-
if (res.ok) {
|
|
2033
|
-
const data = await res.json();
|
|
2034
|
-
if (data.error === "device_limit_exceeded") {
|
|
2035
|
-
const cached2 = await getCachedLicense();
|
|
2036
|
-
if (cached2) return cached2;
|
|
2037
|
-
const raw2 = getRawCachedPlan();
|
|
2038
|
-
if (raw2) return { ...raw2, valid: false };
|
|
2039
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2040
|
-
}
|
|
2041
|
-
if (data.token) {
|
|
2042
|
-
cacheResponse(data.token);
|
|
2043
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
2044
|
-
if (verified) return verified;
|
|
2045
|
-
}
|
|
2046
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2047
|
-
return {
|
|
2048
|
-
valid: data.valid,
|
|
2049
|
-
plan: data.plan,
|
|
2050
|
-
email: data.email,
|
|
2051
|
-
expiresAt: data.expiresAt,
|
|
2052
|
-
deviceLimit: limits.devices,
|
|
2053
|
-
employeeLimit: limits.employees,
|
|
2054
|
-
memoryLimit: limits.memories
|
|
2055
|
-
};
|
|
2056
|
-
}
|
|
2057
|
-
const cached = await getCachedLicense();
|
|
2058
|
-
if (cached) return cached;
|
|
2059
|
-
const raw = getRawCachedPlan();
|
|
2060
|
-
if (raw) return raw;
|
|
2061
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2062
|
-
} catch {
|
|
2063
|
-
const cached = await getCachedLicense();
|
|
2064
|
-
if (cached) return cached;
|
|
2065
|
-
const rawFallback = getRawCachedPlan();
|
|
2066
|
-
if (rawFallback) return rawFallback;
|
|
2067
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
function getCacheAgeMs() {
|
|
2071
|
-
try {
|
|
2072
|
-
const { statSync: statSync3 } = __require("fs");
|
|
2073
|
-
const s = statSync3(CACHE_PATH);
|
|
2074
|
-
return Date.now() - s.mtimeMs;
|
|
2075
|
-
} catch {
|
|
2076
|
-
return Infinity;
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
async function checkLicense() {
|
|
2080
|
-
let key = loadLicense();
|
|
2081
|
-
if (!key) {
|
|
2082
|
-
try {
|
|
2083
|
-
const configPath = path8.join(EXE_AI_DIR, "config.json");
|
|
2084
|
-
if (existsSync7(configPath)) {
|
|
2085
|
-
const raw = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
2086
|
-
const cloud = raw.cloud;
|
|
2087
|
-
if (cloud?.apiKey) {
|
|
2088
|
-
key = cloud.apiKey;
|
|
2089
|
-
saveLicense(key);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
} catch {
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
if (!key) return FREE_LICENSE;
|
|
2096
|
-
const cached = await getCachedLicense();
|
|
2097
|
-
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
2098
|
-
const deviceId = loadDeviceId();
|
|
2099
|
-
return validateLicense(key, deviceId);
|
|
2100
|
-
}
|
|
2101
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
|
|
2102
|
-
var init_license = __esm({
|
|
2103
|
-
"src/lib/license.ts"() {
|
|
2104
|
-
"use strict";
|
|
2105
|
-
init_config();
|
|
2106
|
-
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
2107
|
-
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2108
|
-
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
2109
|
-
API_BASE = "https://askexe.com/cloud";
|
|
2110
|
-
RETRY_DELAY_MS = 500;
|
|
2111
|
-
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
2112
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
2113
|
-
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
2114
|
-
-----END PUBLIC KEY-----`;
|
|
2115
|
-
LICENSE_JWT_ALG = "ES256";
|
|
2116
|
-
PLAN_LIMITS = {
|
|
2117
|
-
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2118
|
-
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
2119
|
-
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
2120
|
-
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
2121
|
-
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
2122
|
-
};
|
|
2123
|
-
FREE_LICENSE = {
|
|
2124
|
-
valid: true,
|
|
2125
|
-
plan: "free",
|
|
2126
|
-
email: "",
|
|
2127
|
-
expiresAt: null,
|
|
2128
|
-
deviceLimit: 1,
|
|
2129
|
-
employeeLimit: 1,
|
|
2130
|
-
memoryLimit: 5e3
|
|
2131
|
-
};
|
|
2132
|
-
CACHE_MAX_AGE_MS = 36e5;
|
|
2133
|
-
}
|
|
2134
|
-
});
|
|
2135
|
-
|
|
2136
|
-
// src/lib/plan-limits.ts
|
|
2137
|
-
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
2138
|
-
import path9 from "path";
|
|
2139
|
-
function getLicenseSync() {
|
|
2140
|
-
try {
|
|
2141
|
-
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2142
|
-
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
2143
|
-
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2144
|
-
const parts = raw.token.split(".");
|
|
2145
|
-
if (parts.length !== 3) return freeLicense();
|
|
2146
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2147
|
-
const plan = payload.plan ?? "free";
|
|
2148
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2149
|
-
return {
|
|
2150
|
-
valid: true,
|
|
2151
|
-
plan,
|
|
2152
|
-
email: payload.sub ?? "",
|
|
2153
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2154
|
-
deviceLimit: limits.devices,
|
|
2155
|
-
employeeLimit: limits.employees,
|
|
2156
|
-
memoryLimit: limits.memories
|
|
2157
|
-
};
|
|
2158
|
-
} catch {
|
|
2159
|
-
return freeLicense();
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
function freeLicense() {
|
|
2163
|
-
const limits = PLAN_LIMITS.free;
|
|
2164
|
-
return {
|
|
2165
|
-
valid: true,
|
|
2166
|
-
plan: "free",
|
|
2167
|
-
email: "",
|
|
2168
|
-
expiresAt: null,
|
|
2169
|
-
deviceLimit: limits.devices,
|
|
2170
|
-
employeeLimit: limits.employees,
|
|
2171
|
-
memoryLimit: limits.memories
|
|
2172
|
-
};
|
|
2449
|
+
|
|
2450
|
+
${sections.join("\n\n")}
|
|
2451
|
+
`;
|
|
2173
2452
|
}
|
|
2174
|
-
async function
|
|
2175
|
-
|
|
2453
|
+
async function storeGlobalProcedure(input2) {
|
|
2454
|
+
const id = randomUUID2();
|
|
2455
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2176
2456
|
const client = getClient();
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2457
|
+
await client.execute({
|
|
2458
|
+
sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
2459
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
2460
|
+
args: [id, input2.title, input2.content, input2.priority ?? "p0", input2.domain ?? null, now, now]
|
|
2461
|
+
});
|
|
2462
|
+
await loadGlobalProcedures();
|
|
2463
|
+
return id;
|
|
2182
2464
|
}
|
|
2183
|
-
async function
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2465
|
+
async function deactivateGlobalProcedure(id) {
|
|
2466
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2467
|
+
const client = getClient();
|
|
2468
|
+
const result = await client.execute({
|
|
2469
|
+
sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
2470
|
+
args: [now, id]
|
|
2471
|
+
});
|
|
2472
|
+
await loadGlobalProcedures();
|
|
2473
|
+
return result.rowsAffected > 0;
|
|
2474
|
+
}
|
|
2475
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
2476
|
+
var init_global_procedures = __esm({
|
|
2477
|
+
"src/lib/global-procedures.ts"() {
|
|
2478
|
+
"use strict";
|
|
2479
|
+
init_database();
|
|
2480
|
+
init_platform_procedures();
|
|
2481
|
+
_customerCache = "";
|
|
2482
|
+
_cacheLoaded = false;
|
|
2483
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
2484
|
+
${p.content}`).join("\n\n");
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
|
|
2488
|
+
// src/lib/notifications.ts
|
|
2489
|
+
import crypto2 from "crypto";
|
|
2490
|
+
import path7 from "path";
|
|
2491
|
+
import os4 from "os";
|
|
2492
|
+
import {
|
|
2493
|
+
readFileSync as readFileSync4,
|
|
2494
|
+
readdirSync as readdirSync2,
|
|
2495
|
+
unlinkSync as unlinkSync3,
|
|
2496
|
+
existsSync as existsSync6,
|
|
2497
|
+
rmdirSync
|
|
2498
|
+
} from "fs";
|
|
2499
|
+
async function writeNotification(notification) {
|
|
2500
|
+
try {
|
|
2501
|
+
const client = getClient();
|
|
2502
|
+
const id = crypto2.randomUUID();
|
|
2503
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2504
|
+
await client.execute({
|
|
2505
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2506
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2507
|
+
args: [
|
|
2508
|
+
id,
|
|
2509
|
+
notification.agentId,
|
|
2510
|
+
notification.agentRole,
|
|
2511
|
+
notification.event,
|
|
2512
|
+
notification.project,
|
|
2513
|
+
notification.summary,
|
|
2514
|
+
notification.taskFile ?? null,
|
|
2515
|
+
now
|
|
2516
|
+
]
|
|
2517
|
+
});
|
|
2518
|
+
} catch (err) {
|
|
2519
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
2520
|
+
`);
|
|
2191
2521
|
}
|
|
2192
2522
|
}
|
|
2193
|
-
function
|
|
2194
|
-
const license = getLicenseSync();
|
|
2195
|
-
if (license.employeeLimit < 0) return;
|
|
2196
|
-
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2197
|
-
let count = 0;
|
|
2523
|
+
async function markAsReadByTaskFile(taskFile) {
|
|
2198
2524
|
try {
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
}
|
|
2525
|
+
const client = getClient();
|
|
2526
|
+
await client.execute({
|
|
2527
|
+
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
2528
|
+
args: [taskFile]
|
|
2529
|
+
});
|
|
2204
2530
|
} catch {
|
|
2205
|
-
throw new PlanLimitError(
|
|
2206
|
-
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
2207
|
-
);
|
|
2208
|
-
}
|
|
2209
|
-
if (count >= license.employeeLimit) {
|
|
2210
|
-
throw new PlanLimitError(
|
|
2211
|
-
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
2212
|
-
);
|
|
2213
2531
|
}
|
|
2214
2532
|
}
|
|
2215
|
-
var
|
|
2216
|
-
|
|
2217
|
-
"src/lib/plan-limits.ts"() {
|
|
2533
|
+
var init_notifications = __esm({
|
|
2534
|
+
"src/lib/notifications.ts"() {
|
|
2218
2535
|
"use strict";
|
|
2219
2536
|
init_database();
|
|
2220
|
-
init_employees();
|
|
2221
|
-
init_license();
|
|
2222
|
-
init_config();
|
|
2223
|
-
PlanLimitError = class extends Error {
|
|
2224
|
-
constructor(message) {
|
|
2225
|
-
super(message);
|
|
2226
|
-
this.name = "PlanLimitError";
|
|
2227
|
-
}
|
|
2228
|
-
};
|
|
2229
|
-
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2230
2537
|
}
|
|
2231
2538
|
});
|
|
2232
2539
|
|
|
2233
|
-
// src/lib/
|
|
2234
|
-
import
|
|
2235
|
-
import { spawn } from "child_process";
|
|
2540
|
+
// src/lib/license.ts
|
|
2541
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
2236
2542
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2237
|
-
import
|
|
2238
|
-
import
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
return;
|
|
2245
|
-
}
|
|
2246
|
-
let newlineIdx;
|
|
2247
|
-
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
2248
|
-
const line = _buffer.slice(0, newlineIdx).trim();
|
|
2249
|
-
_buffer = _buffer.slice(newlineIdx + 1);
|
|
2250
|
-
if (!line) continue;
|
|
2251
|
-
try {
|
|
2252
|
-
const response = JSON.parse(line);
|
|
2253
|
-
const entry = _pending.get(response.id);
|
|
2254
|
-
if (entry) {
|
|
2255
|
-
clearTimeout(entry.timer);
|
|
2256
|
-
_pending.delete(response.id);
|
|
2257
|
-
entry.resolve(response);
|
|
2258
|
-
}
|
|
2259
|
-
} catch {
|
|
2260
|
-
}
|
|
2543
|
+
import path8 from "path";
|
|
2544
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
2545
|
+
async function fetchRetry(url, init) {
|
|
2546
|
+
try {
|
|
2547
|
+
return await fetch(url, init);
|
|
2548
|
+
} catch {
|
|
2549
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
2550
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
2261
2551
|
}
|
|
2262
2552
|
}
|
|
2263
|
-
function
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
process.kill(pid, 0);
|
|
2270
|
-
return;
|
|
2271
|
-
} catch {
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
} catch {
|
|
2275
|
-
}
|
|
2276
|
-
try {
|
|
2277
|
-
unlinkSync3(PID_PATH);
|
|
2278
|
-
} catch {
|
|
2553
|
+
function loadDeviceId() {
|
|
2554
|
+
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
2555
|
+
try {
|
|
2556
|
+
if (existsSync7(deviceJsonPath)) {
|
|
2557
|
+
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
2558
|
+
if (data.deviceId) return data.deviceId;
|
|
2279
2559
|
}
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2560
|
+
} catch {
|
|
2561
|
+
}
|
|
2562
|
+
try {
|
|
2563
|
+
if (existsSync7(DEVICE_ID_PATH)) {
|
|
2564
|
+
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
2565
|
+
if (id2) return id2;
|
|
2283
2566
|
}
|
|
2567
|
+
} catch {
|
|
2284
2568
|
}
|
|
2569
|
+
const id = randomUUID3();
|
|
2570
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2571
|
+
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
2572
|
+
return id;
|
|
2285
2573
|
}
|
|
2286
|
-
function
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2574
|
+
function loadLicense() {
|
|
2575
|
+
try {
|
|
2576
|
+
if (!existsSync7(LICENSE_PATH)) return null;
|
|
2577
|
+
return readFileSync5(LICENSE_PATH, "utf8").trim();
|
|
2578
|
+
} catch {
|
|
2579
|
+
return null;
|
|
2292
2580
|
}
|
|
2293
|
-
return null;
|
|
2294
2581
|
}
|
|
2295
|
-
function
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
}
|
|
2301
|
-
const daemonPath = path10.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2302
|
-
if (!existsSync9(daemonPath)) {
|
|
2303
|
-
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2304
|
-
`);
|
|
2305
|
-
return;
|
|
2306
|
-
}
|
|
2307
|
-
const resolvedPath = daemonPath;
|
|
2308
|
-
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2309
|
-
`);
|
|
2310
|
-
const logPath = path10.join(path10.dirname(SOCKET_PATH), "exed.log");
|
|
2311
|
-
let stderrFd = "ignore";
|
|
2582
|
+
function saveLicense(apiKey) {
|
|
2583
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2584
|
+
writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2585
|
+
}
|
|
2586
|
+
async function verifyLicenseJwt(token) {
|
|
2312
2587
|
try {
|
|
2313
|
-
|
|
2588
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
2589
|
+
const { payload } = await jwtVerify(token, key, {
|
|
2590
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
2591
|
+
});
|
|
2592
|
+
const plan = payload.plan ?? "free";
|
|
2593
|
+
const email = payload.sub ?? "";
|
|
2594
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2595
|
+
return {
|
|
2596
|
+
valid: true,
|
|
2597
|
+
plan,
|
|
2598
|
+
email,
|
|
2599
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2600
|
+
deviceLimit: limits.devices,
|
|
2601
|
+
employeeLimit: limits.employees,
|
|
2602
|
+
memoryLimit: limits.memories
|
|
2603
|
+
};
|
|
2314
2604
|
} catch {
|
|
2605
|
+
return null;
|
|
2315
2606
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2326
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2327
|
-
}
|
|
2328
|
-
});
|
|
2329
|
-
child.unref();
|
|
2330
|
-
if (typeof stderrFd === "number") {
|
|
2331
|
-
try {
|
|
2332
|
-
closeSync(stderrFd);
|
|
2333
|
-
} catch {
|
|
2334
|
-
}
|
|
2607
|
+
}
|
|
2608
|
+
async function getCachedLicense() {
|
|
2609
|
+
try {
|
|
2610
|
+
if (!existsSync7(CACHE_PATH)) return null;
|
|
2611
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2612
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2613
|
+
return await verifyLicenseJwt(raw.token);
|
|
2614
|
+
} catch {
|
|
2615
|
+
return null;
|
|
2335
2616
|
}
|
|
2336
2617
|
}
|
|
2337
|
-
function
|
|
2618
|
+
function readCachedToken() {
|
|
2338
2619
|
try {
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
return
|
|
2620
|
+
if (!existsSync7(CACHE_PATH)) return null;
|
|
2621
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2622
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
2342
2623
|
} catch {
|
|
2343
|
-
|
|
2344
|
-
const stat = statSync2(SPAWN_LOCK_PATH);
|
|
2345
|
-
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
2346
|
-
try {
|
|
2347
|
-
unlinkSync3(SPAWN_LOCK_PATH);
|
|
2348
|
-
} catch {
|
|
2349
|
-
}
|
|
2350
|
-
try {
|
|
2351
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
2352
|
-
closeSync(fd);
|
|
2353
|
-
return true;
|
|
2354
|
-
} catch {
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
} catch {
|
|
2358
|
-
}
|
|
2359
|
-
return false;
|
|
2624
|
+
return null;
|
|
2360
2625
|
}
|
|
2361
2626
|
}
|
|
2362
|
-
function
|
|
2627
|
+
function getRawCachedPlan() {
|
|
2363
2628
|
try {
|
|
2364
|
-
|
|
2629
|
+
const token = readCachedToken();
|
|
2630
|
+
if (!token) return null;
|
|
2631
|
+
const parts = token.split(".");
|
|
2632
|
+
if (parts.length !== 3) return null;
|
|
2633
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2634
|
+
const plan = payload.plan ?? "free";
|
|
2635
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2636
|
+
process.stderr.write(
|
|
2637
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
2638
|
+
`
|
|
2639
|
+
);
|
|
2640
|
+
return {
|
|
2641
|
+
valid: true,
|
|
2642
|
+
plan,
|
|
2643
|
+
email: payload.sub ?? "",
|
|
2644
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2645
|
+
deviceLimit: limits.devices,
|
|
2646
|
+
employeeLimit: limits.employees,
|
|
2647
|
+
memoryLimit: limits.memories
|
|
2648
|
+
};
|
|
2365
2649
|
} catch {
|
|
2650
|
+
return null;
|
|
2366
2651
|
}
|
|
2367
2652
|
}
|
|
2368
|
-
function
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
return;
|
|
2373
|
-
}
|
|
2374
|
-
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
2375
|
-
const connectTimeout = setTimeout(() => {
|
|
2376
|
-
socket.destroy();
|
|
2377
|
-
resolve(false);
|
|
2378
|
-
}, 2e3);
|
|
2379
|
-
socket.on("connect", () => {
|
|
2380
|
-
clearTimeout(connectTimeout);
|
|
2381
|
-
_socket = socket;
|
|
2382
|
-
_connected = true;
|
|
2383
|
-
_buffer = "";
|
|
2384
|
-
socket.on("data", handleData);
|
|
2385
|
-
socket.on("close", () => {
|
|
2386
|
-
_connected = false;
|
|
2387
|
-
_socket = null;
|
|
2388
|
-
for (const [id, entry] of _pending) {
|
|
2389
|
-
clearTimeout(entry.timer);
|
|
2390
|
-
_pending.delete(id);
|
|
2391
|
-
entry.resolve({ error: "Connection closed" });
|
|
2392
|
-
}
|
|
2393
|
-
});
|
|
2394
|
-
socket.on("error", () => {
|
|
2395
|
-
_connected = false;
|
|
2396
|
-
_socket = null;
|
|
2397
|
-
});
|
|
2398
|
-
resolve(true);
|
|
2399
|
-
});
|
|
2400
|
-
socket.on("error", () => {
|
|
2401
|
-
clearTimeout(connectTimeout);
|
|
2402
|
-
resolve(false);
|
|
2403
|
-
});
|
|
2404
|
-
});
|
|
2405
|
-
}
|
|
2406
|
-
async function connectEmbedDaemon() {
|
|
2407
|
-
if (_socket && _connected) return true;
|
|
2408
|
-
if (await connectToSocket()) return true;
|
|
2409
|
-
if (acquireSpawnLock()) {
|
|
2410
|
-
try {
|
|
2411
|
-
cleanupStaleFiles();
|
|
2412
|
-
spawnDaemon();
|
|
2413
|
-
} finally {
|
|
2414
|
-
releaseSpawnLock();
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
const start = Date.now();
|
|
2418
|
-
let delay2 = 100;
|
|
2419
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2420
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
2421
|
-
if (await connectToSocket()) return true;
|
|
2422
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2653
|
+
function cacheResponse(token) {
|
|
2654
|
+
try {
|
|
2655
|
+
writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2656
|
+
} catch {
|
|
2423
2657
|
}
|
|
2424
|
-
return false;
|
|
2425
|
-
}
|
|
2426
|
-
function sendRequest(texts, priority) {
|
|
2427
|
-
return new Promise((resolve) => {
|
|
2428
|
-
if (!_socket || !_connected) {
|
|
2429
|
-
resolve({ error: "Not connected" });
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
const id = randomUUID3();
|
|
2433
|
-
const timer = setTimeout(() => {
|
|
2434
|
-
_pending.delete(id);
|
|
2435
|
-
resolve({ error: "Request timeout" });
|
|
2436
|
-
}, REQUEST_TIMEOUT_MS);
|
|
2437
|
-
_pending.set(id, { resolve, timer });
|
|
2438
|
-
try {
|
|
2439
|
-
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
2440
|
-
} catch {
|
|
2441
|
-
clearTimeout(timer);
|
|
2442
|
-
_pending.delete(id);
|
|
2443
|
-
resolve({ error: "Write failed" });
|
|
2444
|
-
}
|
|
2445
|
-
});
|
|
2446
2658
|
}
|
|
2447
|
-
async function
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
const
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
_pending.set(id, {
|
|
2456
|
-
resolve: (data) => {
|
|
2457
|
-
if (data.health) {
|
|
2458
|
-
resolve(data.health);
|
|
2459
|
-
} else {
|
|
2460
|
-
resolve(null);
|
|
2461
|
-
}
|
|
2462
|
-
},
|
|
2463
|
-
timer
|
|
2659
|
+
async function validateLicense(apiKey, deviceId) {
|
|
2660
|
+
const did = deviceId ?? loadDeviceId();
|
|
2661
|
+
try {
|
|
2662
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2663
|
+
method: "POST",
|
|
2664
|
+
headers: { "Content-Type": "application/json" },
|
|
2665
|
+
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
2666
|
+
signal: AbortSignal.timeout(1e4)
|
|
2464
2667
|
});
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2668
|
+
if (res.ok) {
|
|
2669
|
+
const data = await res.json();
|
|
2670
|
+
if (data.error === "device_limit_exceeded") {
|
|
2671
|
+
const cached2 = await getCachedLicense();
|
|
2672
|
+
if (cached2) return cached2;
|
|
2673
|
+
const raw2 = getRawCachedPlan();
|
|
2674
|
+
if (raw2) return { ...raw2, valid: false };
|
|
2675
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2676
|
+
}
|
|
2677
|
+
if (data.token) {
|
|
2678
|
+
cacheResponse(data.token);
|
|
2679
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
2680
|
+
if (verified) return verified;
|
|
2681
|
+
}
|
|
2682
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2683
|
+
return {
|
|
2684
|
+
valid: data.valid,
|
|
2685
|
+
plan: data.plan,
|
|
2686
|
+
email: data.email,
|
|
2687
|
+
expiresAt: data.expiresAt,
|
|
2688
|
+
deviceLimit: limits.devices,
|
|
2689
|
+
employeeLimit: limits.employees,
|
|
2690
|
+
memoryLimit: limits.memories
|
|
2691
|
+
};
|
|
2471
2692
|
}
|
|
2472
|
-
|
|
2693
|
+
const cached = await getCachedLicense();
|
|
2694
|
+
if (cached) return cached;
|
|
2695
|
+
const raw = getRawCachedPlan();
|
|
2696
|
+
if (raw) return raw;
|
|
2697
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2698
|
+
} catch {
|
|
2699
|
+
const cached = await getCachedLicense();
|
|
2700
|
+
if (cached) return cached;
|
|
2701
|
+
const rawFallback = getRawCachedPlan();
|
|
2702
|
+
if (rawFallback) return rawFallback;
|
|
2703
|
+
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2704
|
+
}
|
|
2473
2705
|
}
|
|
2474
|
-
function
|
|
2475
|
-
|
|
2476
|
-
|
|
2706
|
+
function getCacheAgeMs() {
|
|
2707
|
+
try {
|
|
2708
|
+
const { statSync: statSync2 } = __require("fs");
|
|
2709
|
+
const s = statSync2(CACHE_PATH);
|
|
2710
|
+
return Date.now() - s.mtimeMs;
|
|
2711
|
+
} catch {
|
|
2712
|
+
return Infinity;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
async function checkLicense() {
|
|
2716
|
+
let key = loadLicense();
|
|
2717
|
+
if (!key) {
|
|
2477
2718
|
try {
|
|
2478
|
-
const
|
|
2479
|
-
if (
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2719
|
+
const configPath = path8.join(EXE_AI_DIR, "config.json");
|
|
2720
|
+
if (existsSync7(configPath)) {
|
|
2721
|
+
const raw = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
2722
|
+
const cloud = raw.cloud;
|
|
2723
|
+
if (cloud?.apiKey) {
|
|
2724
|
+
key = cloud.apiKey;
|
|
2725
|
+
saveLicense(key);
|
|
2483
2726
|
}
|
|
2484
2727
|
}
|
|
2485
2728
|
} catch {
|
|
2486
2729
|
}
|
|
2487
2730
|
}
|
|
2488
|
-
if (
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2731
|
+
if (!key) return FREE_LICENSE;
|
|
2732
|
+
const cached = await getCachedLicense();
|
|
2733
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
2734
|
+
const deviceId = loadDeviceId();
|
|
2735
|
+
return validateLicense(key, deviceId);
|
|
2736
|
+
}
|
|
2737
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
|
|
2738
|
+
var init_license = __esm({
|
|
2739
|
+
"src/lib/license.ts"() {
|
|
2740
|
+
"use strict";
|
|
2741
|
+
init_config();
|
|
2742
|
+
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
2743
|
+
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2744
|
+
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
2745
|
+
API_BASE = "https://askexe.com/cloud";
|
|
2746
|
+
RETRY_DELAY_MS = 500;
|
|
2747
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
2748
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
2749
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
2750
|
+
-----END PUBLIC KEY-----`;
|
|
2751
|
+
LICENSE_JWT_ALG = "ES256";
|
|
2752
|
+
PLAN_LIMITS = {
|
|
2753
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2754
|
+
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
2755
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
2756
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
2757
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
2758
|
+
};
|
|
2759
|
+
FREE_LICENSE = {
|
|
2760
|
+
valid: true,
|
|
2761
|
+
plan: "free",
|
|
2762
|
+
email: "",
|
|
2763
|
+
expiresAt: null,
|
|
2764
|
+
deviceLimit: 1,
|
|
2765
|
+
employeeLimit: 1,
|
|
2766
|
+
memoryLimit: 5e3
|
|
2767
|
+
};
|
|
2768
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
2497
2769
|
}
|
|
2770
|
+
});
|
|
2771
|
+
|
|
2772
|
+
// src/lib/plan-limits.ts
|
|
2773
|
+
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
2774
|
+
import path9 from "path";
|
|
2775
|
+
function getLicenseSync() {
|
|
2498
2776
|
try {
|
|
2499
|
-
|
|
2777
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2778
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
2779
|
+
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2780
|
+
const parts = raw.token.split(".");
|
|
2781
|
+
if (parts.length !== 3) return freeLicense();
|
|
2782
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2783
|
+
const plan = payload.plan ?? "free";
|
|
2784
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2785
|
+
return {
|
|
2786
|
+
valid: true,
|
|
2787
|
+
plan,
|
|
2788
|
+
email: payload.sub ?? "",
|
|
2789
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2790
|
+
deviceLimit: limits.devices,
|
|
2791
|
+
employeeLimit: limits.employees,
|
|
2792
|
+
memoryLimit: limits.memories
|
|
2793
|
+
};
|
|
2500
2794
|
} catch {
|
|
2795
|
+
return freeLicense();
|
|
2501
2796
|
}
|
|
2502
|
-
spawnDaemon();
|
|
2503
2797
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
if (await connectToSocket()) break;
|
|
2534
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2535
|
-
}
|
|
2536
|
-
if (!_connected) return null;
|
|
2537
|
-
const retry = await sendRequest([text], priority);
|
|
2538
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2539
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2540
|
-
`);
|
|
2798
|
+
function freeLicense() {
|
|
2799
|
+
const limits = PLAN_LIMITS.free;
|
|
2800
|
+
return {
|
|
2801
|
+
valid: true,
|
|
2802
|
+
plan: "free",
|
|
2803
|
+
email: "",
|
|
2804
|
+
expiresAt: null,
|
|
2805
|
+
deviceLimit: limits.devices,
|
|
2806
|
+
employeeLimit: limits.employees,
|
|
2807
|
+
memoryLimit: limits.memories
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
async function countActiveMemories() {
|
|
2811
|
+
if (!isInitialized()) return 0;
|
|
2812
|
+
const client = getClient();
|
|
2813
|
+
const result = await client.execute(
|
|
2814
|
+
"SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
|
|
2815
|
+
);
|
|
2816
|
+
const row = result.rows[0];
|
|
2817
|
+
return Number(row?.cnt ?? 0);
|
|
2818
|
+
}
|
|
2819
|
+
async function assertMemoryLimit() {
|
|
2820
|
+
const license = await checkLicense();
|
|
2821
|
+
if (license.memoryLimit < 0) return;
|
|
2822
|
+
const count = await countActiveMemories();
|
|
2823
|
+
if (count >= license.memoryLimit) {
|
|
2824
|
+
throw new PlanLimitError(
|
|
2825
|
+
`Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
|
|
2826
|
+
);
|
|
2541
2827
|
}
|
|
2542
|
-
return null;
|
|
2543
2828
|
}
|
|
2544
|
-
function
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2829
|
+
function assertEmployeeLimitSync(rosterPath) {
|
|
2830
|
+
const license = getLicenseSync();
|
|
2831
|
+
if (license.employeeLimit < 0) return;
|
|
2832
|
+
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2833
|
+
let count = 0;
|
|
2834
|
+
try {
|
|
2835
|
+
if (existsSync8(filePath)) {
|
|
2836
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
2837
|
+
const employees = JSON.parse(raw);
|
|
2838
|
+
count = Array.isArray(employees) ? employees.length : 0;
|
|
2839
|
+
}
|
|
2840
|
+
} catch {
|
|
2841
|
+
throw new PlanLimitError(
|
|
2842
|
+
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
2843
|
+
);
|
|
2548
2844
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
_pending.delete(id);
|
|
2554
|
-
entry.resolve({ error: "Client disconnected" });
|
|
2845
|
+
if (count >= license.employeeLimit) {
|
|
2846
|
+
throw new PlanLimitError(
|
|
2847
|
+
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
2848
|
+
);
|
|
2555
2849
|
}
|
|
2556
2850
|
}
|
|
2557
|
-
var
|
|
2558
|
-
var
|
|
2559
|
-
"src/lib/
|
|
2851
|
+
var PlanLimitError, CACHE_PATH2;
|
|
2852
|
+
var init_plan_limits = __esm({
|
|
2853
|
+
"src/lib/plan-limits.ts"() {
|
|
2560
2854
|
"use strict";
|
|
2855
|
+
init_database();
|
|
2856
|
+
init_employees();
|
|
2857
|
+
init_license();
|
|
2561
2858
|
init_config();
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
_connected = false;
|
|
2570
|
-
_buffer = "";
|
|
2571
|
-
_requestCount = 0;
|
|
2572
|
-
HEALTH_CHECK_INTERVAL = 100;
|
|
2573
|
-
_pending = /* @__PURE__ */ new Map();
|
|
2574
|
-
MAX_BUFFER = 1e7;
|
|
2859
|
+
PlanLimitError = class extends Error {
|
|
2860
|
+
constructor(message) {
|
|
2861
|
+
super(message);
|
|
2862
|
+
this.name = "PlanLimitError";
|
|
2863
|
+
}
|
|
2864
|
+
};
|
|
2865
|
+
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2575
2866
|
}
|
|
2576
2867
|
});
|
|
2577
2868
|
|
|
@@ -2644,12 +2935,12 @@ var init_embedder = __esm({
|
|
|
2644
2935
|
});
|
|
2645
2936
|
|
|
2646
2937
|
// src/lib/session-registry.ts
|
|
2647
|
-
import { readFileSync as
|
|
2648
|
-
import
|
|
2938
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync9 } from "fs";
|
|
2939
|
+
import path10 from "path";
|
|
2649
2940
|
import os5 from "os";
|
|
2650
2941
|
function registerSession(entry) {
|
|
2651
|
-
const dir =
|
|
2652
|
-
if (!
|
|
2942
|
+
const dir = path10.dirname(REGISTRY_PATH);
|
|
2943
|
+
if (!existsSync9(dir)) {
|
|
2653
2944
|
mkdirSync3(dir, { recursive: true });
|
|
2654
2945
|
}
|
|
2655
2946
|
const sessions = listSessions();
|
|
@@ -2663,7 +2954,7 @@ function registerSession(entry) {
|
|
|
2663
2954
|
}
|
|
2664
2955
|
function listSessions() {
|
|
2665
2956
|
try {
|
|
2666
|
-
const raw =
|
|
2957
|
+
const raw = readFileSync7(REGISTRY_PATH, "utf8");
|
|
2667
2958
|
return JSON.parse(raw);
|
|
2668
2959
|
} catch {
|
|
2669
2960
|
return [];
|
|
@@ -2673,18 +2964,18 @@ var REGISTRY_PATH;
|
|
|
2673
2964
|
var init_session_registry = __esm({
|
|
2674
2965
|
"src/lib/session-registry.ts"() {
|
|
2675
2966
|
"use strict";
|
|
2676
|
-
REGISTRY_PATH =
|
|
2967
|
+
REGISTRY_PATH = path10.join(os5.homedir(), ".exe-os", "session-registry.json");
|
|
2677
2968
|
}
|
|
2678
2969
|
});
|
|
2679
2970
|
|
|
2680
2971
|
// src/lib/session-key.ts
|
|
2681
|
-
import { execSync as
|
|
2972
|
+
import { execSync as execSync3 } from "child_process";
|
|
2682
2973
|
function getSessionKey() {
|
|
2683
2974
|
if (_cached2) return _cached2;
|
|
2684
2975
|
let pid = process.ppid;
|
|
2685
2976
|
for (let i = 0; i < 10; i++) {
|
|
2686
2977
|
try {
|
|
2687
|
-
const info =
|
|
2978
|
+
const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
|
|
2688
2979
|
encoding: "utf8",
|
|
2689
2980
|
timeout: 2e3
|
|
2690
2981
|
}).trim();
|
|
@@ -2820,14 +3111,14 @@ var init_transport = __esm({
|
|
|
2820
3111
|
});
|
|
2821
3112
|
|
|
2822
3113
|
// src/lib/cc-agent-support.ts
|
|
2823
|
-
import { execSync as
|
|
3114
|
+
import { execSync as execSync4 } from "child_process";
|
|
2824
3115
|
function _resetCcAgentSupportCache() {
|
|
2825
3116
|
_cachedSupport = null;
|
|
2826
3117
|
}
|
|
2827
3118
|
function claudeSupportsAgentFlag() {
|
|
2828
3119
|
if (_cachedSupport !== null) return _cachedSupport;
|
|
2829
3120
|
try {
|
|
2830
|
-
const helpOutput =
|
|
3121
|
+
const helpOutput = execSync4("claude --help 2>&1", {
|
|
2831
3122
|
encoding: "utf-8",
|
|
2832
3123
|
timeout: 5e3
|
|
2833
3124
|
});
|
|
@@ -2869,13 +3160,64 @@ var init_provider_table = __esm({
|
|
|
2869
3160
|
}
|
|
2870
3161
|
});
|
|
2871
3162
|
|
|
3163
|
+
// src/lib/runtime-table.ts
|
|
3164
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
3165
|
+
var init_runtime_table = __esm({
|
|
3166
|
+
"src/lib/runtime-table.ts"() {
|
|
3167
|
+
"use strict";
|
|
3168
|
+
RUNTIME_TABLE = {
|
|
3169
|
+
codex: {
|
|
3170
|
+
binary: "codex",
|
|
3171
|
+
launchMode: "exec",
|
|
3172
|
+
autoApproveFlag: "--full-auto",
|
|
3173
|
+
inlineFlag: "--no-alt-screen",
|
|
3174
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
3175
|
+
defaultModel: "gpt-5.4"
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
DEFAULT_RUNTIME = "claude";
|
|
3179
|
+
}
|
|
3180
|
+
});
|
|
3181
|
+
|
|
3182
|
+
// src/lib/agent-config.ts
|
|
3183
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
|
|
3184
|
+
import path11 from "path";
|
|
3185
|
+
function loadAgentConfig() {
|
|
3186
|
+
if (!existsSync10(AGENT_CONFIG_PATH)) return {};
|
|
3187
|
+
try {
|
|
3188
|
+
return JSON.parse(readFileSync8(AGENT_CONFIG_PATH, "utf-8"));
|
|
3189
|
+
} catch {
|
|
3190
|
+
return {};
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
function getAgentRuntime(agentId) {
|
|
3194
|
+
const config = loadAgentConfig();
|
|
3195
|
+
const entry = config[agentId];
|
|
3196
|
+
if (entry) return entry;
|
|
3197
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
3198
|
+
}
|
|
3199
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
3200
|
+
var init_agent_config = __esm({
|
|
3201
|
+
"src/lib/agent-config.ts"() {
|
|
3202
|
+
"use strict";
|
|
3203
|
+
init_config();
|
|
3204
|
+
init_runtime_table();
|
|
3205
|
+
AGENT_CONFIG_PATH = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
3206
|
+
DEFAULT_MODELS = {
|
|
3207
|
+
claude: "claude-opus-4",
|
|
3208
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
3209
|
+
opencode: "minimax-m2.7"
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
});
|
|
3213
|
+
|
|
2872
3214
|
// src/lib/intercom-queue.ts
|
|
2873
|
-
import { readFileSync as readFileSync9, writeFileSync as
|
|
3215
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
|
|
2874
3216
|
import path12 from "path";
|
|
2875
3217
|
import os6 from "os";
|
|
2876
3218
|
function ensureDir() {
|
|
2877
3219
|
const dir = path12.dirname(QUEUE_PATH);
|
|
2878
|
-
if (!existsSync11(dir))
|
|
3220
|
+
if (!existsSync11(dir)) mkdirSync5(dir, { recursive: true });
|
|
2879
3221
|
}
|
|
2880
3222
|
function readQueue() {
|
|
2881
3223
|
try {
|
|
@@ -2888,7 +3230,7 @@ function readQueue() {
|
|
|
2888
3230
|
function writeQueue(queue) {
|
|
2889
3231
|
ensureDir();
|
|
2890
3232
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
2891
|
-
|
|
3233
|
+
writeFileSync5(tmp, JSON.stringify(queue, null, 2));
|
|
2892
3234
|
renameSync3(tmp, QUEUE_PATH);
|
|
2893
3235
|
}
|
|
2894
3236
|
function queueIntercom(targetSession, reason) {
|
|
@@ -3261,8 +3603,8 @@ __export(tmux_routing_exports, {
|
|
|
3261
3603
|
spawnEmployee: () => spawnEmployee,
|
|
3262
3604
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3263
3605
|
});
|
|
3264
|
-
import { execFileSync as execFileSync2, execSync as
|
|
3265
|
-
import { readFileSync as readFileSync10, writeFileSync as
|
|
3606
|
+
import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
|
|
3607
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
|
|
3266
3608
|
import path13 from "path";
|
|
3267
3609
|
import os7 from "os";
|
|
3268
3610
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -3280,7 +3622,7 @@ function isProcessAlive(pid) {
|
|
|
3280
3622
|
}
|
|
3281
3623
|
function acquireSpawnLock2(sessionName) {
|
|
3282
3624
|
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
3283
|
-
|
|
3625
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3284
3626
|
}
|
|
3285
3627
|
const lockFile = spawnLockPath(sessionName);
|
|
3286
3628
|
if (existsSync12(lockFile)) {
|
|
@@ -3293,7 +3635,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
3293
3635
|
} catch {
|
|
3294
3636
|
}
|
|
3295
3637
|
}
|
|
3296
|
-
|
|
3638
|
+
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3297
3639
|
return true;
|
|
3298
3640
|
}
|
|
3299
3641
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -3378,11 +3720,11 @@ function extractRootExe(name) {
|
|
|
3378
3720
|
}
|
|
3379
3721
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3380
3722
|
if (!existsSync12(SESSION_CACHE)) {
|
|
3381
|
-
|
|
3723
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3382
3724
|
}
|
|
3383
3725
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3384
3726
|
const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3385
|
-
|
|
3727
|
+
writeFileSync6(filePath, JSON.stringify({
|
|
3386
3728
|
parentExe: rootExe,
|
|
3387
3729
|
dispatchedBy: dispatchedBy || rootExe,
|
|
3388
3730
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3461,31 +3803,49 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3461
3803
|
function readDebounceState() {
|
|
3462
3804
|
try {
|
|
3463
3805
|
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
3464
|
-
|
|
3806
|
+
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3807
|
+
const state = {};
|
|
3808
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
3809
|
+
if (typeof val === "number") {
|
|
3810
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
3811
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
3812
|
+
state[key] = val;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
return state;
|
|
3465
3816
|
} catch {
|
|
3466
3817
|
return {};
|
|
3467
3818
|
}
|
|
3468
3819
|
}
|
|
3469
3820
|
function writeDebounceState(state) {
|
|
3470
3821
|
try {
|
|
3471
|
-
if (!existsSync12(SESSION_CACHE))
|
|
3472
|
-
|
|
3822
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3823
|
+
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3473
3824
|
} catch {
|
|
3474
3825
|
}
|
|
3475
3826
|
}
|
|
3476
3827
|
function isDebounced(targetSession) {
|
|
3477
3828
|
const state = readDebounceState();
|
|
3478
|
-
const
|
|
3479
|
-
|
|
3829
|
+
const entry = state[targetSession];
|
|
3830
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
3831
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
3832
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
3833
|
+
state[targetSession].pending++;
|
|
3834
|
+
writeDebounceState(state);
|
|
3835
|
+
return true;
|
|
3836
|
+
}
|
|
3837
|
+
return false;
|
|
3480
3838
|
}
|
|
3481
3839
|
function recordDebounce(targetSession) {
|
|
3482
3840
|
const state = readDebounceState();
|
|
3483
|
-
state[targetSession]
|
|
3841
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
3842
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
3484
3843
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
3485
3844
|
for (const key of Object.keys(state)) {
|
|
3486
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
3845
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
3487
3846
|
}
|
|
3488
3847
|
writeDebounceState(state);
|
|
3848
|
+
return batched;
|
|
3489
3849
|
}
|
|
3490
3850
|
function logIntercom(msg) {
|
|
3491
3851
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -3530,7 +3890,7 @@ function sendIntercom(targetSession) {
|
|
|
3530
3890
|
return "skipped_exe";
|
|
3531
3891
|
}
|
|
3532
3892
|
if (isDebounced(targetSession)) {
|
|
3533
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
3893
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
3534
3894
|
return "debounced";
|
|
3535
3895
|
}
|
|
3536
3896
|
try {
|
|
@@ -3542,14 +3902,14 @@ function sendIntercom(targetSession) {
|
|
|
3542
3902
|
const sessionState = getSessionState(targetSession);
|
|
3543
3903
|
if (sessionState === "no_claude") {
|
|
3544
3904
|
queueIntercom(targetSession, "claude not running in session");
|
|
3545
|
-
recordDebounce(targetSession);
|
|
3546
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
3905
|
+
const batched2 = recordDebounce(targetSession);
|
|
3906
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3547
3907
|
return "queued";
|
|
3548
3908
|
}
|
|
3549
3909
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3550
3910
|
queueIntercom(targetSession, "session busy at send time");
|
|
3551
|
-
recordDebounce(targetSession);
|
|
3552
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
3911
|
+
const batched2 = recordDebounce(targetSession);
|
|
3912
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3553
3913
|
return "queued";
|
|
3554
3914
|
}
|
|
3555
3915
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -3557,8 +3917,8 @@ function sendIntercom(targetSession) {
|
|
|
3557
3917
|
transport.sendKeys(targetSession, "q");
|
|
3558
3918
|
}
|
|
3559
3919
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3560
|
-
recordDebounce(targetSession);
|
|
3561
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
3920
|
+
const batched = recordDebounce(targetSession);
|
|
3921
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
3562
3922
|
return "delivered";
|
|
3563
3923
|
} catch {
|
|
3564
3924
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3588,7 +3948,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3588
3948
|
return true;
|
|
3589
3949
|
}
|
|
3590
3950
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3591
|
-
if (
|
|
3951
|
+
if (isCoordinatorName(employeeName)) {
|
|
3592
3952
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3593
3953
|
}
|
|
3594
3954
|
try {
|
|
@@ -3663,7 +4023,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3663
4023
|
const logDir = path13.join(os7.homedir(), ".exe-os", "session-logs");
|
|
3664
4024
|
const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3665
4025
|
if (!existsSync12(logDir)) {
|
|
3666
|
-
|
|
4026
|
+
mkdirSync6(logDir, { recursive: true });
|
|
3667
4027
|
}
|
|
3668
4028
|
transport.kill(sessionName);
|
|
3669
4029
|
let cleanupSuffix = "";
|
|
@@ -3687,7 +4047,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3687
4047
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3688
4048
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3689
4049
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3690
|
-
|
|
4050
|
+
writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3691
4051
|
} catch {
|
|
3692
4052
|
}
|
|
3693
4053
|
try {
|
|
@@ -3725,14 +4085,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3725
4085
|
if (changed) {
|
|
3726
4086
|
perms.allow = allow;
|
|
3727
4087
|
settings.permissions = perms;
|
|
3728
|
-
|
|
3729
|
-
|
|
4088
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4089
|
+
writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3730
4090
|
}
|
|
3731
4091
|
} catch {
|
|
3732
4092
|
}
|
|
3733
4093
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
3734
4094
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
3735
|
-
const
|
|
4095
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
4096
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
4097
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
4098
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
3736
4099
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
3737
4100
|
let identityFlag = "";
|
|
3738
4101
|
let behaviorsFlag = "";
|
|
@@ -3770,7 +4133,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3770
4133
|
let sessionContextFlag = "";
|
|
3771
4134
|
try {
|
|
3772
4135
|
const ctxDir = path13.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3773
|
-
|
|
4136
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
3774
4137
|
const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
|
|
3775
4138
|
const ctxContent = [
|
|
3776
4139
|
`## Session Context`,
|
|
@@ -3778,7 +4141,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3778
4141
|
`Your parent coordinator session is ${exeSession}.`,
|
|
3779
4142
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3780
4143
|
].join("\n");
|
|
3781
|
-
|
|
4144
|
+
writeFileSync6(ctxFile, ctxContent);
|
|
3782
4145
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
3783
4146
|
} catch {
|
|
3784
4147
|
}
|
|
@@ -3792,9 +4155,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3792
4155
|
}
|
|
3793
4156
|
}
|
|
3794
4157
|
}
|
|
4158
|
+
if (useCodex) {
|
|
4159
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
4160
|
+
if (codexCfg?.apiKeyEnv) {
|
|
4161
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
4162
|
+
if (keyVal) {
|
|
4163
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
4167
|
+
}
|
|
4168
|
+
if (useOpencode) {
|
|
4169
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
4170
|
+
if (ocCfg?.apiKeyEnv) {
|
|
4171
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
4172
|
+
if (keyVal) {
|
|
4173
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4177
|
+
}
|
|
4178
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
4179
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
4180
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
4181
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
3795
4184
|
let spawnCommand;
|
|
3796
4185
|
if (useExeAgent) {
|
|
3797
4186
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
4187
|
+
} else if (useCodex) {
|
|
4188
|
+
process.stderr.write(
|
|
4189
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
4190
|
+
`
|
|
4191
|
+
);
|
|
4192
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
4193
|
+
} else if (useOpencode) {
|
|
4194
|
+
const binName = `${employeeName}-opencode`;
|
|
4195
|
+
process.stderr.write(
|
|
4196
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
4197
|
+
`
|
|
4198
|
+
);
|
|
4199
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
3798
4200
|
} else if (useBinSymlink) {
|
|
3799
4201
|
const binName = `${employeeName}-${ccProvider}`;
|
|
3800
4202
|
process.stderr.write(
|
|
@@ -3817,10 +4219,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3817
4219
|
try {
|
|
3818
4220
|
const mySession = getMySession();
|
|
3819
4221
|
const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
3820
|
-
|
|
4222
|
+
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
3821
4223
|
dispatchedBy: mySession,
|
|
3822
4224
|
rootExe: exeSession,
|
|
3823
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
4225
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
4226
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
4227
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
3824
4228
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3825
4229
|
}));
|
|
3826
4230
|
} catch {
|
|
@@ -3828,7 +4232,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3828
4232
|
let booted = false;
|
|
3829
4233
|
for (let i = 0; i < 30; i++) {
|
|
3830
4234
|
try {
|
|
3831
|
-
|
|
4235
|
+
execSync5("sleep 0.5");
|
|
3832
4236
|
} catch {
|
|
3833
4237
|
}
|
|
3834
4238
|
try {
|
|
@@ -3838,6 +4242,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3838
4242
|
booted = true;
|
|
3839
4243
|
break;
|
|
3840
4244
|
}
|
|
4245
|
+
} else if (useCodex) {
|
|
4246
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
4247
|
+
booted = true;
|
|
4248
|
+
break;
|
|
4249
|
+
}
|
|
3841
4250
|
} else {
|
|
3842
4251
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
3843
4252
|
booted = true;
|
|
@@ -3849,9 +4258,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3849
4258
|
}
|
|
3850
4259
|
if (!booted) {
|
|
3851
4260
|
releaseSpawnLock2(sessionName);
|
|
3852
|
-
|
|
4261
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
4262
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
3853
4263
|
}
|
|
3854
|
-
if (!useExeAgent) {
|
|
4264
|
+
if (!useExeAgent && !useCodex) {
|
|
3855
4265
|
try {
|
|
3856
4266
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
3857
4267
|
} catch {
|
|
@@ -3878,6 +4288,8 @@ var init_tmux_routing = __esm({
|
|
|
3878
4288
|
init_cc_agent_support();
|
|
3879
4289
|
init_mcp_prefix();
|
|
3880
4290
|
init_provider_table();
|
|
4291
|
+
init_agent_config();
|
|
4292
|
+
init_runtime_table();
|
|
3881
4293
|
init_intercom_queue();
|
|
3882
4294
|
init_plan_limits();
|
|
3883
4295
|
init_employees();
|
|
@@ -3921,7 +4333,8 @@ var init_task_scope = __esm({
|
|
|
3921
4333
|
// src/lib/tasks-crud.ts
|
|
3922
4334
|
import crypto4 from "crypto";
|
|
3923
4335
|
import path14 from "path";
|
|
3924
|
-
import
|
|
4336
|
+
import os8 from "os";
|
|
4337
|
+
import { execSync as execSync6 } from "child_process";
|
|
3925
4338
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3926
4339
|
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
3927
4340
|
async function writeCheckpoint(input2) {
|
|
@@ -3964,6 +4377,35 @@ function extractParentFromContext(contextBody) {
|
|
|
3964
4377
|
function slugify(title) {
|
|
3965
4378
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3966
4379
|
}
|
|
4380
|
+
function buildKeywordIndex() {
|
|
4381
|
+
const idx = /* @__PURE__ */ new Map();
|
|
4382
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
4383
|
+
for (const kw of keywords) {
|
|
4384
|
+
const existing = idx.get(kw) ?? [];
|
|
4385
|
+
existing.push(role);
|
|
4386
|
+
idx.set(kw, existing);
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
return idx;
|
|
4390
|
+
}
|
|
4391
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
4392
|
+
const employees = loadEmployeesSync();
|
|
4393
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
4394
|
+
if (!employee) return void 0;
|
|
4395
|
+
const assigneeRole = employee.role;
|
|
4396
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
4397
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
4398
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
4399
|
+
if (text.includes(keyword)) {
|
|
4400
|
+
for (const role of roles) matchedRoles.add(role);
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
if (matchedRoles.size === 0) return void 0;
|
|
4404
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
4405
|
+
if (assigneeRole === "COO") return void 0;
|
|
4406
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
4407
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
4408
|
+
}
|
|
3967
4409
|
async function resolveTask(client, identifier, scopeSession) {
|
|
3968
4410
|
const scope = sessionScopeFilter(scopeSession);
|
|
3969
4411
|
let result = await client.execute({
|
|
@@ -4013,7 +4455,14 @@ async function createTaskCore(input2) {
|
|
|
4013
4455
|
const id = crypto4.randomUUID();
|
|
4014
4456
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4015
4457
|
const slug = slugify(input2.title);
|
|
4016
|
-
|
|
4458
|
+
let earlySessionScope = null;
|
|
4459
|
+
try {
|
|
4460
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
4461
|
+
earlySessionScope = resolveExeSession2();
|
|
4462
|
+
} catch {
|
|
4463
|
+
}
|
|
4464
|
+
const scope = earlySessionScope ?? "default";
|
|
4465
|
+
const taskFile = input2.taskFile ?? `tasks/${scope}/${input2.assignedTo}/${slug}.md`;
|
|
4017
4466
|
let blockedById = null;
|
|
4018
4467
|
const initialStatus = input2.blockedBy ? "blocked" : "open";
|
|
4019
4468
|
if (input2.blockedBy) {
|
|
@@ -4053,6 +4502,13 @@ async function createTaskCore(input2) {
|
|
|
4053
4502
|
if (dupCheck.rows.length > 0) {
|
|
4054
4503
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
4055
4504
|
}
|
|
4505
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
4506
|
+
const laneWarning = checkLaneAffinity(input2.title, input2.context, input2.assignedTo);
|
|
4507
|
+
if (laneWarning) {
|
|
4508
|
+
warning = warning ? `${warning}
|
|
4509
|
+
${laneWarning}` : laneWarning;
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4056
4512
|
if (input2.baseDir) {
|
|
4057
4513
|
try {
|
|
4058
4514
|
await mkdir4(path14.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
@@ -4063,12 +4519,7 @@ async function createTaskCore(input2) {
|
|
|
4063
4519
|
}
|
|
4064
4520
|
}
|
|
4065
4521
|
const complexity = input2.complexity ?? "standard";
|
|
4066
|
-
|
|
4067
|
-
try {
|
|
4068
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
4069
|
-
sessionScope = resolveExeSession2();
|
|
4070
|
-
} catch {
|
|
4071
|
-
}
|
|
4522
|
+
const sessionScope = earlySessionScope;
|
|
4072
4523
|
await client.execute({
|
|
4073
4524
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
4074
4525
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -4095,6 +4546,43 @@ async function createTaskCore(input2) {
|
|
|
4095
4546
|
now
|
|
4096
4547
|
]
|
|
4097
4548
|
});
|
|
4549
|
+
if (input2.baseDir) {
|
|
4550
|
+
try {
|
|
4551
|
+
const EXE_OS_DIR = path14.join(os8.homedir(), ".exe-os");
|
|
4552
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
4553
|
+
const mdDir = path14.dirname(mdPath);
|
|
4554
|
+
if (!existsSync13(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
4555
|
+
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
4556
|
+
const mdContent = `# ${input2.title}
|
|
4557
|
+
|
|
4558
|
+
**ID:** ${id}
|
|
4559
|
+
**Status:** ${initialStatus}
|
|
4560
|
+
**Priority:** ${input2.priority}
|
|
4561
|
+
**Assigned by:** ${input2.assignedBy}
|
|
4562
|
+
**Assigned to:** ${input2.assignedTo}
|
|
4563
|
+
**Project:** ${input2.projectName}
|
|
4564
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
4565
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
4566
|
+
**Reviewer:** ${reviewer}
|
|
4567
|
+
|
|
4568
|
+
## Context
|
|
4569
|
+
|
|
4570
|
+
${input2.context}
|
|
4571
|
+
|
|
4572
|
+
## MANDATORY: When done
|
|
4573
|
+
|
|
4574
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4575
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4576
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4577
|
+
`;
|
|
4578
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
4579
|
+
} catch (err) {
|
|
4580
|
+
process.stderr.write(
|
|
4581
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
4582
|
+
`
|
|
4583
|
+
);
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4098
4586
|
return {
|
|
4099
4587
|
id,
|
|
4100
4588
|
title: input2.title,
|
|
@@ -4167,14 +4655,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
4167
4655
|
if (!identifier || identifier === "unknown") return true;
|
|
4168
4656
|
try {
|
|
4169
4657
|
if (identifier.startsWith("%")) {
|
|
4170
|
-
const output =
|
|
4658
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
4171
4659
|
timeout: 2e3,
|
|
4172
4660
|
encoding: "utf8",
|
|
4173
4661
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4174
4662
|
});
|
|
4175
4663
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
4176
4664
|
} else {
|
|
4177
|
-
|
|
4665
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
4178
4666
|
timeout: 2e3,
|
|
4179
4667
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4180
4668
|
});
|
|
@@ -4183,7 +4671,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
4183
4671
|
} catch {
|
|
4184
4672
|
if (identifier.startsWith("%")) return true;
|
|
4185
4673
|
try {
|
|
4186
|
-
|
|
4674
|
+
execSync6("tmux list-sessions", {
|
|
4187
4675
|
timeout: 2e3,
|
|
4188
4676
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4189
4677
|
});
|
|
@@ -4198,12 +4686,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
4198
4686
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
4199
4687
|
try {
|
|
4200
4688
|
const since = new Date(taskCreatedAt).toISOString();
|
|
4201
|
-
const branch =
|
|
4689
|
+
const branch = execSync6(
|
|
4202
4690
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
4203
4691
|
{ encoding: "utf8", timeout: 3e3 }
|
|
4204
4692
|
).trim();
|
|
4205
4693
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
4206
|
-
const commitCount =
|
|
4694
|
+
const commitCount = execSync6(
|
|
4207
4695
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
4208
4696
|
{ encoding: "utf8", timeout: 5e3 }
|
|
4209
4697
|
).trim();
|
|
@@ -4287,7 +4775,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4287
4775
|
return { row, taskFile, now, taskId };
|
|
4288
4776
|
}
|
|
4289
4777
|
}
|
|
4290
|
-
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || input2.callerAgentId
|
|
4778
|
+
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || isCoordinatorName(input2.callerAgentId))) {
|
|
4291
4779
|
process.stderr.write(
|
|
4292
4780
|
`[tasks] Assigner override: ${input2.callerAgentId} reclaiming ${taskId}
|
|
4293
4781
|
`
|
|
@@ -4399,12 +4887,22 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
4399
4887
|
} catch {
|
|
4400
4888
|
}
|
|
4401
4889
|
}
|
|
4402
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
4890
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
4403
4891
|
var init_tasks_crud = __esm({
|
|
4404
4892
|
"src/lib/tasks-crud.ts"() {
|
|
4405
4893
|
"use strict";
|
|
4406
4894
|
init_database();
|
|
4407
4895
|
init_task_scope();
|
|
4896
|
+
init_employees();
|
|
4897
|
+
LANE_KEYWORDS = {
|
|
4898
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
4899
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
4900
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
4901
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
4902
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
4903
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
4904
|
+
};
|
|
4905
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
4408
4906
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
4409
4907
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
4410
4908
|
}
|
|
@@ -4412,7 +4910,7 @@ var init_tasks_crud = __esm({
|
|
|
4412
4910
|
|
|
4413
4911
|
// src/lib/tasks-review.ts
|
|
4414
4912
|
import path15 from "path";
|
|
4415
|
-
import { existsSync as existsSync14, readdirSync as
|
|
4913
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
4416
4914
|
async function countPendingReviews(sessionScope) {
|
|
4417
4915
|
const client = getClient();
|
|
4418
4916
|
if (sessionScope) {
|
|
@@ -4434,7 +4932,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
4434
4932
|
const result2 = await client.execute({
|
|
4435
4933
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4436
4934
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
4437
|
-
AND
|
|
4935
|
+
AND session_scope = ?`,
|
|
4438
4936
|
args: [sinceIso, sessionScope]
|
|
4439
4937
|
});
|
|
4440
4938
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -4452,7 +4950,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
4452
4950
|
const result2 = await client.execute({
|
|
4453
4951
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
4454
4952
|
WHERE status = 'needs_review'
|
|
4455
|
-
AND
|
|
4953
|
+
AND session_scope = ?
|
|
4456
4954
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
4457
4955
|
args: [sessionScope, limit]
|
|
4458
4956
|
});
|
|
@@ -4573,14 +5071,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4573
5071
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
4574
5072
|
const agent = parts[1];
|
|
4575
5073
|
const slug = parts.slice(2).join("-");
|
|
4576
|
-
const
|
|
5074
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
4577
5075
|
const result = await client.execute({
|
|
4578
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
4579
|
-
args: [now,
|
|
5076
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
5077
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
4580
5078
|
});
|
|
4581
5079
|
if (result.rowsAffected > 0) {
|
|
4582
5080
|
process.stderr.write(
|
|
4583
|
-
`[review-cleanup] Cascaded original task to done
|
|
5081
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
4584
5082
|
`
|
|
4585
5083
|
);
|
|
4586
5084
|
}
|
|
@@ -4595,7 +5093,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4595
5093
|
try {
|
|
4596
5094
|
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
4597
5095
|
if (existsSync14(cacheDir)) {
|
|
4598
|
-
for (const f of
|
|
5096
|
+
for (const f of readdirSync3(cacheDir)) {
|
|
4599
5097
|
if (f.startsWith("review-notified-")) {
|
|
4600
5098
|
unlinkSync5(path15.join(cacheDir, f));
|
|
4601
5099
|
}
|
|
@@ -4720,7 +5218,7 @@ function findSessionForProject(projectName) {
|
|
|
4720
5218
|
const sessions = listSessions();
|
|
4721
5219
|
for (const s of sessions) {
|
|
4722
5220
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4723
|
-
if (proj === projectName &&
|
|
5221
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4724
5222
|
}
|
|
4725
5223
|
return null;
|
|
4726
5224
|
}
|
|
@@ -4766,7 +5264,7 @@ var init_session_scope = __esm({
|
|
|
4766
5264
|
|
|
4767
5265
|
// src/lib/tasks-notify.ts
|
|
4768
5266
|
async function dispatchTaskToEmployee(input2) {
|
|
4769
|
-
if (
|
|
5267
|
+
if (isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
4770
5268
|
let crossProject = false;
|
|
4771
5269
|
if (input2.projectName) {
|
|
4772
5270
|
try {
|
|
@@ -5162,7 +5660,7 @@ __export(tasks_exports, {
|
|
|
5162
5660
|
writeCheckpoint: () => writeCheckpoint
|
|
5163
5661
|
});
|
|
5164
5662
|
import path17 from "path";
|
|
5165
|
-
import { writeFileSync as
|
|
5663
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
|
|
5166
5664
|
async function createTask(input2) {
|
|
5167
5665
|
const result = await createTaskCore(input2);
|
|
5168
5666
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5184,8 +5682,8 @@ async function updateTask(input2) {
|
|
|
5184
5682
|
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
5185
5683
|
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
5186
5684
|
if (input2.status === "in_progress") {
|
|
5187
|
-
|
|
5188
|
-
|
|
5685
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
5686
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5189
5687
|
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
5190
5688
|
try {
|
|
5191
5689
|
unlinkSync6(cachePath);
|
|
@@ -5245,7 +5743,7 @@ async function updateTask(input2) {
|
|
|
5245
5743
|
}
|
|
5246
5744
|
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
5247
5745
|
if (isTerminal) {
|
|
5248
|
-
const isCoordinator =
|
|
5746
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5249
5747
|
if (!isCoordinator) {
|
|
5250
5748
|
notifyTaskDone();
|
|
5251
5749
|
}
|
|
@@ -5270,7 +5768,7 @@ async function updateTask(input2) {
|
|
|
5270
5768
|
}
|
|
5271
5769
|
}
|
|
5272
5770
|
}
|
|
5273
|
-
if (input2.status === "done" &&
|
|
5771
|
+
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5274
5772
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5275
5773
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5276
5774
|
taskId,
|
|
@@ -5286,7 +5784,7 @@ async function updateTask(input2) {
|
|
|
5286
5784
|
});
|
|
5287
5785
|
}
|
|
5288
5786
|
let nextTask;
|
|
5289
|
-
if (isTerminal &&
|
|
5787
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
5290
5788
|
try {
|
|
5291
5789
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
5292
5790
|
} catch {
|
|
@@ -5352,15 +5850,15 @@ __export(worker_gate_exports, {
|
|
|
5352
5850
|
tryAcquireBackfillLock: () => tryAcquireBackfillLock,
|
|
5353
5851
|
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
5354
5852
|
});
|
|
5355
|
-
import { readdirSync as
|
|
5853
|
+
import { readdirSync as readdirSync4, writeFileSync as writeFileSync8, unlinkSync as unlinkSync7, mkdirSync as mkdirSync8, existsSync as existsSync15 } from "fs";
|
|
5356
5854
|
import path18 from "path";
|
|
5357
5855
|
function tryAcquireWorkerSlot() {
|
|
5358
5856
|
try {
|
|
5359
|
-
|
|
5857
|
+
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5360
5858
|
const reservationId = `res-${process.pid}-${Date.now()}`;
|
|
5361
5859
|
const reservationPath = path18.join(WORKER_PID_DIR, `${reservationId}.pid`);
|
|
5362
|
-
|
|
5363
|
-
const files =
|
|
5860
|
+
writeFileSync8(reservationPath, String(process.pid));
|
|
5861
|
+
const files = readdirSync4(WORKER_PID_DIR);
|
|
5364
5862
|
let alive = 0;
|
|
5365
5863
|
for (const f of files) {
|
|
5366
5864
|
if (!f.endsWith(".pid")) continue;
|
|
@@ -5399,8 +5897,8 @@ function tryAcquireWorkerSlot() {
|
|
|
5399
5897
|
}
|
|
5400
5898
|
function registerWorkerPid(pid) {
|
|
5401
5899
|
try {
|
|
5402
|
-
|
|
5403
|
-
|
|
5900
|
+
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5901
|
+
writeFileSync8(path18.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
5404
5902
|
} catch {
|
|
5405
5903
|
}
|
|
5406
5904
|
}
|
|
@@ -5412,7 +5910,7 @@ function cleanupWorkerPid() {
|
|
|
5412
5910
|
}
|
|
5413
5911
|
function tryAcquireBackfillLock() {
|
|
5414
5912
|
try {
|
|
5415
|
-
|
|
5913
|
+
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5416
5914
|
if (existsSync15(BACKFILL_LOCK)) {
|
|
5417
5915
|
try {
|
|
5418
5916
|
const pid = parseInt(
|
|
@@ -5429,7 +5927,7 @@ function tryAcquireBackfillLock() {
|
|
|
5429
5927
|
} catch {
|
|
5430
5928
|
}
|
|
5431
5929
|
}
|
|
5432
|
-
|
|
5930
|
+
writeFileSync8(BACKFILL_LOCK, String(process.pid));
|
|
5433
5931
|
return true;
|
|
5434
5932
|
} catch {
|
|
5435
5933
|
return true;
|
|
@@ -5454,8 +5952,8 @@ var init_worker_gate = __esm({
|
|
|
5454
5952
|
|
|
5455
5953
|
// src/adapters/claude/hooks/ingest-worker.ts
|
|
5456
5954
|
import crypto7 from "crypto";
|
|
5457
|
-
import { execSync as
|
|
5458
|
-
import { mkdirSync as
|
|
5955
|
+
import { execSync as execSync7 } from "child_process";
|
|
5956
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
5459
5957
|
import path19 from "path";
|
|
5460
5958
|
|
|
5461
5959
|
// src/lib/error-detector.ts
|
|
@@ -5553,9 +6051,6 @@ function detectError(data) {
|
|
|
5553
6051
|
}
|
|
5554
6052
|
|
|
5555
6053
|
// src/lib/task-scanner.ts
|
|
5556
|
-
import { readdirSync, readFileSync, existsSync, statSync } from "fs";
|
|
5557
|
-
import { execSync } from "child_process";
|
|
5558
|
-
import path from "path";
|
|
5559
6054
|
var STATUS_RE = /^\*\*Status:\*\*\s*(\w+)/m;
|
|
5560
6055
|
var TITLE_RE = /^# (.+)/m;
|
|
5561
6056
|
|
|
@@ -5565,6 +6060,7 @@ init_project_name();
|
|
|
5565
6060
|
// src/lib/store.ts
|
|
5566
6061
|
init_memory();
|
|
5567
6062
|
init_database();
|
|
6063
|
+
import { createHash } from "crypto";
|
|
5568
6064
|
|
|
5569
6065
|
// src/lib/keychain.ts
|
|
5570
6066
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
@@ -5599,12 +6095,20 @@ async function getMasterKey() {
|
|
|
5599
6095
|
}
|
|
5600
6096
|
const keyPath = getKeyPath();
|
|
5601
6097
|
if (!existsSync4(keyPath)) {
|
|
6098
|
+
process.stderr.write(
|
|
6099
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
6100
|
+
`
|
|
6101
|
+
);
|
|
5602
6102
|
return null;
|
|
5603
6103
|
}
|
|
5604
6104
|
try {
|
|
5605
6105
|
const content = await readFile3(keyPath, "utf-8");
|
|
5606
6106
|
return Buffer.from(content.trim(), "base64");
|
|
5607
|
-
} catch {
|
|
6107
|
+
} catch (err) {
|
|
6108
|
+
process.stderr.write(
|
|
6109
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
6110
|
+
`
|
|
6111
|
+
);
|
|
5608
6112
|
return null;
|
|
5609
6113
|
}
|
|
5610
6114
|
}
|
|
@@ -5693,12 +6197,52 @@ function classifyTier(record) {
|
|
|
5693
6197
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
5694
6198
|
return 3;
|
|
5695
6199
|
}
|
|
6200
|
+
function inferFilePaths(record) {
|
|
6201
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
6202
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
6203
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
6204
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
6205
|
+
}
|
|
6206
|
+
function inferCommitHash(record) {
|
|
6207
|
+
if (record.tool_name !== "Bash") return null;
|
|
6208
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
6209
|
+
return match ? match[1] : null;
|
|
6210
|
+
}
|
|
6211
|
+
function inferLanguageType(record) {
|
|
6212
|
+
const text = record.raw_text;
|
|
6213
|
+
if (!text || text.length < 10) return null;
|
|
6214
|
+
const trimmed = text.trimStart();
|
|
6215
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
6216
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
6217
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
6218
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
6219
|
+
return "mixed";
|
|
6220
|
+
}
|
|
6221
|
+
function inferDomain(record) {
|
|
6222
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
6223
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
6224
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
6225
|
+
return null;
|
|
6226
|
+
}
|
|
5696
6227
|
async function writeMemory(record) {
|
|
5697
6228
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
5698
6229
|
throw new Error(
|
|
5699
6230
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
5700
6231
|
);
|
|
5701
6232
|
}
|
|
6233
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
6234
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
6235
|
+
return;
|
|
6236
|
+
}
|
|
6237
|
+
try {
|
|
6238
|
+
const client = getClient();
|
|
6239
|
+
const existing = await client.execute({
|
|
6240
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
6241
|
+
args: [contentHash, record.agent_id]
|
|
6242
|
+
});
|
|
6243
|
+
if (existing.rows.length > 0) return;
|
|
6244
|
+
} catch {
|
|
6245
|
+
}
|
|
5702
6246
|
const dbRow = {
|
|
5703
6247
|
id: record.id,
|
|
5704
6248
|
agent_id: record.agent_id,
|
|
@@ -5728,7 +6272,23 @@ async function writeMemory(record) {
|
|
|
5728
6272
|
supersedes_id: record.supersedes_id ?? null,
|
|
5729
6273
|
draft: record.draft ? 1 : 0,
|
|
5730
6274
|
memory_type: record.memory_type ?? "raw",
|
|
5731
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
6275
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
6276
|
+
content_hash: contentHash,
|
|
6277
|
+
intent: record.intent ?? null,
|
|
6278
|
+
outcome: record.outcome ?? null,
|
|
6279
|
+
domain: record.domain ?? inferDomain(record),
|
|
6280
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
6281
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
6282
|
+
chain_position: record.chain_position ?? null,
|
|
6283
|
+
review_status: record.review_status ?? null,
|
|
6284
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
6285
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
6286
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
6287
|
+
duration_ms: record.duration_ms ?? null,
|
|
6288
|
+
token_cost: record.token_cost ?? null,
|
|
6289
|
+
audience: record.audience ?? null,
|
|
6290
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
6291
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
5732
6292
|
};
|
|
5733
6293
|
_pendingRecords.push(dbRow);
|
|
5734
6294
|
orgBus.emit({
|
|
@@ -5786,80 +6346,85 @@ async function flushBatch() {
|
|
|
5786
6346
|
const draft = row.draft ? 1 : 0;
|
|
5787
6347
|
const memoryType = row.memory_type ?? "raw";
|
|
5788
6348
|
const trajectory = row.trajectory ?? null;
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
6349
|
+
const contentHash = row.content_hash ?? null;
|
|
6350
|
+
const intent = row.intent ?? null;
|
|
6351
|
+
const outcome = row.outcome ?? null;
|
|
6352
|
+
const domain = row.domain ?? null;
|
|
6353
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
6354
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
6355
|
+
const chainPosition = row.chain_position ?? null;
|
|
6356
|
+
const reviewStatus = row.review_status ?? null;
|
|
6357
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
6358
|
+
const filePaths = row.file_paths ?? null;
|
|
6359
|
+
const commitHash = row.commit_hash ?? null;
|
|
6360
|
+
const durationMs = row.duration_ms ?? null;
|
|
6361
|
+
const tokenCost = row.token_cost ?? null;
|
|
6362
|
+
const audience = row.audience ?? null;
|
|
6363
|
+
const languageType = row.language_type ?? null;
|
|
6364
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
6365
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
5799
6366
|
tool_name, project_name,
|
|
5800
6367
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5801
6368
|
confidence, last_accessed,
|
|
5802
6369
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5803
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
trajectory
|
|
5862
|
-
]
|
|
6370
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
6371
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
6372
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
6373
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
6374
|
+
const metaArgs = [
|
|
6375
|
+
intent,
|
|
6376
|
+
outcome,
|
|
6377
|
+
domain,
|
|
6378
|
+
referencedEntities,
|
|
6379
|
+
retrievalCount,
|
|
6380
|
+
chainPosition,
|
|
6381
|
+
reviewStatus,
|
|
6382
|
+
contextWindowPct,
|
|
6383
|
+
filePaths,
|
|
6384
|
+
commitHash,
|
|
6385
|
+
durationMs,
|
|
6386
|
+
tokenCost,
|
|
6387
|
+
audience,
|
|
6388
|
+
languageType,
|
|
6389
|
+
parentMemoryId
|
|
6390
|
+
];
|
|
6391
|
+
const baseArgs = [
|
|
6392
|
+
row.id,
|
|
6393
|
+
row.agent_id,
|
|
6394
|
+
row.agent_role,
|
|
6395
|
+
row.session_id,
|
|
6396
|
+
row.timestamp,
|
|
6397
|
+
row.tool_name,
|
|
6398
|
+
row.project_name,
|
|
6399
|
+
row.has_error,
|
|
6400
|
+
row.raw_text
|
|
6401
|
+
];
|
|
6402
|
+
const sharedArgs = [
|
|
6403
|
+
row.version,
|
|
6404
|
+
taskId,
|
|
6405
|
+
importance,
|
|
6406
|
+
status,
|
|
6407
|
+
confidence,
|
|
6408
|
+
lastAccessed,
|
|
6409
|
+
workspaceId,
|
|
6410
|
+
documentId,
|
|
6411
|
+
userId,
|
|
6412
|
+
charOffset,
|
|
6413
|
+
pageNumber,
|
|
6414
|
+
sourcePath,
|
|
6415
|
+
sourceType,
|
|
6416
|
+
tier,
|
|
6417
|
+
supersedesId,
|
|
6418
|
+
draft,
|
|
6419
|
+
memoryType,
|
|
6420
|
+
trajectory,
|
|
6421
|
+
contentHash
|
|
6422
|
+
];
|
|
6423
|
+
return {
|
|
6424
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
6425
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
6426
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
6427
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
5863
6428
|
};
|
|
5864
6429
|
};
|
|
5865
6430
|
const globalClient = getClient();
|
|
@@ -6187,7 +6752,7 @@ process.stdin.on("end", async () => {
|
|
|
6187
6752
|
try {
|
|
6188
6753
|
const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6189
6754
|
const flagPath = path19.join(exeDir, "session-cache", "needs-backfill");
|
|
6190
|
-
|
|
6755
|
+
writeFileSync9(flagPath, "1");
|
|
6191
6756
|
} catch (err) {
|
|
6192
6757
|
process.stderr.write(`[ingest-worker] backfill flag write failed: ${err instanceof Error ? err.message : String(err)}
|
|
6193
6758
|
`);
|
|
@@ -6215,9 +6780,9 @@ process.stdin.on("end", async () => {
|
|
|
6215
6780
|
if (status === "done") {
|
|
6216
6781
|
const cwd = data.cwd ?? process.cwd();
|
|
6217
6782
|
try {
|
|
6218
|
-
|
|
6783
|
+
execSync7("git add -u", { cwd, timeout: 1e4, stdio: "ignore" });
|
|
6219
6784
|
const msg = `task(${agentId}): ${title}`;
|
|
6220
|
-
|
|
6785
|
+
execSync7(`git commit --no-verify -m ${JSON.stringify(msg)}`, { cwd, timeout: 3e4, stdio: "ignore" });
|
|
6221
6786
|
} catch (err) {
|
|
6222
6787
|
process.stderr.write(`[ingest-worker] auto-commit failed: ${err instanceof Error ? err.message : String(err)}
|
|
6223
6788
|
`);
|
|
@@ -6298,8 +6863,8 @@ process.stdin.on("end", async () => {
|
|
|
6298
6863
|
}
|
|
6299
6864
|
const cwd = data.cwd ?? process.cwd();
|
|
6300
6865
|
try {
|
|
6301
|
-
|
|
6302
|
-
|
|
6866
|
+
mkdirSync9(path19.join(cwd, "exe/output"), { recursive: true });
|
|
6867
|
+
mkdirSync9(path19.join(cwd, "exe/research"), { recursive: true });
|
|
6303
6868
|
const { ensureGitignoreExe: ensureGitignoreExe2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
6304
6869
|
await ensureGitignoreExe2(cwd);
|
|
6305
6870
|
} catch (err) {
|