@askexenow/exe-os 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
|
@@ -32,6 +32,44 @@ var init_db_retry = __esm({
|
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
// src/lib/secure-files.ts
|
|
36
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
37
|
+
import { chmod, mkdir } from "fs/promises";
|
|
38
|
+
async function ensurePrivateDir(dirPath) {
|
|
39
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
40
|
+
try {
|
|
41
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function ensurePrivateDirSync(dirPath) {
|
|
46
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
47
|
+
try {
|
|
48
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function enforcePrivateFile(filePath) {
|
|
53
|
+
try {
|
|
54
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function enforcePrivateFileSync(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
65
|
+
var init_secure_files = __esm({
|
|
66
|
+
"src/lib/secure-files.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
PRIVATE_DIR_MODE = 448;
|
|
69
|
+
PRIVATE_FILE_MODE = 384;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
35
73
|
// src/lib/config.ts
|
|
36
74
|
var config_exports = {};
|
|
37
75
|
__export(config_exports, {
|
|
@@ -48,8 +86,8 @@ __export(config_exports, {
|
|
|
48
86
|
migrateConfig: () => migrateConfig,
|
|
49
87
|
saveConfig: () => saveConfig
|
|
50
88
|
});
|
|
51
|
-
import { readFile, writeFile
|
|
52
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
89
|
+
import { readFile, writeFile } from "fs/promises";
|
|
90
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
53
91
|
import path from "path";
|
|
54
92
|
import os from "os";
|
|
55
93
|
function resolveDataDir() {
|
|
@@ -57,7 +95,7 @@ function resolveDataDir() {
|
|
|
57
95
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
58
96
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
59
97
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
60
|
-
if (!
|
|
98
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
61
99
|
try {
|
|
62
100
|
renameSync(legacyDir, newDir);
|
|
63
101
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -120,9 +158,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
120
158
|
}
|
|
121
159
|
async function loadConfig() {
|
|
122
160
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
123
|
-
await
|
|
161
|
+
await ensurePrivateDir(dir);
|
|
124
162
|
const configPath = path.join(dir, "config.json");
|
|
125
|
-
if (!
|
|
163
|
+
if (!existsSync2(configPath)) {
|
|
126
164
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
127
165
|
}
|
|
128
166
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -135,6 +173,7 @@ async function loadConfig() {
|
|
|
135
173
|
`);
|
|
136
174
|
try {
|
|
137
175
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
176
|
+
await enforcePrivateFile(configPath);
|
|
138
177
|
} catch {
|
|
139
178
|
}
|
|
140
179
|
}
|
|
@@ -153,7 +192,7 @@ async function loadConfig() {
|
|
|
153
192
|
function loadConfigSync() {
|
|
154
193
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
155
194
|
const configPath = path.join(dir, "config.json");
|
|
156
|
-
if (!
|
|
195
|
+
if (!existsSync2(configPath)) {
|
|
157
196
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
158
197
|
}
|
|
159
198
|
try {
|
|
@@ -171,12 +210,10 @@ function loadConfigSync() {
|
|
|
171
210
|
}
|
|
172
211
|
async function saveConfig(config) {
|
|
173
212
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
174
|
-
await
|
|
213
|
+
await ensurePrivateDir(dir);
|
|
175
214
|
const configPath = path.join(dir, "config.json");
|
|
176
215
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
177
|
-
|
|
178
|
-
await chmod(configPath, 384);
|
|
179
|
-
}
|
|
216
|
+
await enforcePrivateFile(configPath);
|
|
180
217
|
}
|
|
181
218
|
async function loadConfigFrom(configPath) {
|
|
182
219
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -196,6 +233,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
196
233
|
var init_config = __esm({
|
|
197
234
|
"src/lib/config.ts"() {
|
|
198
235
|
"use strict";
|
|
236
|
+
init_secure_files();
|
|
199
237
|
EXE_AI_DIR = resolveDataDir();
|
|
200
238
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
201
239
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -272,6 +310,120 @@ var init_config = __esm({
|
|
|
272
310
|
}
|
|
273
311
|
});
|
|
274
312
|
|
|
313
|
+
// src/lib/runtime-table.ts
|
|
314
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
315
|
+
var init_runtime_table = __esm({
|
|
316
|
+
"src/lib/runtime-table.ts"() {
|
|
317
|
+
"use strict";
|
|
318
|
+
RUNTIME_TABLE = {
|
|
319
|
+
codex: {
|
|
320
|
+
binary: "codex",
|
|
321
|
+
launchMode: "interactive",
|
|
322
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
323
|
+
inlineFlag: "--no-alt-screen",
|
|
324
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
325
|
+
defaultModel: "gpt-5.4"
|
|
326
|
+
},
|
|
327
|
+
opencode: {
|
|
328
|
+
binary: "opencode",
|
|
329
|
+
launchMode: "exec",
|
|
330
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
331
|
+
inlineFlag: "",
|
|
332
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
333
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
DEFAULT_RUNTIME = "claude";
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// src/lib/agent-config.ts
|
|
341
|
+
var agent_config_exports = {};
|
|
342
|
+
__export(agent_config_exports, {
|
|
343
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
344
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
345
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
346
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
347
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
348
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
349
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
350
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
351
|
+
setAgentRuntime: () => setAgentRuntime
|
|
352
|
+
});
|
|
353
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
354
|
+
import path2 from "path";
|
|
355
|
+
function loadAgentConfig() {
|
|
356
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
357
|
+
try {
|
|
358
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
359
|
+
} catch {
|
|
360
|
+
return {};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function saveAgentConfig(config) {
|
|
364
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
365
|
+
ensurePrivateDirSync(dir);
|
|
366
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
367
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
368
|
+
}
|
|
369
|
+
function getAgentRuntime(agentId) {
|
|
370
|
+
const config = loadAgentConfig();
|
|
371
|
+
const entry = config[agentId];
|
|
372
|
+
if (entry) return entry;
|
|
373
|
+
const orgDefault = config["default"];
|
|
374
|
+
if (orgDefault) return orgDefault;
|
|
375
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
376
|
+
}
|
|
377
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
378
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
379
|
+
if (!knownModels) {
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (!knownModels.includes(model)) {
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const config = loadAgentConfig();
|
|
392
|
+
config[agentId] = { runtime, model };
|
|
393
|
+
saveAgentConfig(config);
|
|
394
|
+
return { ok: true };
|
|
395
|
+
}
|
|
396
|
+
function clearAgentRuntime(agentId) {
|
|
397
|
+
const config = loadAgentConfig();
|
|
398
|
+
delete config[agentId];
|
|
399
|
+
saveAgentConfig(config);
|
|
400
|
+
}
|
|
401
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
402
|
+
var init_agent_config = __esm({
|
|
403
|
+
"src/lib/agent-config.ts"() {
|
|
404
|
+
"use strict";
|
|
405
|
+
init_config();
|
|
406
|
+
init_runtime_table();
|
|
407
|
+
init_secure_files();
|
|
408
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
409
|
+
KNOWN_RUNTIMES = {
|
|
410
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
411
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
412
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
413
|
+
};
|
|
414
|
+
RUNTIME_LABELS = {
|
|
415
|
+
claude: "Claude Code (Anthropic)",
|
|
416
|
+
codex: "Codex (OpenAI)",
|
|
417
|
+
opencode: "OpenCode (open source)"
|
|
418
|
+
};
|
|
419
|
+
DEFAULT_MODELS = {
|
|
420
|
+
claude: "claude-opus-4",
|
|
421
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
422
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
275
427
|
// src/lib/employees.ts
|
|
276
428
|
var employees_exports = {};
|
|
277
429
|
__export(employees_exports, {
|
|
@@ -287,6 +439,7 @@ __export(employees_exports, {
|
|
|
287
439
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
288
440
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
289
441
|
hasRole: () => hasRole,
|
|
442
|
+
hireEmployee: () => hireEmployee,
|
|
290
443
|
isCoordinatorName: () => isCoordinatorName,
|
|
291
444
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
292
445
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -299,9 +452,9 @@ __export(employees_exports, {
|
|
|
299
452
|
validateEmployeeName: () => validateEmployeeName
|
|
300
453
|
});
|
|
301
454
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
302
|
-
import { existsSync as
|
|
455
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
303
456
|
import { execSync } from "child_process";
|
|
304
|
-
import
|
|
457
|
+
import path3 from "path";
|
|
305
458
|
import os2 from "os";
|
|
306
459
|
function normalizeRole(role) {
|
|
307
460
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -338,7 +491,7 @@ function validateEmployeeName(name) {
|
|
|
338
491
|
return { valid: true };
|
|
339
492
|
}
|
|
340
493
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
341
|
-
if (!
|
|
494
|
+
if (!existsSync4(employeesPath)) {
|
|
342
495
|
return [];
|
|
343
496
|
}
|
|
344
497
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -349,13 +502,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
349
502
|
}
|
|
350
503
|
}
|
|
351
504
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
352
|
-
await mkdir2(
|
|
505
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
353
506
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
354
507
|
}
|
|
355
508
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
356
|
-
if (!
|
|
509
|
+
if (!existsSync4(employeesPath)) return [];
|
|
357
510
|
try {
|
|
358
|
-
return JSON.parse(
|
|
511
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
359
512
|
} catch {
|
|
360
513
|
return [];
|
|
361
514
|
}
|
|
@@ -397,6 +550,52 @@ function addEmployee(employees, employee) {
|
|
|
397
550
|
}
|
|
398
551
|
return [...employees, normalized];
|
|
399
552
|
}
|
|
553
|
+
function appendToCoordinatorTeam(employee) {
|
|
554
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
555
|
+
if (!coordinator) return;
|
|
556
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
557
|
+
if (!existsSync4(idPath)) return;
|
|
558
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
559
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
560
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
561
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
562
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
563
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
564
|
+
const entry = `
|
|
565
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
566
|
+
`;
|
|
567
|
+
let updated;
|
|
568
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
569
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
570
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
571
|
+
} else {
|
|
572
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
573
|
+
}
|
|
574
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
575
|
+
}
|
|
576
|
+
function capitalize(s) {
|
|
577
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
578
|
+
}
|
|
579
|
+
async function hireEmployee(employee) {
|
|
580
|
+
const employees = await loadEmployees();
|
|
581
|
+
const updated = addEmployee(employees, employee);
|
|
582
|
+
await saveEmployees(updated);
|
|
583
|
+
try {
|
|
584
|
+
appendToCoordinatorTeam(employee);
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
589
|
+
const config = loadAgentConfig2();
|
|
590
|
+
const name = employee.name.toLowerCase();
|
|
591
|
+
if (!config[name] && config["default"]) {
|
|
592
|
+
config[name] = { ...config["default"] };
|
|
593
|
+
saveAgentConfig2(config);
|
|
594
|
+
}
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
return updated;
|
|
598
|
+
}
|
|
400
599
|
async function normalizeRosterCase(rosterPath) {
|
|
401
600
|
const employees = await loadEmployees(rosterPath);
|
|
402
601
|
let changed = false;
|
|
@@ -406,14 +605,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
406
605
|
emp.name = emp.name.toLowerCase();
|
|
407
606
|
changed = true;
|
|
408
607
|
try {
|
|
409
|
-
const identityDir =
|
|
410
|
-
const oldPath =
|
|
411
|
-
const newPath =
|
|
412
|
-
if (
|
|
608
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
609
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
610
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
611
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
413
612
|
renameSync2(oldPath, newPath);
|
|
414
|
-
} else if (
|
|
415
|
-
const content =
|
|
416
|
-
|
|
613
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
614
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
615
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
417
616
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
418
617
|
unlinkSync(oldPath);
|
|
419
618
|
}
|
|
@@ -443,7 +642,7 @@ function registerBinSymlinks(name) {
|
|
|
443
642
|
errors.push("Could not find 'exe-os' in PATH");
|
|
444
643
|
return { created, skipped, errors };
|
|
445
644
|
}
|
|
446
|
-
const binDir =
|
|
645
|
+
const binDir = path3.dirname(exeBinPath);
|
|
447
646
|
let target;
|
|
448
647
|
try {
|
|
449
648
|
target = readlinkSync(exeBinPath);
|
|
@@ -453,8 +652,8 @@ function registerBinSymlinks(name) {
|
|
|
453
652
|
}
|
|
454
653
|
for (const suffix of ["", "-opencode"]) {
|
|
455
654
|
const linkName = `${name}${suffix}`;
|
|
456
|
-
const linkPath =
|
|
457
|
-
if (
|
|
655
|
+
const linkPath = path3.join(binDir, linkName);
|
|
656
|
+
if (existsSync4(linkPath)) {
|
|
458
657
|
skipped.push(linkName);
|
|
459
658
|
continue;
|
|
460
659
|
}
|
|
@@ -467,24 +666,50 @@ function registerBinSymlinks(name) {
|
|
|
467
666
|
}
|
|
468
667
|
return { created, skipped, errors };
|
|
469
668
|
}
|
|
470
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
669
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
471
670
|
var init_employees = __esm({
|
|
472
671
|
"src/lib/employees.ts"() {
|
|
473
672
|
"use strict";
|
|
474
673
|
init_config();
|
|
475
|
-
EMPLOYEES_PATH =
|
|
674
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
476
675
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
477
676
|
COORDINATOR_ROLE = "COO";
|
|
478
677
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
678
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
679
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// src/lib/database-adapter.ts
|
|
684
|
+
import os3 from "os";
|
|
685
|
+
import path4 from "path";
|
|
686
|
+
import { createRequire } from "module";
|
|
687
|
+
import { pathToFileURL } from "url";
|
|
688
|
+
var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
|
|
689
|
+
var init_database_adapter = __esm({
|
|
690
|
+
"src/lib/database-adapter.ts"() {
|
|
691
|
+
"use strict";
|
|
692
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
693
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
694
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
695
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
696
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
697
|
+
};
|
|
698
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
699
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
700
|
+
);
|
|
479
701
|
}
|
|
480
702
|
});
|
|
481
703
|
|
|
482
704
|
// src/lib/database.ts
|
|
483
705
|
import { createClient } from "@libsql/client";
|
|
484
706
|
function getClient() {
|
|
485
|
-
if (!
|
|
707
|
+
if (!_adapterClient) {
|
|
486
708
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
487
709
|
}
|
|
710
|
+
if (process.env.DATABASE_URL) {
|
|
711
|
+
return _adapterClient;
|
|
712
|
+
}
|
|
488
713
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
489
714
|
return _resilientClient;
|
|
490
715
|
}
|
|
@@ -493,132 +718,27 @@ function getClient() {
|
|
|
493
718
|
}
|
|
494
719
|
return _resilientClient;
|
|
495
720
|
}
|
|
496
|
-
var _resilientClient, _daemonClient;
|
|
721
|
+
var _resilientClient, _daemonClient, _adapterClient;
|
|
497
722
|
var init_database = __esm({
|
|
498
723
|
"src/lib/database.ts"() {
|
|
499
724
|
"use strict";
|
|
500
725
|
init_db_retry();
|
|
501
726
|
init_employees();
|
|
727
|
+
init_database_adapter();
|
|
502
728
|
_resilientClient = null;
|
|
503
729
|
_daemonClient = null;
|
|
504
|
-
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// src/lib/notifications.ts
|
|
508
|
-
import crypto from "crypto";
|
|
509
|
-
import path3 from "path";
|
|
510
|
-
import os3 from "os";
|
|
511
|
-
import {
|
|
512
|
-
readFileSync as readFileSync3,
|
|
513
|
-
readdirSync,
|
|
514
|
-
unlinkSync as unlinkSync2,
|
|
515
|
-
existsSync as existsSync3,
|
|
516
|
-
rmdirSync
|
|
517
|
-
} from "fs";
|
|
518
|
-
async function writeNotification(notification) {
|
|
519
|
-
try {
|
|
520
|
-
const client = getClient();
|
|
521
|
-
const id = crypto.randomUUID();
|
|
522
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
523
|
-
await client.execute({
|
|
524
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
525
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
526
|
-
args: [
|
|
527
|
-
id,
|
|
528
|
-
notification.agentId,
|
|
529
|
-
notification.agentRole,
|
|
530
|
-
notification.event,
|
|
531
|
-
notification.project,
|
|
532
|
-
notification.summary,
|
|
533
|
-
notification.taskFile ?? null,
|
|
534
|
-
now
|
|
535
|
-
]
|
|
536
|
-
});
|
|
537
|
-
} catch (err) {
|
|
538
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
539
|
-
`);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
543
|
-
try {
|
|
544
|
-
const client = getClient();
|
|
545
|
-
await client.execute({
|
|
546
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
547
|
-
args: [taskFile]
|
|
548
|
-
});
|
|
549
|
-
} catch {
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
var init_notifications = __esm({
|
|
553
|
-
"src/lib/notifications.ts"() {
|
|
554
|
-
"use strict";
|
|
555
|
-
init_database();
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
// src/lib/state-bus.ts
|
|
560
|
-
var StateBus, orgBus;
|
|
561
|
-
var init_state_bus = __esm({
|
|
562
|
-
"src/lib/state-bus.ts"() {
|
|
563
|
-
"use strict";
|
|
564
|
-
StateBus = class {
|
|
565
|
-
handlers = /* @__PURE__ */ new Map();
|
|
566
|
-
globalHandlers = /* @__PURE__ */ new Set();
|
|
567
|
-
/** Emit an event to all subscribers */
|
|
568
|
-
emit(event) {
|
|
569
|
-
const typeHandlers = this.handlers.get(event.type);
|
|
570
|
-
if (typeHandlers) {
|
|
571
|
-
for (const handler of typeHandlers) {
|
|
572
|
-
try {
|
|
573
|
-
handler(event);
|
|
574
|
-
} catch {
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
for (const handler of this.globalHandlers) {
|
|
579
|
-
try {
|
|
580
|
-
handler(event);
|
|
581
|
-
} catch {
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
/** Subscribe to a specific event type */
|
|
586
|
-
on(type, handler) {
|
|
587
|
-
if (!this.handlers.has(type)) {
|
|
588
|
-
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
589
|
-
}
|
|
590
|
-
this.handlers.get(type).add(handler);
|
|
591
|
-
}
|
|
592
|
-
/** Subscribe to ALL events */
|
|
593
|
-
onAny(handler) {
|
|
594
|
-
this.globalHandlers.add(handler);
|
|
595
|
-
}
|
|
596
|
-
/** Unsubscribe from a specific event type */
|
|
597
|
-
off(type, handler) {
|
|
598
|
-
this.handlers.get(type)?.delete(handler);
|
|
599
|
-
}
|
|
600
|
-
/** Unsubscribe from ALL events */
|
|
601
|
-
offAny(handler) {
|
|
602
|
-
this.globalHandlers.delete(handler);
|
|
603
|
-
}
|
|
604
|
-
/** Remove all listeners */
|
|
605
|
-
clear() {
|
|
606
|
-
this.handlers.clear();
|
|
607
|
-
this.globalHandlers.clear();
|
|
608
|
-
}
|
|
609
|
-
};
|
|
610
|
-
orgBus = new StateBus();
|
|
730
|
+
_adapterClient = null;
|
|
611
731
|
}
|
|
612
732
|
});
|
|
613
733
|
|
|
614
734
|
// src/lib/session-registry.ts
|
|
615
|
-
import { readFileSync as readFileSync4, writeFileSync as
|
|
616
|
-
import
|
|
735
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
736
|
+
import path5 from "path";
|
|
617
737
|
import os4 from "os";
|
|
618
738
|
function registerSession(entry) {
|
|
619
|
-
const dir =
|
|
620
|
-
if (!
|
|
621
|
-
|
|
739
|
+
const dir = path5.dirname(REGISTRY_PATH);
|
|
740
|
+
if (!existsSync5(dir)) {
|
|
741
|
+
mkdirSync2(dir, { recursive: true });
|
|
622
742
|
}
|
|
623
743
|
const sessions = listSessions();
|
|
624
744
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -627,7 +747,7 @@ function registerSession(entry) {
|
|
|
627
747
|
} else {
|
|
628
748
|
sessions.push(entry);
|
|
629
749
|
}
|
|
630
|
-
|
|
750
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
631
751
|
}
|
|
632
752
|
function listSessions() {
|
|
633
753
|
try {
|
|
@@ -641,7 +761,7 @@ var REGISTRY_PATH;
|
|
|
641
761
|
var init_session_registry = __esm({
|
|
642
762
|
"src/lib/session-registry.ts"() {
|
|
643
763
|
"use strict";
|
|
644
|
-
REGISTRY_PATH =
|
|
764
|
+
REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
645
765
|
}
|
|
646
766
|
});
|
|
647
767
|
|
|
@@ -893,67 +1013,6 @@ var init_provider_table = __esm({
|
|
|
893
1013
|
}
|
|
894
1014
|
});
|
|
895
1015
|
|
|
896
|
-
// src/lib/runtime-table.ts
|
|
897
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
898
|
-
var init_runtime_table = __esm({
|
|
899
|
-
"src/lib/runtime-table.ts"() {
|
|
900
|
-
"use strict";
|
|
901
|
-
RUNTIME_TABLE = {
|
|
902
|
-
codex: {
|
|
903
|
-
binary: "codex",
|
|
904
|
-
launchMode: "interactive",
|
|
905
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
906
|
-
inlineFlag: "--no-alt-screen",
|
|
907
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
908
|
-
defaultModel: "gpt-5.4"
|
|
909
|
-
},
|
|
910
|
-
opencode: {
|
|
911
|
-
binary: "opencode",
|
|
912
|
-
launchMode: "exec",
|
|
913
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
914
|
-
inlineFlag: "",
|
|
915
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
916
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
917
|
-
}
|
|
918
|
-
};
|
|
919
|
-
DEFAULT_RUNTIME = "claude";
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
// src/lib/agent-config.ts
|
|
924
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
925
|
-
import path5 from "path";
|
|
926
|
-
function loadAgentConfig() {
|
|
927
|
-
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
928
|
-
try {
|
|
929
|
-
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
930
|
-
} catch {
|
|
931
|
-
return {};
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
function getAgentRuntime(agentId) {
|
|
935
|
-
const config = loadAgentConfig();
|
|
936
|
-
const entry = config[agentId];
|
|
937
|
-
if (entry) return entry;
|
|
938
|
-
const orgDefault = config["default"];
|
|
939
|
-
if (orgDefault) return orgDefault;
|
|
940
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
941
|
-
}
|
|
942
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
943
|
-
var init_agent_config = __esm({
|
|
944
|
-
"src/lib/agent-config.ts"() {
|
|
945
|
-
"use strict";
|
|
946
|
-
init_config();
|
|
947
|
-
init_runtime_table();
|
|
948
|
-
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
949
|
-
DEFAULT_MODELS = {
|
|
950
|
-
claude: "claude-opus-4",
|
|
951
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
952
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
|
|
957
1016
|
// src/lib/intercom-queue.ts
|
|
958
1017
|
var intercom_queue_exports = {};
|
|
959
1018
|
__export(intercom_queue_exports, {
|
|
@@ -963,7 +1022,7 @@ __export(intercom_queue_exports, {
|
|
|
963
1022
|
queueIntercom: () => queueIntercom,
|
|
964
1023
|
readQueue: () => readQueue
|
|
965
1024
|
});
|
|
966
|
-
import { readFileSync as
|
|
1025
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
967
1026
|
import path6 from "path";
|
|
968
1027
|
import os5 from "os";
|
|
969
1028
|
function ensureDir() {
|
|
@@ -973,7 +1032,7 @@ function ensureDir() {
|
|
|
973
1032
|
function readQueue() {
|
|
974
1033
|
try {
|
|
975
1034
|
if (!existsSync6(QUEUE_PATH)) return [];
|
|
976
|
-
return JSON.parse(
|
|
1035
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
977
1036
|
} catch {
|
|
978
1037
|
return [];
|
|
979
1038
|
}
|
|
@@ -1081,8 +1140,11 @@ var init_intercom_queue = __esm({
|
|
|
1081
1140
|
});
|
|
1082
1141
|
|
|
1083
1142
|
// src/lib/license.ts
|
|
1084
|
-
import { readFileSync as
|
|
1143
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
1085
1144
|
import { randomUUID } from "crypto";
|
|
1145
|
+
import { createRequire as createRequire2 } from "module";
|
|
1146
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1147
|
+
import os6 from "os";
|
|
1086
1148
|
import path7 from "path";
|
|
1087
1149
|
import { jwtVerify, importSPKI } from "jose";
|
|
1088
1150
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
@@ -1104,12 +1166,12 @@ var init_license = __esm({
|
|
|
1104
1166
|
});
|
|
1105
1167
|
|
|
1106
1168
|
// src/lib/plan-limits.ts
|
|
1107
|
-
import { readFileSync as
|
|
1169
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
1108
1170
|
import path8 from "path";
|
|
1109
1171
|
function getLicenseSync() {
|
|
1110
1172
|
try {
|
|
1111
1173
|
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
1112
|
-
const raw = JSON.parse(
|
|
1174
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1113
1175
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1114
1176
|
const parts = raw.token.split(".");
|
|
1115
1177
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1148,7 +1210,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1148
1210
|
let count = 0;
|
|
1149
1211
|
try {
|
|
1150
1212
|
if (existsSync8(filePath)) {
|
|
1151
|
-
const raw =
|
|
1213
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1152
1214
|
const employees = JSON.parse(raw);
|
|
1153
1215
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1154
1216
|
}
|
|
@@ -1182,7 +1244,7 @@ var init_plan_limits = __esm({
|
|
|
1182
1244
|
});
|
|
1183
1245
|
|
|
1184
1246
|
// src/lib/session-kill-telemetry.ts
|
|
1185
|
-
import
|
|
1247
|
+
import crypto from "crypto";
|
|
1186
1248
|
async function recordSessionKill(input) {
|
|
1187
1249
|
try {
|
|
1188
1250
|
const client = getClient();
|
|
@@ -1192,7 +1254,7 @@ async function recordSessionKill(input) {
|
|
|
1192
1254
|
ticks_idle, estimated_tokens_saved)
|
|
1193
1255
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
1194
1256
|
args: [
|
|
1195
|
-
|
|
1257
|
+
crypto.randomUUID(),
|
|
1196
1258
|
input.sessionName,
|
|
1197
1259
|
input.agentId,
|
|
1198
1260
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1515,6 +1577,7 @@ __export(tmux_routing_exports, {
|
|
|
1515
1577
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
1516
1578
|
isExeSession: () => isExeSession,
|
|
1517
1579
|
isSessionBusy: () => isSessionBusy,
|
|
1580
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
1518
1581
|
notifyParentExe: () => notifyParentExe,
|
|
1519
1582
|
parseParentExe: () => parseParentExe,
|
|
1520
1583
|
registerParentExe: () => registerParentExe,
|
|
@@ -1525,11 +1588,11 @@ __export(tmux_routing_exports, {
|
|
|
1525
1588
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
1526
1589
|
});
|
|
1527
1590
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
1528
|
-
import { readFileSync as
|
|
1591
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync, readdirSync } from "fs";
|
|
1529
1592
|
import path9 from "path";
|
|
1530
|
-
import
|
|
1593
|
+
import os7 from "os";
|
|
1531
1594
|
import { fileURLToPath } from "url";
|
|
1532
|
-
import { unlinkSync as
|
|
1595
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
1533
1596
|
function spawnLockPath(sessionName) {
|
|
1534
1597
|
return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
1535
1598
|
}
|
|
@@ -1548,7 +1611,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
1548
1611
|
const lockFile = spawnLockPath(sessionName);
|
|
1549
1612
|
if (existsSync9(lockFile)) {
|
|
1550
1613
|
try {
|
|
1551
|
-
const lock = JSON.parse(
|
|
1614
|
+
const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
|
|
1552
1615
|
const age = Date.now() - lock.timestamp;
|
|
1553
1616
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
1554
1617
|
return false;
|
|
@@ -1561,7 +1624,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
1561
1624
|
}
|
|
1562
1625
|
function releaseSpawnLock(sessionName) {
|
|
1563
1626
|
try {
|
|
1564
|
-
|
|
1627
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
1565
1628
|
} catch {
|
|
1566
1629
|
}
|
|
1567
1630
|
}
|
|
@@ -1653,7 +1716,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
1653
1716
|
}
|
|
1654
1717
|
function getParentExe(sessionKey) {
|
|
1655
1718
|
try {
|
|
1656
|
-
const data = JSON.parse(
|
|
1719
|
+
const data = JSON.parse(readFileSync8(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
1657
1720
|
return data.parentExe || null;
|
|
1658
1721
|
} catch {
|
|
1659
1722
|
return null;
|
|
@@ -1661,7 +1724,7 @@ function getParentExe(sessionKey) {
|
|
|
1661
1724
|
}
|
|
1662
1725
|
function getDispatchedBy(sessionKey) {
|
|
1663
1726
|
try {
|
|
1664
|
-
const data = JSON.parse(
|
|
1727
|
+
const data = JSON.parse(readFileSync8(
|
|
1665
1728
|
path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
1666
1729
|
"utf8"
|
|
1667
1730
|
));
|
|
@@ -1733,7 +1796,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
1733
1796
|
function readDebounceState() {
|
|
1734
1797
|
try {
|
|
1735
1798
|
if (!existsSync9(DEBOUNCE_FILE)) return {};
|
|
1736
|
-
const raw = JSON.parse(
|
|
1799
|
+
const raw = JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
|
|
1737
1800
|
const state = {};
|
|
1738
1801
|
for (const [key, val] of Object.entries(raw)) {
|
|
1739
1802
|
if (typeof val === "number") {
|
|
@@ -1860,7 +1923,7 @@ function sendIntercom(targetSession) {
|
|
|
1860
1923
|
const agent = baseAgentName(rawAgent);
|
|
1861
1924
|
const taskDir = path9.join(process.cwd(), "exe", agent);
|
|
1862
1925
|
if (existsSync9(taskDir)) {
|
|
1863
|
-
const files =
|
|
1926
|
+
const files = readdirSync(taskDir).filter(
|
|
1864
1927
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
1865
1928
|
);
|
|
1866
1929
|
if (files.length === 0) {
|
|
@@ -1919,6 +1982,21 @@ function notifyParentExe(sessionKey) {
|
|
|
1919
1982
|
}
|
|
1920
1983
|
return true;
|
|
1921
1984
|
}
|
|
1985
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
1986
|
+
const transport = getTransport();
|
|
1987
|
+
try {
|
|
1988
|
+
const sessions = transport.listSessions();
|
|
1989
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
1990
|
+
execSync4(
|
|
1991
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
1992
|
+
{ timeout: 3e3 }
|
|
1993
|
+
);
|
|
1994
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
1995
|
+
return true;
|
|
1996
|
+
} catch {
|
|
1997
|
+
return false;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
1922
2000
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
1923
2001
|
if (isCoordinatorName(employeeName)) {
|
|
1924
2002
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -1992,7 +2070,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1992
2070
|
const transport = getTransport();
|
|
1993
2071
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
1994
2072
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
1995
|
-
const logDir = path9.join(
|
|
2073
|
+
const logDir = path9.join(os7.homedir(), ".exe-os", "session-logs");
|
|
1996
2074
|
const logFile = path9.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
1997
2075
|
if (!existsSync9(logDir)) {
|
|
1998
2076
|
mkdirSync5(logDir, { recursive: true });
|
|
@@ -2008,10 +2086,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2008
2086
|
} catch {
|
|
2009
2087
|
}
|
|
2010
2088
|
try {
|
|
2011
|
-
const claudeJsonPath = path9.join(
|
|
2089
|
+
const claudeJsonPath = path9.join(os7.homedir(), ".claude.json");
|
|
2012
2090
|
let claudeJson = {};
|
|
2013
2091
|
try {
|
|
2014
|
-
claudeJson = JSON.parse(
|
|
2092
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
2015
2093
|
} catch {
|
|
2016
2094
|
}
|
|
2017
2095
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -2023,13 +2101,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2023
2101
|
} catch {
|
|
2024
2102
|
}
|
|
2025
2103
|
try {
|
|
2026
|
-
const settingsDir = path9.join(
|
|
2104
|
+
const settingsDir = path9.join(os7.homedir(), ".claude", "projects");
|
|
2027
2105
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
2028
2106
|
const projSettingsDir = path9.join(settingsDir, normalizedKey);
|
|
2029
2107
|
const settingsPath = path9.join(projSettingsDir, "settings.json");
|
|
2030
2108
|
let settings = {};
|
|
2031
2109
|
try {
|
|
2032
|
-
settings = JSON.parse(
|
|
2110
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
2033
2111
|
} catch {
|
|
2034
2112
|
}
|
|
2035
2113
|
const perms = settings.permissions ?? {};
|
|
@@ -2074,7 +2152,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2074
2152
|
let legacyFallbackWarned = false;
|
|
2075
2153
|
if (!useExeAgent && !useBinSymlink) {
|
|
2076
2154
|
const identityPath2 = path9.join(
|
|
2077
|
-
|
|
2155
|
+
os7.homedir(),
|
|
2078
2156
|
".exe-os",
|
|
2079
2157
|
"identity",
|
|
2080
2158
|
`${employeeName}.md`
|
|
@@ -2104,7 +2182,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2104
2182
|
}
|
|
2105
2183
|
let sessionContextFlag = "";
|
|
2106
2184
|
try {
|
|
2107
|
-
const ctxDir = path9.join(
|
|
2185
|
+
const ctxDir = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
2108
2186
|
mkdirSync5(ctxDir, { recursive: true });
|
|
2109
2187
|
const ctxFile = path9.join(ctxDir, `session-context-${sessionName}.md`);
|
|
2110
2188
|
const ctxContent = [
|
|
@@ -2265,14 +2343,14 @@ var init_tmux_routing = __esm({
|
|
|
2265
2343
|
init_intercom_queue();
|
|
2266
2344
|
init_plan_limits();
|
|
2267
2345
|
init_employees();
|
|
2268
|
-
SPAWN_LOCK_DIR = path9.join(
|
|
2269
|
-
SESSION_CACHE = path9.join(
|
|
2346
|
+
SPAWN_LOCK_DIR = path9.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
2347
|
+
SESSION_CACHE = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
2270
2348
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
2271
2349
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
2272
2350
|
VERIFY_PANE_LINES = 200;
|
|
2273
2351
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
2274
2352
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
2275
|
-
INTERCOM_LOG2 = path9.join(
|
|
2353
|
+
INTERCOM_LOG2 = path9.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
2276
2354
|
DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2277
2355
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2278
2356
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
@@ -2296,6 +2374,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
2296
2374
|
args: [scope]
|
|
2297
2375
|
};
|
|
2298
2376
|
}
|
|
2377
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
2378
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2379
|
+
if (!scope) return { sql: "", args: [] };
|
|
2380
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2381
|
+
return {
|
|
2382
|
+
sql: ` AND ${col} = ?`,
|
|
2383
|
+
args: [scope]
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2299
2386
|
var init_task_scope = __esm({
|
|
2300
2387
|
"src/lib/task-scope.ts"() {
|
|
2301
2388
|
"use strict";
|
|
@@ -2303,13 +2390,125 @@ var init_task_scope = __esm({
|
|
|
2303
2390
|
}
|
|
2304
2391
|
});
|
|
2305
2392
|
|
|
2393
|
+
// src/lib/notifications.ts
|
|
2394
|
+
import crypto2 from "crypto";
|
|
2395
|
+
import path10 from "path";
|
|
2396
|
+
import os8 from "os";
|
|
2397
|
+
import {
|
|
2398
|
+
readFileSync as readFileSync9,
|
|
2399
|
+
readdirSync as readdirSync2,
|
|
2400
|
+
unlinkSync as unlinkSync3,
|
|
2401
|
+
existsSync as existsSync10,
|
|
2402
|
+
rmdirSync
|
|
2403
|
+
} from "fs";
|
|
2404
|
+
async function writeNotification(notification) {
|
|
2405
|
+
try {
|
|
2406
|
+
const client = getClient();
|
|
2407
|
+
const id = crypto2.randomUUID();
|
|
2408
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2409
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2410
|
+
await client.execute({
|
|
2411
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
2412
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2413
|
+
args: [
|
|
2414
|
+
id,
|
|
2415
|
+
notification.agentId,
|
|
2416
|
+
notification.agentRole,
|
|
2417
|
+
notification.event,
|
|
2418
|
+
notification.project,
|
|
2419
|
+
notification.summary,
|
|
2420
|
+
notification.taskFile ?? null,
|
|
2421
|
+
sessionScope,
|
|
2422
|
+
now
|
|
2423
|
+
]
|
|
2424
|
+
});
|
|
2425
|
+
} catch (err) {
|
|
2426
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
2427
|
+
`);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2431
|
+
try {
|
|
2432
|
+
const client = getClient();
|
|
2433
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2434
|
+
await client.execute({
|
|
2435
|
+
sql: `UPDATE notifications SET read = 1
|
|
2436
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
2437
|
+
args: [taskFile, ...scope.args]
|
|
2438
|
+
});
|
|
2439
|
+
} catch {
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
var init_notifications = __esm({
|
|
2443
|
+
"src/lib/notifications.ts"() {
|
|
2444
|
+
"use strict";
|
|
2445
|
+
init_database();
|
|
2446
|
+
init_task_scope();
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
// src/lib/state-bus.ts
|
|
2451
|
+
var StateBus, orgBus;
|
|
2452
|
+
var init_state_bus = __esm({
|
|
2453
|
+
"src/lib/state-bus.ts"() {
|
|
2454
|
+
"use strict";
|
|
2455
|
+
StateBus = class {
|
|
2456
|
+
handlers = /* @__PURE__ */ new Map();
|
|
2457
|
+
globalHandlers = /* @__PURE__ */ new Set();
|
|
2458
|
+
/** Emit an event to all subscribers */
|
|
2459
|
+
emit(event) {
|
|
2460
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
2461
|
+
if (typeHandlers) {
|
|
2462
|
+
for (const handler of typeHandlers) {
|
|
2463
|
+
try {
|
|
2464
|
+
handler(event);
|
|
2465
|
+
} catch {
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
for (const handler of this.globalHandlers) {
|
|
2470
|
+
try {
|
|
2471
|
+
handler(event);
|
|
2472
|
+
} catch {
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
/** Subscribe to a specific event type */
|
|
2477
|
+
on(type, handler) {
|
|
2478
|
+
if (!this.handlers.has(type)) {
|
|
2479
|
+
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
2480
|
+
}
|
|
2481
|
+
this.handlers.get(type).add(handler);
|
|
2482
|
+
}
|
|
2483
|
+
/** Subscribe to ALL events */
|
|
2484
|
+
onAny(handler) {
|
|
2485
|
+
this.globalHandlers.add(handler);
|
|
2486
|
+
}
|
|
2487
|
+
/** Unsubscribe from a specific event type */
|
|
2488
|
+
off(type, handler) {
|
|
2489
|
+
this.handlers.get(type)?.delete(handler);
|
|
2490
|
+
}
|
|
2491
|
+
/** Unsubscribe from ALL events */
|
|
2492
|
+
offAny(handler) {
|
|
2493
|
+
this.globalHandlers.delete(handler);
|
|
2494
|
+
}
|
|
2495
|
+
/** Remove all listeners */
|
|
2496
|
+
clear() {
|
|
2497
|
+
this.handlers.clear();
|
|
2498
|
+
this.globalHandlers.clear();
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
orgBus = new StateBus();
|
|
2502
|
+
}
|
|
2503
|
+
});
|
|
2504
|
+
|
|
2306
2505
|
// src/lib/tasks-crud.ts
|
|
2307
2506
|
import crypto3 from "crypto";
|
|
2308
|
-
import
|
|
2309
|
-
import
|
|
2507
|
+
import path11 from "path";
|
|
2508
|
+
import os9 from "os";
|
|
2310
2509
|
import { execSync as execSync5 } from "child_process";
|
|
2311
2510
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2312
|
-
import { existsSync as
|
|
2511
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
2313
2512
|
async function writeCheckpoint(input) {
|
|
2314
2513
|
const client = getClient();
|
|
2315
2514
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2484,8 +2683,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2484
2683
|
}
|
|
2485
2684
|
if (input.baseDir) {
|
|
2486
2685
|
try {
|
|
2487
|
-
await mkdir3(
|
|
2488
|
-
await mkdir3(
|
|
2686
|
+
await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2687
|
+
await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2489
2688
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2490
2689
|
await ensureGitignoreExe(input.baseDir);
|
|
2491
2690
|
} catch {
|
|
@@ -2521,13 +2720,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2521
2720
|
});
|
|
2522
2721
|
if (input.baseDir) {
|
|
2523
2722
|
try {
|
|
2524
|
-
const EXE_OS_DIR =
|
|
2525
|
-
const mdPath =
|
|
2526
|
-
const mdDir =
|
|
2527
|
-
if (!
|
|
2723
|
+
const EXE_OS_DIR = path11.join(os9.homedir(), ".exe-os");
|
|
2724
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
2725
|
+
const mdDir = path11.dirname(mdPath);
|
|
2726
|
+
if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2528
2727
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2529
2728
|
const mdContent = `# ${input.title}
|
|
2530
2729
|
|
|
2730
|
+
## MANDATORY: When done
|
|
2731
|
+
|
|
2732
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2733
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2734
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2735
|
+
|
|
2531
2736
|
**ID:** ${id}
|
|
2532
2737
|
**Status:** ${initialStatus}
|
|
2533
2738
|
**Priority:** ${input.priority}
|
|
@@ -2541,12 +2746,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2541
2746
|
## Context
|
|
2542
2747
|
|
|
2543
2748
|
${input.context}
|
|
2544
|
-
|
|
2545
|
-
## MANDATORY: When done
|
|
2546
|
-
|
|
2547
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2548
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2549
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2550
2749
|
`;
|
|
2551
2750
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2552
2751
|
} catch (err) {
|
|
@@ -2795,7 +2994,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2795
2994
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
2796
2995
|
} catch {
|
|
2797
2996
|
}
|
|
2798
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
2997
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2799
2998
|
try {
|
|
2800
2999
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
2801
3000
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -2824,9 +3023,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2824
3023
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2825
3024
|
}
|
|
2826
3025
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2827
|
-
const archPath =
|
|
3026
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2828
3027
|
try {
|
|
2829
|
-
if (
|
|
3028
|
+
if (existsSync11(archPath)) return;
|
|
2830
3029
|
const template = [
|
|
2831
3030
|
`# ${projectName} \u2014 System Architecture`,
|
|
2832
3031
|
"",
|
|
@@ -2859,9 +3058,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2859
3058
|
}
|
|
2860
3059
|
}
|
|
2861
3060
|
async function ensureGitignoreExe(baseDir) {
|
|
2862
|
-
const gitignorePath =
|
|
3061
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
2863
3062
|
try {
|
|
2864
|
-
if (
|
|
3063
|
+
if (existsSync11(gitignorePath)) {
|
|
2865
3064
|
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2866
3065
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2867
3066
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -2893,58 +3092,42 @@ var init_tasks_crud = __esm({
|
|
|
2893
3092
|
});
|
|
2894
3093
|
|
|
2895
3094
|
// src/lib/tasks-review.ts
|
|
2896
|
-
import
|
|
2897
|
-
import { existsSync as
|
|
3095
|
+
import path12 from "path";
|
|
3096
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
2898
3097
|
async function countPendingReviews(sessionScope) {
|
|
2899
3098
|
const client = getClient();
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
args: [sessionScope]
|
|
2904
|
-
});
|
|
2905
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
2906
|
-
}
|
|
3099
|
+
const scope = strictSessionScopeFilter(
|
|
3100
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3101
|
+
);
|
|
2907
3102
|
const result = await client.execute({
|
|
2908
|
-
sql:
|
|
2909
|
-
|
|
3103
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3104
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3105
|
+
args: [...scope.args]
|
|
2910
3106
|
});
|
|
2911
3107
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2912
3108
|
}
|
|
2913
3109
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2914
3110
|
const client = getClient();
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
2919
|
-
AND session_scope = ?`,
|
|
2920
|
-
args: [sinceIso, sessionScope]
|
|
2921
|
-
});
|
|
2922
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
2923
|
-
}
|
|
3111
|
+
const scope = strictSessionScopeFilter(
|
|
3112
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3113
|
+
);
|
|
2924
3114
|
const result = await client.execute({
|
|
2925
3115
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2926
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
2927
|
-
args: [sinceIso]
|
|
3116
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3117
|
+
args: [sinceIso, ...scope.args]
|
|
2928
3118
|
});
|
|
2929
3119
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2930
3120
|
}
|
|
2931
3121
|
async function listPendingReviews(limit, sessionScope) {
|
|
2932
3122
|
const client = getClient();
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
WHERE status = 'needs_review'
|
|
2937
|
-
AND session_scope = ?
|
|
2938
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
2939
|
-
args: [sessionScope, limit]
|
|
2940
|
-
});
|
|
2941
|
-
return result2.rows;
|
|
2942
|
-
}
|
|
3123
|
+
const scope = strictSessionScopeFilter(
|
|
3124
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3125
|
+
);
|
|
2943
3126
|
const result = await client.execute({
|
|
2944
3127
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
2945
|
-
WHERE status = 'needs_review'
|
|
3128
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
2946
3129
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
2947
|
-
args: [limit]
|
|
3130
|
+
args: [...scope.args, limit]
|
|
2948
3131
|
});
|
|
2949
3132
|
return result.rows;
|
|
2950
3133
|
}
|
|
@@ -2956,7 +3139,7 @@ async function cleanupOrphanedReviews() {
|
|
|
2956
3139
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
2957
3140
|
AND assigned_by = 'system'
|
|
2958
3141
|
AND title LIKE 'Review:%'
|
|
2959
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3142
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
2960
3143
|
args: [now]
|
|
2961
3144
|
});
|
|
2962
3145
|
const r1b = await client.execute({
|
|
@@ -3075,11 +3258,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3075
3258
|
);
|
|
3076
3259
|
}
|
|
3077
3260
|
try {
|
|
3078
|
-
const cacheDir =
|
|
3079
|
-
if (
|
|
3261
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
3262
|
+
if (existsSync12(cacheDir)) {
|
|
3080
3263
|
for (const f of readdirSync3(cacheDir)) {
|
|
3081
3264
|
if (f.startsWith("review-notified-")) {
|
|
3082
|
-
unlinkSync4(
|
|
3265
|
+
unlinkSync4(path12.join(cacheDir, f));
|
|
3083
3266
|
}
|
|
3084
3267
|
}
|
|
3085
3268
|
}
|
|
@@ -3096,11 +3279,12 @@ var init_tasks_review = __esm({
|
|
|
3096
3279
|
init_tmux_routing();
|
|
3097
3280
|
init_session_key();
|
|
3098
3281
|
init_state_bus();
|
|
3282
|
+
init_task_scope();
|
|
3099
3283
|
}
|
|
3100
3284
|
});
|
|
3101
3285
|
|
|
3102
3286
|
// src/lib/tasks-chain.ts
|
|
3103
|
-
import
|
|
3287
|
+
import path13 from "path";
|
|
3104
3288
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3105
3289
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3106
3290
|
const client = getClient();
|
|
@@ -3117,7 +3301,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3117
3301
|
});
|
|
3118
3302
|
for (const ur of unblockedRows.rows) {
|
|
3119
3303
|
try {
|
|
3120
|
-
const ubFile =
|
|
3304
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
3121
3305
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3122
3306
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3123
3307
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3152,7 +3336,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3152
3336
|
const scScope = sessionScopeFilter();
|
|
3153
3337
|
const remaining = await client.execute({
|
|
3154
3338
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3155
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3339
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3156
3340
|
args: [parentTaskId, ...scScope.args]
|
|
3157
3341
|
});
|
|
3158
3342
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3186,7 +3370,7 @@ var init_tasks_chain = __esm({
|
|
|
3186
3370
|
|
|
3187
3371
|
// src/lib/project-name.ts
|
|
3188
3372
|
import { execSync as execSync6 } from "child_process";
|
|
3189
|
-
import
|
|
3373
|
+
import path14 from "path";
|
|
3190
3374
|
function getProjectName(cwd) {
|
|
3191
3375
|
const dir = cwd ?? process.cwd();
|
|
3192
3376
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3199,7 +3383,7 @@ function getProjectName(cwd) {
|
|
|
3199
3383
|
timeout: 2e3,
|
|
3200
3384
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3201
3385
|
}).trim();
|
|
3202
|
-
repoRoot =
|
|
3386
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
3203
3387
|
} catch {
|
|
3204
3388
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3205
3389
|
cwd: dir,
|
|
@@ -3208,11 +3392,11 @@ function getProjectName(cwd) {
|
|
|
3208
3392
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3209
3393
|
}).trim();
|
|
3210
3394
|
}
|
|
3211
|
-
_cached2 =
|
|
3395
|
+
_cached2 = path14.basename(repoRoot);
|
|
3212
3396
|
_cachedCwd = dir;
|
|
3213
3397
|
return _cached2;
|
|
3214
3398
|
} catch {
|
|
3215
|
-
_cached2 =
|
|
3399
|
+
_cached2 = path14.basename(dir);
|
|
3216
3400
|
_cachedCwd = dir;
|
|
3217
3401
|
return _cached2;
|
|
3218
3402
|
}
|
|
@@ -3685,7 +3869,7 @@ __export(tasks_exports, {
|
|
|
3685
3869
|
updateTaskStatus: () => updateTaskStatus,
|
|
3686
3870
|
writeCheckpoint: () => writeCheckpoint
|
|
3687
3871
|
});
|
|
3688
|
-
import
|
|
3872
|
+
import path15 from "path";
|
|
3689
3873
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
3690
3874
|
async function createTask(input) {
|
|
3691
3875
|
const result = await createTaskCore(input);
|
|
@@ -3705,12 +3889,12 @@ async function updateTask(input) {
|
|
|
3705
3889
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3706
3890
|
try {
|
|
3707
3891
|
const agent = String(row.assigned_to);
|
|
3708
|
-
const cacheDir =
|
|
3709
|
-
const cachePath =
|
|
3892
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
3893
|
+
const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
|
|
3710
3894
|
if (input.status === "in_progress") {
|
|
3711
3895
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3712
3896
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3713
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3897
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3714
3898
|
try {
|
|
3715
3899
|
unlinkSync5(cachePath);
|
|
3716
3900
|
} catch {
|
|
@@ -3718,10 +3902,10 @@ async function updateTask(input) {
|
|
|
3718
3902
|
}
|
|
3719
3903
|
} catch {
|
|
3720
3904
|
}
|
|
3721
|
-
if (input.status === "done") {
|
|
3905
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3722
3906
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3723
3907
|
}
|
|
3724
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3908
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3725
3909
|
try {
|
|
3726
3910
|
const client = getClient();
|
|
3727
3911
|
const taskTitle = String(row.title);
|
|
@@ -3737,7 +3921,7 @@ async function updateTask(input) {
|
|
|
3737
3921
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3738
3922
|
try {
|
|
3739
3923
|
const draftClient = getClient();
|
|
3740
|
-
if (input.status === "done") {
|
|
3924
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3741
3925
|
await draftClient.execute({
|
|
3742
3926
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3743
3927
|
args: [assignedAgent]
|
|
@@ -3754,7 +3938,7 @@ async function updateTask(input) {
|
|
|
3754
3938
|
try {
|
|
3755
3939
|
const client = getClient();
|
|
3756
3940
|
const cascaded = await client.execute({
|
|
3757
|
-
sql: `UPDATE tasks SET status = '
|
|
3941
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3758
3942
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3759
3943
|
args: [now, taskId]
|
|
3760
3944
|
});
|
|
@@ -3767,14 +3951,14 @@ async function updateTask(input) {
|
|
|
3767
3951
|
} catch {
|
|
3768
3952
|
}
|
|
3769
3953
|
}
|
|
3770
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3954
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
3771
3955
|
if (isTerminal) {
|
|
3772
3956
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3773
3957
|
if (!isCoordinator) {
|
|
3774
3958
|
notifyTaskDone();
|
|
3775
3959
|
}
|
|
3776
3960
|
await markTaskNotificationsRead(taskFile);
|
|
3777
|
-
if (input.status === "done") {
|
|
3961
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3778
3962
|
try {
|
|
3779
3963
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
3780
3964
|
} catch {
|
|
@@ -3794,7 +3978,7 @@ async function updateTask(input) {
|
|
|
3794
3978
|
}
|
|
3795
3979
|
}
|
|
3796
3980
|
}
|
|
3797
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3981
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3798
3982
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3799
3983
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3800
3984
|
taskId,
|
|
@@ -3875,17 +4059,17 @@ __export(identity_exports, {
|
|
|
3875
4059
|
listIdentities: () => listIdentities,
|
|
3876
4060
|
updateIdentity: () => updateIdentity
|
|
3877
4061
|
});
|
|
3878
|
-
import { existsSync as
|
|
4062
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
3879
4063
|
import { readdirSync as readdirSync5 } from "fs";
|
|
3880
|
-
import
|
|
4064
|
+
import path17 from "path";
|
|
3881
4065
|
import { createHash } from "crypto";
|
|
3882
4066
|
function ensureDir2() {
|
|
3883
|
-
if (!
|
|
3884
|
-
mkdirSync8(
|
|
4067
|
+
if (!existsSync13(IDENTITY_DIR2)) {
|
|
4068
|
+
mkdirSync8(IDENTITY_DIR2, { recursive: true });
|
|
3885
4069
|
}
|
|
3886
4070
|
}
|
|
3887
4071
|
function identityPath(agentId) {
|
|
3888
|
-
return
|
|
4072
|
+
return path17.join(IDENTITY_DIR2, `${agentId}.md`);
|
|
3889
4073
|
}
|
|
3890
4074
|
function parseFrontmatter(raw) {
|
|
3891
4075
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -3926,7 +4110,7 @@ function contentHash(content) {
|
|
|
3926
4110
|
}
|
|
3927
4111
|
function getIdentity(agentId) {
|
|
3928
4112
|
const filePath = identityPath(agentId);
|
|
3929
|
-
if (!
|
|
4113
|
+
if (!existsSync13(filePath)) return null;
|
|
3930
4114
|
const raw = readFileSync12(filePath, "utf-8");
|
|
3931
4115
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
3932
4116
|
return {
|
|
@@ -3958,7 +4142,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
3958
4142
|
}
|
|
3959
4143
|
function listIdentities() {
|
|
3960
4144
|
ensureDir2();
|
|
3961
|
-
const files = readdirSync5(
|
|
4145
|
+
const files = readdirSync5(IDENTITY_DIR2).filter((f) => f.endsWith(".md"));
|
|
3962
4146
|
const results = [];
|
|
3963
4147
|
for (const file of files) {
|
|
3964
4148
|
const agentId = file.replace(".md", "");
|
|
@@ -3991,13 +4175,13 @@ ${teamLines.join("\n")}`);
|
|
|
3991
4175
|
}
|
|
3992
4176
|
return parts.join("\n\n");
|
|
3993
4177
|
}
|
|
3994
|
-
var
|
|
4178
|
+
var IDENTITY_DIR2;
|
|
3995
4179
|
var init_identity = __esm({
|
|
3996
4180
|
"src/lib/identity.ts"() {
|
|
3997
4181
|
"use strict";
|
|
3998
4182
|
init_config();
|
|
3999
4183
|
init_database();
|
|
4000
|
-
|
|
4184
|
+
IDENTITY_DIR2 = path17.join(EXE_AI_DIR, "identity");
|
|
4001
4185
|
}
|
|
4002
4186
|
});
|
|
4003
4187
|
|
|
@@ -4552,8 +4736,8 @@ init_session_key();
|
|
|
4552
4736
|
init_employees();
|
|
4553
4737
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6, readdirSync as readdirSync4 } from "fs";
|
|
4554
4738
|
import { execSync as execSync7 } from "child_process";
|
|
4555
|
-
import
|
|
4556
|
-
var CACHE_DIR =
|
|
4739
|
+
import path16 from "path";
|
|
4740
|
+
var CACHE_DIR = path16.join(EXE_AI_DIR, "session-cache");
|
|
4557
4741
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4558
4742
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
4559
4743
|
if (candidate === baseName) return true;
|
|
@@ -4598,7 +4782,7 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
4598
4782
|
return null;
|
|
4599
4783
|
}
|
|
4600
4784
|
function getMarkerPath() {
|
|
4601
|
-
return
|
|
4785
|
+
return path16.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
4602
4786
|
}
|
|
4603
4787
|
function getActiveAgent() {
|
|
4604
4788
|
try {
|
|
@@ -4702,10 +4886,10 @@ function registerCreateTask(server) {
|
|
|
4702
4886
|
skipDispatch: true
|
|
4703
4887
|
});
|
|
4704
4888
|
try {
|
|
4705
|
-
const { existsSync:
|
|
4889
|
+
const { existsSync: existsSync14, mkdirSync: mkdirSync9, writeFileSync: writeFileSync10 } = await import("fs");
|
|
4706
4890
|
const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
|
|
4707
4891
|
const idPath = identityPath2(assigned_to);
|
|
4708
|
-
if (!
|
|
4892
|
+
if (!existsSync14(idPath)) {
|
|
4709
4893
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
4710
4894
|
const employees = await loadEmployees2();
|
|
4711
4895
|
const emp = employees.find((e) => e.name === assigned_to);
|
|
@@ -4714,7 +4898,7 @@ function registerCreateTask(server) {
|
|
|
4714
4898
|
const template = getTemplateForTitle2(emp.role);
|
|
4715
4899
|
if (template) {
|
|
4716
4900
|
const dir = (await import("path")).dirname(idPath);
|
|
4717
|
-
if (!
|
|
4901
|
+
if (!existsSync14(dir)) mkdirSync9(dir, { recursive: true });
|
|
4718
4902
|
writeFileSync10(idPath, template.replace(/^agent_id: \w+/m, `agent_id: ${assigned_to}`), "utf-8");
|
|
4719
4903
|
}
|
|
4720
4904
|
}
|