@askexenow/exe-os 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1295 -856
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +778 -427
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +276 -139
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +677 -388
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +440 -250
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +404 -212
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +412 -220
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +533 -320
- package/dist/hooks/bug-report-worker.js +344 -193
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +402 -210
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3423 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +408 -216
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +541 -328
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +443 -240
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +538 -324
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +935 -587
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +280 -234
- package/dist/lib/tmux-routing.js +172 -125
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1326 -609
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +306 -248
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1848 -199
- package/dist/runtime/index.js +441 -248
- package/dist/tui/App.js +761 -424
- package/package.json +1 -1
package/dist/lib/tasks.js
CHANGED
|
@@ -32,9 +32,34 @@ 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
|
+
async function enforcePrivateFile(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
52
|
+
var init_secure_files = __esm({
|
|
53
|
+
"src/lib/secure-files.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
PRIVATE_DIR_MODE = 448;
|
|
56
|
+
PRIVATE_FILE_MODE = 384;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
35
60
|
// src/lib/config.ts
|
|
36
|
-
import { readFile, writeFile
|
|
37
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
61
|
+
import { readFile, writeFile } from "fs/promises";
|
|
62
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
38
63
|
import path from "path";
|
|
39
64
|
import os from "os";
|
|
40
65
|
function resolveDataDir() {
|
|
@@ -42,7 +67,7 @@ function resolveDataDir() {
|
|
|
42
67
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
43
68
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
44
69
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
45
|
-
if (!
|
|
70
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
46
71
|
try {
|
|
47
72
|
renameSync(legacyDir, newDir);
|
|
48
73
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -105,9 +130,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
105
130
|
}
|
|
106
131
|
async function loadConfig() {
|
|
107
132
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
108
|
-
await
|
|
133
|
+
await ensurePrivateDir(dir);
|
|
109
134
|
const configPath = path.join(dir, "config.json");
|
|
110
|
-
if (!
|
|
135
|
+
if (!existsSync2(configPath)) {
|
|
111
136
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
112
137
|
}
|
|
113
138
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -120,6 +145,7 @@ async function loadConfig() {
|
|
|
120
145
|
`);
|
|
121
146
|
try {
|
|
122
147
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
148
|
+
await enforcePrivateFile(configPath);
|
|
123
149
|
} catch {
|
|
124
150
|
}
|
|
125
151
|
}
|
|
@@ -139,6 +165,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
139
165
|
var init_config = __esm({
|
|
140
166
|
"src/lib/config.ts"() {
|
|
141
167
|
"use strict";
|
|
168
|
+
init_secure_files();
|
|
142
169
|
EXE_AI_DIR = resolveDataDir();
|
|
143
170
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
144
171
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -217,7 +244,7 @@ var init_config = __esm({
|
|
|
217
244
|
|
|
218
245
|
// src/lib/employees.ts
|
|
219
246
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
220
|
-
import { existsSync as
|
|
247
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
221
248
|
import { execSync } from "child_process";
|
|
222
249
|
import path2 from "path";
|
|
223
250
|
import os2 from "os";
|
|
@@ -238,7 +265,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
238
265
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
239
266
|
}
|
|
240
267
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
241
|
-
if (!
|
|
268
|
+
if (!existsSync3(employeesPath)) return [];
|
|
242
269
|
try {
|
|
243
270
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
244
271
|
} catch {
|
|
@@ -326,121 +353,14 @@ var init_database = __esm({
|
|
|
326
353
|
}
|
|
327
354
|
});
|
|
328
355
|
|
|
329
|
-
// src/lib/
|
|
330
|
-
import
|
|
356
|
+
// src/lib/session-registry.ts
|
|
357
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
331
358
|
import path4 from "path";
|
|
332
359
|
import os4 from "os";
|
|
333
|
-
import {
|
|
334
|
-
readFileSync as readFileSync3,
|
|
335
|
-
readdirSync,
|
|
336
|
-
unlinkSync as unlinkSync2,
|
|
337
|
-
existsSync as existsSync3,
|
|
338
|
-
rmdirSync
|
|
339
|
-
} from "fs";
|
|
340
|
-
async function writeNotification(notification) {
|
|
341
|
-
try {
|
|
342
|
-
const client = getClient();
|
|
343
|
-
const id = crypto.randomUUID();
|
|
344
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
345
|
-
await client.execute({
|
|
346
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
347
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
348
|
-
args: [
|
|
349
|
-
id,
|
|
350
|
-
notification.agentId,
|
|
351
|
-
notification.agentRole,
|
|
352
|
-
notification.event,
|
|
353
|
-
notification.project,
|
|
354
|
-
notification.summary,
|
|
355
|
-
notification.taskFile ?? null,
|
|
356
|
-
now
|
|
357
|
-
]
|
|
358
|
-
});
|
|
359
|
-
} catch (err) {
|
|
360
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
361
|
-
`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
365
|
-
try {
|
|
366
|
-
const client = getClient();
|
|
367
|
-
await client.execute({
|
|
368
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
369
|
-
args: [taskFile]
|
|
370
|
-
});
|
|
371
|
-
} catch {
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
var init_notifications = __esm({
|
|
375
|
-
"src/lib/notifications.ts"() {
|
|
376
|
-
"use strict";
|
|
377
|
-
init_database();
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// src/lib/state-bus.ts
|
|
382
|
-
var StateBus, orgBus;
|
|
383
|
-
var init_state_bus = __esm({
|
|
384
|
-
"src/lib/state-bus.ts"() {
|
|
385
|
-
"use strict";
|
|
386
|
-
StateBus = class {
|
|
387
|
-
handlers = /* @__PURE__ */ new Map();
|
|
388
|
-
globalHandlers = /* @__PURE__ */ new Set();
|
|
389
|
-
/** Emit an event to all subscribers */
|
|
390
|
-
emit(event) {
|
|
391
|
-
const typeHandlers = this.handlers.get(event.type);
|
|
392
|
-
if (typeHandlers) {
|
|
393
|
-
for (const handler of typeHandlers) {
|
|
394
|
-
try {
|
|
395
|
-
handler(event);
|
|
396
|
-
} catch {
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
for (const handler of this.globalHandlers) {
|
|
401
|
-
try {
|
|
402
|
-
handler(event);
|
|
403
|
-
} catch {
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
/** Subscribe to a specific event type */
|
|
408
|
-
on(type, handler) {
|
|
409
|
-
if (!this.handlers.has(type)) {
|
|
410
|
-
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
411
|
-
}
|
|
412
|
-
this.handlers.get(type).add(handler);
|
|
413
|
-
}
|
|
414
|
-
/** Subscribe to ALL events */
|
|
415
|
-
onAny(handler) {
|
|
416
|
-
this.globalHandlers.add(handler);
|
|
417
|
-
}
|
|
418
|
-
/** Unsubscribe from a specific event type */
|
|
419
|
-
off(type, handler) {
|
|
420
|
-
this.handlers.get(type)?.delete(handler);
|
|
421
|
-
}
|
|
422
|
-
/** Unsubscribe from ALL events */
|
|
423
|
-
offAny(handler) {
|
|
424
|
-
this.globalHandlers.delete(handler);
|
|
425
|
-
}
|
|
426
|
-
/** Remove all listeners */
|
|
427
|
-
clear() {
|
|
428
|
-
this.handlers.clear();
|
|
429
|
-
this.globalHandlers.clear();
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
orgBus = new StateBus();
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// src/lib/session-registry.ts
|
|
437
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync4 } from "fs";
|
|
438
|
-
import path5 from "path";
|
|
439
|
-
import os5 from "os";
|
|
440
360
|
function registerSession(entry) {
|
|
441
|
-
const dir =
|
|
361
|
+
const dir = path4.dirname(REGISTRY_PATH);
|
|
442
362
|
if (!existsSync4(dir)) {
|
|
443
|
-
|
|
363
|
+
mkdirSync2(dir, { recursive: true });
|
|
444
364
|
}
|
|
445
365
|
const sessions = listSessions();
|
|
446
366
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -453,7 +373,7 @@ function registerSession(entry) {
|
|
|
453
373
|
}
|
|
454
374
|
function listSessions() {
|
|
455
375
|
try {
|
|
456
|
-
const raw =
|
|
376
|
+
const raw = readFileSync3(REGISTRY_PATH, "utf8");
|
|
457
377
|
return JSON.parse(raw);
|
|
458
378
|
} catch {
|
|
459
379
|
return [];
|
|
@@ -463,7 +383,7 @@ var REGISTRY_PATH;
|
|
|
463
383
|
var init_session_registry = __esm({
|
|
464
384
|
"src/lib/session-registry.ts"() {
|
|
465
385
|
"use strict";
|
|
466
|
-
REGISTRY_PATH =
|
|
386
|
+
REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
467
387
|
}
|
|
468
388
|
});
|
|
469
389
|
|
|
@@ -743,12 +663,12 @@ var init_runtime_table = __esm({
|
|
|
743
663
|
});
|
|
744
664
|
|
|
745
665
|
// src/lib/agent-config.ts
|
|
746
|
-
import { readFileSync as
|
|
747
|
-
import
|
|
666
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
667
|
+
import path5 from "path";
|
|
748
668
|
function loadAgentConfig() {
|
|
749
669
|
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
750
670
|
try {
|
|
751
|
-
return JSON.parse(
|
|
671
|
+
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
752
672
|
} catch {
|
|
753
673
|
return {};
|
|
754
674
|
}
|
|
@@ -767,7 +687,8 @@ var init_agent_config = __esm({
|
|
|
767
687
|
"use strict";
|
|
768
688
|
init_config();
|
|
769
689
|
init_runtime_table();
|
|
770
|
-
|
|
690
|
+
init_secure_files();
|
|
691
|
+
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
771
692
|
DEFAULT_MODELS = {
|
|
772
693
|
claude: "claude-opus-4",
|
|
773
694
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -785,17 +706,17 @@ __export(intercom_queue_exports, {
|
|
|
785
706
|
queueIntercom: () => queueIntercom,
|
|
786
707
|
readQueue: () => readQueue
|
|
787
708
|
});
|
|
788
|
-
import { readFileSync as
|
|
789
|
-
import
|
|
790
|
-
import
|
|
709
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
710
|
+
import path6 from "path";
|
|
711
|
+
import os5 from "os";
|
|
791
712
|
function ensureDir() {
|
|
792
|
-
const dir =
|
|
713
|
+
const dir = path6.dirname(QUEUE_PATH);
|
|
793
714
|
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
794
715
|
}
|
|
795
716
|
function readQueue() {
|
|
796
717
|
try {
|
|
797
718
|
if (!existsSync6(QUEUE_PATH)) return [];
|
|
798
|
-
return JSON.parse(
|
|
719
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
799
720
|
} catch {
|
|
800
721
|
return [];
|
|
801
722
|
}
|
|
@@ -895,26 +816,29 @@ var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
|
895
816
|
var init_intercom_queue = __esm({
|
|
896
817
|
"src/lib/intercom-queue.ts"() {
|
|
897
818
|
"use strict";
|
|
898
|
-
QUEUE_PATH =
|
|
819
|
+
QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
899
820
|
MAX_RETRIES = 5;
|
|
900
821
|
TTL_MS = 60 * 60 * 1e3;
|
|
901
|
-
INTERCOM_LOG =
|
|
822
|
+
INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
902
823
|
}
|
|
903
824
|
});
|
|
904
825
|
|
|
905
826
|
// src/lib/license.ts
|
|
906
|
-
import { readFileSync as
|
|
827
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
907
828
|
import { randomUUID } from "crypto";
|
|
908
|
-
import
|
|
829
|
+
import { createRequire as createRequire2 } from "module";
|
|
830
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
831
|
+
import os6 from "os";
|
|
832
|
+
import path7 from "path";
|
|
909
833
|
import { jwtVerify, importSPKI } from "jose";
|
|
910
834
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
911
835
|
var init_license = __esm({
|
|
912
836
|
"src/lib/license.ts"() {
|
|
913
837
|
"use strict";
|
|
914
838
|
init_config();
|
|
915
|
-
LICENSE_PATH =
|
|
916
|
-
CACHE_PATH =
|
|
917
|
-
DEVICE_ID_PATH =
|
|
839
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
840
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
841
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
918
842
|
PLAN_LIMITS = {
|
|
919
843
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
920
844
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -926,12 +850,12 @@ var init_license = __esm({
|
|
|
926
850
|
});
|
|
927
851
|
|
|
928
852
|
// src/lib/plan-limits.ts
|
|
929
|
-
import { readFileSync as
|
|
930
|
-
import
|
|
853
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
854
|
+
import path8 from "path";
|
|
931
855
|
function getLicenseSync() {
|
|
932
856
|
try {
|
|
933
857
|
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
934
|
-
const raw = JSON.parse(
|
|
858
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
935
859
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
936
860
|
const parts = raw.token.split(".");
|
|
937
861
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -970,7 +894,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
970
894
|
let count = 0;
|
|
971
895
|
try {
|
|
972
896
|
if (existsSync8(filePath)) {
|
|
973
|
-
const raw =
|
|
897
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
974
898
|
const employees = JSON.parse(raw);
|
|
975
899
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
976
900
|
}
|
|
@@ -999,12 +923,12 @@ var init_plan_limits = __esm({
|
|
|
999
923
|
this.name = "PlanLimitError";
|
|
1000
924
|
}
|
|
1001
925
|
};
|
|
1002
|
-
CACHE_PATH2 =
|
|
926
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
1003
927
|
}
|
|
1004
928
|
});
|
|
1005
929
|
|
|
1006
930
|
// src/lib/session-kill-telemetry.ts
|
|
1007
|
-
import
|
|
931
|
+
import crypto from "crypto";
|
|
1008
932
|
async function recordSessionKill(input) {
|
|
1009
933
|
try {
|
|
1010
934
|
const client = getClient();
|
|
@@ -1014,7 +938,7 @@ async function recordSessionKill(input) {
|
|
|
1014
938
|
ticks_idle, estimated_tokens_saved)
|
|
1015
939
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
1016
940
|
args: [
|
|
1017
|
-
|
|
941
|
+
crypto.randomUUID(),
|
|
1018
942
|
input.sessionName,
|
|
1019
943
|
input.agentId,
|
|
1020
944
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1337,6 +1261,7 @@ __export(tmux_routing_exports, {
|
|
|
1337
1261
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
1338
1262
|
isExeSession: () => isExeSession,
|
|
1339
1263
|
isSessionBusy: () => isSessionBusy,
|
|
1264
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
1340
1265
|
notifyParentExe: () => notifyParentExe,
|
|
1341
1266
|
parseParentExe: () => parseParentExe,
|
|
1342
1267
|
registerParentExe: () => registerParentExe,
|
|
@@ -1347,13 +1272,13 @@ __export(tmux_routing_exports, {
|
|
|
1347
1272
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
1348
1273
|
});
|
|
1349
1274
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
1350
|
-
import { readFileSync as
|
|
1351
|
-
import
|
|
1275
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync, readdirSync } from "fs";
|
|
1276
|
+
import path9 from "path";
|
|
1352
1277
|
import os7 from "os";
|
|
1353
1278
|
import { fileURLToPath } from "url";
|
|
1354
|
-
import { unlinkSync as
|
|
1279
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
1355
1280
|
function spawnLockPath(sessionName) {
|
|
1356
|
-
return
|
|
1281
|
+
return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
1357
1282
|
}
|
|
1358
1283
|
function isProcessAlive(pid) {
|
|
1359
1284
|
try {
|
|
@@ -1370,7 +1295,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
1370
1295
|
const lockFile = spawnLockPath(sessionName);
|
|
1371
1296
|
if (existsSync9(lockFile)) {
|
|
1372
1297
|
try {
|
|
1373
|
-
const lock = JSON.parse(
|
|
1298
|
+
const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
|
|
1374
1299
|
const age = Date.now() - lock.timestamp;
|
|
1375
1300
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
1376
1301
|
return false;
|
|
@@ -1383,15 +1308,15 @@ function acquireSpawnLock(sessionName) {
|
|
|
1383
1308
|
}
|
|
1384
1309
|
function releaseSpawnLock(sessionName) {
|
|
1385
1310
|
try {
|
|
1386
|
-
|
|
1311
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
1387
1312
|
} catch {
|
|
1388
1313
|
}
|
|
1389
1314
|
}
|
|
1390
1315
|
function resolveBehaviorsExporterScript() {
|
|
1391
1316
|
try {
|
|
1392
1317
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1393
|
-
const scriptPath =
|
|
1394
|
-
|
|
1318
|
+
const scriptPath = path9.join(
|
|
1319
|
+
path9.dirname(thisFile),
|
|
1395
1320
|
"..",
|
|
1396
1321
|
"bin",
|
|
1397
1322
|
"exe-export-behaviors.js"
|
|
@@ -1466,7 +1391,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
1466
1391
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
1467
1392
|
}
|
|
1468
1393
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
1469
|
-
const filePath =
|
|
1394
|
+
const filePath = path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
1470
1395
|
writeFileSync6(filePath, JSON.stringify({
|
|
1471
1396
|
parentExe: rootExe,
|
|
1472
1397
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -1475,7 +1400,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
1475
1400
|
}
|
|
1476
1401
|
function getParentExe(sessionKey) {
|
|
1477
1402
|
try {
|
|
1478
|
-
const data = JSON.parse(
|
|
1403
|
+
const data = JSON.parse(readFileSync8(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
1479
1404
|
return data.parentExe || null;
|
|
1480
1405
|
} catch {
|
|
1481
1406
|
return null;
|
|
@@ -1483,8 +1408,8 @@ function getParentExe(sessionKey) {
|
|
|
1483
1408
|
}
|
|
1484
1409
|
function getDispatchedBy(sessionKey) {
|
|
1485
1410
|
try {
|
|
1486
|
-
const data = JSON.parse(
|
|
1487
|
-
|
|
1411
|
+
const data = JSON.parse(readFileSync8(
|
|
1412
|
+
path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
1488
1413
|
"utf8"
|
|
1489
1414
|
));
|
|
1490
1415
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -1555,7 +1480,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
1555
1480
|
function readDebounceState() {
|
|
1556
1481
|
try {
|
|
1557
1482
|
if (!existsSync9(DEBOUNCE_FILE)) return {};
|
|
1558
|
-
const raw = JSON.parse(
|
|
1483
|
+
const raw = JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
|
|
1559
1484
|
const state = {};
|
|
1560
1485
|
for (const [key, val] of Object.entries(raw)) {
|
|
1561
1486
|
if (typeof val === "number") {
|
|
@@ -1670,7 +1595,7 @@ function sendIntercom(targetSession) {
|
|
|
1670
1595
|
try {
|
|
1671
1596
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
1672
1597
|
const agent = baseAgentName(rawAgent);
|
|
1673
|
-
const markerPath =
|
|
1598
|
+
const markerPath = path9.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
1674
1599
|
if (existsSync9(markerPath)) {
|
|
1675
1600
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
1676
1601
|
return "debounced";
|
|
@@ -1680,9 +1605,9 @@ function sendIntercom(targetSession) {
|
|
|
1680
1605
|
try {
|
|
1681
1606
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
1682
1607
|
const agent = baseAgentName(rawAgent);
|
|
1683
|
-
const taskDir =
|
|
1608
|
+
const taskDir = path9.join(process.cwd(), "exe", agent);
|
|
1684
1609
|
if (existsSync9(taskDir)) {
|
|
1685
|
-
const files =
|
|
1610
|
+
const files = readdirSync(taskDir).filter(
|
|
1686
1611
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
1687
1612
|
);
|
|
1688
1613
|
if (files.length === 0) {
|
|
@@ -1741,6 +1666,21 @@ function notifyParentExe(sessionKey) {
|
|
|
1741
1666
|
}
|
|
1742
1667
|
return true;
|
|
1743
1668
|
}
|
|
1669
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
1670
|
+
const transport = getTransport();
|
|
1671
|
+
try {
|
|
1672
|
+
const sessions = transport.listSessions();
|
|
1673
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
1674
|
+
execSync4(
|
|
1675
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
1676
|
+
{ timeout: 3e3 }
|
|
1677
|
+
);
|
|
1678
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
1679
|
+
return true;
|
|
1680
|
+
} catch {
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1744
1684
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
1745
1685
|
if (isCoordinatorName(employeeName)) {
|
|
1746
1686
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -1814,8 +1754,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1814
1754
|
const transport = getTransport();
|
|
1815
1755
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
1816
1756
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
1817
|
-
const logDir =
|
|
1818
|
-
const logFile =
|
|
1757
|
+
const logDir = path9.join(os7.homedir(), ".exe-os", "session-logs");
|
|
1758
|
+
const logFile = path9.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
1819
1759
|
if (!existsSync9(logDir)) {
|
|
1820
1760
|
mkdirSync5(logDir, { recursive: true });
|
|
1821
1761
|
}
|
|
@@ -1823,17 +1763,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1823
1763
|
let cleanupSuffix = "";
|
|
1824
1764
|
try {
|
|
1825
1765
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1826
|
-
const cleanupScript =
|
|
1766
|
+
const cleanupScript = path9.join(path9.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
1827
1767
|
if (existsSync9(cleanupScript)) {
|
|
1828
1768
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
1829
1769
|
}
|
|
1830
1770
|
} catch {
|
|
1831
1771
|
}
|
|
1832
1772
|
try {
|
|
1833
|
-
const claudeJsonPath =
|
|
1773
|
+
const claudeJsonPath = path9.join(os7.homedir(), ".claude.json");
|
|
1834
1774
|
let claudeJson = {};
|
|
1835
1775
|
try {
|
|
1836
|
-
claudeJson = JSON.parse(
|
|
1776
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
1837
1777
|
} catch {
|
|
1838
1778
|
}
|
|
1839
1779
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -1845,13 +1785,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1845
1785
|
} catch {
|
|
1846
1786
|
}
|
|
1847
1787
|
try {
|
|
1848
|
-
const settingsDir =
|
|
1788
|
+
const settingsDir = path9.join(os7.homedir(), ".claude", "projects");
|
|
1849
1789
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
1850
|
-
const projSettingsDir =
|
|
1851
|
-
const settingsPath =
|
|
1790
|
+
const projSettingsDir = path9.join(settingsDir, normalizedKey);
|
|
1791
|
+
const settingsPath = path9.join(projSettingsDir, "settings.json");
|
|
1852
1792
|
let settings = {};
|
|
1853
1793
|
try {
|
|
1854
|
-
settings = JSON.parse(
|
|
1794
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
1855
1795
|
} catch {
|
|
1856
1796
|
}
|
|
1857
1797
|
const perms = settings.permissions ?? {};
|
|
@@ -1895,7 +1835,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1895
1835
|
let behaviorsFlag = "";
|
|
1896
1836
|
let legacyFallbackWarned = false;
|
|
1897
1837
|
if (!useExeAgent && !useBinSymlink) {
|
|
1898
|
-
const identityPath =
|
|
1838
|
+
const identityPath = path9.join(
|
|
1899
1839
|
os7.homedir(),
|
|
1900
1840
|
".exe-os",
|
|
1901
1841
|
"identity",
|
|
@@ -1911,7 +1851,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1911
1851
|
}
|
|
1912
1852
|
const behaviorsFile = exportBehaviorsSync(
|
|
1913
1853
|
employeeName,
|
|
1914
|
-
|
|
1854
|
+
path9.basename(spawnCwd),
|
|
1915
1855
|
sessionName
|
|
1916
1856
|
);
|
|
1917
1857
|
if (behaviorsFile) {
|
|
@@ -1926,9 +1866,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1926
1866
|
}
|
|
1927
1867
|
let sessionContextFlag = "";
|
|
1928
1868
|
try {
|
|
1929
|
-
const ctxDir =
|
|
1869
|
+
const ctxDir = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
1930
1870
|
mkdirSync5(ctxDir, { recursive: true });
|
|
1931
|
-
const ctxFile =
|
|
1871
|
+
const ctxFile = path9.join(ctxDir, `session-context-${sessionName}.md`);
|
|
1932
1872
|
const ctxContent = [
|
|
1933
1873
|
`## Session Context`,
|
|
1934
1874
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -2012,7 +1952,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2012
1952
|
transport.pipeLog(sessionName, logFile);
|
|
2013
1953
|
try {
|
|
2014
1954
|
const mySession = getMySession();
|
|
2015
|
-
const dispatchInfo =
|
|
1955
|
+
const dispatchInfo = path9.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
2016
1956
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
2017
1957
|
dispatchedBy: mySession,
|
|
2018
1958
|
rootExe: exeSession,
|
|
@@ -2087,15 +2027,15 @@ var init_tmux_routing = __esm({
|
|
|
2087
2027
|
init_intercom_queue();
|
|
2088
2028
|
init_plan_limits();
|
|
2089
2029
|
init_employees();
|
|
2090
|
-
SPAWN_LOCK_DIR =
|
|
2091
|
-
SESSION_CACHE =
|
|
2030
|
+
SPAWN_LOCK_DIR = path9.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
2031
|
+
SESSION_CACHE = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
2092
2032
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
2093
2033
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
2094
2034
|
VERIFY_PANE_LINES = 200;
|
|
2095
2035
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
2096
2036
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
2097
|
-
INTERCOM_LOG2 =
|
|
2098
|
-
DEBOUNCE_FILE =
|
|
2037
|
+
INTERCOM_LOG2 = path9.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
2038
|
+
DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2099
2039
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2100
2040
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
2101
2041
|
}
|
|
@@ -2118,6 +2058,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
2118
2058
|
args: [scope]
|
|
2119
2059
|
};
|
|
2120
2060
|
}
|
|
2061
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
2062
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2063
|
+
if (!scope) return { sql: "", args: [] };
|
|
2064
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2065
|
+
return {
|
|
2066
|
+
sql: ` AND ${col} = ?`,
|
|
2067
|
+
args: [scope]
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2121
2070
|
var init_task_scope = __esm({
|
|
2122
2071
|
"src/lib/task-scope.ts"() {
|
|
2123
2072
|
"use strict";
|
|
@@ -2125,13 +2074,125 @@ var init_task_scope = __esm({
|
|
|
2125
2074
|
}
|
|
2126
2075
|
});
|
|
2127
2076
|
|
|
2077
|
+
// src/lib/notifications.ts
|
|
2078
|
+
import crypto2 from "crypto";
|
|
2079
|
+
import path10 from "path";
|
|
2080
|
+
import os8 from "os";
|
|
2081
|
+
import {
|
|
2082
|
+
readFileSync as readFileSync9,
|
|
2083
|
+
readdirSync as readdirSync2,
|
|
2084
|
+
unlinkSync as unlinkSync3,
|
|
2085
|
+
existsSync as existsSync10,
|
|
2086
|
+
rmdirSync
|
|
2087
|
+
} from "fs";
|
|
2088
|
+
async function writeNotification(notification) {
|
|
2089
|
+
try {
|
|
2090
|
+
const client = getClient();
|
|
2091
|
+
const id = crypto2.randomUUID();
|
|
2092
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2093
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2094
|
+
await client.execute({
|
|
2095
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
2096
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2097
|
+
args: [
|
|
2098
|
+
id,
|
|
2099
|
+
notification.agentId,
|
|
2100
|
+
notification.agentRole,
|
|
2101
|
+
notification.event,
|
|
2102
|
+
notification.project,
|
|
2103
|
+
notification.summary,
|
|
2104
|
+
notification.taskFile ?? null,
|
|
2105
|
+
sessionScope,
|
|
2106
|
+
now
|
|
2107
|
+
]
|
|
2108
|
+
});
|
|
2109
|
+
} catch (err) {
|
|
2110
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
2111
|
+
`);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2115
|
+
try {
|
|
2116
|
+
const client = getClient();
|
|
2117
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2118
|
+
await client.execute({
|
|
2119
|
+
sql: `UPDATE notifications SET read = 1
|
|
2120
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
2121
|
+
args: [taskFile, ...scope.args]
|
|
2122
|
+
});
|
|
2123
|
+
} catch {
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
var init_notifications = __esm({
|
|
2127
|
+
"src/lib/notifications.ts"() {
|
|
2128
|
+
"use strict";
|
|
2129
|
+
init_database();
|
|
2130
|
+
init_task_scope();
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
// src/lib/state-bus.ts
|
|
2135
|
+
var StateBus, orgBus;
|
|
2136
|
+
var init_state_bus = __esm({
|
|
2137
|
+
"src/lib/state-bus.ts"() {
|
|
2138
|
+
"use strict";
|
|
2139
|
+
StateBus = class {
|
|
2140
|
+
handlers = /* @__PURE__ */ new Map();
|
|
2141
|
+
globalHandlers = /* @__PURE__ */ new Set();
|
|
2142
|
+
/** Emit an event to all subscribers */
|
|
2143
|
+
emit(event) {
|
|
2144
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
2145
|
+
if (typeHandlers) {
|
|
2146
|
+
for (const handler of typeHandlers) {
|
|
2147
|
+
try {
|
|
2148
|
+
handler(event);
|
|
2149
|
+
} catch {
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
for (const handler of this.globalHandlers) {
|
|
2154
|
+
try {
|
|
2155
|
+
handler(event);
|
|
2156
|
+
} catch {
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
/** Subscribe to a specific event type */
|
|
2161
|
+
on(type, handler) {
|
|
2162
|
+
if (!this.handlers.has(type)) {
|
|
2163
|
+
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
2164
|
+
}
|
|
2165
|
+
this.handlers.get(type).add(handler);
|
|
2166
|
+
}
|
|
2167
|
+
/** Subscribe to ALL events */
|
|
2168
|
+
onAny(handler) {
|
|
2169
|
+
this.globalHandlers.add(handler);
|
|
2170
|
+
}
|
|
2171
|
+
/** Unsubscribe from a specific event type */
|
|
2172
|
+
off(type, handler) {
|
|
2173
|
+
this.handlers.get(type)?.delete(handler);
|
|
2174
|
+
}
|
|
2175
|
+
/** Unsubscribe from ALL events */
|
|
2176
|
+
offAny(handler) {
|
|
2177
|
+
this.globalHandlers.delete(handler);
|
|
2178
|
+
}
|
|
2179
|
+
/** Remove all listeners */
|
|
2180
|
+
clear() {
|
|
2181
|
+
this.handlers.clear();
|
|
2182
|
+
this.globalHandlers.clear();
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
orgBus = new StateBus();
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2128
2189
|
// src/lib/tasks-crud.ts
|
|
2129
2190
|
import crypto3 from "crypto";
|
|
2130
2191
|
import path11 from "path";
|
|
2131
|
-
import
|
|
2192
|
+
import os9 from "os";
|
|
2132
2193
|
import { execSync as execSync5 } from "child_process";
|
|
2133
2194
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2134
|
-
import { existsSync as
|
|
2195
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
2135
2196
|
async function writeCheckpoint(input) {
|
|
2136
2197
|
const client = getClient();
|
|
2137
2198
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2343,13 +2404,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2343
2404
|
});
|
|
2344
2405
|
if (input.baseDir) {
|
|
2345
2406
|
try {
|
|
2346
|
-
const EXE_OS_DIR = path11.join(
|
|
2407
|
+
const EXE_OS_DIR = path11.join(os9.homedir(), ".exe-os");
|
|
2347
2408
|
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
2348
2409
|
const mdDir = path11.dirname(mdPath);
|
|
2349
|
-
if (!
|
|
2410
|
+
if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2350
2411
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2351
2412
|
const mdContent = `# ${input.title}
|
|
2352
2413
|
|
|
2414
|
+
## MANDATORY: When done
|
|
2415
|
+
|
|
2416
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2417
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2418
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2419
|
+
|
|
2353
2420
|
**ID:** ${id}
|
|
2354
2421
|
**Status:** ${initialStatus}
|
|
2355
2422
|
**Priority:** ${input.priority}
|
|
@@ -2363,12 +2430,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2363
2430
|
## Context
|
|
2364
2431
|
|
|
2365
2432
|
${input.context}
|
|
2366
|
-
|
|
2367
|
-
## MANDATORY: When done
|
|
2368
|
-
|
|
2369
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2370
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2371
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2372
2433
|
`;
|
|
2373
2434
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2374
2435
|
} catch (err) {
|
|
@@ -2617,7 +2678,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2617
2678
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
2618
2679
|
} catch {
|
|
2619
2680
|
}
|
|
2620
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
2681
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2621
2682
|
try {
|
|
2622
2683
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
2623
2684
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -2648,7 +2709,7 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2648
2709
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2649
2710
|
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2650
2711
|
try {
|
|
2651
|
-
if (
|
|
2712
|
+
if (existsSync11(archPath)) return;
|
|
2652
2713
|
const template = [
|
|
2653
2714
|
`# ${projectName} \u2014 System Architecture`,
|
|
2654
2715
|
"",
|
|
@@ -2683,7 +2744,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2683
2744
|
async function ensureGitignoreExe(baseDir) {
|
|
2684
2745
|
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
2685
2746
|
try {
|
|
2686
|
-
if (
|
|
2747
|
+
if (existsSync11(gitignorePath)) {
|
|
2687
2748
|
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2688
2749
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2689
2750
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -2716,57 +2777,41 @@ var init_tasks_crud = __esm({
|
|
|
2716
2777
|
|
|
2717
2778
|
// src/lib/tasks-review.ts
|
|
2718
2779
|
import path12 from "path";
|
|
2719
|
-
import { existsSync as
|
|
2780
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
2720
2781
|
async function countPendingReviews(sessionScope) {
|
|
2721
2782
|
const client = getClient();
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
args: [sessionScope]
|
|
2726
|
-
});
|
|
2727
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
2728
|
-
}
|
|
2783
|
+
const scope = strictSessionScopeFilter(
|
|
2784
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2785
|
+
);
|
|
2729
2786
|
const result = await client.execute({
|
|
2730
|
-
sql:
|
|
2731
|
-
|
|
2787
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2788
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
2789
|
+
args: [...scope.args]
|
|
2732
2790
|
});
|
|
2733
2791
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2734
2792
|
}
|
|
2735
2793
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2736
2794
|
const client = getClient();
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
2741
|
-
AND session_scope = ?`,
|
|
2742
|
-
args: [sinceIso, sessionScope]
|
|
2743
|
-
});
|
|
2744
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
2745
|
-
}
|
|
2795
|
+
const scope = strictSessionScopeFilter(
|
|
2796
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2797
|
+
);
|
|
2746
2798
|
const result = await client.execute({
|
|
2747
2799
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2748
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
2749
|
-
args: [sinceIso]
|
|
2800
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
2801
|
+
args: [sinceIso, ...scope.args]
|
|
2750
2802
|
});
|
|
2751
2803
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2752
2804
|
}
|
|
2753
2805
|
async function listPendingReviews(limit, sessionScope) {
|
|
2754
2806
|
const client = getClient();
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
WHERE status = 'needs_review'
|
|
2759
|
-
AND session_scope = ?
|
|
2760
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
2761
|
-
args: [sessionScope, limit]
|
|
2762
|
-
});
|
|
2763
|
-
return result2.rows;
|
|
2764
|
-
}
|
|
2807
|
+
const scope = strictSessionScopeFilter(
|
|
2808
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2809
|
+
);
|
|
2765
2810
|
const result = await client.execute({
|
|
2766
2811
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
2767
|
-
WHERE status = 'needs_review'
|
|
2812
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
2768
2813
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
2769
|
-
args: [limit]
|
|
2814
|
+
args: [...scope.args, limit]
|
|
2770
2815
|
});
|
|
2771
2816
|
return result.rows;
|
|
2772
2817
|
}
|
|
@@ -2778,7 +2823,7 @@ async function cleanupOrphanedReviews() {
|
|
|
2778
2823
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
2779
2824
|
AND assigned_by = 'system'
|
|
2780
2825
|
AND title LIKE 'Review:%'
|
|
2781
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
2826
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
2782
2827
|
args: [now]
|
|
2783
2828
|
});
|
|
2784
2829
|
const r1b = await client.execute({
|
|
@@ -2898,7 +2943,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2898
2943
|
}
|
|
2899
2944
|
try {
|
|
2900
2945
|
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
2901
|
-
if (
|
|
2946
|
+
if (existsSync12(cacheDir)) {
|
|
2902
2947
|
for (const f of readdirSync3(cacheDir)) {
|
|
2903
2948
|
if (f.startsWith("review-notified-")) {
|
|
2904
2949
|
unlinkSync4(path12.join(cacheDir, f));
|
|
@@ -2918,6 +2963,7 @@ var init_tasks_review = __esm({
|
|
|
2918
2963
|
init_tmux_routing();
|
|
2919
2964
|
init_session_key();
|
|
2920
2965
|
init_state_bus();
|
|
2966
|
+
init_task_scope();
|
|
2921
2967
|
}
|
|
2922
2968
|
});
|
|
2923
2969
|
|
|
@@ -2974,7 +3020,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
2974
3020
|
const scScope = sessionScopeFilter();
|
|
2975
3021
|
const remaining = await client.execute({
|
|
2976
3022
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2977
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3023
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
2978
3024
|
args: [parentTaskId, ...scScope.args]
|
|
2979
3025
|
});
|
|
2980
3026
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3532,7 +3578,7 @@ async function updateTask(input) {
|
|
|
3532
3578
|
if (input.status === "in_progress") {
|
|
3533
3579
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3534
3580
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3535
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3581
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3536
3582
|
try {
|
|
3537
3583
|
unlinkSync5(cachePath);
|
|
3538
3584
|
} catch {
|
|
@@ -3540,10 +3586,10 @@ async function updateTask(input) {
|
|
|
3540
3586
|
}
|
|
3541
3587
|
} catch {
|
|
3542
3588
|
}
|
|
3543
|
-
if (input.status === "done") {
|
|
3589
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3544
3590
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3545
3591
|
}
|
|
3546
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3592
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3547
3593
|
try {
|
|
3548
3594
|
const client = getClient();
|
|
3549
3595
|
const taskTitle = String(row.title);
|
|
@@ -3559,7 +3605,7 @@ async function updateTask(input) {
|
|
|
3559
3605
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3560
3606
|
try {
|
|
3561
3607
|
const draftClient = getClient();
|
|
3562
|
-
if (input.status === "done") {
|
|
3608
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3563
3609
|
await draftClient.execute({
|
|
3564
3610
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3565
3611
|
args: [assignedAgent]
|
|
@@ -3576,7 +3622,7 @@ async function updateTask(input) {
|
|
|
3576
3622
|
try {
|
|
3577
3623
|
const client = getClient();
|
|
3578
3624
|
const cascaded = await client.execute({
|
|
3579
|
-
sql: `UPDATE tasks SET status = '
|
|
3625
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3580
3626
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3581
3627
|
args: [now, taskId]
|
|
3582
3628
|
});
|
|
@@ -3589,14 +3635,14 @@ async function updateTask(input) {
|
|
|
3589
3635
|
} catch {
|
|
3590
3636
|
}
|
|
3591
3637
|
}
|
|
3592
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3638
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
3593
3639
|
if (isTerminal) {
|
|
3594
3640
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3595
3641
|
if (!isCoordinator) {
|
|
3596
3642
|
notifyTaskDone();
|
|
3597
3643
|
}
|
|
3598
3644
|
await markTaskNotificationsRead(taskFile);
|
|
3599
|
-
if (input.status === "done") {
|
|
3645
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3600
3646
|
try {
|
|
3601
3647
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
3602
3648
|
} catch {
|
|
@@ -3616,7 +3662,7 @@ async function updateTask(input) {
|
|
|
3616
3662
|
}
|
|
3617
3663
|
}
|
|
3618
3664
|
}
|
|
3619
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3665
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3620
3666
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3621
3667
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3622
3668
|
taskId,
|