@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
|
@@ -80,9 +80,47 @@ var init_db_retry = __esm({
|
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
// src/lib/secure-files.ts
|
|
84
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
85
|
+
import { chmod, mkdir } from "fs/promises";
|
|
86
|
+
async function ensurePrivateDir(dirPath) {
|
|
87
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
88
|
+
try {
|
|
89
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function ensurePrivateDirSync(dirPath) {
|
|
94
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
95
|
+
try {
|
|
96
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function enforcePrivateFile(filePath) {
|
|
101
|
+
try {
|
|
102
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function enforcePrivateFileSync(filePath) {
|
|
107
|
+
try {
|
|
108
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
113
|
+
var init_secure_files = __esm({
|
|
114
|
+
"src/lib/secure-files.ts"() {
|
|
115
|
+
"use strict";
|
|
116
|
+
PRIVATE_DIR_MODE = 448;
|
|
117
|
+
PRIVATE_FILE_MODE = 384;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
83
121
|
// src/lib/config.ts
|
|
84
|
-
import { readFile, writeFile
|
|
85
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
122
|
+
import { readFile, writeFile } from "fs/promises";
|
|
123
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
86
124
|
import path from "path";
|
|
87
125
|
import os from "os";
|
|
88
126
|
function resolveDataDir() {
|
|
@@ -90,7 +128,7 @@ function resolveDataDir() {
|
|
|
90
128
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
91
129
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
92
130
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
93
|
-
if (!
|
|
131
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
94
132
|
try {
|
|
95
133
|
renameSync(legacyDir, newDir);
|
|
96
134
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -153,9 +191,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
153
191
|
}
|
|
154
192
|
async function loadConfig() {
|
|
155
193
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
156
|
-
await
|
|
194
|
+
await ensurePrivateDir(dir);
|
|
157
195
|
const configPath = path.join(dir, "config.json");
|
|
158
|
-
if (!
|
|
196
|
+
if (!existsSync2(configPath)) {
|
|
159
197
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
160
198
|
}
|
|
161
199
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -168,6 +206,7 @@ async function loadConfig() {
|
|
|
168
206
|
`);
|
|
169
207
|
try {
|
|
170
208
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
209
|
+
await enforcePrivateFile(configPath);
|
|
171
210
|
} catch {
|
|
172
211
|
}
|
|
173
212
|
}
|
|
@@ -187,6 +226,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
187
226
|
var init_config = __esm({
|
|
188
227
|
"src/lib/config.ts"() {
|
|
189
228
|
"use strict";
|
|
229
|
+
init_secure_files();
|
|
190
230
|
EXE_AI_DIR = resolveDataDir();
|
|
191
231
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
192
232
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -303,10 +343,10 @@ __export(agent_config_exports, {
|
|
|
303
343
|
saveAgentConfig: () => saveAgentConfig,
|
|
304
344
|
setAgentRuntime: () => setAgentRuntime
|
|
305
345
|
});
|
|
306
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as
|
|
346
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
307
347
|
import path2 from "path";
|
|
308
348
|
function loadAgentConfig() {
|
|
309
|
-
if (!
|
|
349
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
310
350
|
try {
|
|
311
351
|
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
312
352
|
} catch {
|
|
@@ -315,8 +355,9 @@ function loadAgentConfig() {
|
|
|
315
355
|
}
|
|
316
356
|
function saveAgentConfig(config) {
|
|
317
357
|
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
318
|
-
|
|
358
|
+
ensurePrivateDirSync(dir);
|
|
319
359
|
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
360
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
320
361
|
}
|
|
321
362
|
function getAgentRuntime(agentId) {
|
|
322
363
|
const config = loadAgentConfig();
|
|
@@ -356,6 +397,7 @@ var init_agent_config = __esm({
|
|
|
356
397
|
"use strict";
|
|
357
398
|
init_config();
|
|
358
399
|
init_runtime_table();
|
|
400
|
+
init_secure_files();
|
|
359
401
|
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
360
402
|
KNOWN_RUNTIMES = {
|
|
361
403
|
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
@@ -403,7 +445,7 @@ __export(employees_exports, {
|
|
|
403
445
|
validateEmployeeName: () => validateEmployeeName
|
|
404
446
|
});
|
|
405
447
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
406
|
-
import { existsSync as
|
|
448
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
407
449
|
import { execSync } from "child_process";
|
|
408
450
|
import path3 from "path";
|
|
409
451
|
import os2 from "os";
|
|
@@ -442,7 +484,7 @@ function validateEmployeeName(name) {
|
|
|
442
484
|
return { valid: true };
|
|
443
485
|
}
|
|
444
486
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
445
|
-
if (!
|
|
487
|
+
if (!existsSync4(employeesPath)) {
|
|
446
488
|
return [];
|
|
447
489
|
}
|
|
448
490
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -457,7 +499,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
457
499
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
458
500
|
}
|
|
459
501
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
460
|
-
if (!
|
|
502
|
+
if (!existsSync4(employeesPath)) return [];
|
|
461
503
|
try {
|
|
462
504
|
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
463
505
|
} catch {
|
|
@@ -505,7 +547,7 @@ function appendToCoordinatorTeam(employee) {
|
|
|
505
547
|
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
506
548
|
if (!coordinator) return;
|
|
507
549
|
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
508
|
-
if (!
|
|
550
|
+
if (!existsSync4(idPath)) return;
|
|
509
551
|
const content = readFileSync3(idPath, "utf-8");
|
|
510
552
|
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
511
553
|
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
@@ -559,9 +601,9 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
559
601
|
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
560
602
|
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
561
603
|
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
562
|
-
if (
|
|
604
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
563
605
|
renameSync2(oldPath, newPath);
|
|
564
|
-
} else if (
|
|
606
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
565
607
|
const content = readFileSync3(oldPath, "utf-8");
|
|
566
608
|
writeFileSync2(newPath, content, "utf-8");
|
|
567
609
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
@@ -604,7 +646,7 @@ function registerBinSymlinks(name) {
|
|
|
604
646
|
for (const suffix of ["", "-opencode"]) {
|
|
605
647
|
const linkName = `${name}${suffix}`;
|
|
606
648
|
const linkPath = path3.join(binDir, linkName);
|
|
607
|
-
if (
|
|
649
|
+
if (existsSync4(linkPath)) {
|
|
608
650
|
skipped.push(linkName);
|
|
609
651
|
continue;
|
|
610
652
|
}
|
|
@@ -1557,6 +1599,7 @@ async function ensureSchema() {
|
|
|
1557
1599
|
project TEXT NOT NULL,
|
|
1558
1600
|
summary TEXT NOT NULL,
|
|
1559
1601
|
task_file TEXT,
|
|
1602
|
+
session_scope TEXT,
|
|
1560
1603
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1561
1604
|
created_at TEXT NOT NULL
|
|
1562
1605
|
);
|
|
@@ -1565,7 +1608,7 @@ async function ensureSchema() {
|
|
|
1565
1608
|
ON notifications(read);
|
|
1566
1609
|
|
|
1567
1610
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1568
|
-
ON notifications(agent_id);
|
|
1611
|
+
ON notifications(agent_id, session_scope);
|
|
1569
1612
|
|
|
1570
1613
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1571
1614
|
ON notifications(task_file);
|
|
@@ -1603,6 +1646,7 @@ async function ensureSchema() {
|
|
|
1603
1646
|
target_agent TEXT NOT NULL,
|
|
1604
1647
|
target_project TEXT,
|
|
1605
1648
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1649
|
+
session_scope TEXT,
|
|
1606
1650
|
content TEXT NOT NULL,
|
|
1607
1651
|
priority TEXT DEFAULT 'normal',
|
|
1608
1652
|
status TEXT DEFAULT 'pending',
|
|
@@ -1616,10 +1660,31 @@ async function ensureSchema() {
|
|
|
1616
1660
|
);
|
|
1617
1661
|
|
|
1618
1662
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1619
|
-
ON messages(target_agent, status);
|
|
1663
|
+
ON messages(target_agent, session_scope, status);
|
|
1620
1664
|
|
|
1621
1665
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1622
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1666
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1667
|
+
`);
|
|
1668
|
+
try {
|
|
1669
|
+
await client.execute({
|
|
1670
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1671
|
+
args: []
|
|
1672
|
+
});
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
try {
|
|
1676
|
+
await client.execute({
|
|
1677
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1678
|
+
args: []
|
|
1679
|
+
});
|
|
1680
|
+
} catch {
|
|
1681
|
+
}
|
|
1682
|
+
await client.executeMultiple(`
|
|
1683
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1684
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1685
|
+
|
|
1686
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1687
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1623
1688
|
`);
|
|
1624
1689
|
try {
|
|
1625
1690
|
await client.execute({
|
|
@@ -2203,6 +2268,13 @@ async function ensureSchema() {
|
|
|
2203
2268
|
} catch {
|
|
2204
2269
|
}
|
|
2205
2270
|
}
|
|
2271
|
+
try {
|
|
2272
|
+
await client.execute({
|
|
2273
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2274
|
+
args: []
|
|
2275
|
+
});
|
|
2276
|
+
} catch {
|
|
2277
|
+
}
|
|
2206
2278
|
}
|
|
2207
2279
|
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
2208
2280
|
var init_database = __esm({
|
|
@@ -2280,6 +2352,7 @@ var shard_manager_exports = {};
|
|
|
2280
2352
|
__export(shard_manager_exports, {
|
|
2281
2353
|
disposeShards: () => disposeShards,
|
|
2282
2354
|
ensureShardSchema: () => ensureShardSchema,
|
|
2355
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2283
2356
|
getReadyShardClient: () => getReadyShardClient,
|
|
2284
2357
|
getShardClient: () => getShardClient,
|
|
2285
2358
|
getShardsDir: () => getShardsDir,
|
|
@@ -2289,14 +2362,17 @@ __export(shard_manager_exports, {
|
|
|
2289
2362
|
shardExists: () => shardExists
|
|
2290
2363
|
});
|
|
2291
2364
|
import path6 from "path";
|
|
2292
|
-
import { existsSync as
|
|
2365
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2293
2366
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2294
2367
|
function initShardManager(encryptionKey) {
|
|
2295
2368
|
_encryptionKey = encryptionKey;
|
|
2296
|
-
if (!
|
|
2369
|
+
if (!existsSync6(SHARDS_DIR)) {
|
|
2297
2370
|
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2298
2371
|
}
|
|
2299
2372
|
_shardingEnabled = true;
|
|
2373
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2374
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2375
|
+
_evictionTimer.unref();
|
|
2300
2376
|
}
|
|
2301
2377
|
function isShardingEnabled() {
|
|
2302
2378
|
return _shardingEnabled;
|
|
@@ -2313,21 +2389,28 @@ function getShardClient(projectName) {
|
|
|
2313
2389
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2314
2390
|
}
|
|
2315
2391
|
const cached = _shards.get(safeName);
|
|
2316
|
-
if (cached)
|
|
2392
|
+
if (cached) {
|
|
2393
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2394
|
+
return cached;
|
|
2395
|
+
}
|
|
2396
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2397
|
+
evictLRU();
|
|
2398
|
+
}
|
|
2317
2399
|
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
2318
2400
|
const client = createClient2({
|
|
2319
2401
|
url: `file:${dbPath}`,
|
|
2320
2402
|
encryptionKey: _encryptionKey
|
|
2321
2403
|
});
|
|
2322
2404
|
_shards.set(safeName, client);
|
|
2405
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2323
2406
|
return client;
|
|
2324
2407
|
}
|
|
2325
2408
|
function shardExists(projectName) {
|
|
2326
2409
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2327
|
-
return
|
|
2410
|
+
return existsSync6(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
2328
2411
|
}
|
|
2329
2412
|
function listShards() {
|
|
2330
|
-
if (!
|
|
2413
|
+
if (!existsSync6(SHARDS_DIR)) return [];
|
|
2331
2414
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2332
2415
|
}
|
|
2333
2416
|
async function ensureShardSchema(client) {
|
|
@@ -2379,6 +2462,8 @@ async function ensureShardSchema(client) {
|
|
|
2379
2462
|
for (const col of [
|
|
2380
2463
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2381
2464
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2465
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2466
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2382
2467
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2383
2468
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2384
2469
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2516,21 +2601,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2516
2601
|
await ensureShardSchema(client);
|
|
2517
2602
|
return client;
|
|
2518
2603
|
}
|
|
2604
|
+
function evictLRU() {
|
|
2605
|
+
let oldest = null;
|
|
2606
|
+
let oldestTime = Infinity;
|
|
2607
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2608
|
+
if (time < oldestTime) {
|
|
2609
|
+
oldestTime = time;
|
|
2610
|
+
oldest = name;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
if (oldest) {
|
|
2614
|
+
const client = _shards.get(oldest);
|
|
2615
|
+
if (client) {
|
|
2616
|
+
client.close();
|
|
2617
|
+
}
|
|
2618
|
+
_shards.delete(oldest);
|
|
2619
|
+
_shardLastAccess.delete(oldest);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
function evictIdleShards() {
|
|
2623
|
+
const now = Date.now();
|
|
2624
|
+
const toEvict = [];
|
|
2625
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2626
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2627
|
+
toEvict.push(name);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
for (const name of toEvict) {
|
|
2631
|
+
const client = _shards.get(name);
|
|
2632
|
+
if (client) {
|
|
2633
|
+
client.close();
|
|
2634
|
+
}
|
|
2635
|
+
_shards.delete(name);
|
|
2636
|
+
_shardLastAccess.delete(name);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
function getOpenShardCount() {
|
|
2640
|
+
return _shards.size;
|
|
2641
|
+
}
|
|
2519
2642
|
function disposeShards() {
|
|
2643
|
+
if (_evictionTimer) {
|
|
2644
|
+
clearInterval(_evictionTimer);
|
|
2645
|
+
_evictionTimer = null;
|
|
2646
|
+
}
|
|
2520
2647
|
for (const [, client] of _shards) {
|
|
2521
2648
|
client.close();
|
|
2522
2649
|
}
|
|
2523
2650
|
_shards.clear();
|
|
2651
|
+
_shardLastAccess.clear();
|
|
2524
2652
|
_shardingEnabled = false;
|
|
2525
2653
|
_encryptionKey = null;
|
|
2526
2654
|
}
|
|
2527
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2655
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2528
2656
|
var init_shard_manager = __esm({
|
|
2529
2657
|
"src/lib/shard-manager.ts"() {
|
|
2530
2658
|
"use strict";
|
|
2531
2659
|
init_config();
|
|
2532
2660
|
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
2661
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2662
|
+
MAX_OPEN_SHARDS = 10;
|
|
2663
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2533
2664
|
_shards = /* @__PURE__ */ new Map();
|
|
2665
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2666
|
+
_evictionTimer = null;
|
|
2534
2667
|
_encryptionKey = null;
|
|
2535
2668
|
_shardingEnabled = false;
|
|
2536
2669
|
}
|
|
@@ -2723,64 +2856,12 @@ ${p.content}`).join("\n\n");
|
|
|
2723
2856
|
}
|
|
2724
2857
|
});
|
|
2725
2858
|
|
|
2726
|
-
// src/lib/
|
|
2727
|
-
import
|
|
2859
|
+
// src/lib/session-registry.ts
|
|
2860
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
2728
2861
|
import path7 from "path";
|
|
2729
2862
|
import os5 from "os";
|
|
2730
|
-
import {
|
|
2731
|
-
readFileSync as readFileSync4,
|
|
2732
|
-
readdirSync as readdirSync2,
|
|
2733
|
-
unlinkSync as unlinkSync2,
|
|
2734
|
-
existsSync as existsSync6,
|
|
2735
|
-
rmdirSync
|
|
2736
|
-
} from "fs";
|
|
2737
|
-
async function writeNotification(notification) {
|
|
2738
|
-
try {
|
|
2739
|
-
const client = getClient();
|
|
2740
|
-
const id = crypto.randomUUID();
|
|
2741
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2742
|
-
await client.execute({
|
|
2743
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2744
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2745
|
-
args: [
|
|
2746
|
-
id,
|
|
2747
|
-
notification.agentId,
|
|
2748
|
-
notification.agentRole,
|
|
2749
|
-
notification.event,
|
|
2750
|
-
notification.project,
|
|
2751
|
-
notification.summary,
|
|
2752
|
-
notification.taskFile ?? null,
|
|
2753
|
-
now
|
|
2754
|
-
]
|
|
2755
|
-
});
|
|
2756
|
-
} catch (err) {
|
|
2757
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
2758
|
-
`);
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
2762
|
-
try {
|
|
2763
|
-
const client = getClient();
|
|
2764
|
-
await client.execute({
|
|
2765
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
2766
|
-
args: [taskFile]
|
|
2767
|
-
});
|
|
2768
|
-
} catch {
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
var init_notifications = __esm({
|
|
2772
|
-
"src/lib/notifications.ts"() {
|
|
2773
|
-
"use strict";
|
|
2774
|
-
init_database();
|
|
2775
|
-
}
|
|
2776
|
-
});
|
|
2777
|
-
|
|
2778
|
-
// src/lib/session-registry.ts
|
|
2779
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
2780
|
-
import path8 from "path";
|
|
2781
|
-
import os6 from "os";
|
|
2782
2863
|
function registerSession(entry) {
|
|
2783
|
-
const dir =
|
|
2864
|
+
const dir = path7.dirname(REGISTRY_PATH);
|
|
2784
2865
|
if (!existsSync7(dir)) {
|
|
2785
2866
|
mkdirSync3(dir, { recursive: true });
|
|
2786
2867
|
}
|
|
@@ -2795,7 +2876,7 @@ function registerSession(entry) {
|
|
|
2795
2876
|
}
|
|
2796
2877
|
function listSessions() {
|
|
2797
2878
|
try {
|
|
2798
|
-
const raw =
|
|
2879
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
2799
2880
|
return JSON.parse(raw);
|
|
2800
2881
|
} catch {
|
|
2801
2882
|
return [];
|
|
@@ -2805,7 +2886,7 @@ var REGISTRY_PATH;
|
|
|
2805
2886
|
var init_session_registry = __esm({
|
|
2806
2887
|
"src/lib/session-registry.ts"() {
|
|
2807
2888
|
"use strict";
|
|
2808
|
-
REGISTRY_PATH =
|
|
2889
|
+
REGISTRY_PATH = path7.join(os5.homedir(), ".exe-os", "session-registry.json");
|
|
2809
2890
|
}
|
|
2810
2891
|
});
|
|
2811
2892
|
|
|
@@ -3066,17 +3147,17 @@ __export(intercom_queue_exports, {
|
|
|
3066
3147
|
queueIntercom: () => queueIntercom,
|
|
3067
3148
|
readQueue: () => readQueue
|
|
3068
3149
|
});
|
|
3069
|
-
import { readFileSync as
|
|
3070
|
-
import
|
|
3071
|
-
import
|
|
3150
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
3151
|
+
import path8 from "path";
|
|
3152
|
+
import os6 from "os";
|
|
3072
3153
|
function ensureDir() {
|
|
3073
|
-
const dir =
|
|
3154
|
+
const dir = path8.dirname(QUEUE_PATH);
|
|
3074
3155
|
if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
|
|
3075
3156
|
}
|
|
3076
3157
|
function readQueue() {
|
|
3077
3158
|
try {
|
|
3078
3159
|
if (!existsSync8(QUEUE_PATH)) return [];
|
|
3079
|
-
return JSON.parse(
|
|
3160
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
3080
3161
|
} catch {
|
|
3081
3162
|
return [];
|
|
3082
3163
|
}
|
|
@@ -3176,26 +3257,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
3176
3257
|
var init_intercom_queue = __esm({
|
|
3177
3258
|
"src/lib/intercom-queue.ts"() {
|
|
3178
3259
|
"use strict";
|
|
3179
|
-
QUEUE_PATH =
|
|
3260
|
+
QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
3180
3261
|
MAX_RETRIES2 = 5;
|
|
3181
3262
|
TTL_MS = 60 * 60 * 1e3;
|
|
3182
|
-
INTERCOM_LOG =
|
|
3263
|
+
INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
3183
3264
|
}
|
|
3184
3265
|
});
|
|
3185
3266
|
|
|
3186
3267
|
// src/lib/license.ts
|
|
3187
|
-
import { readFileSync as
|
|
3268
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
3188
3269
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3189
|
-
import
|
|
3270
|
+
import { createRequire as createRequire2 } from "module";
|
|
3271
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3272
|
+
import os7 from "os";
|
|
3273
|
+
import path9 from "path";
|
|
3190
3274
|
import { jwtVerify, importSPKI } from "jose";
|
|
3191
3275
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
3192
3276
|
var init_license = __esm({
|
|
3193
3277
|
"src/lib/license.ts"() {
|
|
3194
3278
|
"use strict";
|
|
3195
3279
|
init_config();
|
|
3196
|
-
LICENSE_PATH =
|
|
3197
|
-
CACHE_PATH =
|
|
3198
|
-
DEVICE_ID_PATH =
|
|
3280
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3281
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3282
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
3199
3283
|
PLAN_LIMITS = {
|
|
3200
3284
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3201
3285
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3207,12 +3291,12 @@ var init_license = __esm({
|
|
|
3207
3291
|
});
|
|
3208
3292
|
|
|
3209
3293
|
// src/lib/plan-limits.ts
|
|
3210
|
-
import { readFileSync as
|
|
3211
|
-
import
|
|
3294
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
3295
|
+
import path10 from "path";
|
|
3212
3296
|
function getLicenseSync() {
|
|
3213
3297
|
try {
|
|
3214
3298
|
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3215
|
-
const raw = JSON.parse(
|
|
3299
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
3216
3300
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3217
3301
|
const parts = raw.token.split(".");
|
|
3218
3302
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -3251,7 +3335,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3251
3335
|
let count = 0;
|
|
3252
3336
|
try {
|
|
3253
3337
|
if (existsSync10(filePath)) {
|
|
3254
|
-
const raw =
|
|
3338
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
3255
3339
|
const employees = JSON.parse(raw);
|
|
3256
3340
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
3257
3341
|
}
|
|
@@ -3280,12 +3364,12 @@ var init_plan_limits = __esm({
|
|
|
3280
3364
|
this.name = "PlanLimitError";
|
|
3281
3365
|
}
|
|
3282
3366
|
};
|
|
3283
|
-
CACHE_PATH2 =
|
|
3367
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3284
3368
|
}
|
|
3285
3369
|
});
|
|
3286
3370
|
|
|
3287
3371
|
// src/lib/session-kill-telemetry.ts
|
|
3288
|
-
import
|
|
3372
|
+
import crypto from "crypto";
|
|
3289
3373
|
async function recordSessionKill(input) {
|
|
3290
3374
|
try {
|
|
3291
3375
|
const client = getClient();
|
|
@@ -3295,7 +3379,7 @@ async function recordSessionKill(input) {
|
|
|
3295
3379
|
ticks_idle, estimated_tokens_saved)
|
|
3296
3380
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3297
3381
|
args: [
|
|
3298
|
-
|
|
3382
|
+
crypto.randomUUID(),
|
|
3299
3383
|
input.sessionName,
|
|
3300
3384
|
input.agentId,
|
|
3301
3385
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3618,6 +3702,7 @@ __export(tmux_routing_exports, {
|
|
|
3618
3702
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
3619
3703
|
isExeSession: () => isExeSession,
|
|
3620
3704
|
isSessionBusy: () => isSessionBusy,
|
|
3705
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
3621
3706
|
notifyParentExe: () => notifyParentExe,
|
|
3622
3707
|
parseParentExe: () => parseParentExe,
|
|
3623
3708
|
registerParentExe: () => registerParentExe,
|
|
@@ -3628,13 +3713,13 @@ __export(tmux_routing_exports, {
|
|
|
3628
3713
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3629
3714
|
});
|
|
3630
3715
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
3631
|
-
import { readFileSync as
|
|
3632
|
-
import
|
|
3716
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync, readdirSync as readdirSync2 } from "fs";
|
|
3717
|
+
import path11 from "path";
|
|
3633
3718
|
import os8 from "os";
|
|
3634
3719
|
import { fileURLToPath } from "url";
|
|
3635
|
-
import { unlinkSync as
|
|
3720
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
3636
3721
|
function spawnLockPath(sessionName) {
|
|
3637
|
-
return
|
|
3722
|
+
return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3638
3723
|
}
|
|
3639
3724
|
function isProcessAlive(pid) {
|
|
3640
3725
|
try {
|
|
@@ -3651,7 +3736,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
3651
3736
|
const lockFile = spawnLockPath(sessionName);
|
|
3652
3737
|
if (existsSync11(lockFile)) {
|
|
3653
3738
|
try {
|
|
3654
|
-
const lock = JSON.parse(
|
|
3739
|
+
const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
|
|
3655
3740
|
const age = Date.now() - lock.timestamp;
|
|
3656
3741
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3657
3742
|
return false;
|
|
@@ -3664,15 +3749,15 @@ function acquireSpawnLock(sessionName) {
|
|
|
3664
3749
|
}
|
|
3665
3750
|
function releaseSpawnLock(sessionName) {
|
|
3666
3751
|
try {
|
|
3667
|
-
|
|
3752
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
3668
3753
|
} catch {
|
|
3669
3754
|
}
|
|
3670
3755
|
}
|
|
3671
3756
|
function resolveBehaviorsExporterScript() {
|
|
3672
3757
|
try {
|
|
3673
3758
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3674
|
-
const scriptPath =
|
|
3675
|
-
|
|
3759
|
+
const scriptPath = path11.join(
|
|
3760
|
+
path11.dirname(thisFile),
|
|
3676
3761
|
"..",
|
|
3677
3762
|
"bin",
|
|
3678
3763
|
"exe-export-behaviors.js"
|
|
@@ -3747,7 +3832,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3747
3832
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3748
3833
|
}
|
|
3749
3834
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3750
|
-
const filePath =
|
|
3835
|
+
const filePath = path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3751
3836
|
writeFileSync6(filePath, JSON.stringify({
|
|
3752
3837
|
parentExe: rootExe,
|
|
3753
3838
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3756,7 +3841,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3756
3841
|
}
|
|
3757
3842
|
function getParentExe(sessionKey) {
|
|
3758
3843
|
try {
|
|
3759
|
-
const data = JSON.parse(
|
|
3844
|
+
const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3760
3845
|
return data.parentExe || null;
|
|
3761
3846
|
} catch {
|
|
3762
3847
|
return null;
|
|
@@ -3764,8 +3849,8 @@ function getParentExe(sessionKey) {
|
|
|
3764
3849
|
}
|
|
3765
3850
|
function getDispatchedBy(sessionKey) {
|
|
3766
3851
|
try {
|
|
3767
|
-
const data = JSON.parse(
|
|
3768
|
-
|
|
3852
|
+
const data = JSON.parse(readFileSync8(
|
|
3853
|
+
path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3769
3854
|
"utf8"
|
|
3770
3855
|
));
|
|
3771
3856
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3836,7 +3921,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3836
3921
|
function readDebounceState() {
|
|
3837
3922
|
try {
|
|
3838
3923
|
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
3839
|
-
const raw = JSON.parse(
|
|
3924
|
+
const raw = JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
|
|
3840
3925
|
const state = {};
|
|
3841
3926
|
for (const [key, val] of Object.entries(raw)) {
|
|
3842
3927
|
if (typeof val === "number") {
|
|
@@ -3951,7 +4036,7 @@ function sendIntercom(targetSession) {
|
|
|
3951
4036
|
try {
|
|
3952
4037
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
3953
4038
|
const agent = baseAgentName(rawAgent);
|
|
3954
|
-
const markerPath =
|
|
4039
|
+
const markerPath = path11.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
3955
4040
|
if (existsSync11(markerPath)) {
|
|
3956
4041
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
3957
4042
|
return "debounced";
|
|
@@ -3961,9 +4046,9 @@ function sendIntercom(targetSession) {
|
|
|
3961
4046
|
try {
|
|
3962
4047
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
3963
4048
|
const agent = baseAgentName(rawAgent);
|
|
3964
|
-
const taskDir =
|
|
4049
|
+
const taskDir = path11.join(process.cwd(), "exe", agent);
|
|
3965
4050
|
if (existsSync11(taskDir)) {
|
|
3966
|
-
const files =
|
|
4051
|
+
const files = readdirSync2(taskDir).filter(
|
|
3967
4052
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
3968
4053
|
);
|
|
3969
4054
|
if (files.length === 0) {
|
|
@@ -4022,6 +4107,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4022
4107
|
}
|
|
4023
4108
|
return true;
|
|
4024
4109
|
}
|
|
4110
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
4111
|
+
const transport = getTransport();
|
|
4112
|
+
try {
|
|
4113
|
+
const sessions = transport.listSessions();
|
|
4114
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
4115
|
+
execSync4(
|
|
4116
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
4117
|
+
{ timeout: 3e3 }
|
|
4118
|
+
);
|
|
4119
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
4120
|
+
return true;
|
|
4121
|
+
} catch {
|
|
4122
|
+
return false;
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4025
4125
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4026
4126
|
if (isCoordinatorName(employeeName)) {
|
|
4027
4127
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4095,8 +4195,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4095
4195
|
const transport = getTransport();
|
|
4096
4196
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4097
4197
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4098
|
-
const logDir =
|
|
4099
|
-
const logFile =
|
|
4198
|
+
const logDir = path11.join(os8.homedir(), ".exe-os", "session-logs");
|
|
4199
|
+
const logFile = path11.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4100
4200
|
if (!existsSync11(logDir)) {
|
|
4101
4201
|
mkdirSync6(logDir, { recursive: true });
|
|
4102
4202
|
}
|
|
@@ -4104,17 +4204,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4104
4204
|
let cleanupSuffix = "";
|
|
4105
4205
|
try {
|
|
4106
4206
|
const thisFile = fileURLToPath(import.meta.url);
|
|
4107
|
-
const cleanupScript =
|
|
4207
|
+
const cleanupScript = path11.join(path11.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4108
4208
|
if (existsSync11(cleanupScript)) {
|
|
4109
4209
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4110
4210
|
}
|
|
4111
4211
|
} catch {
|
|
4112
4212
|
}
|
|
4113
4213
|
try {
|
|
4114
|
-
const claudeJsonPath =
|
|
4214
|
+
const claudeJsonPath = path11.join(os8.homedir(), ".claude.json");
|
|
4115
4215
|
let claudeJson = {};
|
|
4116
4216
|
try {
|
|
4117
|
-
claudeJson = JSON.parse(
|
|
4217
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
4118
4218
|
} catch {
|
|
4119
4219
|
}
|
|
4120
4220
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4126,13 +4226,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4126
4226
|
} catch {
|
|
4127
4227
|
}
|
|
4128
4228
|
try {
|
|
4129
|
-
const settingsDir =
|
|
4229
|
+
const settingsDir = path11.join(os8.homedir(), ".claude", "projects");
|
|
4130
4230
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4131
|
-
const projSettingsDir =
|
|
4132
|
-
const settingsPath =
|
|
4231
|
+
const projSettingsDir = path11.join(settingsDir, normalizedKey);
|
|
4232
|
+
const settingsPath = path11.join(projSettingsDir, "settings.json");
|
|
4133
4233
|
let settings = {};
|
|
4134
4234
|
try {
|
|
4135
|
-
settings = JSON.parse(
|
|
4235
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
4136
4236
|
} catch {
|
|
4137
4237
|
}
|
|
4138
4238
|
const perms = settings.permissions ?? {};
|
|
@@ -4176,7 +4276,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4176
4276
|
let behaviorsFlag = "";
|
|
4177
4277
|
let legacyFallbackWarned = false;
|
|
4178
4278
|
if (!useExeAgent && !useBinSymlink) {
|
|
4179
|
-
const identityPath =
|
|
4279
|
+
const identityPath = path11.join(
|
|
4180
4280
|
os8.homedir(),
|
|
4181
4281
|
".exe-os",
|
|
4182
4282
|
"identity",
|
|
@@ -4192,7 +4292,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4192
4292
|
}
|
|
4193
4293
|
const behaviorsFile = exportBehaviorsSync(
|
|
4194
4294
|
employeeName,
|
|
4195
|
-
|
|
4295
|
+
path11.basename(spawnCwd),
|
|
4196
4296
|
sessionName
|
|
4197
4297
|
);
|
|
4198
4298
|
if (behaviorsFile) {
|
|
@@ -4207,9 +4307,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4207
4307
|
}
|
|
4208
4308
|
let sessionContextFlag = "";
|
|
4209
4309
|
try {
|
|
4210
|
-
const ctxDir =
|
|
4310
|
+
const ctxDir = path11.join(os8.homedir(), ".exe-os", "session-cache");
|
|
4211
4311
|
mkdirSync6(ctxDir, { recursive: true });
|
|
4212
|
-
const ctxFile =
|
|
4312
|
+
const ctxFile = path11.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4213
4313
|
const ctxContent = [
|
|
4214
4314
|
`## Session Context`,
|
|
4215
4315
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4293,7 +4393,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4293
4393
|
transport.pipeLog(sessionName, logFile);
|
|
4294
4394
|
try {
|
|
4295
4395
|
const mySession = getMySession();
|
|
4296
|
-
const dispatchInfo =
|
|
4396
|
+
const dispatchInfo = path11.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4297
4397
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
4298
4398
|
dispatchedBy: mySession,
|
|
4299
4399
|
rootExe: exeSession,
|
|
@@ -4368,15 +4468,15 @@ var init_tmux_routing = __esm({
|
|
|
4368
4468
|
init_intercom_queue();
|
|
4369
4469
|
init_plan_limits();
|
|
4370
4470
|
init_employees();
|
|
4371
|
-
SPAWN_LOCK_DIR =
|
|
4372
|
-
SESSION_CACHE =
|
|
4471
|
+
SPAWN_LOCK_DIR = path11.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
4472
|
+
SESSION_CACHE = path11.join(os8.homedir(), ".exe-os", "session-cache");
|
|
4373
4473
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4374
4474
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4375
4475
|
VERIFY_PANE_LINES = 200;
|
|
4376
4476
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4377
4477
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
4378
|
-
INTERCOM_LOG2 =
|
|
4379
|
-
DEBOUNCE_FILE =
|
|
4478
|
+
INTERCOM_LOG2 = path11.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
4479
|
+
DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4380
4480
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4381
4481
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
4382
4482
|
}
|
|
@@ -4399,6 +4499,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
4399
4499
|
args: [scope]
|
|
4400
4500
|
};
|
|
4401
4501
|
}
|
|
4502
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
4503
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
4504
|
+
if (!scope) return { sql: "", args: [] };
|
|
4505
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
4506
|
+
return {
|
|
4507
|
+
sql: ` AND ${col} = ?`,
|
|
4508
|
+
args: [scope]
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
4402
4511
|
var init_task_scope = __esm({
|
|
4403
4512
|
"src/lib/task-scope.ts"() {
|
|
4404
4513
|
"use strict";
|
|
@@ -4406,13 +4515,70 @@ var init_task_scope = __esm({
|
|
|
4406
4515
|
}
|
|
4407
4516
|
});
|
|
4408
4517
|
|
|
4518
|
+
// src/lib/notifications.ts
|
|
4519
|
+
import crypto2 from "crypto";
|
|
4520
|
+
import path12 from "path";
|
|
4521
|
+
import os9 from "os";
|
|
4522
|
+
import {
|
|
4523
|
+
readFileSync as readFileSync9,
|
|
4524
|
+
readdirSync as readdirSync3,
|
|
4525
|
+
unlinkSync as unlinkSync3,
|
|
4526
|
+
existsSync as existsSync12,
|
|
4527
|
+
rmdirSync
|
|
4528
|
+
} from "fs";
|
|
4529
|
+
async function writeNotification(notification) {
|
|
4530
|
+
try {
|
|
4531
|
+
const client = getClient();
|
|
4532
|
+
const id = crypto2.randomUUID();
|
|
4533
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4534
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
4535
|
+
await client.execute({
|
|
4536
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4537
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4538
|
+
args: [
|
|
4539
|
+
id,
|
|
4540
|
+
notification.agentId,
|
|
4541
|
+
notification.agentRole,
|
|
4542
|
+
notification.event,
|
|
4543
|
+
notification.project,
|
|
4544
|
+
notification.summary,
|
|
4545
|
+
notification.taskFile ?? null,
|
|
4546
|
+
sessionScope,
|
|
4547
|
+
now
|
|
4548
|
+
]
|
|
4549
|
+
});
|
|
4550
|
+
} catch (err) {
|
|
4551
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
4552
|
+
`);
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4556
|
+
try {
|
|
4557
|
+
const client = getClient();
|
|
4558
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4559
|
+
await client.execute({
|
|
4560
|
+
sql: `UPDATE notifications SET read = 1
|
|
4561
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
4562
|
+
args: [taskFile, ...scope.args]
|
|
4563
|
+
});
|
|
4564
|
+
} catch {
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
var init_notifications = __esm({
|
|
4568
|
+
"src/lib/notifications.ts"() {
|
|
4569
|
+
"use strict";
|
|
4570
|
+
init_database();
|
|
4571
|
+
init_task_scope();
|
|
4572
|
+
}
|
|
4573
|
+
});
|
|
4574
|
+
|
|
4409
4575
|
// src/lib/tasks-crud.ts
|
|
4410
4576
|
import crypto3 from "crypto";
|
|
4411
4577
|
import path13 from "path";
|
|
4412
|
-
import
|
|
4578
|
+
import os10 from "os";
|
|
4413
4579
|
import { execSync as execSync5 } from "child_process";
|
|
4414
4580
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
4415
|
-
import { existsSync as
|
|
4581
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
4416
4582
|
async function writeCheckpoint(input) {
|
|
4417
4583
|
const client = getClient();
|
|
4418
4584
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -4624,13 +4790,19 @@ ${laneWarning}` : laneWarning;
|
|
|
4624
4790
|
});
|
|
4625
4791
|
if (input.baseDir) {
|
|
4626
4792
|
try {
|
|
4627
|
-
const EXE_OS_DIR = path13.join(
|
|
4793
|
+
const EXE_OS_DIR = path13.join(os10.homedir(), ".exe-os");
|
|
4628
4794
|
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
4629
4795
|
const mdDir = path13.dirname(mdPath);
|
|
4630
|
-
if (!
|
|
4796
|
+
if (!existsSync13(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
4631
4797
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
4632
4798
|
const mdContent = `# ${input.title}
|
|
4633
4799
|
|
|
4800
|
+
## MANDATORY: When done
|
|
4801
|
+
|
|
4802
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4803
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4804
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4805
|
+
|
|
4634
4806
|
**ID:** ${id}
|
|
4635
4807
|
**Status:** ${initialStatus}
|
|
4636
4808
|
**Priority:** ${input.priority}
|
|
@@ -4644,12 +4816,6 @@ ${laneWarning}` : laneWarning;
|
|
|
4644
4816
|
## Context
|
|
4645
4817
|
|
|
4646
4818
|
${input.context}
|
|
4647
|
-
|
|
4648
|
-
## MANDATORY: When done
|
|
4649
|
-
|
|
4650
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
4651
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4652
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4653
4819
|
`;
|
|
4654
4820
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
4655
4821
|
} catch (err) {
|
|
@@ -4898,7 +5064,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4898
5064
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4899
5065
|
} catch {
|
|
4900
5066
|
}
|
|
4901
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5067
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4902
5068
|
try {
|
|
4903
5069
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4904
5070
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -4929,7 +5095,7 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
4929
5095
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
4930
5096
|
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
4931
5097
|
try {
|
|
4932
|
-
if (
|
|
5098
|
+
if (existsSync13(archPath)) return;
|
|
4933
5099
|
const template = [
|
|
4934
5100
|
`# ${projectName} \u2014 System Architecture`,
|
|
4935
5101
|
"",
|
|
@@ -4964,7 +5130,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
4964
5130
|
async function ensureGitignoreExe(baseDir) {
|
|
4965
5131
|
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
4966
5132
|
try {
|
|
4967
|
-
if (
|
|
5133
|
+
if (existsSync13(gitignorePath)) {
|
|
4968
5134
|
const content = readFileSync10(gitignorePath, "utf-8");
|
|
4969
5135
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4970
5136
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -4997,57 +5163,41 @@ var init_tasks_crud = __esm({
|
|
|
4997
5163
|
|
|
4998
5164
|
// src/lib/tasks-review.ts
|
|
4999
5165
|
import path14 from "path";
|
|
5000
|
-
import { existsSync as
|
|
5166
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
5001
5167
|
async function countPendingReviews(sessionScope) {
|
|
5002
5168
|
const client = getClient();
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
args: [sessionScope]
|
|
5007
|
-
});
|
|
5008
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
5009
|
-
}
|
|
5169
|
+
const scope = strictSessionScopeFilter(
|
|
5170
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5171
|
+
);
|
|
5010
5172
|
const result = await client.execute({
|
|
5011
|
-
sql:
|
|
5012
|
-
|
|
5173
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5174
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
5175
|
+
args: [...scope.args]
|
|
5013
5176
|
});
|
|
5014
5177
|
return Number(result.rows[0]?.cnt) || 0;
|
|
5015
5178
|
}
|
|
5016
5179
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
5017
5180
|
const client = getClient();
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
5022
|
-
AND session_scope = ?`,
|
|
5023
|
-
args: [sinceIso, sessionScope]
|
|
5024
|
-
});
|
|
5025
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
5026
|
-
}
|
|
5181
|
+
const scope = strictSessionScopeFilter(
|
|
5182
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5183
|
+
);
|
|
5027
5184
|
const result = await client.execute({
|
|
5028
5185
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5029
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
5030
|
-
args: [sinceIso]
|
|
5186
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
5187
|
+
args: [sinceIso, ...scope.args]
|
|
5031
5188
|
});
|
|
5032
5189
|
return Number(result.rows[0]?.cnt) || 0;
|
|
5033
5190
|
}
|
|
5034
5191
|
async function listPendingReviews(limit, sessionScope) {
|
|
5035
5192
|
const client = getClient();
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
WHERE status = 'needs_review'
|
|
5040
|
-
AND session_scope = ?
|
|
5041
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
5042
|
-
args: [sessionScope, limit]
|
|
5043
|
-
});
|
|
5044
|
-
return result2.rows;
|
|
5045
|
-
}
|
|
5193
|
+
const scope = strictSessionScopeFilter(
|
|
5194
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5195
|
+
);
|
|
5046
5196
|
const result = await client.execute({
|
|
5047
5197
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
5048
|
-
WHERE status = 'needs_review'
|
|
5198
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
5049
5199
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
5050
|
-
args: [limit]
|
|
5200
|
+
args: [...scope.args, limit]
|
|
5051
5201
|
});
|
|
5052
5202
|
return result.rows;
|
|
5053
5203
|
}
|
|
@@ -5059,7 +5209,7 @@ async function cleanupOrphanedReviews() {
|
|
|
5059
5209
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
5060
5210
|
AND assigned_by = 'system'
|
|
5061
5211
|
AND title LIKE 'Review:%'
|
|
5062
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
5212
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
5063
5213
|
args: [now]
|
|
5064
5214
|
});
|
|
5065
5215
|
const r1b = await client.execute({
|
|
@@ -5179,7 +5329,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5179
5329
|
}
|
|
5180
5330
|
try {
|
|
5181
5331
|
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
5182
|
-
if (
|
|
5332
|
+
if (existsSync14(cacheDir)) {
|
|
5183
5333
|
for (const f of readdirSync4(cacheDir)) {
|
|
5184
5334
|
if (f.startsWith("review-notified-")) {
|
|
5185
5335
|
unlinkSync4(path14.join(cacheDir, f));
|
|
@@ -5199,6 +5349,7 @@ var init_tasks_review = __esm({
|
|
|
5199
5349
|
init_tmux_routing();
|
|
5200
5350
|
init_session_key();
|
|
5201
5351
|
init_state_bus();
|
|
5352
|
+
init_task_scope();
|
|
5202
5353
|
}
|
|
5203
5354
|
});
|
|
5204
5355
|
|
|
@@ -5255,7 +5406,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
5255
5406
|
const scScope = sessionScopeFilter();
|
|
5256
5407
|
const remaining = await client.execute({
|
|
5257
5408
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5258
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
5409
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
5259
5410
|
args: [parentTaskId, ...scScope.args]
|
|
5260
5411
|
});
|
|
5261
5412
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -5813,7 +5964,7 @@ async function updateTask(input) {
|
|
|
5813
5964
|
if (input.status === "in_progress") {
|
|
5814
5965
|
mkdirSync7(cacheDir, { recursive: true });
|
|
5815
5966
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5816
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5967
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5817
5968
|
try {
|
|
5818
5969
|
unlinkSync5(cachePath);
|
|
5819
5970
|
} catch {
|
|
@@ -5821,10 +5972,10 @@ async function updateTask(input) {
|
|
|
5821
5972
|
}
|
|
5822
5973
|
} catch {
|
|
5823
5974
|
}
|
|
5824
|
-
if (input.status === "done") {
|
|
5975
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5825
5976
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5826
5977
|
}
|
|
5827
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5978
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5828
5979
|
try {
|
|
5829
5980
|
const client = getClient();
|
|
5830
5981
|
const taskTitle = String(row.title);
|
|
@@ -5840,7 +5991,7 @@ async function updateTask(input) {
|
|
|
5840
5991
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5841
5992
|
try {
|
|
5842
5993
|
const draftClient = getClient();
|
|
5843
|
-
if (input.status === "done") {
|
|
5994
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5844
5995
|
await draftClient.execute({
|
|
5845
5996
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5846
5997
|
args: [assignedAgent]
|
|
@@ -5857,7 +6008,7 @@ async function updateTask(input) {
|
|
|
5857
6008
|
try {
|
|
5858
6009
|
const client = getClient();
|
|
5859
6010
|
const cascaded = await client.execute({
|
|
5860
|
-
sql: `UPDATE tasks SET status = '
|
|
6011
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5861
6012
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5862
6013
|
args: [now, taskId]
|
|
5863
6014
|
});
|
|
@@ -5870,14 +6021,14 @@ async function updateTask(input) {
|
|
|
5870
6021
|
} catch {
|
|
5871
6022
|
}
|
|
5872
6023
|
}
|
|
5873
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
6024
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5874
6025
|
if (isTerminal) {
|
|
5875
6026
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5876
6027
|
if (!isCoordinator) {
|
|
5877
6028
|
notifyTaskDone();
|
|
5878
6029
|
}
|
|
5879
6030
|
await markTaskNotificationsRead(taskFile);
|
|
5880
|
-
if (input.status === "done") {
|
|
6031
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5881
6032
|
try {
|
|
5882
6033
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5883
6034
|
} catch {
|
|
@@ -5897,7 +6048,7 @@ async function updateTask(input) {
|
|
|
5897
6048
|
}
|
|
5898
6049
|
}
|
|
5899
6050
|
}
|
|
5900
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6051
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5901
6052
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5902
6053
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5903
6054
|
taskId,
|
|
@@ -5975,7 +6126,7 @@ init_database();
|
|
|
5975
6126
|
|
|
5976
6127
|
// src/lib/keychain.ts
|
|
5977
6128
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
5978
|
-
import { existsSync as
|
|
6129
|
+
import { existsSync as existsSync5 } from "fs";
|
|
5979
6130
|
import path5 from "path";
|
|
5980
6131
|
import os4 from "os";
|
|
5981
6132
|
var SERVICE = "exe-mem";
|
|
@@ -6005,7 +6156,7 @@ async function getMasterKey() {
|
|
|
6005
6156
|
}
|
|
6006
6157
|
}
|
|
6007
6158
|
const keyPath = getKeyPath();
|
|
6008
|
-
if (!
|
|
6159
|
+
if (!existsSync5(keyPath)) {
|
|
6009
6160
|
process.stderr.write(
|
|
6010
6161
|
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
6011
6162
|
`
|