@askexenow/exe-os 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/lib/tmux-routing.js
CHANGED
|
@@ -307,9 +307,34 @@ var init_provider_table = __esm({
|
|
|
307
307
|
}
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
+
// src/lib/secure-files.ts
|
|
311
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
312
|
+
import { chmod, mkdir } from "fs/promises";
|
|
313
|
+
async function ensurePrivateDir(dirPath) {
|
|
314
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
315
|
+
try {
|
|
316
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function enforcePrivateFile(filePath) {
|
|
321
|
+
try {
|
|
322
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
327
|
+
var init_secure_files = __esm({
|
|
328
|
+
"src/lib/secure-files.ts"() {
|
|
329
|
+
"use strict";
|
|
330
|
+
PRIVATE_DIR_MODE = 448;
|
|
331
|
+
PRIVATE_FILE_MODE = 384;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
310
335
|
// src/lib/config.ts
|
|
311
|
-
import { readFile, writeFile
|
|
312
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
336
|
+
import { readFile, writeFile } from "fs/promises";
|
|
337
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
313
338
|
import path2 from "path";
|
|
314
339
|
import os2 from "os";
|
|
315
340
|
function resolveDataDir() {
|
|
@@ -317,7 +342,7 @@ function resolveDataDir() {
|
|
|
317
342
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
318
343
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
319
344
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
320
|
-
if (!
|
|
345
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
321
346
|
try {
|
|
322
347
|
renameSync(legacyDir, newDir);
|
|
323
348
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -380,9 +405,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
380
405
|
}
|
|
381
406
|
async function loadConfig() {
|
|
382
407
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
383
|
-
await
|
|
408
|
+
await ensurePrivateDir(dir);
|
|
384
409
|
const configPath = path2.join(dir, "config.json");
|
|
385
|
-
if (!
|
|
410
|
+
if (!existsSync3(configPath)) {
|
|
386
411
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
387
412
|
}
|
|
388
413
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -395,6 +420,7 @@ async function loadConfig() {
|
|
|
395
420
|
`);
|
|
396
421
|
try {
|
|
397
422
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
423
|
+
await enforcePrivateFile(configPath);
|
|
398
424
|
} catch {
|
|
399
425
|
}
|
|
400
426
|
}
|
|
@@ -414,6 +440,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
414
440
|
var init_config = __esm({
|
|
415
441
|
"src/lib/config.ts"() {
|
|
416
442
|
"use strict";
|
|
443
|
+
init_secure_files();
|
|
417
444
|
EXE_AI_DIR = resolveDataDir();
|
|
418
445
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
419
446
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -518,10 +545,10 @@ var init_runtime_table = __esm({
|
|
|
518
545
|
});
|
|
519
546
|
|
|
520
547
|
// src/lib/agent-config.ts
|
|
521
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
548
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
522
549
|
import path3 from "path";
|
|
523
550
|
function loadAgentConfig() {
|
|
524
|
-
if (!
|
|
551
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
525
552
|
try {
|
|
526
553
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
527
554
|
} catch {
|
|
@@ -542,6 +569,7 @@ var init_agent_config = __esm({
|
|
|
542
569
|
"use strict";
|
|
543
570
|
init_config();
|
|
544
571
|
init_runtime_table();
|
|
572
|
+
init_secure_files();
|
|
545
573
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
546
574
|
DEFAULT_MODELS = {
|
|
547
575
|
claude: "claude-opus-4",
|
|
@@ -560,16 +588,16 @@ __export(intercom_queue_exports, {
|
|
|
560
588
|
queueIntercom: () => queueIntercom,
|
|
561
589
|
readQueue: () => readQueue
|
|
562
590
|
});
|
|
563
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
591
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
564
592
|
import path4 from "path";
|
|
565
593
|
import os3 from "os";
|
|
566
594
|
function ensureDir() {
|
|
567
595
|
const dir = path4.dirname(QUEUE_PATH);
|
|
568
|
-
if (!
|
|
596
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
569
597
|
}
|
|
570
598
|
function readQueue() {
|
|
571
599
|
try {
|
|
572
|
-
if (!
|
|
600
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
573
601
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
574
602
|
} catch {
|
|
575
603
|
return [];
|
|
@@ -686,7 +714,7 @@ var init_db_retry = __esm({
|
|
|
686
714
|
|
|
687
715
|
// src/lib/employees.ts
|
|
688
716
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
689
|
-
import { existsSync as
|
|
717
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
690
718
|
import { execSync as execSync3 } from "child_process";
|
|
691
719
|
import path5 from "path";
|
|
692
720
|
import os4 from "os";
|
|
@@ -707,7 +735,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
707
735
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
708
736
|
}
|
|
709
737
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
710
|
-
if (!
|
|
738
|
+
if (!existsSync6(employeesPath)) return [];
|
|
711
739
|
try {
|
|
712
740
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
713
741
|
} catch {
|
|
@@ -731,7 +759,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
731
759
|
if (!emp) return false;
|
|
732
760
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
733
761
|
}
|
|
734
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
762
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
735
763
|
var init_employees = __esm({
|
|
736
764
|
"src/lib/employees.ts"() {
|
|
737
765
|
"use strict";
|
|
@@ -740,15 +768,40 @@ var init_employees = __esm({
|
|
|
740
768
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
741
769
|
COORDINATOR_ROLE = "COO";
|
|
742
770
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
771
|
+
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// src/lib/database-adapter.ts
|
|
776
|
+
import os5 from "os";
|
|
777
|
+
import path6 from "path";
|
|
778
|
+
import { createRequire } from "module";
|
|
779
|
+
import { pathToFileURL } from "url";
|
|
780
|
+
var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
|
|
781
|
+
var init_database_adapter = __esm({
|
|
782
|
+
"src/lib/database-adapter.ts"() {
|
|
783
|
+
"use strict";
|
|
784
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
785
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
786
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
787
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
788
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
789
|
+
};
|
|
790
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
791
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
792
|
+
);
|
|
743
793
|
}
|
|
744
794
|
});
|
|
745
795
|
|
|
746
796
|
// src/lib/database.ts
|
|
747
797
|
import { createClient } from "@libsql/client";
|
|
748
798
|
function getClient() {
|
|
749
|
-
if (!
|
|
799
|
+
if (!_adapterClient) {
|
|
750
800
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
751
801
|
}
|
|
802
|
+
if (process.env.DATABASE_URL) {
|
|
803
|
+
return _adapterClient;
|
|
804
|
+
}
|
|
752
805
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
753
806
|
return _resilientClient;
|
|
754
807
|
}
|
|
@@ -757,30 +810,35 @@ function getClient() {
|
|
|
757
810
|
}
|
|
758
811
|
return _resilientClient;
|
|
759
812
|
}
|
|
760
|
-
var _resilientClient, _daemonClient;
|
|
813
|
+
var _resilientClient, _daemonClient, _adapterClient;
|
|
761
814
|
var init_database = __esm({
|
|
762
815
|
"src/lib/database.ts"() {
|
|
763
816
|
"use strict";
|
|
764
817
|
init_db_retry();
|
|
765
818
|
init_employees();
|
|
819
|
+
init_database_adapter();
|
|
766
820
|
_resilientClient = null;
|
|
767
821
|
_daemonClient = null;
|
|
822
|
+
_adapterClient = null;
|
|
768
823
|
}
|
|
769
824
|
});
|
|
770
825
|
|
|
771
826
|
// src/lib/license.ts
|
|
772
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as
|
|
827
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
773
828
|
import { randomUUID } from "crypto";
|
|
774
|
-
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";
|
|
775
833
|
import { jwtVerify, importSPKI } from "jose";
|
|
776
834
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
777
835
|
var init_license = __esm({
|
|
778
836
|
"src/lib/license.ts"() {
|
|
779
837
|
"use strict";
|
|
780
838
|
init_config();
|
|
781
|
-
LICENSE_PATH =
|
|
782
|
-
CACHE_PATH =
|
|
783
|
-
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");
|
|
784
842
|
PLAN_LIMITS = {
|
|
785
843
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
786
844
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -792,11 +850,11 @@ var init_license = __esm({
|
|
|
792
850
|
});
|
|
793
851
|
|
|
794
852
|
// src/lib/plan-limits.ts
|
|
795
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
796
|
-
import
|
|
853
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
854
|
+
import path8 from "path";
|
|
797
855
|
function getLicenseSync() {
|
|
798
856
|
try {
|
|
799
|
-
if (!
|
|
857
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
800
858
|
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
801
859
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
802
860
|
const parts = raw.token.split(".");
|
|
@@ -835,7 +893,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
835
893
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
836
894
|
let count = 0;
|
|
837
895
|
try {
|
|
838
|
-
if (
|
|
896
|
+
if (existsSync8(filePath)) {
|
|
839
897
|
const raw = readFileSync7(filePath, "utf8");
|
|
840
898
|
const employees = JSON.parse(raw);
|
|
841
899
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
@@ -865,19 +923,52 @@ var init_plan_limits = __esm({
|
|
|
865
923
|
this.name = "PlanLimitError";
|
|
866
924
|
}
|
|
867
925
|
};
|
|
868
|
-
CACHE_PATH2 =
|
|
926
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
// src/lib/task-scope.ts
|
|
931
|
+
function getCurrentSessionScope() {
|
|
932
|
+
try {
|
|
933
|
+
return resolveExeSession();
|
|
934
|
+
} catch {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
939
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
940
|
+
if (!scope) return { sql: "", args: [] };
|
|
941
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
942
|
+
return {
|
|
943
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
944
|
+
args: [scope]
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
948
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
949
|
+
if (!scope) return { sql: "", args: [] };
|
|
950
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
951
|
+
return {
|
|
952
|
+
sql: ` AND ${col} = ?`,
|
|
953
|
+
args: [scope]
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
var init_task_scope = __esm({
|
|
957
|
+
"src/lib/task-scope.ts"() {
|
|
958
|
+
"use strict";
|
|
959
|
+
init_tmux_routing();
|
|
869
960
|
}
|
|
870
961
|
});
|
|
871
962
|
|
|
872
963
|
// src/lib/notifications.ts
|
|
873
964
|
import crypto from "crypto";
|
|
874
|
-
import
|
|
875
|
-
import
|
|
965
|
+
import path9 from "path";
|
|
966
|
+
import os7 from "os";
|
|
876
967
|
import {
|
|
877
968
|
readFileSync as readFileSync8,
|
|
878
969
|
readdirSync,
|
|
879
970
|
unlinkSync as unlinkSync2,
|
|
880
|
-
existsSync as
|
|
971
|
+
existsSync as existsSync9,
|
|
881
972
|
rmdirSync
|
|
882
973
|
} from "fs";
|
|
883
974
|
async function writeNotification(notification) {
|
|
@@ -885,9 +976,10 @@ async function writeNotification(notification) {
|
|
|
885
976
|
const client = getClient();
|
|
886
977
|
const id = crypto.randomUUID();
|
|
887
978
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
979
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
888
980
|
await client.execute({
|
|
889
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
890
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
981
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
982
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
891
983
|
args: [
|
|
892
984
|
id,
|
|
893
985
|
notification.agentId,
|
|
@@ -896,6 +988,7 @@ async function writeNotification(notification) {
|
|
|
896
988
|
notification.project,
|
|
897
989
|
notification.summary,
|
|
898
990
|
notification.taskFile ?? null,
|
|
991
|
+
sessionScope,
|
|
899
992
|
now
|
|
900
993
|
]
|
|
901
994
|
});
|
|
@@ -904,12 +997,14 @@ async function writeNotification(notification) {
|
|
|
904
997
|
`);
|
|
905
998
|
}
|
|
906
999
|
}
|
|
907
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
1000
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
908
1001
|
try {
|
|
909
1002
|
const client = getClient();
|
|
1003
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
910
1004
|
await client.execute({
|
|
911
|
-
sql:
|
|
912
|
-
|
|
1005
|
+
sql: `UPDATE notifications SET read = 1
|
|
1006
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
1007
|
+
args: [taskFile, ...scope.args]
|
|
913
1008
|
});
|
|
914
1009
|
} catch {
|
|
915
1010
|
}
|
|
@@ -918,6 +1013,7 @@ var init_notifications = __esm({
|
|
|
918
1013
|
"src/lib/notifications.ts"() {
|
|
919
1014
|
"use strict";
|
|
920
1015
|
init_database();
|
|
1016
|
+
init_task_scope();
|
|
921
1017
|
}
|
|
922
1018
|
});
|
|
923
1019
|
|
|
@@ -955,30 +1051,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
955
1051
|
}
|
|
956
1052
|
});
|
|
957
1053
|
|
|
958
|
-
// src/lib/task-scope.ts
|
|
959
|
-
function getCurrentSessionScope() {
|
|
960
|
-
try {
|
|
961
|
-
return resolveExeSession();
|
|
962
|
-
} catch {
|
|
963
|
-
return null;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
967
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
968
|
-
if (!scope) return { sql: "", args: [] };
|
|
969
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
970
|
-
return {
|
|
971
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
972
|
-
args: [scope]
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
var init_task_scope = __esm({
|
|
976
|
-
"src/lib/task-scope.ts"() {
|
|
977
|
-
"use strict";
|
|
978
|
-
init_tmux_routing();
|
|
979
|
-
}
|
|
980
|
-
});
|
|
981
|
-
|
|
982
1054
|
// src/lib/state-bus.ts
|
|
983
1055
|
var StateBus, orgBus;
|
|
984
1056
|
var init_state_bus = __esm({
|
|
@@ -1036,11 +1108,11 @@ var init_state_bus = __esm({
|
|
|
1036
1108
|
|
|
1037
1109
|
// src/lib/tasks-crud.ts
|
|
1038
1110
|
import crypto3 from "crypto";
|
|
1039
|
-
import
|
|
1040
|
-
import
|
|
1111
|
+
import path10 from "path";
|
|
1112
|
+
import os8 from "os";
|
|
1041
1113
|
import { execSync as execSync4 } from "child_process";
|
|
1042
1114
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1043
|
-
import { existsSync as
|
|
1115
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
1044
1116
|
async function writeCheckpoint(input) {
|
|
1045
1117
|
const client = getClient();
|
|
1046
1118
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1215,8 +1287,8 @@ ${laneWarning}` : laneWarning;
|
|
|
1215
1287
|
}
|
|
1216
1288
|
if (input.baseDir) {
|
|
1217
1289
|
try {
|
|
1218
|
-
await mkdir3(
|
|
1219
|
-
await mkdir3(
|
|
1290
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
1291
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1220
1292
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1221
1293
|
await ensureGitignoreExe(input.baseDir);
|
|
1222
1294
|
} catch {
|
|
@@ -1252,13 +1324,19 @@ ${laneWarning}` : laneWarning;
|
|
|
1252
1324
|
});
|
|
1253
1325
|
if (input.baseDir) {
|
|
1254
1326
|
try {
|
|
1255
|
-
const EXE_OS_DIR =
|
|
1256
|
-
const mdPath =
|
|
1257
|
-
const mdDir =
|
|
1258
|
-
if (!
|
|
1327
|
+
const EXE_OS_DIR = path10.join(os8.homedir(), ".exe-os");
|
|
1328
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
1329
|
+
const mdDir = path10.dirname(mdPath);
|
|
1330
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
1259
1331
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
1260
1332
|
const mdContent = `# ${input.title}
|
|
1261
1333
|
|
|
1334
|
+
## MANDATORY: When done
|
|
1335
|
+
|
|
1336
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
1337
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
1338
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
1339
|
+
|
|
1262
1340
|
**ID:** ${id}
|
|
1263
1341
|
**Status:** ${initialStatus}
|
|
1264
1342
|
**Priority:** ${input.priority}
|
|
@@ -1272,12 +1350,6 @@ ${laneWarning}` : laneWarning;
|
|
|
1272
1350
|
## Context
|
|
1273
1351
|
|
|
1274
1352
|
${input.context}
|
|
1275
|
-
|
|
1276
|
-
## MANDATORY: When done
|
|
1277
|
-
|
|
1278
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
1279
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
1280
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
1281
1353
|
`;
|
|
1282
1354
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
1283
1355
|
} catch (err) {
|
|
@@ -1526,7 +1598,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1526
1598
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
1527
1599
|
} catch {
|
|
1528
1600
|
}
|
|
1529
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
1601
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
1530
1602
|
try {
|
|
1531
1603
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
1532
1604
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -1555,9 +1627,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
1555
1627
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
1556
1628
|
}
|
|
1557
1629
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
1558
|
-
const archPath =
|
|
1630
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
1559
1631
|
try {
|
|
1560
|
-
if (
|
|
1632
|
+
if (existsSync10(archPath)) return;
|
|
1561
1633
|
const template = [
|
|
1562
1634
|
`# ${projectName} \u2014 System Architecture`,
|
|
1563
1635
|
"",
|
|
@@ -1590,9 +1662,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
1590
1662
|
}
|
|
1591
1663
|
}
|
|
1592
1664
|
async function ensureGitignoreExe(baseDir) {
|
|
1593
|
-
const gitignorePath =
|
|
1665
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
1594
1666
|
try {
|
|
1595
|
-
if (
|
|
1667
|
+
if (existsSync10(gitignorePath)) {
|
|
1596
1668
|
const content = readFileSync9(gitignorePath, "utf-8");
|
|
1597
1669
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
1598
1670
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -1624,58 +1696,42 @@ var init_tasks_crud = __esm({
|
|
|
1624
1696
|
});
|
|
1625
1697
|
|
|
1626
1698
|
// src/lib/tasks-review.ts
|
|
1627
|
-
import
|
|
1628
|
-
import { existsSync as
|
|
1699
|
+
import path11 from "path";
|
|
1700
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1629
1701
|
async function countPendingReviews(sessionScope) {
|
|
1630
1702
|
const client = getClient();
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
args: [sessionScope]
|
|
1635
|
-
});
|
|
1636
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
1637
|
-
}
|
|
1703
|
+
const scope = strictSessionScopeFilter(
|
|
1704
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
1705
|
+
);
|
|
1638
1706
|
const result = await client.execute({
|
|
1639
|
-
sql:
|
|
1640
|
-
|
|
1707
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
1708
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
1709
|
+
args: [...scope.args]
|
|
1641
1710
|
});
|
|
1642
1711
|
return Number(result.rows[0]?.cnt) || 0;
|
|
1643
1712
|
}
|
|
1644
1713
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
1645
1714
|
const client = getClient();
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
1650
|
-
AND session_scope = ?`,
|
|
1651
|
-
args: [sinceIso, sessionScope]
|
|
1652
|
-
});
|
|
1653
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
1654
|
-
}
|
|
1715
|
+
const scope = strictSessionScopeFilter(
|
|
1716
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
1717
|
+
);
|
|
1655
1718
|
const result = await client.execute({
|
|
1656
1719
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
1657
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
1658
|
-
args: [sinceIso]
|
|
1720
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
1721
|
+
args: [sinceIso, ...scope.args]
|
|
1659
1722
|
});
|
|
1660
1723
|
return Number(result.rows[0]?.cnt) || 0;
|
|
1661
1724
|
}
|
|
1662
1725
|
async function listPendingReviews(limit, sessionScope) {
|
|
1663
1726
|
const client = getClient();
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
WHERE status = 'needs_review'
|
|
1668
|
-
AND session_scope = ?
|
|
1669
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
1670
|
-
args: [sessionScope, limit]
|
|
1671
|
-
});
|
|
1672
|
-
return result2.rows;
|
|
1673
|
-
}
|
|
1727
|
+
const scope = strictSessionScopeFilter(
|
|
1728
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
1729
|
+
);
|
|
1674
1730
|
const result = await client.execute({
|
|
1675
1731
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
1676
|
-
WHERE status = 'needs_review'
|
|
1732
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
1677
1733
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
1678
|
-
args: [limit]
|
|
1734
|
+
args: [...scope.args, limit]
|
|
1679
1735
|
});
|
|
1680
1736
|
return result.rows;
|
|
1681
1737
|
}
|
|
@@ -1687,7 +1743,7 @@ async function cleanupOrphanedReviews() {
|
|
|
1687
1743
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
1688
1744
|
AND assigned_by = 'system'
|
|
1689
1745
|
AND title LIKE 'Review:%'
|
|
1690
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
1746
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
1691
1747
|
args: [now]
|
|
1692
1748
|
});
|
|
1693
1749
|
const r1b = await client.execute({
|
|
@@ -1806,11 +1862,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
1806
1862
|
);
|
|
1807
1863
|
}
|
|
1808
1864
|
try {
|
|
1809
|
-
const cacheDir =
|
|
1810
|
-
if (
|
|
1865
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
1866
|
+
if (existsSync11(cacheDir)) {
|
|
1811
1867
|
for (const f of readdirSync2(cacheDir)) {
|
|
1812
1868
|
if (f.startsWith("review-notified-")) {
|
|
1813
|
-
unlinkSync3(
|
|
1869
|
+
unlinkSync3(path11.join(cacheDir, f));
|
|
1814
1870
|
}
|
|
1815
1871
|
}
|
|
1816
1872
|
}
|
|
@@ -1827,11 +1883,12 @@ var init_tasks_review = __esm({
|
|
|
1827
1883
|
init_tmux_routing();
|
|
1828
1884
|
init_session_key();
|
|
1829
1885
|
init_state_bus();
|
|
1886
|
+
init_task_scope();
|
|
1830
1887
|
}
|
|
1831
1888
|
});
|
|
1832
1889
|
|
|
1833
1890
|
// src/lib/tasks-chain.ts
|
|
1834
|
-
import
|
|
1891
|
+
import path12 from "path";
|
|
1835
1892
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
1836
1893
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
1837
1894
|
const client = getClient();
|
|
@@ -1848,7 +1905,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
1848
1905
|
});
|
|
1849
1906
|
for (const ur of unblockedRows.rows) {
|
|
1850
1907
|
try {
|
|
1851
|
-
const ubFile =
|
|
1908
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
1852
1909
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
1853
1910
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
1854
1911
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -1883,7 +1940,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
1883
1940
|
const scScope = sessionScopeFilter();
|
|
1884
1941
|
const remaining = await client.execute({
|
|
1885
1942
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
1886
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
1943
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
1887
1944
|
args: [parentTaskId, ...scScope.args]
|
|
1888
1945
|
});
|
|
1889
1946
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -1917,7 +1974,7 @@ var init_tasks_chain = __esm({
|
|
|
1917
1974
|
|
|
1918
1975
|
// src/lib/project-name.ts
|
|
1919
1976
|
import { execSync as execSync5 } from "child_process";
|
|
1920
|
-
import
|
|
1977
|
+
import path13 from "path";
|
|
1921
1978
|
function getProjectName(cwd) {
|
|
1922
1979
|
const dir = cwd ?? process.cwd();
|
|
1923
1980
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -1930,7 +1987,7 @@ function getProjectName(cwd) {
|
|
|
1930
1987
|
timeout: 2e3,
|
|
1931
1988
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1932
1989
|
}).trim();
|
|
1933
|
-
repoRoot =
|
|
1990
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
1934
1991
|
} catch {
|
|
1935
1992
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
1936
1993
|
cwd: dir,
|
|
@@ -1939,11 +1996,11 @@ function getProjectName(cwd) {
|
|
|
1939
1996
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1940
1997
|
}).trim();
|
|
1941
1998
|
}
|
|
1942
|
-
_cached2 =
|
|
1999
|
+
_cached2 = path13.basename(repoRoot);
|
|
1943
2000
|
_cachedCwd = dir;
|
|
1944
2001
|
return _cached2;
|
|
1945
2002
|
} catch {
|
|
1946
|
-
_cached2 =
|
|
2003
|
+
_cached2 = path13.basename(dir);
|
|
1947
2004
|
_cachedCwd = dir;
|
|
1948
2005
|
return _cached2;
|
|
1949
2006
|
}
|
|
@@ -2416,7 +2473,7 @@ __export(tasks_exports, {
|
|
|
2416
2473
|
updateTaskStatus: () => updateTaskStatus,
|
|
2417
2474
|
writeCheckpoint: () => writeCheckpoint
|
|
2418
2475
|
});
|
|
2419
|
-
import
|
|
2476
|
+
import path14 from "path";
|
|
2420
2477
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
2421
2478
|
async function createTask(input) {
|
|
2422
2479
|
const result = await createTaskCore(input);
|
|
@@ -2436,12 +2493,12 @@ async function updateTask(input) {
|
|
|
2436
2493
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
2437
2494
|
try {
|
|
2438
2495
|
const agent = String(row.assigned_to);
|
|
2439
|
-
const cacheDir =
|
|
2440
|
-
const cachePath =
|
|
2496
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
2497
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
2441
2498
|
if (input.status === "in_progress") {
|
|
2442
2499
|
mkdirSync5(cacheDir, { recursive: true });
|
|
2443
2500
|
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
2444
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
2501
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
2445
2502
|
try {
|
|
2446
2503
|
unlinkSync4(cachePath);
|
|
2447
2504
|
} catch {
|
|
@@ -2449,10 +2506,10 @@ async function updateTask(input) {
|
|
|
2449
2506
|
}
|
|
2450
2507
|
} catch {
|
|
2451
2508
|
}
|
|
2452
|
-
if (input.status === "done") {
|
|
2509
|
+
if (input.status === "done" || input.status === "closed") {
|
|
2453
2510
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
2454
2511
|
}
|
|
2455
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
2512
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2456
2513
|
try {
|
|
2457
2514
|
const client = getClient();
|
|
2458
2515
|
const taskTitle = String(row.title);
|
|
@@ -2468,7 +2525,7 @@ async function updateTask(input) {
|
|
|
2468
2525
|
if (!isCoordinatorName(assignedAgent)) {
|
|
2469
2526
|
try {
|
|
2470
2527
|
const draftClient = getClient();
|
|
2471
|
-
if (input.status === "done") {
|
|
2528
|
+
if (input.status === "done" || input.status === "closed") {
|
|
2472
2529
|
await draftClient.execute({
|
|
2473
2530
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
2474
2531
|
args: [assignedAgent]
|
|
@@ -2485,7 +2542,7 @@ async function updateTask(input) {
|
|
|
2485
2542
|
try {
|
|
2486
2543
|
const client = getClient();
|
|
2487
2544
|
const cascaded = await client.execute({
|
|
2488
|
-
sql: `UPDATE tasks SET status = '
|
|
2545
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
2489
2546
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
2490
2547
|
args: [now, taskId]
|
|
2491
2548
|
});
|
|
@@ -2498,14 +2555,14 @@ async function updateTask(input) {
|
|
|
2498
2555
|
} catch {
|
|
2499
2556
|
}
|
|
2500
2557
|
}
|
|
2501
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
2558
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
2502
2559
|
if (isTerminal) {
|
|
2503
2560
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
2504
2561
|
if (!isCoordinator) {
|
|
2505
2562
|
notifyTaskDone();
|
|
2506
2563
|
}
|
|
2507
2564
|
await markTaskNotificationsRead(taskFile);
|
|
2508
|
-
if (input.status === "done") {
|
|
2565
|
+
if (input.status === "done" || input.status === "closed") {
|
|
2509
2566
|
try {
|
|
2510
2567
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
2511
2568
|
} catch {
|
|
@@ -2525,7 +2582,7 @@ async function updateTask(input) {
|
|
|
2525
2582
|
}
|
|
2526
2583
|
}
|
|
2527
2584
|
}
|
|
2528
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
2585
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
2529
2586
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
2530
2587
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
2531
2588
|
taskId,
|
|
@@ -2897,6 +2954,7 @@ __export(tmux_routing_exports, {
|
|
|
2897
2954
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
2898
2955
|
isExeSession: () => isExeSession,
|
|
2899
2956
|
isSessionBusy: () => isSessionBusy,
|
|
2957
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
2900
2958
|
notifyParentExe: () => notifyParentExe,
|
|
2901
2959
|
parseParentExe: () => parseParentExe,
|
|
2902
2960
|
registerParentExe: () => registerParentExe,
|
|
@@ -2907,13 +2965,13 @@ __export(tmux_routing_exports, {
|
|
|
2907
2965
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
2908
2966
|
});
|
|
2909
2967
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
2910
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as
|
|
2911
|
-
import
|
|
2912
|
-
import
|
|
2968
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
2969
|
+
import path15 from "path";
|
|
2970
|
+
import os9 from "os";
|
|
2913
2971
|
import { fileURLToPath } from "url";
|
|
2914
2972
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
2915
2973
|
function spawnLockPath(sessionName) {
|
|
2916
|
-
return
|
|
2974
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2917
2975
|
}
|
|
2918
2976
|
function isProcessAlive(pid) {
|
|
2919
2977
|
try {
|
|
@@ -2924,11 +2982,11 @@ function isProcessAlive(pid) {
|
|
|
2924
2982
|
}
|
|
2925
2983
|
}
|
|
2926
2984
|
function acquireSpawnLock(sessionName) {
|
|
2927
|
-
if (!
|
|
2985
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
2928
2986
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
2929
2987
|
}
|
|
2930
2988
|
const lockFile = spawnLockPath(sessionName);
|
|
2931
|
-
if (
|
|
2989
|
+
if (existsSync12(lockFile)) {
|
|
2932
2990
|
try {
|
|
2933
2991
|
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
2934
2992
|
const age = Date.now() - lock.timestamp;
|
|
@@ -2950,13 +3008,13 @@ function releaseSpawnLock(sessionName) {
|
|
|
2950
3008
|
function resolveBehaviorsExporterScript() {
|
|
2951
3009
|
try {
|
|
2952
3010
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2953
|
-
const scriptPath =
|
|
2954
|
-
|
|
3011
|
+
const scriptPath = path15.join(
|
|
3012
|
+
path15.dirname(thisFile),
|
|
2955
3013
|
"..",
|
|
2956
3014
|
"bin",
|
|
2957
3015
|
"exe-export-behaviors.js"
|
|
2958
3016
|
);
|
|
2959
|
-
return
|
|
3017
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
2960
3018
|
} catch {
|
|
2961
3019
|
return null;
|
|
2962
3020
|
}
|
|
@@ -3022,11 +3080,11 @@ function extractRootExe(name) {
|
|
|
3022
3080
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3023
3081
|
}
|
|
3024
3082
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3025
|
-
if (!
|
|
3083
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
3026
3084
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3027
3085
|
}
|
|
3028
3086
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3029
|
-
const filePath =
|
|
3087
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3030
3088
|
writeFileSync7(filePath, JSON.stringify({
|
|
3031
3089
|
parentExe: rootExe,
|
|
3032
3090
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3035,7 +3093,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3035
3093
|
}
|
|
3036
3094
|
function getParentExe(sessionKey) {
|
|
3037
3095
|
try {
|
|
3038
|
-
const data = JSON.parse(readFileSync10(
|
|
3096
|
+
const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3039
3097
|
return data.parentExe || null;
|
|
3040
3098
|
} catch {
|
|
3041
3099
|
return null;
|
|
@@ -3044,7 +3102,7 @@ function getParentExe(sessionKey) {
|
|
|
3044
3102
|
function getDispatchedBy(sessionKey) {
|
|
3045
3103
|
try {
|
|
3046
3104
|
const data = JSON.parse(readFileSync10(
|
|
3047
|
-
|
|
3105
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3048
3106
|
"utf8"
|
|
3049
3107
|
));
|
|
3050
3108
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3114,7 +3172,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3114
3172
|
}
|
|
3115
3173
|
function readDebounceState() {
|
|
3116
3174
|
try {
|
|
3117
|
-
if (!
|
|
3175
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
3118
3176
|
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3119
3177
|
const state = {};
|
|
3120
3178
|
for (const [key, val] of Object.entries(raw)) {
|
|
@@ -3131,7 +3189,7 @@ function readDebounceState() {
|
|
|
3131
3189
|
}
|
|
3132
3190
|
function writeDebounceState(state) {
|
|
3133
3191
|
try {
|
|
3134
|
-
if (!
|
|
3192
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3135
3193
|
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3136
3194
|
} catch {
|
|
3137
3195
|
}
|
|
@@ -3230,8 +3288,8 @@ function sendIntercom(targetSession) {
|
|
|
3230
3288
|
try {
|
|
3231
3289
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
3232
3290
|
const agent = baseAgentName(rawAgent);
|
|
3233
|
-
const markerPath =
|
|
3234
|
-
if (
|
|
3291
|
+
const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
3292
|
+
if (existsSync12(markerPath)) {
|
|
3235
3293
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
3236
3294
|
return "debounced";
|
|
3237
3295
|
}
|
|
@@ -3240,8 +3298,8 @@ function sendIntercom(targetSession) {
|
|
|
3240
3298
|
try {
|
|
3241
3299
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
3242
3300
|
const agent = baseAgentName(rawAgent);
|
|
3243
|
-
const taskDir =
|
|
3244
|
-
if (
|
|
3301
|
+
const taskDir = path15.join(process.cwd(), "exe", agent);
|
|
3302
|
+
if (existsSync12(taskDir)) {
|
|
3245
3303
|
const files = readdirSync3(taskDir).filter(
|
|
3246
3304
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
3247
3305
|
);
|
|
@@ -3301,6 +3359,21 @@ function notifyParentExe(sessionKey) {
|
|
|
3301
3359
|
}
|
|
3302
3360
|
return true;
|
|
3303
3361
|
}
|
|
3362
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
3363
|
+
const transport = getTransport();
|
|
3364
|
+
try {
|
|
3365
|
+
const sessions = transport.listSessions();
|
|
3366
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
3367
|
+
execSync6(
|
|
3368
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
3369
|
+
{ timeout: 3e3 }
|
|
3370
|
+
);
|
|
3371
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
3372
|
+
return true;
|
|
3373
|
+
} catch {
|
|
3374
|
+
return false;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3304
3377
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3305
3378
|
if (isCoordinatorName(employeeName)) {
|
|
3306
3379
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -3374,23 +3447,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3374
3447
|
const transport = getTransport();
|
|
3375
3448
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3376
3449
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3377
|
-
const logDir =
|
|
3378
|
-
const logFile =
|
|
3379
|
-
if (!
|
|
3450
|
+
const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
|
|
3451
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3452
|
+
if (!existsSync12(logDir)) {
|
|
3380
3453
|
mkdirSync6(logDir, { recursive: true });
|
|
3381
3454
|
}
|
|
3382
3455
|
transport.kill(sessionName);
|
|
3383
3456
|
let cleanupSuffix = "";
|
|
3384
3457
|
try {
|
|
3385
3458
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3386
|
-
const cleanupScript =
|
|
3387
|
-
if (
|
|
3459
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
3460
|
+
if (existsSync12(cleanupScript)) {
|
|
3388
3461
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3389
3462
|
}
|
|
3390
3463
|
} catch {
|
|
3391
3464
|
}
|
|
3392
3465
|
try {
|
|
3393
|
-
const claudeJsonPath =
|
|
3466
|
+
const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
|
|
3394
3467
|
let claudeJson = {};
|
|
3395
3468
|
try {
|
|
3396
3469
|
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
@@ -3405,10 +3478,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3405
3478
|
} catch {
|
|
3406
3479
|
}
|
|
3407
3480
|
try {
|
|
3408
|
-
const settingsDir =
|
|
3481
|
+
const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
|
|
3409
3482
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3410
|
-
const projSettingsDir =
|
|
3411
|
-
const settingsPath =
|
|
3483
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
3484
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
3412
3485
|
let settings = {};
|
|
3413
3486
|
try {
|
|
3414
3487
|
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
@@ -3455,8 +3528,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3455
3528
|
let behaviorsFlag = "";
|
|
3456
3529
|
let legacyFallbackWarned = false;
|
|
3457
3530
|
if (!useExeAgent && !useBinSymlink) {
|
|
3458
|
-
const identityPath =
|
|
3459
|
-
|
|
3531
|
+
const identityPath = path15.join(
|
|
3532
|
+
os9.homedir(),
|
|
3460
3533
|
".exe-os",
|
|
3461
3534
|
"identity",
|
|
3462
3535
|
`${employeeName}.md`
|
|
@@ -3465,13 +3538,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3465
3538
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
3466
3539
|
if (hasAgentFlag) {
|
|
3467
3540
|
identityFlag = ` --agent ${employeeName}`;
|
|
3468
|
-
} else if (
|
|
3541
|
+
} else if (existsSync12(identityPath)) {
|
|
3469
3542
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
3470
3543
|
legacyFallbackWarned = true;
|
|
3471
3544
|
}
|
|
3472
3545
|
const behaviorsFile = exportBehaviorsSync(
|
|
3473
3546
|
employeeName,
|
|
3474
|
-
|
|
3547
|
+
path15.basename(spawnCwd),
|
|
3475
3548
|
sessionName
|
|
3476
3549
|
);
|
|
3477
3550
|
if (behaviorsFile) {
|
|
@@ -3486,9 +3559,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3486
3559
|
}
|
|
3487
3560
|
let sessionContextFlag = "";
|
|
3488
3561
|
try {
|
|
3489
|
-
const ctxDir =
|
|
3562
|
+
const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
3490
3563
|
mkdirSync6(ctxDir, { recursive: true });
|
|
3491
|
-
const ctxFile =
|
|
3564
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
3492
3565
|
const ctxContent = [
|
|
3493
3566
|
`## Session Context`,
|
|
3494
3567
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -3572,7 +3645,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3572
3645
|
transport.pipeLog(sessionName, logFile);
|
|
3573
3646
|
try {
|
|
3574
3647
|
const mySession = getMySession();
|
|
3575
|
-
const dispatchInfo =
|
|
3648
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
3576
3649
|
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
3577
3650
|
dispatchedBy: mySession,
|
|
3578
3651
|
rootExe: exeSession,
|
|
@@ -3646,15 +3719,15 @@ var init_tmux_routing = __esm({
|
|
|
3646
3719
|
init_intercom_queue();
|
|
3647
3720
|
init_plan_limits();
|
|
3648
3721
|
init_employees();
|
|
3649
|
-
SPAWN_LOCK_DIR =
|
|
3650
|
-
SESSION_CACHE =
|
|
3722
|
+
SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
3723
|
+
SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
3651
3724
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3652
3725
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3653
3726
|
VERIFY_PANE_LINES = 200;
|
|
3654
3727
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3655
3728
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
3656
|
-
INTERCOM_LOG2 =
|
|
3657
|
-
DEBOUNCE_FILE =
|
|
3729
|
+
INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
3730
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3658
3731
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3659
3732
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
3660
3733
|
}
|
|
@@ -3673,6 +3746,7 @@ export {
|
|
|
3673
3746
|
isEmployeeAlive,
|
|
3674
3747
|
isExeSession,
|
|
3675
3748
|
isSessionBusy,
|
|
3749
|
+
notifyCoordinatorTaskCompletion,
|
|
3676
3750
|
notifyParentExe,
|
|
3677
3751
|
parseParentExe,
|
|
3678
3752
|
registerParentExe,
|