@askexenow/exe-os 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1295 -856
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +778 -427
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +276 -139
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +677 -388
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +440 -250
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +404 -212
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +412 -220
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +533 -320
- package/dist/hooks/bug-report-worker.js +344 -193
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +402 -210
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3423 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +408 -216
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +541 -328
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +443 -240
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +538 -324
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +935 -587
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +280 -234
- package/dist/lib/tmux-routing.js +172 -125
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1326 -609
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +306 -248
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1848 -199
- package/dist/runtime/index.js +441 -248
- package/dist/tui/App.js +761 -424
- package/package.json +1 -1
package/dist/bin/exe-dispatch.js
CHANGED
|
@@ -308,9 +308,34 @@ var init_provider_table = __esm({
|
|
|
308
308
|
}
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
+
// src/lib/secure-files.ts
|
|
312
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
313
|
+
import { chmod, mkdir } from "fs/promises";
|
|
314
|
+
async function ensurePrivateDir(dirPath) {
|
|
315
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
316
|
+
try {
|
|
317
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function enforcePrivateFile(filePath) {
|
|
322
|
+
try {
|
|
323
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
328
|
+
var init_secure_files = __esm({
|
|
329
|
+
"src/lib/secure-files.ts"() {
|
|
330
|
+
"use strict";
|
|
331
|
+
PRIVATE_DIR_MODE = 448;
|
|
332
|
+
PRIVATE_FILE_MODE = 384;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
311
336
|
// src/lib/config.ts
|
|
312
|
-
import { readFile, writeFile
|
|
313
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
337
|
+
import { readFile, writeFile } from "fs/promises";
|
|
338
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
314
339
|
import path2 from "path";
|
|
315
340
|
import os2 from "os";
|
|
316
341
|
function resolveDataDir() {
|
|
@@ -318,7 +343,7 @@ function resolveDataDir() {
|
|
|
318
343
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
319
344
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
320
345
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
321
|
-
if (!
|
|
346
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
322
347
|
try {
|
|
323
348
|
renameSync(legacyDir, newDir);
|
|
324
349
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -381,9 +406,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
381
406
|
}
|
|
382
407
|
async function loadConfig() {
|
|
383
408
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
384
|
-
await
|
|
409
|
+
await ensurePrivateDir(dir);
|
|
385
410
|
const configPath = path2.join(dir, "config.json");
|
|
386
|
-
if (!
|
|
411
|
+
if (!existsSync3(configPath)) {
|
|
387
412
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
388
413
|
}
|
|
389
414
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -396,6 +421,7 @@ async function loadConfig() {
|
|
|
396
421
|
`);
|
|
397
422
|
try {
|
|
398
423
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
424
|
+
await enforcePrivateFile(configPath);
|
|
399
425
|
} catch {
|
|
400
426
|
}
|
|
401
427
|
}
|
|
@@ -415,6 +441,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
415
441
|
var init_config = __esm({
|
|
416
442
|
"src/lib/config.ts"() {
|
|
417
443
|
"use strict";
|
|
444
|
+
init_secure_files();
|
|
418
445
|
EXE_AI_DIR = resolveDataDir();
|
|
419
446
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
420
447
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -519,10 +546,10 @@ var init_runtime_table = __esm({
|
|
|
519
546
|
});
|
|
520
547
|
|
|
521
548
|
// src/lib/agent-config.ts
|
|
522
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
549
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
523
550
|
import path3 from "path";
|
|
524
551
|
function loadAgentConfig() {
|
|
525
|
-
if (!
|
|
552
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
526
553
|
try {
|
|
527
554
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
528
555
|
} catch {
|
|
@@ -543,6 +570,7 @@ var init_agent_config = __esm({
|
|
|
543
570
|
"use strict";
|
|
544
571
|
init_config();
|
|
545
572
|
init_runtime_table();
|
|
573
|
+
init_secure_files();
|
|
546
574
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
547
575
|
DEFAULT_MODELS = {
|
|
548
576
|
claude: "claude-opus-4",
|
|
@@ -561,16 +589,16 @@ __export(intercom_queue_exports, {
|
|
|
561
589
|
queueIntercom: () => queueIntercom,
|
|
562
590
|
readQueue: () => readQueue
|
|
563
591
|
});
|
|
564
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
592
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
565
593
|
import path4 from "path";
|
|
566
594
|
import os3 from "os";
|
|
567
595
|
function ensureDir() {
|
|
568
596
|
const dir = path4.dirname(QUEUE_PATH);
|
|
569
|
-
if (!
|
|
597
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
570
598
|
}
|
|
571
599
|
function readQueue() {
|
|
572
600
|
try {
|
|
573
|
-
if (!
|
|
601
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
574
602
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
575
603
|
} catch {
|
|
576
604
|
return [];
|
|
@@ -735,7 +763,7 @@ var init_db_retry = __esm({
|
|
|
735
763
|
|
|
736
764
|
// src/lib/employees.ts
|
|
737
765
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
738
|
-
import { existsSync as
|
|
766
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
739
767
|
import { execSync as execSync3 } from "child_process";
|
|
740
768
|
import path5 from "path";
|
|
741
769
|
import os4 from "os";
|
|
@@ -756,7 +784,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
756
784
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
757
785
|
}
|
|
758
786
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
759
|
-
if (!
|
|
787
|
+
if (!existsSync6(employeesPath)) return [];
|
|
760
788
|
try {
|
|
761
789
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
762
790
|
} catch {
|
|
@@ -1719,6 +1747,7 @@ async function ensureSchema() {
|
|
|
1719
1747
|
project TEXT NOT NULL,
|
|
1720
1748
|
summary TEXT NOT NULL,
|
|
1721
1749
|
task_file TEXT,
|
|
1750
|
+
session_scope TEXT,
|
|
1722
1751
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1723
1752
|
created_at TEXT NOT NULL
|
|
1724
1753
|
);
|
|
@@ -1727,7 +1756,7 @@ async function ensureSchema() {
|
|
|
1727
1756
|
ON notifications(read);
|
|
1728
1757
|
|
|
1729
1758
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1730
|
-
ON notifications(agent_id);
|
|
1759
|
+
ON notifications(agent_id, session_scope);
|
|
1731
1760
|
|
|
1732
1761
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1733
1762
|
ON notifications(task_file);
|
|
@@ -1765,6 +1794,7 @@ async function ensureSchema() {
|
|
|
1765
1794
|
target_agent TEXT NOT NULL,
|
|
1766
1795
|
target_project TEXT,
|
|
1767
1796
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1797
|
+
session_scope TEXT,
|
|
1768
1798
|
content TEXT NOT NULL,
|
|
1769
1799
|
priority TEXT DEFAULT 'normal',
|
|
1770
1800
|
status TEXT DEFAULT 'pending',
|
|
@@ -1778,10 +1808,31 @@ async function ensureSchema() {
|
|
|
1778
1808
|
);
|
|
1779
1809
|
|
|
1780
1810
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1781
|
-
ON messages(target_agent, status);
|
|
1811
|
+
ON messages(target_agent, session_scope, status);
|
|
1782
1812
|
|
|
1783
1813
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1784
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1814
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1815
|
+
`);
|
|
1816
|
+
try {
|
|
1817
|
+
await client.execute({
|
|
1818
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1819
|
+
args: []
|
|
1820
|
+
});
|
|
1821
|
+
} catch {
|
|
1822
|
+
}
|
|
1823
|
+
try {
|
|
1824
|
+
await client.execute({
|
|
1825
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1826
|
+
args: []
|
|
1827
|
+
});
|
|
1828
|
+
} catch {
|
|
1829
|
+
}
|
|
1830
|
+
await client.executeMultiple(`
|
|
1831
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1832
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1833
|
+
|
|
1834
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1835
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1785
1836
|
`);
|
|
1786
1837
|
try {
|
|
1787
1838
|
await client.execute({
|
|
@@ -2365,6 +2416,13 @@ async function ensureSchema() {
|
|
|
2365
2416
|
} catch {
|
|
2366
2417
|
}
|
|
2367
2418
|
}
|
|
2419
|
+
try {
|
|
2420
|
+
await client.execute({
|
|
2421
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2422
|
+
args: []
|
|
2423
|
+
});
|
|
2424
|
+
} catch {
|
|
2425
|
+
}
|
|
2368
2426
|
}
|
|
2369
2427
|
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
2370
2428
|
var init_database = __esm({
|
|
@@ -2383,8 +2441,11 @@ var init_database = __esm({
|
|
|
2383
2441
|
});
|
|
2384
2442
|
|
|
2385
2443
|
// src/lib/license.ts
|
|
2386
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as
|
|
2444
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2387
2445
|
import { randomUUID } from "crypto";
|
|
2446
|
+
import { createRequire as createRequire2 } from "module";
|
|
2447
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2448
|
+
import os6 from "os";
|
|
2388
2449
|
import path7 from "path";
|
|
2389
2450
|
import { jwtVerify, importSPKI } from "jose";
|
|
2390
2451
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
@@ -2406,11 +2467,11 @@ var init_license = __esm({
|
|
|
2406
2467
|
});
|
|
2407
2468
|
|
|
2408
2469
|
// src/lib/plan-limits.ts
|
|
2409
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
2470
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
2410
2471
|
import path8 from "path";
|
|
2411
2472
|
function getLicenseSync() {
|
|
2412
2473
|
try {
|
|
2413
|
-
if (!
|
|
2474
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2414
2475
|
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
2415
2476
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2416
2477
|
const parts = raw.token.split(".");
|
|
@@ -2449,7 +2510,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2449
2510
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2450
2511
|
let count = 0;
|
|
2451
2512
|
try {
|
|
2452
|
-
if (
|
|
2513
|
+
if (existsSync8(filePath)) {
|
|
2453
2514
|
const raw = readFileSync7(filePath, "utf8");
|
|
2454
2515
|
const employees = JSON.parse(raw);
|
|
2455
2516
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
@@ -2483,15 +2544,48 @@ var init_plan_limits = __esm({
|
|
|
2483
2544
|
}
|
|
2484
2545
|
});
|
|
2485
2546
|
|
|
2547
|
+
// src/lib/task-scope.ts
|
|
2548
|
+
function getCurrentSessionScope() {
|
|
2549
|
+
try {
|
|
2550
|
+
return resolveExeSession();
|
|
2551
|
+
} catch {
|
|
2552
|
+
return null;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
2556
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2557
|
+
if (!scope) return { sql: "", args: [] };
|
|
2558
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2559
|
+
return {
|
|
2560
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
2561
|
+
args: [scope]
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
2565
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2566
|
+
if (!scope) return { sql: "", args: [] };
|
|
2567
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2568
|
+
return {
|
|
2569
|
+
sql: ` AND ${col} = ?`,
|
|
2570
|
+
args: [scope]
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
var init_task_scope = __esm({
|
|
2574
|
+
"src/lib/task-scope.ts"() {
|
|
2575
|
+
"use strict";
|
|
2576
|
+
init_tmux_routing();
|
|
2577
|
+
}
|
|
2578
|
+
});
|
|
2579
|
+
|
|
2486
2580
|
// src/lib/notifications.ts
|
|
2487
2581
|
import crypto from "crypto";
|
|
2488
2582
|
import path9 from "path";
|
|
2489
|
-
import
|
|
2583
|
+
import os7 from "os";
|
|
2490
2584
|
import {
|
|
2491
2585
|
readFileSync as readFileSync8,
|
|
2492
2586
|
readdirSync,
|
|
2493
2587
|
unlinkSync as unlinkSync2,
|
|
2494
|
-
existsSync as
|
|
2588
|
+
existsSync as existsSync9,
|
|
2495
2589
|
rmdirSync
|
|
2496
2590
|
} from "fs";
|
|
2497
2591
|
async function writeNotification(notification) {
|
|
@@ -2499,9 +2593,10 @@ async function writeNotification(notification) {
|
|
|
2499
2593
|
const client = getClient();
|
|
2500
2594
|
const id = crypto.randomUUID();
|
|
2501
2595
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2596
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2502
2597
|
await client.execute({
|
|
2503
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2504
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2598
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
2599
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2505
2600
|
args: [
|
|
2506
2601
|
id,
|
|
2507
2602
|
notification.agentId,
|
|
@@ -2510,6 +2605,7 @@ async function writeNotification(notification) {
|
|
|
2510
2605
|
notification.project,
|
|
2511
2606
|
notification.summary,
|
|
2512
2607
|
notification.taskFile ?? null,
|
|
2608
|
+
sessionScope,
|
|
2513
2609
|
now
|
|
2514
2610
|
]
|
|
2515
2611
|
});
|
|
@@ -2518,12 +2614,14 @@ async function writeNotification(notification) {
|
|
|
2518
2614
|
`);
|
|
2519
2615
|
}
|
|
2520
2616
|
}
|
|
2521
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
2617
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2522
2618
|
try {
|
|
2523
2619
|
const client = getClient();
|
|
2620
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2524
2621
|
await client.execute({
|
|
2525
|
-
sql:
|
|
2526
|
-
|
|
2622
|
+
sql: `UPDATE notifications SET read = 1
|
|
2623
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
2624
|
+
args: [taskFile, ...scope.args]
|
|
2527
2625
|
});
|
|
2528
2626
|
} catch {
|
|
2529
2627
|
}
|
|
@@ -2532,6 +2630,7 @@ var init_notifications = __esm({
|
|
|
2532
2630
|
"src/lib/notifications.ts"() {
|
|
2533
2631
|
"use strict";
|
|
2534
2632
|
init_database();
|
|
2633
|
+
init_task_scope();
|
|
2535
2634
|
}
|
|
2536
2635
|
});
|
|
2537
2636
|
|
|
@@ -2569,30 +2668,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
2569
2668
|
}
|
|
2570
2669
|
});
|
|
2571
2670
|
|
|
2572
|
-
// src/lib/task-scope.ts
|
|
2573
|
-
function getCurrentSessionScope() {
|
|
2574
|
-
try {
|
|
2575
|
-
return resolveExeSession();
|
|
2576
|
-
} catch {
|
|
2577
|
-
return null;
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
2581
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2582
|
-
if (!scope) return { sql: "", args: [] };
|
|
2583
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2584
|
-
return {
|
|
2585
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
2586
|
-
args: [scope]
|
|
2587
|
-
};
|
|
2588
|
-
}
|
|
2589
|
-
var init_task_scope = __esm({
|
|
2590
|
-
"src/lib/task-scope.ts"() {
|
|
2591
|
-
"use strict";
|
|
2592
|
-
init_tmux_routing();
|
|
2593
|
-
}
|
|
2594
|
-
});
|
|
2595
|
-
|
|
2596
2671
|
// src/lib/state-bus.ts
|
|
2597
2672
|
var StateBus, orgBus;
|
|
2598
2673
|
var init_state_bus = __esm({
|
|
@@ -2651,10 +2726,10 @@ var init_state_bus = __esm({
|
|
|
2651
2726
|
// src/lib/tasks-crud.ts
|
|
2652
2727
|
import crypto3 from "crypto";
|
|
2653
2728
|
import path10 from "path";
|
|
2654
|
-
import
|
|
2729
|
+
import os8 from "os";
|
|
2655
2730
|
import { execSync as execSync4 } from "child_process";
|
|
2656
2731
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2657
|
-
import { existsSync as
|
|
2732
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
2658
2733
|
async function writeCheckpoint(input) {
|
|
2659
2734
|
const client = getClient();
|
|
2660
2735
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2866,13 +2941,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2866
2941
|
});
|
|
2867
2942
|
if (input.baseDir) {
|
|
2868
2943
|
try {
|
|
2869
|
-
const EXE_OS_DIR = path10.join(
|
|
2944
|
+
const EXE_OS_DIR = path10.join(os8.homedir(), ".exe-os");
|
|
2870
2945
|
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2871
2946
|
const mdDir = path10.dirname(mdPath);
|
|
2872
|
-
if (!
|
|
2947
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2873
2948
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2874
2949
|
const mdContent = `# ${input.title}
|
|
2875
2950
|
|
|
2951
|
+
## MANDATORY: When done
|
|
2952
|
+
|
|
2953
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2954
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2955
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2956
|
+
|
|
2876
2957
|
**ID:** ${id}
|
|
2877
2958
|
**Status:** ${initialStatus}
|
|
2878
2959
|
**Priority:** ${input.priority}
|
|
@@ -2886,12 +2967,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2886
2967
|
## Context
|
|
2887
2968
|
|
|
2888
2969
|
${input.context}
|
|
2889
|
-
|
|
2890
|
-
## MANDATORY: When done
|
|
2891
|
-
|
|
2892
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2893
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2894
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2895
2970
|
`;
|
|
2896
2971
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2897
2972
|
} catch (err) {
|
|
@@ -3140,7 +3215,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3140
3215
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3141
3216
|
} catch {
|
|
3142
3217
|
}
|
|
3143
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3218
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3144
3219
|
try {
|
|
3145
3220
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3146
3221
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3171,7 +3246,7 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3171
3246
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3172
3247
|
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3173
3248
|
try {
|
|
3174
|
-
if (
|
|
3249
|
+
if (existsSync10(archPath)) return;
|
|
3175
3250
|
const template = [
|
|
3176
3251
|
`# ${projectName} \u2014 System Architecture`,
|
|
3177
3252
|
"",
|
|
@@ -3206,7 +3281,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3206
3281
|
async function ensureGitignoreExe(baseDir) {
|
|
3207
3282
|
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
3208
3283
|
try {
|
|
3209
|
-
if (
|
|
3284
|
+
if (existsSync10(gitignorePath)) {
|
|
3210
3285
|
const content = readFileSync9(gitignorePath, "utf-8");
|
|
3211
3286
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3212
3287
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -3239,57 +3314,41 @@ var init_tasks_crud = __esm({
|
|
|
3239
3314
|
|
|
3240
3315
|
// src/lib/tasks-review.ts
|
|
3241
3316
|
import path11 from "path";
|
|
3242
|
-
import { existsSync as
|
|
3317
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
3243
3318
|
async function countPendingReviews(sessionScope) {
|
|
3244
3319
|
const client = getClient();
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
args: [sessionScope]
|
|
3249
|
-
});
|
|
3250
|
-
return Number(result3.rows[0]?.cnt) || 0;
|
|
3251
|
-
}
|
|
3320
|
+
const scope = strictSessionScopeFilter(
|
|
3321
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3322
|
+
);
|
|
3252
3323
|
const result2 = await client.execute({
|
|
3253
|
-
sql:
|
|
3254
|
-
|
|
3324
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3325
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3326
|
+
args: [...scope.args]
|
|
3255
3327
|
});
|
|
3256
3328
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
3257
3329
|
}
|
|
3258
3330
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3259
3331
|
const client = getClient();
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3264
|
-
AND session_scope = ?`,
|
|
3265
|
-
args: [sinceIso, sessionScope]
|
|
3266
|
-
});
|
|
3267
|
-
return Number(result3.rows[0]?.cnt) || 0;
|
|
3268
|
-
}
|
|
3332
|
+
const scope = strictSessionScopeFilter(
|
|
3333
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3334
|
+
);
|
|
3269
3335
|
const result2 = await client.execute({
|
|
3270
3336
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3271
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3272
|
-
args: [sinceIso]
|
|
3337
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3338
|
+
args: [sinceIso, ...scope.args]
|
|
3273
3339
|
});
|
|
3274
3340
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
3275
3341
|
}
|
|
3276
3342
|
async function listPendingReviews(limit, sessionScope) {
|
|
3277
3343
|
const client = getClient();
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
WHERE status = 'needs_review'
|
|
3282
|
-
AND session_scope = ?
|
|
3283
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3284
|
-
args: [sessionScope, limit]
|
|
3285
|
-
});
|
|
3286
|
-
return result3.rows;
|
|
3287
|
-
}
|
|
3344
|
+
const scope = strictSessionScopeFilter(
|
|
3345
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3346
|
+
);
|
|
3288
3347
|
const result2 = await client.execute({
|
|
3289
3348
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3290
|
-
WHERE status = 'needs_review'
|
|
3349
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3291
3350
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3292
|
-
args: [limit]
|
|
3351
|
+
args: [...scope.args, limit]
|
|
3293
3352
|
});
|
|
3294
3353
|
return result2.rows;
|
|
3295
3354
|
}
|
|
@@ -3301,7 +3360,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3301
3360
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3302
3361
|
AND assigned_by = 'system'
|
|
3303
3362
|
AND title LIKE 'Review:%'
|
|
3304
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3363
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3305
3364
|
args: [now]
|
|
3306
3365
|
});
|
|
3307
3366
|
const r1b = await client.execute({
|
|
@@ -3421,7 +3480,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3421
3480
|
}
|
|
3422
3481
|
try {
|
|
3423
3482
|
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3424
|
-
if (
|
|
3483
|
+
if (existsSync11(cacheDir)) {
|
|
3425
3484
|
for (const f of readdirSync2(cacheDir)) {
|
|
3426
3485
|
if (f.startsWith("review-notified-")) {
|
|
3427
3486
|
unlinkSync3(path11.join(cacheDir, f));
|
|
@@ -3441,6 +3500,7 @@ var init_tasks_review = __esm({
|
|
|
3441
3500
|
init_tmux_routing();
|
|
3442
3501
|
init_session_key();
|
|
3443
3502
|
init_state_bus();
|
|
3503
|
+
init_task_scope();
|
|
3444
3504
|
}
|
|
3445
3505
|
});
|
|
3446
3506
|
|
|
@@ -3497,7 +3557,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3497
3557
|
const scScope = sessionScopeFilter();
|
|
3498
3558
|
const remaining = await client.execute({
|
|
3499
3559
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3500
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3560
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3501
3561
|
args: [parentTaskId, ...scScope.args]
|
|
3502
3562
|
});
|
|
3503
3563
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4055,7 +4115,7 @@ async function updateTask(input) {
|
|
|
4055
4115
|
if (input.status === "in_progress") {
|
|
4056
4116
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4057
4117
|
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4058
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4118
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4059
4119
|
try {
|
|
4060
4120
|
unlinkSync4(cachePath);
|
|
4061
4121
|
} catch {
|
|
@@ -4063,10 +4123,10 @@ async function updateTask(input) {
|
|
|
4063
4123
|
}
|
|
4064
4124
|
} catch {
|
|
4065
4125
|
}
|
|
4066
|
-
if (input.status === "done") {
|
|
4126
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4067
4127
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4068
4128
|
}
|
|
4069
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4129
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4070
4130
|
try {
|
|
4071
4131
|
const client = getClient();
|
|
4072
4132
|
const taskTitle = String(row.title);
|
|
@@ -4082,7 +4142,7 @@ async function updateTask(input) {
|
|
|
4082
4142
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4083
4143
|
try {
|
|
4084
4144
|
const draftClient = getClient();
|
|
4085
|
-
if (input.status === "done") {
|
|
4145
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4086
4146
|
await draftClient.execute({
|
|
4087
4147
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4088
4148
|
args: [assignedAgent]
|
|
@@ -4099,7 +4159,7 @@ async function updateTask(input) {
|
|
|
4099
4159
|
try {
|
|
4100
4160
|
const client = getClient();
|
|
4101
4161
|
const cascaded = await client.execute({
|
|
4102
|
-
sql: `UPDATE tasks SET status = '
|
|
4162
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4103
4163
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4104
4164
|
args: [now, taskId]
|
|
4105
4165
|
});
|
|
@@ -4112,14 +4172,14 @@ async function updateTask(input) {
|
|
|
4112
4172
|
} catch {
|
|
4113
4173
|
}
|
|
4114
4174
|
}
|
|
4115
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4175
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4116
4176
|
if (isTerminal) {
|
|
4117
4177
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4118
4178
|
if (!isCoordinator) {
|
|
4119
4179
|
notifyTaskDone();
|
|
4120
4180
|
}
|
|
4121
4181
|
await markTaskNotificationsRead(taskFile);
|
|
4122
|
-
if (input.status === "done") {
|
|
4182
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4123
4183
|
try {
|
|
4124
4184
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4125
4185
|
} catch {
|
|
@@ -4139,7 +4199,7 @@ async function updateTask(input) {
|
|
|
4139
4199
|
}
|
|
4140
4200
|
}
|
|
4141
4201
|
}
|
|
4142
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4202
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4143
4203
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4144
4204
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4145
4205
|
taskId,
|
|
@@ -4511,6 +4571,7 @@ __export(tmux_routing_exports, {
|
|
|
4511
4571
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4512
4572
|
isExeSession: () => isExeSession,
|
|
4513
4573
|
isSessionBusy: () => isSessionBusy,
|
|
4574
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4514
4575
|
notifyParentExe: () => notifyParentExe,
|
|
4515
4576
|
parseParentExe: () => parseParentExe,
|
|
4516
4577
|
registerParentExe: () => registerParentExe,
|
|
@@ -4521,9 +4582,9 @@ __export(tmux_routing_exports, {
|
|
|
4521
4582
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4522
4583
|
});
|
|
4523
4584
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
4524
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as
|
|
4585
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
4525
4586
|
import path15 from "path";
|
|
4526
|
-
import
|
|
4587
|
+
import os9 from "os";
|
|
4527
4588
|
import { fileURLToPath } from "url";
|
|
4528
4589
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
4529
4590
|
function spawnLockPath(sessionName) {
|
|
@@ -4538,11 +4599,11 @@ function isProcessAlive(pid) {
|
|
|
4538
4599
|
}
|
|
4539
4600
|
}
|
|
4540
4601
|
function acquireSpawnLock(sessionName) {
|
|
4541
|
-
if (!
|
|
4602
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4542
4603
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4543
4604
|
}
|
|
4544
4605
|
const lockFile = spawnLockPath(sessionName);
|
|
4545
|
-
if (
|
|
4606
|
+
if (existsSync12(lockFile)) {
|
|
4546
4607
|
try {
|
|
4547
4608
|
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
4548
4609
|
const age = Date.now() - lock.timestamp;
|
|
@@ -4570,7 +4631,7 @@ function resolveBehaviorsExporterScript() {
|
|
|
4570
4631
|
"bin",
|
|
4571
4632
|
"exe-export-behaviors.js"
|
|
4572
4633
|
);
|
|
4573
|
-
return
|
|
4634
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
4574
4635
|
} catch {
|
|
4575
4636
|
return null;
|
|
4576
4637
|
}
|
|
@@ -4636,7 +4697,7 @@ function extractRootExe(name) {
|
|
|
4636
4697
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4637
4698
|
}
|
|
4638
4699
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4639
|
-
if (!
|
|
4700
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4640
4701
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4641
4702
|
}
|
|
4642
4703
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
@@ -4728,7 +4789,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4728
4789
|
}
|
|
4729
4790
|
function readDebounceState() {
|
|
4730
4791
|
try {
|
|
4731
|
-
if (!
|
|
4792
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4732
4793
|
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
4733
4794
|
const state = {};
|
|
4734
4795
|
for (const [key, val] of Object.entries(raw)) {
|
|
@@ -4745,7 +4806,7 @@ function readDebounceState() {
|
|
|
4745
4806
|
}
|
|
4746
4807
|
function writeDebounceState(state) {
|
|
4747
4808
|
try {
|
|
4748
|
-
if (!
|
|
4809
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4749
4810
|
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4750
4811
|
} catch {
|
|
4751
4812
|
}
|
|
@@ -4845,7 +4906,7 @@ function sendIntercom(targetSession) {
|
|
|
4845
4906
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4846
4907
|
const agent = baseAgentName(rawAgent);
|
|
4847
4908
|
const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
4848
|
-
if (
|
|
4909
|
+
if (existsSync12(markerPath)) {
|
|
4849
4910
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4850
4911
|
return "debounced";
|
|
4851
4912
|
}
|
|
@@ -4855,7 +4916,7 @@ function sendIntercom(targetSession) {
|
|
|
4855
4916
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4856
4917
|
const agent = baseAgentName(rawAgent);
|
|
4857
4918
|
const taskDir = path15.join(process.cwd(), "exe", agent);
|
|
4858
|
-
if (
|
|
4919
|
+
if (existsSync12(taskDir)) {
|
|
4859
4920
|
const files = readdirSync3(taskDir).filter(
|
|
4860
4921
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4861
4922
|
);
|
|
@@ -4915,6 +4976,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4915
4976
|
}
|
|
4916
4977
|
return true;
|
|
4917
4978
|
}
|
|
4979
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
4980
|
+
const transport = getTransport();
|
|
4981
|
+
try {
|
|
4982
|
+
const sessions = transport.listSessions();
|
|
4983
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
4984
|
+
execSync6(
|
|
4985
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
4986
|
+
{ timeout: 3e3 }
|
|
4987
|
+
);
|
|
4988
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
4989
|
+
return true;
|
|
4990
|
+
} catch {
|
|
4991
|
+
return false;
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4918
4994
|
function ensureEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
4919
4995
|
if (isCoordinatorName(employeeName2)) {
|
|
4920
4996
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4988,9 +5064,9 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4988
5064
|
const transport = getTransport();
|
|
4989
5065
|
const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
|
|
4990
5066
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
|
|
4991
|
-
const logDir = path15.join(
|
|
5067
|
+
const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
|
|
4992
5068
|
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4993
|
-
if (!
|
|
5069
|
+
if (!existsSync12(logDir)) {
|
|
4994
5070
|
mkdirSync6(logDir, { recursive: true });
|
|
4995
5071
|
}
|
|
4996
5072
|
transport.kill(sessionName);
|
|
@@ -4998,13 +5074,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4998
5074
|
try {
|
|
4999
5075
|
const thisFile = fileURLToPath(import.meta.url);
|
|
5000
5076
|
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5001
|
-
if (
|
|
5077
|
+
if (existsSync12(cleanupScript)) {
|
|
5002
5078
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
|
|
5003
5079
|
}
|
|
5004
5080
|
} catch {
|
|
5005
5081
|
}
|
|
5006
5082
|
try {
|
|
5007
|
-
const claudeJsonPath = path15.join(
|
|
5083
|
+
const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
|
|
5008
5084
|
let claudeJson = {};
|
|
5009
5085
|
try {
|
|
5010
5086
|
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
@@ -5019,7 +5095,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5019
5095
|
} catch {
|
|
5020
5096
|
}
|
|
5021
5097
|
try {
|
|
5022
|
-
const settingsDir = path15.join(
|
|
5098
|
+
const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
|
|
5023
5099
|
const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
|
|
5024
5100
|
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
5025
5101
|
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
@@ -5070,7 +5146,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5070
5146
|
let legacyFallbackWarned = false;
|
|
5071
5147
|
if (!useExeAgent && !useBinSymlink) {
|
|
5072
5148
|
const identityPath = path15.join(
|
|
5073
|
-
|
|
5149
|
+
os9.homedir(),
|
|
5074
5150
|
".exe-os",
|
|
5075
5151
|
"identity",
|
|
5076
5152
|
`${employeeName2}.md`
|
|
@@ -5079,7 +5155,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5079
5155
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5080
5156
|
if (hasAgentFlag) {
|
|
5081
5157
|
identityFlag = ` --agent ${employeeName2}`;
|
|
5082
|
-
} else if (
|
|
5158
|
+
} else if (existsSync12(identityPath)) {
|
|
5083
5159
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5084
5160
|
legacyFallbackWarned = true;
|
|
5085
5161
|
}
|
|
@@ -5100,7 +5176,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5100
5176
|
}
|
|
5101
5177
|
let sessionContextFlag = "";
|
|
5102
5178
|
try {
|
|
5103
|
-
const ctxDir = path15.join(
|
|
5179
|
+
const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
5104
5180
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5105
5181
|
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5106
5182
|
const ctxContent = [
|
|
@@ -5261,14 +5337,14 @@ var init_tmux_routing = __esm({
|
|
|
5261
5337
|
init_intercom_queue();
|
|
5262
5338
|
init_plan_limits();
|
|
5263
5339
|
init_employees();
|
|
5264
|
-
SPAWN_LOCK_DIR = path15.join(
|
|
5265
|
-
SESSION_CACHE = path15.join(
|
|
5340
|
+
SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
5341
|
+
SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
5266
5342
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5267
5343
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5268
5344
|
VERIFY_PANE_LINES = 200;
|
|
5269
5345
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5270
5346
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5271
|
-
INTERCOM_LOG2 = path15.join(
|
|
5347
|
+
INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
5272
5348
|
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5273
5349
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5274
5350
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
@@ -5280,6 +5356,7 @@ var shard_manager_exports = {};
|
|
|
5280
5356
|
__export(shard_manager_exports, {
|
|
5281
5357
|
disposeShards: () => disposeShards,
|
|
5282
5358
|
ensureShardSchema: () => ensureShardSchema,
|
|
5359
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5283
5360
|
getReadyShardClient: () => getReadyShardClient,
|
|
5284
5361
|
getShardClient: () => getShardClient,
|
|
5285
5362
|
getShardsDir: () => getShardsDir,
|
|
@@ -5289,14 +5366,17 @@ __export(shard_manager_exports, {
|
|
|
5289
5366
|
shardExists: () => shardExists
|
|
5290
5367
|
});
|
|
5291
5368
|
import path17 from "path";
|
|
5292
|
-
import { existsSync as
|
|
5369
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5293
5370
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5294
5371
|
function initShardManager(encryptionKey) {
|
|
5295
5372
|
_encryptionKey = encryptionKey;
|
|
5296
|
-
if (!
|
|
5373
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5297
5374
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5298
5375
|
}
|
|
5299
5376
|
_shardingEnabled = true;
|
|
5377
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
5378
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
5379
|
+
_evictionTimer.unref();
|
|
5300
5380
|
}
|
|
5301
5381
|
function isShardingEnabled() {
|
|
5302
5382
|
return _shardingEnabled;
|
|
@@ -5313,21 +5393,28 @@ function getShardClient(projectName) {
|
|
|
5313
5393
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5314
5394
|
}
|
|
5315
5395
|
const cached = _shards.get(safeName);
|
|
5316
|
-
if (cached)
|
|
5396
|
+
if (cached) {
|
|
5397
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5398
|
+
return cached;
|
|
5399
|
+
}
|
|
5400
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
5401
|
+
evictLRU();
|
|
5402
|
+
}
|
|
5317
5403
|
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
5318
5404
|
const client = createClient2({
|
|
5319
5405
|
url: `file:${dbPath}`,
|
|
5320
5406
|
encryptionKey: _encryptionKey
|
|
5321
5407
|
});
|
|
5322
5408
|
_shards.set(safeName, client);
|
|
5409
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5323
5410
|
return client;
|
|
5324
5411
|
}
|
|
5325
5412
|
function shardExists(projectName) {
|
|
5326
5413
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5327
|
-
return
|
|
5414
|
+
return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
5328
5415
|
}
|
|
5329
5416
|
function listShards() {
|
|
5330
|
-
if (!
|
|
5417
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
5331
5418
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5332
5419
|
}
|
|
5333
5420
|
async function ensureShardSchema(client) {
|
|
@@ -5379,6 +5466,8 @@ async function ensureShardSchema(client) {
|
|
|
5379
5466
|
for (const col of [
|
|
5380
5467
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5381
5468
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
5469
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
5470
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5382
5471
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5383
5472
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5384
5473
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5516,21 +5605,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5516
5605
|
await ensureShardSchema(client);
|
|
5517
5606
|
return client;
|
|
5518
5607
|
}
|
|
5608
|
+
function evictLRU() {
|
|
5609
|
+
let oldest = null;
|
|
5610
|
+
let oldestTime = Infinity;
|
|
5611
|
+
for (const [name, time] of _shardLastAccess) {
|
|
5612
|
+
if (time < oldestTime) {
|
|
5613
|
+
oldestTime = time;
|
|
5614
|
+
oldest = name;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
if (oldest) {
|
|
5618
|
+
const client = _shards.get(oldest);
|
|
5619
|
+
if (client) {
|
|
5620
|
+
client.close();
|
|
5621
|
+
}
|
|
5622
|
+
_shards.delete(oldest);
|
|
5623
|
+
_shardLastAccess.delete(oldest);
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
function evictIdleShards() {
|
|
5627
|
+
const now = Date.now();
|
|
5628
|
+
const toEvict = [];
|
|
5629
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
5630
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
5631
|
+
toEvict.push(name);
|
|
5632
|
+
}
|
|
5633
|
+
}
|
|
5634
|
+
for (const name of toEvict) {
|
|
5635
|
+
const client = _shards.get(name);
|
|
5636
|
+
if (client) {
|
|
5637
|
+
client.close();
|
|
5638
|
+
}
|
|
5639
|
+
_shards.delete(name);
|
|
5640
|
+
_shardLastAccess.delete(name);
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
function getOpenShardCount() {
|
|
5644
|
+
return _shards.size;
|
|
5645
|
+
}
|
|
5519
5646
|
function disposeShards() {
|
|
5647
|
+
if (_evictionTimer) {
|
|
5648
|
+
clearInterval(_evictionTimer);
|
|
5649
|
+
_evictionTimer = null;
|
|
5650
|
+
}
|
|
5520
5651
|
for (const [, client] of _shards) {
|
|
5521
5652
|
client.close();
|
|
5522
5653
|
}
|
|
5523
5654
|
_shards.clear();
|
|
5655
|
+
_shardLastAccess.clear();
|
|
5524
5656
|
_shardingEnabled = false;
|
|
5525
5657
|
_encryptionKey = null;
|
|
5526
5658
|
}
|
|
5527
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
5659
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5528
5660
|
var init_shard_manager = __esm({
|
|
5529
5661
|
"src/lib/shard-manager.ts"() {
|
|
5530
5662
|
"use strict";
|
|
5531
5663
|
init_config();
|
|
5532
5664
|
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
5665
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
5666
|
+
MAX_OPEN_SHARDS = 10;
|
|
5667
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5533
5668
|
_shards = /* @__PURE__ */ new Map();
|
|
5669
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
5670
|
+
_evictionTimer = null;
|
|
5534
5671
|
_encryptionKey = null;
|
|
5535
5672
|
_shardingEnabled = false;
|
|
5536
5673
|
}
|
|
@@ -5733,13 +5870,13 @@ init_database();
|
|
|
5733
5870
|
|
|
5734
5871
|
// src/lib/keychain.ts
|
|
5735
5872
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5736
|
-
import { existsSync as
|
|
5873
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5737
5874
|
import path16 from "path";
|
|
5738
|
-
import
|
|
5875
|
+
import os10 from "os";
|
|
5739
5876
|
var SERVICE = "exe-mem";
|
|
5740
5877
|
var ACCOUNT = "master-key";
|
|
5741
5878
|
function getKeyDir() {
|
|
5742
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(
|
|
5879
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os10.homedir(), ".exe-os");
|
|
5743
5880
|
}
|
|
5744
5881
|
function getKeyPath() {
|
|
5745
5882
|
return path16.join(getKeyDir(), "master.key");
|
|
@@ -5763,9 +5900,9 @@ async function getMasterKey() {
|
|
|
5763
5900
|
}
|
|
5764
5901
|
}
|
|
5765
5902
|
const keyPath = getKeyPath();
|
|
5766
|
-
if (!
|
|
5903
|
+
if (!existsSync13(keyPath)) {
|
|
5767
5904
|
process.stderr.write(
|
|
5768
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5905
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5769
5906
|
`
|
|
5770
5907
|
);
|
|
5771
5908
|
return null;
|