@askexenow/exe-os 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1411 -953
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +793 -485
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +566 -357
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +530 -319
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +547 -336
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +664 -431
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +1049 -680
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +448 -371
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
package/dist/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,229 @@ var init_task_scope = __esm({
|
|
|
2125
2074
|
}
|
|
2126
2075
|
});
|
|
2127
2076
|
|
|
2128
|
-
// src/lib/
|
|
2129
|
-
import
|
|
2130
|
-
import
|
|
2077
|
+
// src/lib/notifications.ts
|
|
2078
|
+
import crypto2 from "crypto";
|
|
2079
|
+
import path10 from "path";
|
|
2131
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
|
+
|
|
2189
|
+
// src/lib/project-name.ts
|
|
2132
2190
|
import { execSync as execSync5 } from "child_process";
|
|
2191
|
+
import path11 from "path";
|
|
2192
|
+
function getProjectName(cwd) {
|
|
2193
|
+
const dir = cwd ?? process.cwd();
|
|
2194
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
2195
|
+
try {
|
|
2196
|
+
let repoRoot;
|
|
2197
|
+
try {
|
|
2198
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
2199
|
+
cwd: dir,
|
|
2200
|
+
encoding: "utf8",
|
|
2201
|
+
timeout: 2e3,
|
|
2202
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2203
|
+
}).trim();
|
|
2204
|
+
repoRoot = path11.dirname(gitCommonDir);
|
|
2205
|
+
} catch {
|
|
2206
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2207
|
+
cwd: dir,
|
|
2208
|
+
encoding: "utf8",
|
|
2209
|
+
timeout: 2e3,
|
|
2210
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2211
|
+
}).trim();
|
|
2212
|
+
}
|
|
2213
|
+
_cached2 = path11.basename(repoRoot);
|
|
2214
|
+
_cachedCwd = dir;
|
|
2215
|
+
return _cached2;
|
|
2216
|
+
} catch {
|
|
2217
|
+
_cached2 = path11.basename(dir);
|
|
2218
|
+
_cachedCwd = dir;
|
|
2219
|
+
return _cached2;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
var _cached2, _cachedCwd;
|
|
2223
|
+
var init_project_name = __esm({
|
|
2224
|
+
"src/lib/project-name.ts"() {
|
|
2225
|
+
"use strict";
|
|
2226
|
+
_cached2 = null;
|
|
2227
|
+
_cachedCwd = null;
|
|
2228
|
+
}
|
|
2229
|
+
});
|
|
2230
|
+
|
|
2231
|
+
// src/lib/session-scope.ts
|
|
2232
|
+
var session_scope_exports = {};
|
|
2233
|
+
__export(session_scope_exports, {
|
|
2234
|
+
assertSessionScope: () => assertSessionScope,
|
|
2235
|
+
findSessionForProject: () => findSessionForProject,
|
|
2236
|
+
getSessionProject: () => getSessionProject
|
|
2237
|
+
});
|
|
2238
|
+
function getSessionProject(sessionName) {
|
|
2239
|
+
const sessions = listSessions();
|
|
2240
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
2241
|
+
if (!entry) return null;
|
|
2242
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
2243
|
+
return parts[parts.length - 1] ?? null;
|
|
2244
|
+
}
|
|
2245
|
+
function findSessionForProject(projectName) {
|
|
2246
|
+
const sessions = listSessions();
|
|
2247
|
+
for (const s of sessions) {
|
|
2248
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2249
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2250
|
+
}
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
function assertSessionScope(actionType, targetProject) {
|
|
2254
|
+
try {
|
|
2255
|
+
const currentProject = getProjectName();
|
|
2256
|
+
const exeSession = resolveExeSession();
|
|
2257
|
+
if (!exeSession) {
|
|
2258
|
+
return { allowed: true, reason: "no_session" };
|
|
2259
|
+
}
|
|
2260
|
+
if (currentProject === targetProject) {
|
|
2261
|
+
return {
|
|
2262
|
+
allowed: true,
|
|
2263
|
+
reason: "same_session",
|
|
2264
|
+
currentProject,
|
|
2265
|
+
targetProject
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
process.stderr.write(
|
|
2269
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
2270
|
+
`
|
|
2271
|
+
);
|
|
2272
|
+
return {
|
|
2273
|
+
allowed: false,
|
|
2274
|
+
reason: "cross_session_denied",
|
|
2275
|
+
currentProject,
|
|
2276
|
+
targetProject,
|
|
2277
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
2278
|
+
};
|
|
2279
|
+
} catch {
|
|
2280
|
+
return { allowed: true, reason: "no_session" };
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
var init_session_scope = __esm({
|
|
2284
|
+
"src/lib/session-scope.ts"() {
|
|
2285
|
+
"use strict";
|
|
2286
|
+
init_session_registry();
|
|
2287
|
+
init_project_name();
|
|
2288
|
+
init_tmux_routing();
|
|
2289
|
+
init_employees();
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
|
|
2293
|
+
// src/lib/tasks-crud.ts
|
|
2294
|
+
import crypto3 from "crypto";
|
|
2295
|
+
import path12 from "path";
|
|
2296
|
+
import os9 from "os";
|
|
2297
|
+
import { execSync as execSync6 } from "child_process";
|
|
2133
2298
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2134
|
-
import { existsSync as
|
|
2299
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
2135
2300
|
async function writeCheckpoint(input) {
|
|
2136
2301
|
const client = getClient();
|
|
2137
2302
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2251,9 +2416,24 @@ async function createTaskCore(input) {
|
|
|
2251
2416
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2252
2417
|
const slug = slugify(input.title);
|
|
2253
2418
|
let earlySessionScope = null;
|
|
2419
|
+
let scopeMismatchWarning;
|
|
2254
2420
|
try {
|
|
2255
2421
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2256
|
-
|
|
2422
|
+
const resolved = resolveExeSession2();
|
|
2423
|
+
if (resolved && input.projectName) {
|
|
2424
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
2425
|
+
const sessionProject = getSessionProject2(resolved);
|
|
2426
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
2427
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
2428
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
2429
|
+
`);
|
|
2430
|
+
earlySessionScope = null;
|
|
2431
|
+
} else {
|
|
2432
|
+
earlySessionScope = resolved;
|
|
2433
|
+
}
|
|
2434
|
+
} else {
|
|
2435
|
+
earlySessionScope = resolved;
|
|
2436
|
+
}
|
|
2257
2437
|
} catch {
|
|
2258
2438
|
}
|
|
2259
2439
|
const scope = earlySessionScope ?? "default";
|
|
@@ -2304,10 +2484,14 @@ async function createTaskCore(input) {
|
|
|
2304
2484
|
${laneWarning}` : laneWarning;
|
|
2305
2485
|
}
|
|
2306
2486
|
}
|
|
2487
|
+
if (scopeMismatchWarning) {
|
|
2488
|
+
warning = warning ? `${warning}
|
|
2489
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
2490
|
+
}
|
|
2307
2491
|
if (input.baseDir) {
|
|
2308
2492
|
try {
|
|
2309
|
-
await mkdir3(
|
|
2310
|
-
await mkdir3(
|
|
2493
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2494
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2311
2495
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2312
2496
|
await ensureGitignoreExe(input.baseDir);
|
|
2313
2497
|
} catch {
|
|
@@ -2343,13 +2527,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2343
2527
|
});
|
|
2344
2528
|
if (input.baseDir) {
|
|
2345
2529
|
try {
|
|
2346
|
-
const EXE_OS_DIR =
|
|
2347
|
-
const mdPath =
|
|
2348
|
-
const mdDir =
|
|
2349
|
-
if (!
|
|
2530
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
2531
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
2532
|
+
const mdDir = path12.dirname(mdPath);
|
|
2533
|
+
if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2350
2534
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2351
2535
|
const mdContent = `# ${input.title}
|
|
2352
2536
|
|
|
2537
|
+
## MANDATORY: When done
|
|
2538
|
+
|
|
2539
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2540
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2541
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2542
|
+
|
|
2353
2543
|
**ID:** ${id}
|
|
2354
2544
|
**Status:** ${initialStatus}
|
|
2355
2545
|
**Priority:** ${input.priority}
|
|
@@ -2363,12 +2553,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2363
2553
|
## Context
|
|
2364
2554
|
|
|
2365
2555
|
${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
2556
|
`;
|
|
2373
2557
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2374
2558
|
} catch (err) {
|
|
@@ -2450,14 +2634,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
2450
2634
|
if (!identifier || identifier === "unknown") return true;
|
|
2451
2635
|
try {
|
|
2452
2636
|
if (identifier.startsWith("%")) {
|
|
2453
|
-
const output =
|
|
2637
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
2454
2638
|
timeout: 2e3,
|
|
2455
2639
|
encoding: "utf8",
|
|
2456
2640
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2457
2641
|
});
|
|
2458
2642
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2459
2643
|
} else {
|
|
2460
|
-
|
|
2644
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2461
2645
|
timeout: 2e3,
|
|
2462
2646
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2463
2647
|
});
|
|
@@ -2466,7 +2650,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
2466
2650
|
} catch {
|
|
2467
2651
|
if (identifier.startsWith("%")) return true;
|
|
2468
2652
|
try {
|
|
2469
|
-
|
|
2653
|
+
execSync6("tmux list-sessions", {
|
|
2470
2654
|
timeout: 2e3,
|
|
2471
2655
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2472
2656
|
});
|
|
@@ -2481,12 +2665,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
2481
2665
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
2482
2666
|
try {
|
|
2483
2667
|
const since = new Date(taskCreatedAt).toISOString();
|
|
2484
|
-
const branch =
|
|
2668
|
+
const branch = execSync6(
|
|
2485
2669
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
2486
2670
|
{ encoding: "utf8", timeout: 3e3 }
|
|
2487
2671
|
).trim();
|
|
2488
2672
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
2489
|
-
const commitCount =
|
|
2673
|
+
const commitCount = execSync6(
|
|
2490
2674
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
2491
2675
|
{ encoding: "utf8", timeout: 5e3 }
|
|
2492
2676
|
).trim();
|
|
@@ -2617,7 +2801,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2617
2801
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
2618
2802
|
} catch {
|
|
2619
2803
|
}
|
|
2620
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
2804
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2621
2805
|
try {
|
|
2622
2806
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
2623
2807
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -2646,9 +2830,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2646
2830
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2647
2831
|
}
|
|
2648
2832
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2649
|
-
const archPath =
|
|
2833
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2650
2834
|
try {
|
|
2651
|
-
if (
|
|
2835
|
+
if (existsSync11(archPath)) return;
|
|
2652
2836
|
const template = [
|
|
2653
2837
|
`# ${projectName} \u2014 System Architecture`,
|
|
2654
2838
|
"",
|
|
@@ -2681,9 +2865,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2681
2865
|
}
|
|
2682
2866
|
}
|
|
2683
2867
|
async function ensureGitignoreExe(baseDir) {
|
|
2684
|
-
const gitignorePath =
|
|
2868
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
2685
2869
|
try {
|
|
2686
|
-
if (
|
|
2870
|
+
if (existsSync11(gitignorePath)) {
|
|
2687
2871
|
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2688
2872
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2689
2873
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -2715,58 +2899,42 @@ var init_tasks_crud = __esm({
|
|
|
2715
2899
|
});
|
|
2716
2900
|
|
|
2717
2901
|
// src/lib/tasks-review.ts
|
|
2718
|
-
import
|
|
2719
|
-
import { existsSync as
|
|
2902
|
+
import path13 from "path";
|
|
2903
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
2720
2904
|
async function countPendingReviews(sessionScope) {
|
|
2721
2905
|
const client = getClient();
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
args: [sessionScope]
|
|
2726
|
-
});
|
|
2727
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
2728
|
-
}
|
|
2906
|
+
const scope = strictSessionScopeFilter(
|
|
2907
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2908
|
+
);
|
|
2729
2909
|
const result = await client.execute({
|
|
2730
|
-
sql:
|
|
2731
|
-
|
|
2910
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2911
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
2912
|
+
args: [...scope.args]
|
|
2732
2913
|
});
|
|
2733
2914
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2734
2915
|
}
|
|
2735
2916
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2736
2917
|
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
|
-
}
|
|
2918
|
+
const scope = strictSessionScopeFilter(
|
|
2919
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2920
|
+
);
|
|
2746
2921
|
const result = await client.execute({
|
|
2747
2922
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2748
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
2749
|
-
args: [sinceIso]
|
|
2923
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
2924
|
+
args: [sinceIso, ...scope.args]
|
|
2750
2925
|
});
|
|
2751
2926
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2752
2927
|
}
|
|
2753
2928
|
async function listPendingReviews(limit, sessionScope) {
|
|
2754
2929
|
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
|
-
}
|
|
2930
|
+
const scope = strictSessionScopeFilter(
|
|
2931
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2932
|
+
);
|
|
2765
2933
|
const result = await client.execute({
|
|
2766
2934
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
2767
|
-
WHERE status = 'needs_review'
|
|
2935
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
2768
2936
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
2769
|
-
args: [limit]
|
|
2937
|
+
args: [...scope.args, limit]
|
|
2770
2938
|
});
|
|
2771
2939
|
return result.rows;
|
|
2772
2940
|
}
|
|
@@ -2778,7 +2946,7 @@ async function cleanupOrphanedReviews() {
|
|
|
2778
2946
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
2779
2947
|
AND assigned_by = 'system'
|
|
2780
2948
|
AND title LIKE 'Review:%'
|
|
2781
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
2949
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
2782
2950
|
args: [now]
|
|
2783
2951
|
});
|
|
2784
2952
|
const r1b = await client.execute({
|
|
@@ -2897,11 +3065,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2897
3065
|
);
|
|
2898
3066
|
}
|
|
2899
3067
|
try {
|
|
2900
|
-
const cacheDir =
|
|
2901
|
-
if (
|
|
3068
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
3069
|
+
if (existsSync12(cacheDir)) {
|
|
2902
3070
|
for (const f of readdirSync3(cacheDir)) {
|
|
2903
3071
|
if (f.startsWith("review-notified-")) {
|
|
2904
|
-
unlinkSync4(
|
|
3072
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
2905
3073
|
}
|
|
2906
3074
|
}
|
|
2907
3075
|
}
|
|
@@ -2918,11 +3086,12 @@ var init_tasks_review = __esm({
|
|
|
2918
3086
|
init_tmux_routing();
|
|
2919
3087
|
init_session_key();
|
|
2920
3088
|
init_state_bus();
|
|
3089
|
+
init_task_scope();
|
|
2921
3090
|
}
|
|
2922
3091
|
});
|
|
2923
3092
|
|
|
2924
3093
|
// src/lib/tasks-chain.ts
|
|
2925
|
-
import
|
|
3094
|
+
import path14 from "path";
|
|
2926
3095
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2927
3096
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2928
3097
|
const client = getClient();
|
|
@@ -2939,7 +3108,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2939
3108
|
});
|
|
2940
3109
|
for (const ur of unblockedRows.rows) {
|
|
2941
3110
|
try {
|
|
2942
|
-
const ubFile =
|
|
3111
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
2943
3112
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2944
3113
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2945
3114
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2974,7 +3143,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
2974
3143
|
const scScope = sessionScopeFilter();
|
|
2975
3144
|
const remaining = await client.execute({
|
|
2976
3145
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2977
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3146
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
2978
3147
|
args: [parentTaskId, ...scScope.args]
|
|
2979
3148
|
});
|
|
2980
3149
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3006,110 +3175,6 @@ var init_tasks_chain = __esm({
|
|
|
3006
3175
|
}
|
|
3007
3176
|
});
|
|
3008
3177
|
|
|
3009
|
-
// src/lib/project-name.ts
|
|
3010
|
-
import { execSync as execSync6 } from "child_process";
|
|
3011
|
-
import path14 from "path";
|
|
3012
|
-
function getProjectName(cwd) {
|
|
3013
|
-
const dir = cwd ?? process.cwd();
|
|
3014
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3015
|
-
try {
|
|
3016
|
-
let repoRoot;
|
|
3017
|
-
try {
|
|
3018
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3019
|
-
cwd: dir,
|
|
3020
|
-
encoding: "utf8",
|
|
3021
|
-
timeout: 2e3,
|
|
3022
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3023
|
-
}).trim();
|
|
3024
|
-
repoRoot = path14.dirname(gitCommonDir);
|
|
3025
|
-
} catch {
|
|
3026
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3027
|
-
cwd: dir,
|
|
3028
|
-
encoding: "utf8",
|
|
3029
|
-
timeout: 2e3,
|
|
3030
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3031
|
-
}).trim();
|
|
3032
|
-
}
|
|
3033
|
-
_cached2 = path14.basename(repoRoot);
|
|
3034
|
-
_cachedCwd = dir;
|
|
3035
|
-
return _cached2;
|
|
3036
|
-
} catch {
|
|
3037
|
-
_cached2 = path14.basename(dir);
|
|
3038
|
-
_cachedCwd = dir;
|
|
3039
|
-
return _cached2;
|
|
3040
|
-
}
|
|
3041
|
-
}
|
|
3042
|
-
var _cached2, _cachedCwd;
|
|
3043
|
-
var init_project_name = __esm({
|
|
3044
|
-
"src/lib/project-name.ts"() {
|
|
3045
|
-
"use strict";
|
|
3046
|
-
_cached2 = null;
|
|
3047
|
-
_cachedCwd = null;
|
|
3048
|
-
}
|
|
3049
|
-
});
|
|
3050
|
-
|
|
3051
|
-
// src/lib/session-scope.ts
|
|
3052
|
-
var session_scope_exports = {};
|
|
3053
|
-
__export(session_scope_exports, {
|
|
3054
|
-
assertSessionScope: () => assertSessionScope,
|
|
3055
|
-
findSessionForProject: () => findSessionForProject,
|
|
3056
|
-
getSessionProject: () => getSessionProject
|
|
3057
|
-
});
|
|
3058
|
-
function getSessionProject(sessionName) {
|
|
3059
|
-
const sessions = listSessions();
|
|
3060
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3061
|
-
if (!entry) return null;
|
|
3062
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3063
|
-
return parts[parts.length - 1] ?? null;
|
|
3064
|
-
}
|
|
3065
|
-
function findSessionForProject(projectName) {
|
|
3066
|
-
const sessions = listSessions();
|
|
3067
|
-
for (const s of sessions) {
|
|
3068
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3069
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3070
|
-
}
|
|
3071
|
-
return null;
|
|
3072
|
-
}
|
|
3073
|
-
function assertSessionScope(actionType, targetProject) {
|
|
3074
|
-
try {
|
|
3075
|
-
const currentProject = getProjectName();
|
|
3076
|
-
const exeSession = resolveExeSession();
|
|
3077
|
-
if (!exeSession) {
|
|
3078
|
-
return { allowed: true, reason: "no_session" };
|
|
3079
|
-
}
|
|
3080
|
-
if (currentProject === targetProject) {
|
|
3081
|
-
return {
|
|
3082
|
-
allowed: true,
|
|
3083
|
-
reason: "same_session",
|
|
3084
|
-
currentProject,
|
|
3085
|
-
targetProject
|
|
3086
|
-
};
|
|
3087
|
-
}
|
|
3088
|
-
process.stderr.write(
|
|
3089
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3090
|
-
`
|
|
3091
|
-
);
|
|
3092
|
-
return {
|
|
3093
|
-
allowed: false,
|
|
3094
|
-
reason: "cross_session_denied",
|
|
3095
|
-
currentProject,
|
|
3096
|
-
targetProject,
|
|
3097
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3098
|
-
};
|
|
3099
|
-
} catch {
|
|
3100
|
-
return { allowed: true, reason: "no_session" };
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
var init_session_scope = __esm({
|
|
3104
|
-
"src/lib/session-scope.ts"() {
|
|
3105
|
-
"use strict";
|
|
3106
|
-
init_session_registry();
|
|
3107
|
-
init_project_name();
|
|
3108
|
-
init_tmux_routing();
|
|
3109
|
-
init_employees();
|
|
3110
|
-
}
|
|
3111
|
-
});
|
|
3112
|
-
|
|
3113
3178
|
// src/lib/tasks-notify.ts
|
|
3114
3179
|
async function dispatchTaskToEmployee(input) {
|
|
3115
3180
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -3532,7 +3597,7 @@ async function updateTask(input) {
|
|
|
3532
3597
|
if (input.status === "in_progress") {
|
|
3533
3598
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3534
3599
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3535
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3600
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3536
3601
|
try {
|
|
3537
3602
|
unlinkSync5(cachePath);
|
|
3538
3603
|
} catch {
|
|
@@ -3540,10 +3605,10 @@ async function updateTask(input) {
|
|
|
3540
3605
|
}
|
|
3541
3606
|
} catch {
|
|
3542
3607
|
}
|
|
3543
|
-
if (input.status === "done") {
|
|
3608
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3544
3609
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3545
3610
|
}
|
|
3546
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3611
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3547
3612
|
try {
|
|
3548
3613
|
const client = getClient();
|
|
3549
3614
|
const taskTitle = String(row.title);
|
|
@@ -3559,7 +3624,7 @@ async function updateTask(input) {
|
|
|
3559
3624
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3560
3625
|
try {
|
|
3561
3626
|
const draftClient = getClient();
|
|
3562
|
-
if (input.status === "done") {
|
|
3627
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3563
3628
|
await draftClient.execute({
|
|
3564
3629
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3565
3630
|
args: [assignedAgent]
|
|
@@ -3576,7 +3641,7 @@ async function updateTask(input) {
|
|
|
3576
3641
|
try {
|
|
3577
3642
|
const client = getClient();
|
|
3578
3643
|
const cascaded = await client.execute({
|
|
3579
|
-
sql: `UPDATE tasks SET status = '
|
|
3644
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3580
3645
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3581
3646
|
args: [now, taskId]
|
|
3582
3647
|
});
|
|
@@ -3589,14 +3654,14 @@ async function updateTask(input) {
|
|
|
3589
3654
|
} catch {
|
|
3590
3655
|
}
|
|
3591
3656
|
}
|
|
3592
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3657
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
3593
3658
|
if (isTerminal) {
|
|
3594
3659
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3595
3660
|
if (!isCoordinator) {
|
|
3596
3661
|
notifyTaskDone();
|
|
3597
3662
|
}
|
|
3598
3663
|
await markTaskNotificationsRead(taskFile);
|
|
3599
|
-
if (input.status === "done") {
|
|
3664
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3600
3665
|
try {
|
|
3601
3666
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
3602
3667
|
} catch {
|
|
@@ -3616,7 +3681,7 @@ async function updateTask(input) {
|
|
|
3616
3681
|
}
|
|
3617
3682
|
}
|
|
3618
3683
|
}
|
|
3619
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3684
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3620
3685
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3621
3686
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3622
3687
|
taskId,
|