@askexenow/exe-os 0.9.8 → 0.9.10
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 +1411 -953
- 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 +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- 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 +793 -485
- 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 +566 -357
- 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 +530 -319
- 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 +547 -336
- 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 +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -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 +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- 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 +664 -431
- 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 +1049 -680
- 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 +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- 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 +448 -371
- 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 +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
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({
|
|
@@ -2648,13 +2723,117 @@ var init_state_bus = __esm({
|
|
|
2648
2723
|
}
|
|
2649
2724
|
});
|
|
2650
2725
|
|
|
2726
|
+
// src/lib/project-name.ts
|
|
2727
|
+
import { execSync as execSync4 } from "child_process";
|
|
2728
|
+
import path10 from "path";
|
|
2729
|
+
function getProjectName(cwd) {
|
|
2730
|
+
const dir = cwd ?? process.cwd();
|
|
2731
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
2732
|
+
try {
|
|
2733
|
+
let repoRoot;
|
|
2734
|
+
try {
|
|
2735
|
+
const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
|
|
2736
|
+
cwd: dir,
|
|
2737
|
+
encoding: "utf8",
|
|
2738
|
+
timeout: 2e3,
|
|
2739
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2740
|
+
}).trim();
|
|
2741
|
+
repoRoot = path10.dirname(gitCommonDir);
|
|
2742
|
+
} catch {
|
|
2743
|
+
repoRoot = execSync4("git rev-parse --show-toplevel", {
|
|
2744
|
+
cwd: dir,
|
|
2745
|
+
encoding: "utf8",
|
|
2746
|
+
timeout: 2e3,
|
|
2747
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2748
|
+
}).trim();
|
|
2749
|
+
}
|
|
2750
|
+
_cached2 = path10.basename(repoRoot);
|
|
2751
|
+
_cachedCwd = dir;
|
|
2752
|
+
return _cached2;
|
|
2753
|
+
} catch {
|
|
2754
|
+
_cached2 = path10.basename(dir);
|
|
2755
|
+
_cachedCwd = dir;
|
|
2756
|
+
return _cached2;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
var _cached2, _cachedCwd;
|
|
2760
|
+
var init_project_name = __esm({
|
|
2761
|
+
"src/lib/project-name.ts"() {
|
|
2762
|
+
"use strict";
|
|
2763
|
+
_cached2 = null;
|
|
2764
|
+
_cachedCwd = null;
|
|
2765
|
+
}
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2768
|
+
// src/lib/session-scope.ts
|
|
2769
|
+
var session_scope_exports = {};
|
|
2770
|
+
__export(session_scope_exports, {
|
|
2771
|
+
assertSessionScope: () => assertSessionScope,
|
|
2772
|
+
findSessionForProject: () => findSessionForProject,
|
|
2773
|
+
getSessionProject: () => getSessionProject
|
|
2774
|
+
});
|
|
2775
|
+
function getSessionProject(sessionName) {
|
|
2776
|
+
const sessions = listSessions();
|
|
2777
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
2778
|
+
if (!entry) return null;
|
|
2779
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
2780
|
+
return parts[parts.length - 1] ?? null;
|
|
2781
|
+
}
|
|
2782
|
+
function findSessionForProject(projectName) {
|
|
2783
|
+
const sessions = listSessions();
|
|
2784
|
+
for (const s of sessions) {
|
|
2785
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2786
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2787
|
+
}
|
|
2788
|
+
return null;
|
|
2789
|
+
}
|
|
2790
|
+
function assertSessionScope(actionType, targetProject) {
|
|
2791
|
+
try {
|
|
2792
|
+
const currentProject = getProjectName();
|
|
2793
|
+
const exeSession2 = resolveExeSession();
|
|
2794
|
+
if (!exeSession2) {
|
|
2795
|
+
return { allowed: true, reason: "no_session" };
|
|
2796
|
+
}
|
|
2797
|
+
if (currentProject === targetProject) {
|
|
2798
|
+
return {
|
|
2799
|
+
allowed: true,
|
|
2800
|
+
reason: "same_session",
|
|
2801
|
+
currentProject,
|
|
2802
|
+
targetProject
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
process.stderr.write(
|
|
2806
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
2807
|
+
`
|
|
2808
|
+
);
|
|
2809
|
+
return {
|
|
2810
|
+
allowed: false,
|
|
2811
|
+
reason: "cross_session_denied",
|
|
2812
|
+
currentProject,
|
|
2813
|
+
targetProject,
|
|
2814
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
2815
|
+
};
|
|
2816
|
+
} catch {
|
|
2817
|
+
return { allowed: true, reason: "no_session" };
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
var init_session_scope = __esm({
|
|
2821
|
+
"src/lib/session-scope.ts"() {
|
|
2822
|
+
"use strict";
|
|
2823
|
+
init_session_registry();
|
|
2824
|
+
init_project_name();
|
|
2825
|
+
init_tmux_routing();
|
|
2826
|
+
init_employees();
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2651
2830
|
// src/lib/tasks-crud.ts
|
|
2652
2831
|
import crypto3 from "crypto";
|
|
2653
|
-
import
|
|
2654
|
-
import
|
|
2655
|
-
import { execSync as
|
|
2832
|
+
import path11 from "path";
|
|
2833
|
+
import os8 from "os";
|
|
2834
|
+
import { execSync as execSync5 } from "child_process";
|
|
2656
2835
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2657
|
-
import { existsSync as
|
|
2836
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
2658
2837
|
async function writeCheckpoint(input) {
|
|
2659
2838
|
const client = getClient();
|
|
2660
2839
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2774,9 +2953,24 @@ async function createTaskCore(input) {
|
|
|
2774
2953
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2775
2954
|
const slug = slugify(input.title);
|
|
2776
2955
|
let earlySessionScope = null;
|
|
2956
|
+
let scopeMismatchWarning;
|
|
2777
2957
|
try {
|
|
2778
2958
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2779
|
-
|
|
2959
|
+
const resolved = resolveExeSession2();
|
|
2960
|
+
if (resolved && input.projectName) {
|
|
2961
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
2962
|
+
const sessionProject = getSessionProject2(resolved);
|
|
2963
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
2964
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
2965
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
2966
|
+
`);
|
|
2967
|
+
earlySessionScope = null;
|
|
2968
|
+
} else {
|
|
2969
|
+
earlySessionScope = resolved;
|
|
2970
|
+
}
|
|
2971
|
+
} else {
|
|
2972
|
+
earlySessionScope = resolved;
|
|
2973
|
+
}
|
|
2780
2974
|
} catch {
|
|
2781
2975
|
}
|
|
2782
2976
|
const scope = earlySessionScope ?? "default";
|
|
@@ -2827,10 +3021,14 @@ async function createTaskCore(input) {
|
|
|
2827
3021
|
${laneWarning}` : laneWarning;
|
|
2828
3022
|
}
|
|
2829
3023
|
}
|
|
3024
|
+
if (scopeMismatchWarning) {
|
|
3025
|
+
warning = warning ? `${warning}
|
|
3026
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
3027
|
+
}
|
|
2830
3028
|
if (input.baseDir) {
|
|
2831
3029
|
try {
|
|
2832
|
-
await mkdir3(
|
|
2833
|
-
await mkdir3(
|
|
3030
|
+
await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3031
|
+
await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2834
3032
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2835
3033
|
await ensureGitignoreExe(input.baseDir);
|
|
2836
3034
|
} catch {
|
|
@@ -2866,13 +3064,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2866
3064
|
});
|
|
2867
3065
|
if (input.baseDir) {
|
|
2868
3066
|
try {
|
|
2869
|
-
const EXE_OS_DIR =
|
|
2870
|
-
const mdPath =
|
|
2871
|
-
const mdDir =
|
|
2872
|
-
if (!
|
|
3067
|
+
const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
|
|
3068
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
3069
|
+
const mdDir = path11.dirname(mdPath);
|
|
3070
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2873
3071
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2874
3072
|
const mdContent = `# ${input.title}
|
|
2875
3073
|
|
|
3074
|
+
## MANDATORY: When done
|
|
3075
|
+
|
|
3076
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3077
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3078
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3079
|
+
|
|
2876
3080
|
**ID:** ${id}
|
|
2877
3081
|
**Status:** ${initialStatus}
|
|
2878
3082
|
**Priority:** ${input.priority}
|
|
@@ -2886,12 +3090,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2886
3090
|
## Context
|
|
2887
3091
|
|
|
2888
3092
|
${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
3093
|
`;
|
|
2896
3094
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2897
3095
|
} catch (err) {
|
|
@@ -2973,14 +3171,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
2973
3171
|
if (!identifier || identifier === "unknown") return true;
|
|
2974
3172
|
try {
|
|
2975
3173
|
if (identifier.startsWith("%")) {
|
|
2976
|
-
const output =
|
|
3174
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
2977
3175
|
timeout: 2e3,
|
|
2978
3176
|
encoding: "utf8",
|
|
2979
3177
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2980
3178
|
});
|
|
2981
3179
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2982
3180
|
} else {
|
|
2983
|
-
|
|
3181
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2984
3182
|
timeout: 2e3,
|
|
2985
3183
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2986
3184
|
});
|
|
@@ -2989,7 +3187,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
2989
3187
|
} catch {
|
|
2990
3188
|
if (identifier.startsWith("%")) return true;
|
|
2991
3189
|
try {
|
|
2992
|
-
|
|
3190
|
+
execSync5("tmux list-sessions", {
|
|
2993
3191
|
timeout: 2e3,
|
|
2994
3192
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2995
3193
|
});
|
|
@@ -3004,12 +3202,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
3004
3202
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
3005
3203
|
try {
|
|
3006
3204
|
const since = new Date(taskCreatedAt).toISOString();
|
|
3007
|
-
const branch =
|
|
3205
|
+
const branch = execSync5(
|
|
3008
3206
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
3009
3207
|
{ encoding: "utf8", timeout: 3e3 }
|
|
3010
3208
|
).trim();
|
|
3011
3209
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
3012
|
-
const commitCount =
|
|
3210
|
+
const commitCount = execSync5(
|
|
3013
3211
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
3014
3212
|
{ encoding: "utf8", timeout: 5e3 }
|
|
3015
3213
|
).trim();
|
|
@@ -3140,7 +3338,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3140
3338
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3141
3339
|
} catch {
|
|
3142
3340
|
}
|
|
3143
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3341
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3144
3342
|
try {
|
|
3145
3343
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3146
3344
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3169,9 +3367,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3169
3367
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3170
3368
|
}
|
|
3171
3369
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3172
|
-
const archPath =
|
|
3370
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3173
3371
|
try {
|
|
3174
|
-
if (
|
|
3372
|
+
if (existsSync10(archPath)) return;
|
|
3175
3373
|
const template = [
|
|
3176
3374
|
`# ${projectName} \u2014 System Architecture`,
|
|
3177
3375
|
"",
|
|
@@ -3204,9 +3402,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3204
3402
|
}
|
|
3205
3403
|
}
|
|
3206
3404
|
async function ensureGitignoreExe(baseDir) {
|
|
3207
|
-
const gitignorePath =
|
|
3405
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
3208
3406
|
try {
|
|
3209
|
-
if (
|
|
3407
|
+
if (existsSync10(gitignorePath)) {
|
|
3210
3408
|
const content = readFileSync9(gitignorePath, "utf-8");
|
|
3211
3409
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3212
3410
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -3238,58 +3436,42 @@ var init_tasks_crud = __esm({
|
|
|
3238
3436
|
});
|
|
3239
3437
|
|
|
3240
3438
|
// src/lib/tasks-review.ts
|
|
3241
|
-
import
|
|
3242
|
-
import { existsSync as
|
|
3439
|
+
import path12 from "path";
|
|
3440
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
3243
3441
|
async function countPendingReviews(sessionScope) {
|
|
3244
3442
|
const client = getClient();
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
args: [sessionScope]
|
|
3249
|
-
});
|
|
3250
|
-
return Number(result3.rows[0]?.cnt) || 0;
|
|
3251
|
-
}
|
|
3443
|
+
const scope = strictSessionScopeFilter(
|
|
3444
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3445
|
+
);
|
|
3252
3446
|
const result2 = await client.execute({
|
|
3253
|
-
sql:
|
|
3254
|
-
|
|
3447
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3448
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3449
|
+
args: [...scope.args]
|
|
3255
3450
|
});
|
|
3256
3451
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
3257
3452
|
}
|
|
3258
3453
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3259
3454
|
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
|
-
}
|
|
3455
|
+
const scope = strictSessionScopeFilter(
|
|
3456
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3457
|
+
);
|
|
3269
3458
|
const result2 = await client.execute({
|
|
3270
3459
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3271
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3272
|
-
args: [sinceIso]
|
|
3460
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3461
|
+
args: [sinceIso, ...scope.args]
|
|
3273
3462
|
});
|
|
3274
3463
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
3275
3464
|
}
|
|
3276
3465
|
async function listPendingReviews(limit, sessionScope) {
|
|
3277
3466
|
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
|
-
}
|
|
3467
|
+
const scope = strictSessionScopeFilter(
|
|
3468
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3469
|
+
);
|
|
3288
3470
|
const result2 = await client.execute({
|
|
3289
3471
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3290
|
-
WHERE status = 'needs_review'
|
|
3472
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3291
3473
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3292
|
-
args: [limit]
|
|
3474
|
+
args: [...scope.args, limit]
|
|
3293
3475
|
});
|
|
3294
3476
|
return result2.rows;
|
|
3295
3477
|
}
|
|
@@ -3301,7 +3483,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3301
3483
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3302
3484
|
AND assigned_by = 'system'
|
|
3303
3485
|
AND title LIKE 'Review:%'
|
|
3304
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3486
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3305
3487
|
args: [now]
|
|
3306
3488
|
});
|
|
3307
3489
|
const r1b = await client.execute({
|
|
@@ -3420,11 +3602,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3420
3602
|
);
|
|
3421
3603
|
}
|
|
3422
3604
|
try {
|
|
3423
|
-
const cacheDir =
|
|
3424
|
-
if (
|
|
3605
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
3606
|
+
if (existsSync11(cacheDir)) {
|
|
3425
3607
|
for (const f of readdirSync2(cacheDir)) {
|
|
3426
3608
|
if (f.startsWith("review-notified-")) {
|
|
3427
|
-
unlinkSync3(
|
|
3609
|
+
unlinkSync3(path12.join(cacheDir, f));
|
|
3428
3610
|
}
|
|
3429
3611
|
}
|
|
3430
3612
|
}
|
|
@@ -3441,11 +3623,12 @@ var init_tasks_review = __esm({
|
|
|
3441
3623
|
init_tmux_routing();
|
|
3442
3624
|
init_session_key();
|
|
3443
3625
|
init_state_bus();
|
|
3626
|
+
init_task_scope();
|
|
3444
3627
|
}
|
|
3445
3628
|
});
|
|
3446
3629
|
|
|
3447
3630
|
// src/lib/tasks-chain.ts
|
|
3448
|
-
import
|
|
3631
|
+
import path13 from "path";
|
|
3449
3632
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3450
3633
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3451
3634
|
const client = getClient();
|
|
@@ -3462,7 +3645,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3462
3645
|
});
|
|
3463
3646
|
for (const ur of unblockedRows.rows) {
|
|
3464
3647
|
try {
|
|
3465
|
-
const ubFile =
|
|
3648
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
3466
3649
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3467
3650
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3468
3651
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3497,7 +3680,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3497
3680
|
const scScope = sessionScopeFilter();
|
|
3498
3681
|
const remaining = await client.execute({
|
|
3499
3682
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3500
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3683
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3501
3684
|
args: [parentTaskId, ...scScope.args]
|
|
3502
3685
|
});
|
|
3503
3686
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3529,110 +3712,6 @@ var init_tasks_chain = __esm({
|
|
|
3529
3712
|
}
|
|
3530
3713
|
});
|
|
3531
3714
|
|
|
3532
|
-
// src/lib/project-name.ts
|
|
3533
|
-
import { execSync as execSync5 } from "child_process";
|
|
3534
|
-
import path13 from "path";
|
|
3535
|
-
function getProjectName(cwd) {
|
|
3536
|
-
const dir = cwd ?? process.cwd();
|
|
3537
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3538
|
-
try {
|
|
3539
|
-
let repoRoot;
|
|
3540
|
-
try {
|
|
3541
|
-
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3542
|
-
cwd: dir,
|
|
3543
|
-
encoding: "utf8",
|
|
3544
|
-
timeout: 2e3,
|
|
3545
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3546
|
-
}).trim();
|
|
3547
|
-
repoRoot = path13.dirname(gitCommonDir);
|
|
3548
|
-
} catch {
|
|
3549
|
-
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3550
|
-
cwd: dir,
|
|
3551
|
-
encoding: "utf8",
|
|
3552
|
-
timeout: 2e3,
|
|
3553
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3554
|
-
}).trim();
|
|
3555
|
-
}
|
|
3556
|
-
_cached2 = path13.basename(repoRoot);
|
|
3557
|
-
_cachedCwd = dir;
|
|
3558
|
-
return _cached2;
|
|
3559
|
-
} catch {
|
|
3560
|
-
_cached2 = path13.basename(dir);
|
|
3561
|
-
_cachedCwd = dir;
|
|
3562
|
-
return _cached2;
|
|
3563
|
-
}
|
|
3564
|
-
}
|
|
3565
|
-
var _cached2, _cachedCwd;
|
|
3566
|
-
var init_project_name = __esm({
|
|
3567
|
-
"src/lib/project-name.ts"() {
|
|
3568
|
-
"use strict";
|
|
3569
|
-
_cached2 = null;
|
|
3570
|
-
_cachedCwd = null;
|
|
3571
|
-
}
|
|
3572
|
-
});
|
|
3573
|
-
|
|
3574
|
-
// src/lib/session-scope.ts
|
|
3575
|
-
var session_scope_exports = {};
|
|
3576
|
-
__export(session_scope_exports, {
|
|
3577
|
-
assertSessionScope: () => assertSessionScope,
|
|
3578
|
-
findSessionForProject: () => findSessionForProject,
|
|
3579
|
-
getSessionProject: () => getSessionProject
|
|
3580
|
-
});
|
|
3581
|
-
function getSessionProject(sessionName) {
|
|
3582
|
-
const sessions = listSessions();
|
|
3583
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3584
|
-
if (!entry) return null;
|
|
3585
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3586
|
-
return parts[parts.length - 1] ?? null;
|
|
3587
|
-
}
|
|
3588
|
-
function findSessionForProject(projectName) {
|
|
3589
|
-
const sessions = listSessions();
|
|
3590
|
-
for (const s of sessions) {
|
|
3591
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3592
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3593
|
-
}
|
|
3594
|
-
return null;
|
|
3595
|
-
}
|
|
3596
|
-
function assertSessionScope(actionType, targetProject) {
|
|
3597
|
-
try {
|
|
3598
|
-
const currentProject = getProjectName();
|
|
3599
|
-
const exeSession2 = resolveExeSession();
|
|
3600
|
-
if (!exeSession2) {
|
|
3601
|
-
return { allowed: true, reason: "no_session" };
|
|
3602
|
-
}
|
|
3603
|
-
if (currentProject === targetProject) {
|
|
3604
|
-
return {
|
|
3605
|
-
allowed: true,
|
|
3606
|
-
reason: "same_session",
|
|
3607
|
-
currentProject,
|
|
3608
|
-
targetProject
|
|
3609
|
-
};
|
|
3610
|
-
}
|
|
3611
|
-
process.stderr.write(
|
|
3612
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3613
|
-
`
|
|
3614
|
-
);
|
|
3615
|
-
return {
|
|
3616
|
-
allowed: false,
|
|
3617
|
-
reason: "cross_session_denied",
|
|
3618
|
-
currentProject,
|
|
3619
|
-
targetProject,
|
|
3620
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3621
|
-
};
|
|
3622
|
-
} catch {
|
|
3623
|
-
return { allowed: true, reason: "no_session" };
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
var init_session_scope = __esm({
|
|
3627
|
-
"src/lib/session-scope.ts"() {
|
|
3628
|
-
"use strict";
|
|
3629
|
-
init_session_registry();
|
|
3630
|
-
init_project_name();
|
|
3631
|
-
init_tmux_routing();
|
|
3632
|
-
init_employees();
|
|
3633
|
-
}
|
|
3634
|
-
});
|
|
3635
|
-
|
|
3636
3715
|
// src/lib/tasks-notify.ts
|
|
3637
3716
|
async function dispatchTaskToEmployee(input) {
|
|
3638
3717
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4055,7 +4134,7 @@ async function updateTask(input) {
|
|
|
4055
4134
|
if (input.status === "in_progress") {
|
|
4056
4135
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4057
4136
|
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4058
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4137
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4059
4138
|
try {
|
|
4060
4139
|
unlinkSync4(cachePath);
|
|
4061
4140
|
} catch {
|
|
@@ -4063,10 +4142,10 @@ async function updateTask(input) {
|
|
|
4063
4142
|
}
|
|
4064
4143
|
} catch {
|
|
4065
4144
|
}
|
|
4066
|
-
if (input.status === "done") {
|
|
4145
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4067
4146
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4068
4147
|
}
|
|
4069
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4148
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4070
4149
|
try {
|
|
4071
4150
|
const client = getClient();
|
|
4072
4151
|
const taskTitle = String(row.title);
|
|
@@ -4082,7 +4161,7 @@ async function updateTask(input) {
|
|
|
4082
4161
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4083
4162
|
try {
|
|
4084
4163
|
const draftClient = getClient();
|
|
4085
|
-
if (input.status === "done") {
|
|
4164
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4086
4165
|
await draftClient.execute({
|
|
4087
4166
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4088
4167
|
args: [assignedAgent]
|
|
@@ -4099,7 +4178,7 @@ async function updateTask(input) {
|
|
|
4099
4178
|
try {
|
|
4100
4179
|
const client = getClient();
|
|
4101
4180
|
const cascaded = await client.execute({
|
|
4102
|
-
sql: `UPDATE tasks SET status = '
|
|
4181
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4103
4182
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4104
4183
|
args: [now, taskId]
|
|
4105
4184
|
});
|
|
@@ -4112,14 +4191,14 @@ async function updateTask(input) {
|
|
|
4112
4191
|
} catch {
|
|
4113
4192
|
}
|
|
4114
4193
|
}
|
|
4115
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4194
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4116
4195
|
if (isTerminal) {
|
|
4117
4196
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4118
4197
|
if (!isCoordinator) {
|
|
4119
4198
|
notifyTaskDone();
|
|
4120
4199
|
}
|
|
4121
4200
|
await markTaskNotificationsRead(taskFile);
|
|
4122
|
-
if (input.status === "done") {
|
|
4201
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4123
4202
|
try {
|
|
4124
4203
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4125
4204
|
} catch {
|
|
@@ -4139,7 +4218,7 @@ async function updateTask(input) {
|
|
|
4139
4218
|
}
|
|
4140
4219
|
}
|
|
4141
4220
|
}
|
|
4142
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4221
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4143
4222
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4144
4223
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4145
4224
|
taskId,
|
|
@@ -4511,6 +4590,7 @@ __export(tmux_routing_exports, {
|
|
|
4511
4590
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4512
4591
|
isExeSession: () => isExeSession,
|
|
4513
4592
|
isSessionBusy: () => isSessionBusy,
|
|
4593
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4514
4594
|
notifyParentExe: () => notifyParentExe,
|
|
4515
4595
|
parseParentExe: () => parseParentExe,
|
|
4516
4596
|
registerParentExe: () => registerParentExe,
|
|
@@ -4521,9 +4601,9 @@ __export(tmux_routing_exports, {
|
|
|
4521
4601
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4522
4602
|
});
|
|
4523
4603
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
4524
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as
|
|
4604
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
4525
4605
|
import path15 from "path";
|
|
4526
|
-
import
|
|
4606
|
+
import os9 from "os";
|
|
4527
4607
|
import { fileURLToPath } from "url";
|
|
4528
4608
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
4529
4609
|
function spawnLockPath(sessionName) {
|
|
@@ -4538,11 +4618,11 @@ function isProcessAlive(pid) {
|
|
|
4538
4618
|
}
|
|
4539
4619
|
}
|
|
4540
4620
|
function acquireSpawnLock(sessionName) {
|
|
4541
|
-
if (!
|
|
4621
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4542
4622
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4543
4623
|
}
|
|
4544
4624
|
const lockFile = spawnLockPath(sessionName);
|
|
4545
|
-
if (
|
|
4625
|
+
if (existsSync12(lockFile)) {
|
|
4546
4626
|
try {
|
|
4547
4627
|
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
4548
4628
|
const age = Date.now() - lock.timestamp;
|
|
@@ -4570,7 +4650,7 @@ function resolveBehaviorsExporterScript() {
|
|
|
4570
4650
|
"bin",
|
|
4571
4651
|
"exe-export-behaviors.js"
|
|
4572
4652
|
);
|
|
4573
|
-
return
|
|
4653
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
4574
4654
|
} catch {
|
|
4575
4655
|
return null;
|
|
4576
4656
|
}
|
|
@@ -4636,7 +4716,7 @@ function extractRootExe(name) {
|
|
|
4636
4716
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4637
4717
|
}
|
|
4638
4718
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4639
|
-
if (!
|
|
4719
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4640
4720
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4641
4721
|
}
|
|
4642
4722
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
@@ -4728,7 +4808,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4728
4808
|
}
|
|
4729
4809
|
function readDebounceState() {
|
|
4730
4810
|
try {
|
|
4731
|
-
if (!
|
|
4811
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4732
4812
|
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
4733
4813
|
const state = {};
|
|
4734
4814
|
for (const [key, val] of Object.entries(raw)) {
|
|
@@ -4745,7 +4825,7 @@ function readDebounceState() {
|
|
|
4745
4825
|
}
|
|
4746
4826
|
function writeDebounceState(state) {
|
|
4747
4827
|
try {
|
|
4748
|
-
if (!
|
|
4828
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4749
4829
|
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4750
4830
|
} catch {
|
|
4751
4831
|
}
|
|
@@ -4845,7 +4925,7 @@ function sendIntercom(targetSession) {
|
|
|
4845
4925
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4846
4926
|
const agent = baseAgentName(rawAgent);
|
|
4847
4927
|
const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
4848
|
-
if (
|
|
4928
|
+
if (existsSync12(markerPath)) {
|
|
4849
4929
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4850
4930
|
return "debounced";
|
|
4851
4931
|
}
|
|
@@ -4855,7 +4935,7 @@ function sendIntercom(targetSession) {
|
|
|
4855
4935
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4856
4936
|
const agent = baseAgentName(rawAgent);
|
|
4857
4937
|
const taskDir = path15.join(process.cwd(), "exe", agent);
|
|
4858
|
-
if (
|
|
4938
|
+
if (existsSync12(taskDir)) {
|
|
4859
4939
|
const files = readdirSync3(taskDir).filter(
|
|
4860
4940
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4861
4941
|
);
|
|
@@ -4915,6 +4995,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4915
4995
|
}
|
|
4916
4996
|
return true;
|
|
4917
4997
|
}
|
|
4998
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
4999
|
+
const transport = getTransport();
|
|
5000
|
+
try {
|
|
5001
|
+
const sessions = transport.listSessions();
|
|
5002
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5003
|
+
execSync6(
|
|
5004
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5005
|
+
{ timeout: 3e3 }
|
|
5006
|
+
);
|
|
5007
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5008
|
+
return true;
|
|
5009
|
+
} catch {
|
|
5010
|
+
return false;
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
4918
5013
|
function ensureEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
4919
5014
|
if (isCoordinatorName(employeeName2)) {
|
|
4920
5015
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4988,9 +5083,9 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4988
5083
|
const transport = getTransport();
|
|
4989
5084
|
const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
|
|
4990
5085
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
|
|
4991
|
-
const logDir = path15.join(
|
|
5086
|
+
const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
|
|
4992
5087
|
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4993
|
-
if (!
|
|
5088
|
+
if (!existsSync12(logDir)) {
|
|
4994
5089
|
mkdirSync6(logDir, { recursive: true });
|
|
4995
5090
|
}
|
|
4996
5091
|
transport.kill(sessionName);
|
|
@@ -4998,13 +5093,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4998
5093
|
try {
|
|
4999
5094
|
const thisFile = fileURLToPath(import.meta.url);
|
|
5000
5095
|
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5001
|
-
if (
|
|
5096
|
+
if (existsSync12(cleanupScript)) {
|
|
5002
5097
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
|
|
5003
5098
|
}
|
|
5004
5099
|
} catch {
|
|
5005
5100
|
}
|
|
5006
5101
|
try {
|
|
5007
|
-
const claudeJsonPath = path15.join(
|
|
5102
|
+
const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
|
|
5008
5103
|
let claudeJson = {};
|
|
5009
5104
|
try {
|
|
5010
5105
|
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
@@ -5019,7 +5114,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5019
5114
|
} catch {
|
|
5020
5115
|
}
|
|
5021
5116
|
try {
|
|
5022
|
-
const settingsDir = path15.join(
|
|
5117
|
+
const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
|
|
5023
5118
|
const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
|
|
5024
5119
|
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
5025
5120
|
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
@@ -5070,7 +5165,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5070
5165
|
let legacyFallbackWarned = false;
|
|
5071
5166
|
if (!useExeAgent && !useBinSymlink) {
|
|
5072
5167
|
const identityPath = path15.join(
|
|
5073
|
-
|
|
5168
|
+
os9.homedir(),
|
|
5074
5169
|
".exe-os",
|
|
5075
5170
|
"identity",
|
|
5076
5171
|
`${employeeName2}.md`
|
|
@@ -5079,7 +5174,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5079
5174
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5080
5175
|
if (hasAgentFlag) {
|
|
5081
5176
|
identityFlag = ` --agent ${employeeName2}`;
|
|
5082
|
-
} else if (
|
|
5177
|
+
} else if (existsSync12(identityPath)) {
|
|
5083
5178
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5084
5179
|
legacyFallbackWarned = true;
|
|
5085
5180
|
}
|
|
@@ -5100,7 +5195,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
5100
5195
|
}
|
|
5101
5196
|
let sessionContextFlag = "";
|
|
5102
5197
|
try {
|
|
5103
|
-
const ctxDir = path15.join(
|
|
5198
|
+
const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
5104
5199
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5105
5200
|
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5106
5201
|
const ctxContent = [
|
|
@@ -5261,14 +5356,14 @@ var init_tmux_routing = __esm({
|
|
|
5261
5356
|
init_intercom_queue();
|
|
5262
5357
|
init_plan_limits();
|
|
5263
5358
|
init_employees();
|
|
5264
|
-
SPAWN_LOCK_DIR = path15.join(
|
|
5265
|
-
SESSION_CACHE = path15.join(
|
|
5359
|
+
SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
5360
|
+
SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
5266
5361
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5267
5362
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5268
5363
|
VERIFY_PANE_LINES = 200;
|
|
5269
5364
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5270
5365
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5271
|
-
INTERCOM_LOG2 = path15.join(
|
|
5366
|
+
INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
5272
5367
|
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5273
5368
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5274
5369
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
@@ -5280,6 +5375,7 @@ var shard_manager_exports = {};
|
|
|
5280
5375
|
__export(shard_manager_exports, {
|
|
5281
5376
|
disposeShards: () => disposeShards,
|
|
5282
5377
|
ensureShardSchema: () => ensureShardSchema,
|
|
5378
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5283
5379
|
getReadyShardClient: () => getReadyShardClient,
|
|
5284
5380
|
getShardClient: () => getShardClient,
|
|
5285
5381
|
getShardsDir: () => getShardsDir,
|
|
@@ -5289,14 +5385,17 @@ __export(shard_manager_exports, {
|
|
|
5289
5385
|
shardExists: () => shardExists
|
|
5290
5386
|
});
|
|
5291
5387
|
import path17 from "path";
|
|
5292
|
-
import { existsSync as
|
|
5388
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5293
5389
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5294
5390
|
function initShardManager(encryptionKey) {
|
|
5295
5391
|
_encryptionKey = encryptionKey;
|
|
5296
|
-
if (!
|
|
5392
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5297
5393
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5298
5394
|
}
|
|
5299
5395
|
_shardingEnabled = true;
|
|
5396
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
5397
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
5398
|
+
_evictionTimer.unref();
|
|
5300
5399
|
}
|
|
5301
5400
|
function isShardingEnabled() {
|
|
5302
5401
|
return _shardingEnabled;
|
|
@@ -5313,21 +5412,28 @@ function getShardClient(projectName) {
|
|
|
5313
5412
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5314
5413
|
}
|
|
5315
5414
|
const cached = _shards.get(safeName);
|
|
5316
|
-
if (cached)
|
|
5415
|
+
if (cached) {
|
|
5416
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5417
|
+
return cached;
|
|
5418
|
+
}
|
|
5419
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
5420
|
+
evictLRU();
|
|
5421
|
+
}
|
|
5317
5422
|
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
5318
5423
|
const client = createClient2({
|
|
5319
5424
|
url: `file:${dbPath}`,
|
|
5320
5425
|
encryptionKey: _encryptionKey
|
|
5321
5426
|
});
|
|
5322
5427
|
_shards.set(safeName, client);
|
|
5428
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5323
5429
|
return client;
|
|
5324
5430
|
}
|
|
5325
5431
|
function shardExists(projectName) {
|
|
5326
5432
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5327
|
-
return
|
|
5433
|
+
return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
5328
5434
|
}
|
|
5329
5435
|
function listShards() {
|
|
5330
|
-
if (!
|
|
5436
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
5331
5437
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5332
5438
|
}
|
|
5333
5439
|
async function ensureShardSchema(client) {
|
|
@@ -5379,6 +5485,8 @@ async function ensureShardSchema(client) {
|
|
|
5379
5485
|
for (const col of [
|
|
5380
5486
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5381
5487
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
5488
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
5489
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5382
5490
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5383
5491
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5384
5492
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5516,21 +5624,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5516
5624
|
await ensureShardSchema(client);
|
|
5517
5625
|
return client;
|
|
5518
5626
|
}
|
|
5627
|
+
function evictLRU() {
|
|
5628
|
+
let oldest = null;
|
|
5629
|
+
let oldestTime = Infinity;
|
|
5630
|
+
for (const [name, time] of _shardLastAccess) {
|
|
5631
|
+
if (time < oldestTime) {
|
|
5632
|
+
oldestTime = time;
|
|
5633
|
+
oldest = name;
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
if (oldest) {
|
|
5637
|
+
const client = _shards.get(oldest);
|
|
5638
|
+
if (client) {
|
|
5639
|
+
client.close();
|
|
5640
|
+
}
|
|
5641
|
+
_shards.delete(oldest);
|
|
5642
|
+
_shardLastAccess.delete(oldest);
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5645
|
+
function evictIdleShards() {
|
|
5646
|
+
const now = Date.now();
|
|
5647
|
+
const toEvict = [];
|
|
5648
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
5649
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
5650
|
+
toEvict.push(name);
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5653
|
+
for (const name of toEvict) {
|
|
5654
|
+
const client = _shards.get(name);
|
|
5655
|
+
if (client) {
|
|
5656
|
+
client.close();
|
|
5657
|
+
}
|
|
5658
|
+
_shards.delete(name);
|
|
5659
|
+
_shardLastAccess.delete(name);
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
function getOpenShardCount() {
|
|
5663
|
+
return _shards.size;
|
|
5664
|
+
}
|
|
5519
5665
|
function disposeShards() {
|
|
5666
|
+
if (_evictionTimer) {
|
|
5667
|
+
clearInterval(_evictionTimer);
|
|
5668
|
+
_evictionTimer = null;
|
|
5669
|
+
}
|
|
5520
5670
|
for (const [, client] of _shards) {
|
|
5521
5671
|
client.close();
|
|
5522
5672
|
}
|
|
5523
5673
|
_shards.clear();
|
|
5674
|
+
_shardLastAccess.clear();
|
|
5524
5675
|
_shardingEnabled = false;
|
|
5525
5676
|
_encryptionKey = null;
|
|
5526
5677
|
}
|
|
5527
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
5678
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5528
5679
|
var init_shard_manager = __esm({
|
|
5529
5680
|
"src/lib/shard-manager.ts"() {
|
|
5530
5681
|
"use strict";
|
|
5531
5682
|
init_config();
|
|
5532
5683
|
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
5684
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
5685
|
+
MAX_OPEN_SHARDS = 10;
|
|
5686
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5533
5687
|
_shards = /* @__PURE__ */ new Map();
|
|
5688
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
5689
|
+
_evictionTimer = null;
|
|
5534
5690
|
_encryptionKey = null;
|
|
5535
5691
|
_shardingEnabled = false;
|
|
5536
5692
|
}
|
|
@@ -5733,13 +5889,13 @@ init_database();
|
|
|
5733
5889
|
|
|
5734
5890
|
// src/lib/keychain.ts
|
|
5735
5891
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5736
|
-
import { existsSync as
|
|
5892
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5737
5893
|
import path16 from "path";
|
|
5738
|
-
import
|
|
5894
|
+
import os10 from "os";
|
|
5739
5895
|
var SERVICE = "exe-mem";
|
|
5740
5896
|
var ACCOUNT = "master-key";
|
|
5741
5897
|
function getKeyDir() {
|
|
5742
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(
|
|
5898
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os10.homedir(), ".exe-os");
|
|
5743
5899
|
}
|
|
5744
5900
|
function getKeyPath() {
|
|
5745
5901
|
return path16.join(getKeyDir(), "master.key");
|
|
@@ -5763,9 +5919,9 @@ async function getMasterKey() {
|
|
|
5763
5919
|
}
|
|
5764
5920
|
}
|
|
5765
5921
|
const keyPath = getKeyPath();
|
|
5766
|
-
if (!
|
|
5922
|
+
if (!existsSync13(keyPath)) {
|
|
5767
5923
|
process.stderr.write(
|
|
5768
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5924
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5769
5925
|
`
|
|
5770
5926
|
);
|
|
5771
5927
|
return null;
|