@askexenow/exe-os 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1411 -953
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +793 -485
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +566 -357
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +530 -319
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +547 -336
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +664 -431
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +1049 -680
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +448 -371
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
|
@@ -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");
|
|
@@ -312,10 +350,10 @@ __export(agent_config_exports, {
|
|
|
312
350
|
saveAgentConfig: () => saveAgentConfig,
|
|
313
351
|
setAgentRuntime: () => setAgentRuntime
|
|
314
352
|
});
|
|
315
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as
|
|
353
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
316
354
|
import path2 from "path";
|
|
317
355
|
function loadAgentConfig() {
|
|
318
|
-
if (!
|
|
356
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
319
357
|
try {
|
|
320
358
|
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
321
359
|
} catch {
|
|
@@ -324,8 +362,9 @@ function loadAgentConfig() {
|
|
|
324
362
|
}
|
|
325
363
|
function saveAgentConfig(config) {
|
|
326
364
|
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
327
|
-
|
|
365
|
+
ensurePrivateDirSync(dir);
|
|
328
366
|
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
367
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
329
368
|
}
|
|
330
369
|
function getAgentRuntime(agentId) {
|
|
331
370
|
const config = loadAgentConfig();
|
|
@@ -365,6 +404,7 @@ var init_agent_config = __esm({
|
|
|
365
404
|
"use strict";
|
|
366
405
|
init_config();
|
|
367
406
|
init_runtime_table();
|
|
407
|
+
init_secure_files();
|
|
368
408
|
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
369
409
|
KNOWN_RUNTIMES = {
|
|
370
410
|
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
@@ -412,7 +452,7 @@ __export(employees_exports, {
|
|
|
412
452
|
validateEmployeeName: () => validateEmployeeName
|
|
413
453
|
});
|
|
414
454
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
415
|
-
import { existsSync as
|
|
455
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
416
456
|
import { execSync } from "child_process";
|
|
417
457
|
import path3 from "path";
|
|
418
458
|
import os2 from "os";
|
|
@@ -451,7 +491,7 @@ function validateEmployeeName(name) {
|
|
|
451
491
|
return { valid: true };
|
|
452
492
|
}
|
|
453
493
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
454
|
-
if (!
|
|
494
|
+
if (!existsSync4(employeesPath)) {
|
|
455
495
|
return [];
|
|
456
496
|
}
|
|
457
497
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -466,7 +506,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
466
506
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
467
507
|
}
|
|
468
508
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
469
|
-
if (!
|
|
509
|
+
if (!existsSync4(employeesPath)) return [];
|
|
470
510
|
try {
|
|
471
511
|
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
472
512
|
} catch {
|
|
@@ -514,7 +554,7 @@ function appendToCoordinatorTeam(employee) {
|
|
|
514
554
|
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
515
555
|
if (!coordinator) return;
|
|
516
556
|
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
517
|
-
if (!
|
|
557
|
+
if (!existsSync4(idPath)) return;
|
|
518
558
|
const content = readFileSync3(idPath, "utf-8");
|
|
519
559
|
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
520
560
|
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
@@ -568,9 +608,9 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
568
608
|
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
569
609
|
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
570
610
|
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
571
|
-
if (
|
|
611
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
572
612
|
renameSync2(oldPath, newPath);
|
|
573
|
-
} else if (
|
|
613
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
574
614
|
const content = readFileSync3(oldPath, "utf-8");
|
|
575
615
|
writeFileSync2(newPath, content, "utf-8");
|
|
576
616
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
@@ -613,7 +653,7 @@ function registerBinSymlinks(name) {
|
|
|
613
653
|
for (const suffix of ["", "-opencode"]) {
|
|
614
654
|
const linkName = `${name}${suffix}`;
|
|
615
655
|
const linkPath = path3.join(binDir, linkName);
|
|
616
|
-
if (
|
|
656
|
+
if (existsSync4(linkPath)) {
|
|
617
657
|
skipped.push(linkName);
|
|
618
658
|
continue;
|
|
619
659
|
}
|
|
@@ -691,119 +731,12 @@ var init_database = __esm({
|
|
|
691
731
|
}
|
|
692
732
|
});
|
|
693
733
|
|
|
694
|
-
// src/lib/
|
|
695
|
-
import
|
|
734
|
+
// src/lib/session-registry.ts
|
|
735
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
696
736
|
import path5 from "path";
|
|
697
737
|
import os4 from "os";
|
|
698
|
-
import {
|
|
699
|
-
readFileSync as readFileSync4,
|
|
700
|
-
readdirSync,
|
|
701
|
-
unlinkSync as unlinkSync2,
|
|
702
|
-
existsSync as existsSync4,
|
|
703
|
-
rmdirSync
|
|
704
|
-
} from "fs";
|
|
705
|
-
async function writeNotification(notification) {
|
|
706
|
-
try {
|
|
707
|
-
const client = getClient();
|
|
708
|
-
const id = crypto.randomUUID();
|
|
709
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
710
|
-
await client.execute({
|
|
711
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
712
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
713
|
-
args: [
|
|
714
|
-
id,
|
|
715
|
-
notification.agentId,
|
|
716
|
-
notification.agentRole,
|
|
717
|
-
notification.event,
|
|
718
|
-
notification.project,
|
|
719
|
-
notification.summary,
|
|
720
|
-
notification.taskFile ?? null,
|
|
721
|
-
now
|
|
722
|
-
]
|
|
723
|
-
});
|
|
724
|
-
} catch (err) {
|
|
725
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
726
|
-
`);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
730
|
-
try {
|
|
731
|
-
const client = getClient();
|
|
732
|
-
await client.execute({
|
|
733
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
734
|
-
args: [taskFile]
|
|
735
|
-
});
|
|
736
|
-
} catch {
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
var init_notifications = __esm({
|
|
740
|
-
"src/lib/notifications.ts"() {
|
|
741
|
-
"use strict";
|
|
742
|
-
init_database();
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
// src/lib/state-bus.ts
|
|
747
|
-
var StateBus, orgBus;
|
|
748
|
-
var init_state_bus = __esm({
|
|
749
|
-
"src/lib/state-bus.ts"() {
|
|
750
|
-
"use strict";
|
|
751
|
-
StateBus = class {
|
|
752
|
-
handlers = /* @__PURE__ */ new Map();
|
|
753
|
-
globalHandlers = /* @__PURE__ */ new Set();
|
|
754
|
-
/** Emit an event to all subscribers */
|
|
755
|
-
emit(event) {
|
|
756
|
-
const typeHandlers = this.handlers.get(event.type);
|
|
757
|
-
if (typeHandlers) {
|
|
758
|
-
for (const handler of typeHandlers) {
|
|
759
|
-
try {
|
|
760
|
-
handler(event);
|
|
761
|
-
} catch {
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
for (const handler of this.globalHandlers) {
|
|
766
|
-
try {
|
|
767
|
-
handler(event);
|
|
768
|
-
} catch {
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
/** Subscribe to a specific event type */
|
|
773
|
-
on(type, handler) {
|
|
774
|
-
if (!this.handlers.has(type)) {
|
|
775
|
-
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
776
|
-
}
|
|
777
|
-
this.handlers.get(type).add(handler);
|
|
778
|
-
}
|
|
779
|
-
/** Subscribe to ALL events */
|
|
780
|
-
onAny(handler) {
|
|
781
|
-
this.globalHandlers.add(handler);
|
|
782
|
-
}
|
|
783
|
-
/** Unsubscribe from a specific event type */
|
|
784
|
-
off(type, handler) {
|
|
785
|
-
this.handlers.get(type)?.delete(handler);
|
|
786
|
-
}
|
|
787
|
-
/** Unsubscribe from ALL events */
|
|
788
|
-
offAny(handler) {
|
|
789
|
-
this.globalHandlers.delete(handler);
|
|
790
|
-
}
|
|
791
|
-
/** Remove all listeners */
|
|
792
|
-
clear() {
|
|
793
|
-
this.handlers.clear();
|
|
794
|
-
this.globalHandlers.clear();
|
|
795
|
-
}
|
|
796
|
-
};
|
|
797
|
-
orgBus = new StateBus();
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
// src/lib/session-registry.ts
|
|
802
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
803
|
-
import path6 from "path";
|
|
804
|
-
import os5 from "os";
|
|
805
738
|
function registerSession(entry) {
|
|
806
|
-
const dir =
|
|
739
|
+
const dir = path5.dirname(REGISTRY_PATH);
|
|
807
740
|
if (!existsSync5(dir)) {
|
|
808
741
|
mkdirSync2(dir, { recursive: true });
|
|
809
742
|
}
|
|
@@ -818,7 +751,7 @@ function registerSession(entry) {
|
|
|
818
751
|
}
|
|
819
752
|
function listSessions() {
|
|
820
753
|
try {
|
|
821
|
-
const raw =
|
|
754
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
822
755
|
return JSON.parse(raw);
|
|
823
756
|
} catch {
|
|
824
757
|
return [];
|
|
@@ -828,7 +761,7 @@ var REGISTRY_PATH;
|
|
|
828
761
|
var init_session_registry = __esm({
|
|
829
762
|
"src/lib/session-registry.ts"() {
|
|
830
763
|
"use strict";
|
|
831
|
-
REGISTRY_PATH =
|
|
764
|
+
REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
832
765
|
}
|
|
833
766
|
});
|
|
834
767
|
|
|
@@ -1089,17 +1022,17 @@ __export(intercom_queue_exports, {
|
|
|
1089
1022
|
queueIntercom: () => queueIntercom,
|
|
1090
1023
|
readQueue: () => readQueue
|
|
1091
1024
|
});
|
|
1092
|
-
import { readFileSync as
|
|
1093
|
-
import
|
|
1094
|
-
import
|
|
1025
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
1026
|
+
import path6 from "path";
|
|
1027
|
+
import os5 from "os";
|
|
1095
1028
|
function ensureDir() {
|
|
1096
|
-
const dir =
|
|
1029
|
+
const dir = path6.dirname(QUEUE_PATH);
|
|
1097
1030
|
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
1098
1031
|
}
|
|
1099
1032
|
function readQueue() {
|
|
1100
1033
|
try {
|
|
1101
1034
|
if (!existsSync6(QUEUE_PATH)) return [];
|
|
1102
|
-
return JSON.parse(
|
|
1035
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
1103
1036
|
} catch {
|
|
1104
1037
|
return [];
|
|
1105
1038
|
}
|
|
@@ -1199,26 +1132,29 @@ var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
|
1199
1132
|
var init_intercom_queue = __esm({
|
|
1200
1133
|
"src/lib/intercom-queue.ts"() {
|
|
1201
1134
|
"use strict";
|
|
1202
|
-
QUEUE_PATH =
|
|
1135
|
+
QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
1203
1136
|
MAX_RETRIES = 5;
|
|
1204
1137
|
TTL_MS = 60 * 60 * 1e3;
|
|
1205
|
-
INTERCOM_LOG =
|
|
1138
|
+
INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
1206
1139
|
}
|
|
1207
1140
|
});
|
|
1208
1141
|
|
|
1209
1142
|
// src/lib/license.ts
|
|
1210
|
-
import { readFileSync as
|
|
1143
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
1211
1144
|
import { randomUUID } from "crypto";
|
|
1212
|
-
import
|
|
1145
|
+
import { createRequire as createRequire2 } from "module";
|
|
1146
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1147
|
+
import os6 from "os";
|
|
1148
|
+
import path7 from "path";
|
|
1213
1149
|
import { jwtVerify, importSPKI } from "jose";
|
|
1214
1150
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1215
1151
|
var init_license = __esm({
|
|
1216
1152
|
"src/lib/license.ts"() {
|
|
1217
1153
|
"use strict";
|
|
1218
1154
|
init_config();
|
|
1219
|
-
LICENSE_PATH =
|
|
1220
|
-
CACHE_PATH =
|
|
1221
|
-
DEVICE_ID_PATH =
|
|
1155
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
1156
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1157
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1222
1158
|
PLAN_LIMITS = {
|
|
1223
1159
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1224
1160
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1230,12 +1166,12 @@ var init_license = __esm({
|
|
|
1230
1166
|
});
|
|
1231
1167
|
|
|
1232
1168
|
// src/lib/plan-limits.ts
|
|
1233
|
-
import { readFileSync as
|
|
1234
|
-
import
|
|
1169
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
1170
|
+
import path8 from "path";
|
|
1235
1171
|
function getLicenseSync() {
|
|
1236
1172
|
try {
|
|
1237
1173
|
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
1238
|
-
const raw = JSON.parse(
|
|
1174
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1239
1175
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1240
1176
|
const parts = raw.token.split(".");
|
|
1241
1177
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1274,7 +1210,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1274
1210
|
let count = 0;
|
|
1275
1211
|
try {
|
|
1276
1212
|
if (existsSync8(filePath)) {
|
|
1277
|
-
const raw =
|
|
1213
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1278
1214
|
const employees = JSON.parse(raw);
|
|
1279
1215
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1280
1216
|
}
|
|
@@ -1303,12 +1239,12 @@ var init_plan_limits = __esm({
|
|
|
1303
1239
|
this.name = "PlanLimitError";
|
|
1304
1240
|
}
|
|
1305
1241
|
};
|
|
1306
|
-
CACHE_PATH2 =
|
|
1242
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
1307
1243
|
}
|
|
1308
1244
|
});
|
|
1309
1245
|
|
|
1310
1246
|
// src/lib/session-kill-telemetry.ts
|
|
1311
|
-
import
|
|
1247
|
+
import crypto from "crypto";
|
|
1312
1248
|
async function recordSessionKill(input) {
|
|
1313
1249
|
try {
|
|
1314
1250
|
const client = getClient();
|
|
@@ -1318,7 +1254,7 @@ async function recordSessionKill(input) {
|
|
|
1318
1254
|
ticks_idle, estimated_tokens_saved)
|
|
1319
1255
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
1320
1256
|
args: [
|
|
1321
|
-
|
|
1257
|
+
crypto.randomUUID(),
|
|
1322
1258
|
input.sessionName,
|
|
1323
1259
|
input.agentId,
|
|
1324
1260
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1641,6 +1577,7 @@ __export(tmux_routing_exports, {
|
|
|
1641
1577
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
1642
1578
|
isExeSession: () => isExeSession,
|
|
1643
1579
|
isSessionBusy: () => isSessionBusy,
|
|
1580
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
1644
1581
|
notifyParentExe: () => notifyParentExe,
|
|
1645
1582
|
parseParentExe: () => parseParentExe,
|
|
1646
1583
|
registerParentExe: () => registerParentExe,
|
|
@@ -1651,13 +1588,13 @@ __export(tmux_routing_exports, {
|
|
|
1651
1588
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
1652
1589
|
});
|
|
1653
1590
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
1654
|
-
import { readFileSync as
|
|
1655
|
-
import
|
|
1591
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync, readdirSync } from "fs";
|
|
1592
|
+
import path9 from "path";
|
|
1656
1593
|
import os7 from "os";
|
|
1657
1594
|
import { fileURLToPath } from "url";
|
|
1658
|
-
import { unlinkSync as
|
|
1595
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
1659
1596
|
function spawnLockPath(sessionName) {
|
|
1660
|
-
return
|
|
1597
|
+
return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
1661
1598
|
}
|
|
1662
1599
|
function isProcessAlive(pid) {
|
|
1663
1600
|
try {
|
|
@@ -1674,7 +1611,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
1674
1611
|
const lockFile = spawnLockPath(sessionName);
|
|
1675
1612
|
if (existsSync9(lockFile)) {
|
|
1676
1613
|
try {
|
|
1677
|
-
const lock = JSON.parse(
|
|
1614
|
+
const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
|
|
1678
1615
|
const age = Date.now() - lock.timestamp;
|
|
1679
1616
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
1680
1617
|
return false;
|
|
@@ -1687,15 +1624,15 @@ function acquireSpawnLock(sessionName) {
|
|
|
1687
1624
|
}
|
|
1688
1625
|
function releaseSpawnLock(sessionName) {
|
|
1689
1626
|
try {
|
|
1690
|
-
|
|
1627
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
1691
1628
|
} catch {
|
|
1692
1629
|
}
|
|
1693
1630
|
}
|
|
1694
1631
|
function resolveBehaviorsExporterScript() {
|
|
1695
1632
|
try {
|
|
1696
1633
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1697
|
-
const scriptPath =
|
|
1698
|
-
|
|
1634
|
+
const scriptPath = path9.join(
|
|
1635
|
+
path9.dirname(thisFile),
|
|
1699
1636
|
"..",
|
|
1700
1637
|
"bin",
|
|
1701
1638
|
"exe-export-behaviors.js"
|
|
@@ -1770,7 +1707,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
1770
1707
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
1771
1708
|
}
|
|
1772
1709
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
1773
|
-
const filePath =
|
|
1710
|
+
const filePath = path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
1774
1711
|
writeFileSync6(filePath, JSON.stringify({
|
|
1775
1712
|
parentExe: rootExe,
|
|
1776
1713
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -1779,7 +1716,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
1779
1716
|
}
|
|
1780
1717
|
function getParentExe(sessionKey) {
|
|
1781
1718
|
try {
|
|
1782
|
-
const data = JSON.parse(
|
|
1719
|
+
const data = JSON.parse(readFileSync8(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
1783
1720
|
return data.parentExe || null;
|
|
1784
1721
|
} catch {
|
|
1785
1722
|
return null;
|
|
@@ -1787,8 +1724,8 @@ function getParentExe(sessionKey) {
|
|
|
1787
1724
|
}
|
|
1788
1725
|
function getDispatchedBy(sessionKey) {
|
|
1789
1726
|
try {
|
|
1790
|
-
const data = JSON.parse(
|
|
1791
|
-
|
|
1727
|
+
const data = JSON.parse(readFileSync8(
|
|
1728
|
+
path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
1792
1729
|
"utf8"
|
|
1793
1730
|
));
|
|
1794
1731
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -1859,7 +1796,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
1859
1796
|
function readDebounceState() {
|
|
1860
1797
|
try {
|
|
1861
1798
|
if (!existsSync9(DEBOUNCE_FILE)) return {};
|
|
1862
|
-
const raw = JSON.parse(
|
|
1799
|
+
const raw = JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
|
|
1863
1800
|
const state = {};
|
|
1864
1801
|
for (const [key, val] of Object.entries(raw)) {
|
|
1865
1802
|
if (typeof val === "number") {
|
|
@@ -1974,7 +1911,7 @@ function sendIntercom(targetSession) {
|
|
|
1974
1911
|
try {
|
|
1975
1912
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
1976
1913
|
const agent = baseAgentName(rawAgent);
|
|
1977
|
-
const markerPath =
|
|
1914
|
+
const markerPath = path9.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
1978
1915
|
if (existsSync9(markerPath)) {
|
|
1979
1916
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
1980
1917
|
return "debounced";
|
|
@@ -1984,9 +1921,9 @@ function sendIntercom(targetSession) {
|
|
|
1984
1921
|
try {
|
|
1985
1922
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
1986
1923
|
const agent = baseAgentName(rawAgent);
|
|
1987
|
-
const taskDir =
|
|
1924
|
+
const taskDir = path9.join(process.cwd(), "exe", agent);
|
|
1988
1925
|
if (existsSync9(taskDir)) {
|
|
1989
|
-
const files =
|
|
1926
|
+
const files = readdirSync(taskDir).filter(
|
|
1990
1927
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
1991
1928
|
);
|
|
1992
1929
|
if (files.length === 0) {
|
|
@@ -2045,6 +1982,21 @@ function notifyParentExe(sessionKey) {
|
|
|
2045
1982
|
}
|
|
2046
1983
|
return true;
|
|
2047
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
|
+
}
|
|
2048
2000
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
2049
2001
|
if (isCoordinatorName(employeeName)) {
|
|
2050
2002
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -2118,8 +2070,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2118
2070
|
const transport = getTransport();
|
|
2119
2071
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
2120
2072
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
2121
|
-
const logDir =
|
|
2122
|
-
const logFile =
|
|
2073
|
+
const logDir = path9.join(os7.homedir(), ".exe-os", "session-logs");
|
|
2074
|
+
const logFile = path9.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
2123
2075
|
if (!existsSync9(logDir)) {
|
|
2124
2076
|
mkdirSync5(logDir, { recursive: true });
|
|
2125
2077
|
}
|
|
@@ -2127,17 +2079,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2127
2079
|
let cleanupSuffix = "";
|
|
2128
2080
|
try {
|
|
2129
2081
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2130
|
-
const cleanupScript =
|
|
2082
|
+
const cleanupScript = path9.join(path9.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
2131
2083
|
if (existsSync9(cleanupScript)) {
|
|
2132
2084
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
2133
2085
|
}
|
|
2134
2086
|
} catch {
|
|
2135
2087
|
}
|
|
2136
2088
|
try {
|
|
2137
|
-
const claudeJsonPath =
|
|
2089
|
+
const claudeJsonPath = path9.join(os7.homedir(), ".claude.json");
|
|
2138
2090
|
let claudeJson = {};
|
|
2139
2091
|
try {
|
|
2140
|
-
claudeJson = JSON.parse(
|
|
2092
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
2141
2093
|
} catch {
|
|
2142
2094
|
}
|
|
2143
2095
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -2149,13 +2101,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2149
2101
|
} catch {
|
|
2150
2102
|
}
|
|
2151
2103
|
try {
|
|
2152
|
-
const settingsDir =
|
|
2104
|
+
const settingsDir = path9.join(os7.homedir(), ".claude", "projects");
|
|
2153
2105
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
2154
|
-
const projSettingsDir =
|
|
2155
|
-
const settingsPath =
|
|
2106
|
+
const projSettingsDir = path9.join(settingsDir, normalizedKey);
|
|
2107
|
+
const settingsPath = path9.join(projSettingsDir, "settings.json");
|
|
2156
2108
|
let settings = {};
|
|
2157
2109
|
try {
|
|
2158
|
-
settings = JSON.parse(
|
|
2110
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
2159
2111
|
} catch {
|
|
2160
2112
|
}
|
|
2161
2113
|
const perms = settings.permissions ?? {};
|
|
@@ -2199,7 +2151,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2199
2151
|
let behaviorsFlag = "";
|
|
2200
2152
|
let legacyFallbackWarned = false;
|
|
2201
2153
|
if (!useExeAgent && !useBinSymlink) {
|
|
2202
|
-
const identityPath2 =
|
|
2154
|
+
const identityPath2 = path9.join(
|
|
2203
2155
|
os7.homedir(),
|
|
2204
2156
|
".exe-os",
|
|
2205
2157
|
"identity",
|
|
@@ -2215,7 +2167,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2215
2167
|
}
|
|
2216
2168
|
const behaviorsFile = exportBehaviorsSync(
|
|
2217
2169
|
employeeName,
|
|
2218
|
-
|
|
2170
|
+
path9.basename(spawnCwd),
|
|
2219
2171
|
sessionName
|
|
2220
2172
|
);
|
|
2221
2173
|
if (behaviorsFile) {
|
|
@@ -2230,9 +2182,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2230
2182
|
}
|
|
2231
2183
|
let sessionContextFlag = "";
|
|
2232
2184
|
try {
|
|
2233
|
-
const ctxDir =
|
|
2185
|
+
const ctxDir = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
2234
2186
|
mkdirSync5(ctxDir, { recursive: true });
|
|
2235
|
-
const ctxFile =
|
|
2187
|
+
const ctxFile = path9.join(ctxDir, `session-context-${sessionName}.md`);
|
|
2236
2188
|
const ctxContent = [
|
|
2237
2189
|
`## Session Context`,
|
|
2238
2190
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -2316,7 +2268,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2316
2268
|
transport.pipeLog(sessionName, logFile);
|
|
2317
2269
|
try {
|
|
2318
2270
|
const mySession = getMySession();
|
|
2319
|
-
const dispatchInfo =
|
|
2271
|
+
const dispatchInfo = path9.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
2320
2272
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
2321
2273
|
dispatchedBy: mySession,
|
|
2322
2274
|
rootExe: exeSession,
|
|
@@ -2391,15 +2343,15 @@ var init_tmux_routing = __esm({
|
|
|
2391
2343
|
init_intercom_queue();
|
|
2392
2344
|
init_plan_limits();
|
|
2393
2345
|
init_employees();
|
|
2394
|
-
SPAWN_LOCK_DIR =
|
|
2395
|
-
SESSION_CACHE =
|
|
2346
|
+
SPAWN_LOCK_DIR = path9.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
2347
|
+
SESSION_CACHE = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
2396
2348
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
2397
2349
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
2398
2350
|
VERIFY_PANE_LINES = 200;
|
|
2399
2351
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
2400
2352
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
2401
|
-
INTERCOM_LOG2 =
|
|
2402
|
-
DEBOUNCE_FILE =
|
|
2353
|
+
INTERCOM_LOG2 = path9.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
2354
|
+
DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2403
2355
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2404
2356
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
2405
2357
|
}
|
|
@@ -2422,6 +2374,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
2422
2374
|
args: [scope]
|
|
2423
2375
|
};
|
|
2424
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
|
+
}
|
|
2425
2386
|
var init_task_scope = __esm({
|
|
2426
2387
|
"src/lib/task-scope.ts"() {
|
|
2427
2388
|
"use strict";
|
|
@@ -2429,13 +2390,229 @@ var init_task_scope = __esm({
|
|
|
2429
2390
|
}
|
|
2430
2391
|
});
|
|
2431
2392
|
|
|
2432
|
-
// src/lib/
|
|
2433
|
-
import
|
|
2434
|
-
import
|
|
2393
|
+
// src/lib/notifications.ts
|
|
2394
|
+
import crypto2 from "crypto";
|
|
2395
|
+
import path10 from "path";
|
|
2435
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
|
+
|
|
2505
|
+
// src/lib/project-name.ts
|
|
2436
2506
|
import { execSync as execSync5 } from "child_process";
|
|
2507
|
+
import path11 from "path";
|
|
2508
|
+
function getProjectName(cwd) {
|
|
2509
|
+
const dir = cwd ?? process.cwd();
|
|
2510
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
2511
|
+
try {
|
|
2512
|
+
let repoRoot;
|
|
2513
|
+
try {
|
|
2514
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
2515
|
+
cwd: dir,
|
|
2516
|
+
encoding: "utf8",
|
|
2517
|
+
timeout: 2e3,
|
|
2518
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2519
|
+
}).trim();
|
|
2520
|
+
repoRoot = path11.dirname(gitCommonDir);
|
|
2521
|
+
} catch {
|
|
2522
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2523
|
+
cwd: dir,
|
|
2524
|
+
encoding: "utf8",
|
|
2525
|
+
timeout: 2e3,
|
|
2526
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2527
|
+
}).trim();
|
|
2528
|
+
}
|
|
2529
|
+
_cached2 = path11.basename(repoRoot);
|
|
2530
|
+
_cachedCwd = dir;
|
|
2531
|
+
return _cached2;
|
|
2532
|
+
} catch {
|
|
2533
|
+
_cached2 = path11.basename(dir);
|
|
2534
|
+
_cachedCwd = dir;
|
|
2535
|
+
return _cached2;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
var _cached2, _cachedCwd;
|
|
2539
|
+
var init_project_name = __esm({
|
|
2540
|
+
"src/lib/project-name.ts"() {
|
|
2541
|
+
"use strict";
|
|
2542
|
+
_cached2 = null;
|
|
2543
|
+
_cachedCwd = null;
|
|
2544
|
+
}
|
|
2545
|
+
});
|
|
2546
|
+
|
|
2547
|
+
// src/lib/session-scope.ts
|
|
2548
|
+
var session_scope_exports = {};
|
|
2549
|
+
__export(session_scope_exports, {
|
|
2550
|
+
assertSessionScope: () => assertSessionScope,
|
|
2551
|
+
findSessionForProject: () => findSessionForProject,
|
|
2552
|
+
getSessionProject: () => getSessionProject
|
|
2553
|
+
});
|
|
2554
|
+
function getSessionProject(sessionName) {
|
|
2555
|
+
const sessions = listSessions();
|
|
2556
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
2557
|
+
if (!entry) return null;
|
|
2558
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
2559
|
+
return parts[parts.length - 1] ?? null;
|
|
2560
|
+
}
|
|
2561
|
+
function findSessionForProject(projectName) {
|
|
2562
|
+
const sessions = listSessions();
|
|
2563
|
+
for (const s of sessions) {
|
|
2564
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2565
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2566
|
+
}
|
|
2567
|
+
return null;
|
|
2568
|
+
}
|
|
2569
|
+
function assertSessionScope(actionType, targetProject) {
|
|
2570
|
+
try {
|
|
2571
|
+
const currentProject = getProjectName();
|
|
2572
|
+
const exeSession = resolveExeSession();
|
|
2573
|
+
if (!exeSession) {
|
|
2574
|
+
return { allowed: true, reason: "no_session" };
|
|
2575
|
+
}
|
|
2576
|
+
if (currentProject === targetProject) {
|
|
2577
|
+
return {
|
|
2578
|
+
allowed: true,
|
|
2579
|
+
reason: "same_session",
|
|
2580
|
+
currentProject,
|
|
2581
|
+
targetProject
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
process.stderr.write(
|
|
2585
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
2586
|
+
`
|
|
2587
|
+
);
|
|
2588
|
+
return {
|
|
2589
|
+
allowed: false,
|
|
2590
|
+
reason: "cross_session_denied",
|
|
2591
|
+
currentProject,
|
|
2592
|
+
targetProject,
|
|
2593
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
2594
|
+
};
|
|
2595
|
+
} catch {
|
|
2596
|
+
return { allowed: true, reason: "no_session" };
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
var init_session_scope = __esm({
|
|
2600
|
+
"src/lib/session-scope.ts"() {
|
|
2601
|
+
"use strict";
|
|
2602
|
+
init_session_registry();
|
|
2603
|
+
init_project_name();
|
|
2604
|
+
init_tmux_routing();
|
|
2605
|
+
init_employees();
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2608
|
+
|
|
2609
|
+
// src/lib/tasks-crud.ts
|
|
2610
|
+
import crypto3 from "crypto";
|
|
2611
|
+
import path12 from "path";
|
|
2612
|
+
import os9 from "os";
|
|
2613
|
+
import { execSync as execSync6 } from "child_process";
|
|
2437
2614
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2438
|
-
import { existsSync as
|
|
2615
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
2439
2616
|
async function writeCheckpoint(input) {
|
|
2440
2617
|
const client = getClient();
|
|
2441
2618
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2555,9 +2732,24 @@ async function createTaskCore(input) {
|
|
|
2555
2732
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2556
2733
|
const slug = slugify(input.title);
|
|
2557
2734
|
let earlySessionScope = null;
|
|
2735
|
+
let scopeMismatchWarning;
|
|
2558
2736
|
try {
|
|
2559
2737
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2560
|
-
|
|
2738
|
+
const resolved = resolveExeSession2();
|
|
2739
|
+
if (resolved && input.projectName) {
|
|
2740
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
2741
|
+
const sessionProject = getSessionProject2(resolved);
|
|
2742
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
2743
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
2744
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
2745
|
+
`);
|
|
2746
|
+
earlySessionScope = null;
|
|
2747
|
+
} else {
|
|
2748
|
+
earlySessionScope = resolved;
|
|
2749
|
+
}
|
|
2750
|
+
} else {
|
|
2751
|
+
earlySessionScope = resolved;
|
|
2752
|
+
}
|
|
2561
2753
|
} catch {
|
|
2562
2754
|
}
|
|
2563
2755
|
const scope = earlySessionScope ?? "default";
|
|
@@ -2608,10 +2800,14 @@ async function createTaskCore(input) {
|
|
|
2608
2800
|
${laneWarning}` : laneWarning;
|
|
2609
2801
|
}
|
|
2610
2802
|
}
|
|
2803
|
+
if (scopeMismatchWarning) {
|
|
2804
|
+
warning = warning ? `${warning}
|
|
2805
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
2806
|
+
}
|
|
2611
2807
|
if (input.baseDir) {
|
|
2612
2808
|
try {
|
|
2613
|
-
await mkdir3(
|
|
2614
|
-
await mkdir3(
|
|
2809
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2810
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2615
2811
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2616
2812
|
await ensureGitignoreExe(input.baseDir);
|
|
2617
2813
|
} catch {
|
|
@@ -2647,13 +2843,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2647
2843
|
});
|
|
2648
2844
|
if (input.baseDir) {
|
|
2649
2845
|
try {
|
|
2650
|
-
const EXE_OS_DIR =
|
|
2651
|
-
const mdPath =
|
|
2652
|
-
const mdDir =
|
|
2653
|
-
if (!
|
|
2846
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
2847
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
2848
|
+
const mdDir = path12.dirname(mdPath);
|
|
2849
|
+
if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2654
2850
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2655
2851
|
const mdContent = `# ${input.title}
|
|
2656
2852
|
|
|
2853
|
+
## MANDATORY: When done
|
|
2854
|
+
|
|
2855
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2856
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2857
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2858
|
+
|
|
2657
2859
|
**ID:** ${id}
|
|
2658
2860
|
**Status:** ${initialStatus}
|
|
2659
2861
|
**Priority:** ${input.priority}
|
|
@@ -2667,12 +2869,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2667
2869
|
## Context
|
|
2668
2870
|
|
|
2669
2871
|
${input.context}
|
|
2670
|
-
|
|
2671
|
-
## MANDATORY: When done
|
|
2672
|
-
|
|
2673
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2674
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2675
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2676
2872
|
`;
|
|
2677
2873
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2678
2874
|
} catch (err) {
|
|
@@ -2754,14 +2950,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
2754
2950
|
if (!identifier || identifier === "unknown") return true;
|
|
2755
2951
|
try {
|
|
2756
2952
|
if (identifier.startsWith("%")) {
|
|
2757
|
-
const output =
|
|
2953
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
2758
2954
|
timeout: 2e3,
|
|
2759
2955
|
encoding: "utf8",
|
|
2760
2956
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2761
2957
|
});
|
|
2762
2958
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2763
2959
|
} else {
|
|
2764
|
-
|
|
2960
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2765
2961
|
timeout: 2e3,
|
|
2766
2962
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2767
2963
|
});
|
|
@@ -2770,7 +2966,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
2770
2966
|
} catch {
|
|
2771
2967
|
if (identifier.startsWith("%")) return true;
|
|
2772
2968
|
try {
|
|
2773
|
-
|
|
2969
|
+
execSync6("tmux list-sessions", {
|
|
2774
2970
|
timeout: 2e3,
|
|
2775
2971
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2776
2972
|
});
|
|
@@ -2785,12 +2981,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
2785
2981
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
2786
2982
|
try {
|
|
2787
2983
|
const since = new Date(taskCreatedAt).toISOString();
|
|
2788
|
-
const branch =
|
|
2984
|
+
const branch = execSync6(
|
|
2789
2985
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
2790
2986
|
{ encoding: "utf8", timeout: 3e3 }
|
|
2791
2987
|
).trim();
|
|
2792
2988
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
2793
|
-
const commitCount =
|
|
2989
|
+
const commitCount = execSync6(
|
|
2794
2990
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
2795
2991
|
{ encoding: "utf8", timeout: 5e3 }
|
|
2796
2992
|
).trim();
|
|
@@ -2921,7 +3117,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2921
3117
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
2922
3118
|
} catch {
|
|
2923
3119
|
}
|
|
2924
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3120
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2925
3121
|
try {
|
|
2926
3122
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
2927
3123
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -2950,9 +3146,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2950
3146
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2951
3147
|
}
|
|
2952
3148
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2953
|
-
const archPath =
|
|
3149
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2954
3150
|
try {
|
|
2955
|
-
if (
|
|
3151
|
+
if (existsSync11(archPath)) return;
|
|
2956
3152
|
const template = [
|
|
2957
3153
|
`# ${projectName} \u2014 System Architecture`,
|
|
2958
3154
|
"",
|
|
@@ -2985,9 +3181,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2985
3181
|
}
|
|
2986
3182
|
}
|
|
2987
3183
|
async function ensureGitignoreExe(baseDir) {
|
|
2988
|
-
const gitignorePath =
|
|
3184
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
2989
3185
|
try {
|
|
2990
|
-
if (
|
|
3186
|
+
if (existsSync11(gitignorePath)) {
|
|
2991
3187
|
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2992
3188
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2993
3189
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -3019,58 +3215,42 @@ var init_tasks_crud = __esm({
|
|
|
3019
3215
|
});
|
|
3020
3216
|
|
|
3021
3217
|
// src/lib/tasks-review.ts
|
|
3022
|
-
import
|
|
3023
|
-
import { existsSync as
|
|
3218
|
+
import path13 from "path";
|
|
3219
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
3024
3220
|
async function countPendingReviews(sessionScope) {
|
|
3025
3221
|
const client = getClient();
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
args: [sessionScope]
|
|
3030
|
-
});
|
|
3031
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3032
|
-
}
|
|
3222
|
+
const scope = strictSessionScopeFilter(
|
|
3223
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3224
|
+
);
|
|
3033
3225
|
const result = await client.execute({
|
|
3034
|
-
sql:
|
|
3035
|
-
|
|
3226
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3227
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3228
|
+
args: [...scope.args]
|
|
3036
3229
|
});
|
|
3037
3230
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3038
3231
|
}
|
|
3039
3232
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3040
3233
|
const client = getClient();
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3045
|
-
AND session_scope = ?`,
|
|
3046
|
-
args: [sinceIso, sessionScope]
|
|
3047
|
-
});
|
|
3048
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3049
|
-
}
|
|
3234
|
+
const scope = strictSessionScopeFilter(
|
|
3235
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3236
|
+
);
|
|
3050
3237
|
const result = await client.execute({
|
|
3051
3238
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3052
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3053
|
-
args: [sinceIso]
|
|
3239
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3240
|
+
args: [sinceIso, ...scope.args]
|
|
3054
3241
|
});
|
|
3055
3242
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3056
3243
|
}
|
|
3057
3244
|
async function listPendingReviews(limit, sessionScope) {
|
|
3058
3245
|
const client = getClient();
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
WHERE status = 'needs_review'
|
|
3063
|
-
AND session_scope = ?
|
|
3064
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3065
|
-
args: [sessionScope, limit]
|
|
3066
|
-
});
|
|
3067
|
-
return result2.rows;
|
|
3068
|
-
}
|
|
3246
|
+
const scope = strictSessionScopeFilter(
|
|
3247
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3248
|
+
);
|
|
3069
3249
|
const result = await client.execute({
|
|
3070
3250
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3071
|
-
WHERE status = 'needs_review'
|
|
3251
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3072
3252
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3073
|
-
args: [limit]
|
|
3253
|
+
args: [...scope.args, limit]
|
|
3074
3254
|
});
|
|
3075
3255
|
return result.rows;
|
|
3076
3256
|
}
|
|
@@ -3082,7 +3262,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3082
3262
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3083
3263
|
AND assigned_by = 'system'
|
|
3084
3264
|
AND title LIKE 'Review:%'
|
|
3085
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3265
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3086
3266
|
args: [now]
|
|
3087
3267
|
});
|
|
3088
3268
|
const r1b = await client.execute({
|
|
@@ -3201,11 +3381,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3201
3381
|
);
|
|
3202
3382
|
}
|
|
3203
3383
|
try {
|
|
3204
|
-
const cacheDir =
|
|
3205
|
-
if (
|
|
3384
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
3385
|
+
if (existsSync12(cacheDir)) {
|
|
3206
3386
|
for (const f of readdirSync3(cacheDir)) {
|
|
3207
3387
|
if (f.startsWith("review-notified-")) {
|
|
3208
|
-
unlinkSync4(
|
|
3388
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
3209
3389
|
}
|
|
3210
3390
|
}
|
|
3211
3391
|
}
|
|
@@ -3222,11 +3402,12 @@ var init_tasks_review = __esm({
|
|
|
3222
3402
|
init_tmux_routing();
|
|
3223
3403
|
init_session_key();
|
|
3224
3404
|
init_state_bus();
|
|
3405
|
+
init_task_scope();
|
|
3225
3406
|
}
|
|
3226
3407
|
});
|
|
3227
3408
|
|
|
3228
3409
|
// src/lib/tasks-chain.ts
|
|
3229
|
-
import
|
|
3410
|
+
import path14 from "path";
|
|
3230
3411
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3231
3412
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3232
3413
|
const client = getClient();
|
|
@@ -3243,7 +3424,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3243
3424
|
});
|
|
3244
3425
|
for (const ur of unblockedRows.rows) {
|
|
3245
3426
|
try {
|
|
3246
|
-
const ubFile =
|
|
3427
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
3247
3428
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3248
3429
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3249
3430
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3278,7 +3459,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3278
3459
|
const scScope = sessionScopeFilter();
|
|
3279
3460
|
const remaining = await client.execute({
|
|
3280
3461
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3281
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3462
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3282
3463
|
args: [parentTaskId, ...scScope.args]
|
|
3283
3464
|
});
|
|
3284
3465
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3310,110 +3491,6 @@ var init_tasks_chain = __esm({
|
|
|
3310
3491
|
}
|
|
3311
3492
|
});
|
|
3312
3493
|
|
|
3313
|
-
// src/lib/project-name.ts
|
|
3314
|
-
import { execSync as execSync6 } from "child_process";
|
|
3315
|
-
import path14 from "path";
|
|
3316
|
-
function getProjectName(cwd) {
|
|
3317
|
-
const dir = cwd ?? process.cwd();
|
|
3318
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3319
|
-
try {
|
|
3320
|
-
let repoRoot;
|
|
3321
|
-
try {
|
|
3322
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3323
|
-
cwd: dir,
|
|
3324
|
-
encoding: "utf8",
|
|
3325
|
-
timeout: 2e3,
|
|
3326
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3327
|
-
}).trim();
|
|
3328
|
-
repoRoot = path14.dirname(gitCommonDir);
|
|
3329
|
-
} catch {
|
|
3330
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3331
|
-
cwd: dir,
|
|
3332
|
-
encoding: "utf8",
|
|
3333
|
-
timeout: 2e3,
|
|
3334
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3335
|
-
}).trim();
|
|
3336
|
-
}
|
|
3337
|
-
_cached2 = path14.basename(repoRoot);
|
|
3338
|
-
_cachedCwd = dir;
|
|
3339
|
-
return _cached2;
|
|
3340
|
-
} catch {
|
|
3341
|
-
_cached2 = path14.basename(dir);
|
|
3342
|
-
_cachedCwd = dir;
|
|
3343
|
-
return _cached2;
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
var _cached2, _cachedCwd;
|
|
3347
|
-
var init_project_name = __esm({
|
|
3348
|
-
"src/lib/project-name.ts"() {
|
|
3349
|
-
"use strict";
|
|
3350
|
-
_cached2 = null;
|
|
3351
|
-
_cachedCwd = null;
|
|
3352
|
-
}
|
|
3353
|
-
});
|
|
3354
|
-
|
|
3355
|
-
// src/lib/session-scope.ts
|
|
3356
|
-
var session_scope_exports = {};
|
|
3357
|
-
__export(session_scope_exports, {
|
|
3358
|
-
assertSessionScope: () => assertSessionScope,
|
|
3359
|
-
findSessionForProject: () => findSessionForProject,
|
|
3360
|
-
getSessionProject: () => getSessionProject
|
|
3361
|
-
});
|
|
3362
|
-
function getSessionProject(sessionName) {
|
|
3363
|
-
const sessions = listSessions();
|
|
3364
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3365
|
-
if (!entry) return null;
|
|
3366
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3367
|
-
return parts[parts.length - 1] ?? null;
|
|
3368
|
-
}
|
|
3369
|
-
function findSessionForProject(projectName) {
|
|
3370
|
-
const sessions = listSessions();
|
|
3371
|
-
for (const s of sessions) {
|
|
3372
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3373
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3374
|
-
}
|
|
3375
|
-
return null;
|
|
3376
|
-
}
|
|
3377
|
-
function assertSessionScope(actionType, targetProject) {
|
|
3378
|
-
try {
|
|
3379
|
-
const currentProject = getProjectName();
|
|
3380
|
-
const exeSession = resolveExeSession();
|
|
3381
|
-
if (!exeSession) {
|
|
3382
|
-
return { allowed: true, reason: "no_session" };
|
|
3383
|
-
}
|
|
3384
|
-
if (currentProject === targetProject) {
|
|
3385
|
-
return {
|
|
3386
|
-
allowed: true,
|
|
3387
|
-
reason: "same_session",
|
|
3388
|
-
currentProject,
|
|
3389
|
-
targetProject
|
|
3390
|
-
};
|
|
3391
|
-
}
|
|
3392
|
-
process.stderr.write(
|
|
3393
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3394
|
-
`
|
|
3395
|
-
);
|
|
3396
|
-
return {
|
|
3397
|
-
allowed: false,
|
|
3398
|
-
reason: "cross_session_denied",
|
|
3399
|
-
currentProject,
|
|
3400
|
-
targetProject,
|
|
3401
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3402
|
-
};
|
|
3403
|
-
} catch {
|
|
3404
|
-
return { allowed: true, reason: "no_session" };
|
|
3405
|
-
}
|
|
3406
|
-
}
|
|
3407
|
-
var init_session_scope = __esm({
|
|
3408
|
-
"src/lib/session-scope.ts"() {
|
|
3409
|
-
"use strict";
|
|
3410
|
-
init_session_registry();
|
|
3411
|
-
init_project_name();
|
|
3412
|
-
init_tmux_routing();
|
|
3413
|
-
init_employees();
|
|
3414
|
-
}
|
|
3415
|
-
});
|
|
3416
|
-
|
|
3417
3494
|
// src/lib/tasks-notify.ts
|
|
3418
3495
|
async function dispatchTaskToEmployee(input) {
|
|
3419
3496
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -3836,7 +3913,7 @@ async function updateTask(input) {
|
|
|
3836
3913
|
if (input.status === "in_progress") {
|
|
3837
3914
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3838
3915
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3839
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3916
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3840
3917
|
try {
|
|
3841
3918
|
unlinkSync5(cachePath);
|
|
3842
3919
|
} catch {
|
|
@@ -3844,10 +3921,10 @@ async function updateTask(input) {
|
|
|
3844
3921
|
}
|
|
3845
3922
|
} catch {
|
|
3846
3923
|
}
|
|
3847
|
-
if (input.status === "done") {
|
|
3924
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3848
3925
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3849
3926
|
}
|
|
3850
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3927
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3851
3928
|
try {
|
|
3852
3929
|
const client = getClient();
|
|
3853
3930
|
const taskTitle = String(row.title);
|
|
@@ -3863,7 +3940,7 @@ async function updateTask(input) {
|
|
|
3863
3940
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3864
3941
|
try {
|
|
3865
3942
|
const draftClient = getClient();
|
|
3866
|
-
if (input.status === "done") {
|
|
3943
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3867
3944
|
await draftClient.execute({
|
|
3868
3945
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3869
3946
|
args: [assignedAgent]
|
|
@@ -3880,7 +3957,7 @@ async function updateTask(input) {
|
|
|
3880
3957
|
try {
|
|
3881
3958
|
const client = getClient();
|
|
3882
3959
|
const cascaded = await client.execute({
|
|
3883
|
-
sql: `UPDATE tasks SET status = '
|
|
3960
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3884
3961
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3885
3962
|
args: [now, taskId]
|
|
3886
3963
|
});
|
|
@@ -3893,14 +3970,14 @@ async function updateTask(input) {
|
|
|
3893
3970
|
} catch {
|
|
3894
3971
|
}
|
|
3895
3972
|
}
|
|
3896
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3973
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
3897
3974
|
if (isTerminal) {
|
|
3898
3975
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3899
3976
|
if (!isCoordinator) {
|
|
3900
3977
|
notifyTaskDone();
|
|
3901
3978
|
}
|
|
3902
3979
|
await markTaskNotificationsRead(taskFile);
|
|
3903
|
-
if (input.status === "done") {
|
|
3980
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3904
3981
|
try {
|
|
3905
3982
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
3906
3983
|
} catch {
|
|
@@ -3920,7 +3997,7 @@ async function updateTask(input) {
|
|
|
3920
3997
|
}
|
|
3921
3998
|
}
|
|
3922
3999
|
}
|
|
3923
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4000
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3924
4001
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3925
4002
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3926
4003
|
taskId,
|
|
@@ -4001,12 +4078,12 @@ __export(identity_exports, {
|
|
|
4001
4078
|
listIdentities: () => listIdentities,
|
|
4002
4079
|
updateIdentity: () => updateIdentity
|
|
4003
4080
|
});
|
|
4004
|
-
import { existsSync as
|
|
4081
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
4005
4082
|
import { readdirSync as readdirSync5 } from "fs";
|
|
4006
4083
|
import path17 from "path";
|
|
4007
4084
|
import { createHash } from "crypto";
|
|
4008
4085
|
function ensureDir2() {
|
|
4009
|
-
if (!
|
|
4086
|
+
if (!existsSync13(IDENTITY_DIR2)) {
|
|
4010
4087
|
mkdirSync8(IDENTITY_DIR2, { recursive: true });
|
|
4011
4088
|
}
|
|
4012
4089
|
}
|
|
@@ -4052,7 +4129,7 @@ function contentHash(content) {
|
|
|
4052
4129
|
}
|
|
4053
4130
|
function getIdentity(agentId) {
|
|
4054
4131
|
const filePath = identityPath(agentId);
|
|
4055
|
-
if (!
|
|
4132
|
+
if (!existsSync13(filePath)) return null;
|
|
4056
4133
|
const raw = readFileSync12(filePath, "utf-8");
|
|
4057
4134
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
4058
4135
|
return {
|
|
@@ -4828,10 +4905,10 @@ function registerCreateTask(server) {
|
|
|
4828
4905
|
skipDispatch: true
|
|
4829
4906
|
});
|
|
4830
4907
|
try {
|
|
4831
|
-
const { existsSync:
|
|
4908
|
+
const { existsSync: existsSync14, mkdirSync: mkdirSync9, writeFileSync: writeFileSync10 } = await import("fs");
|
|
4832
4909
|
const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
|
|
4833
4910
|
const idPath = identityPath2(assigned_to);
|
|
4834
|
-
if (!
|
|
4911
|
+
if (!existsSync14(idPath)) {
|
|
4835
4912
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
4836
4913
|
const employees = await loadEmployees2();
|
|
4837
4914
|
const emp = employees.find((e) => e.name === assigned_to);
|
|
@@ -4840,7 +4917,7 @@ function registerCreateTask(server) {
|
|
|
4840
4917
|
const template = getTemplateForTitle2(emp.role);
|
|
4841
4918
|
if (template) {
|
|
4842
4919
|
const dir = (await import("path")).dirname(idPath);
|
|
4843
|
-
if (!
|
|
4920
|
+
if (!existsSync14(dir)) mkdirSync9(dir, { recursive: true });
|
|
4844
4921
|
writeFileSync10(idPath, template.replace(/^agent_id: \w+/m, `agent_id: ${assigned_to}`), "utf-8");
|
|
4845
4922
|
}
|
|
4846
4923
|
}
|