@askexenow/exe-os 0.8.85 → 0.8.86
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/cleanup-stale-review-tasks.js +57 -19
- package/dist/bin/cli.js +507 -337
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +3 -3
- package/dist/bin/exe-boot.js +344 -346
- package/dist/bin/exe-dispatch.js +375 -250
- package/dist/bin/exe-forget.js +5 -1
- package/dist/bin/exe-gateway.js +260 -135
- package/dist/bin/exe-healthcheck.js +133 -1
- package/dist/bin/exe-heartbeat.js +72 -31
- package/dist/bin/exe-link.js +25 -2
- package/dist/bin/exe-new-employee.js +22 -0
- package/dist/bin/exe-pending-messages.js +55 -17
- package/dist/bin/exe-pending-reviews.js +57 -19
- package/dist/bin/exe-search.js +6 -2
- package/dist/bin/exe-session-cleanup.js +260 -135
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +57 -19
- package/dist/bin/git-sweep.js +391 -266
- package/dist/bin/install.js +22 -0
- package/dist/bin/scan-tasks.js +394 -269
- package/dist/bin/setup.js +47 -2
- package/dist/gateway/index.js +257 -132
- package/dist/hooks/bug-report-worker.js +242 -117
- package/dist/hooks/commit-complete.js +389 -264
- package/dist/hooks/error-recall.js +6 -2
- package/dist/hooks/ingest-worker.js +314 -193
- package/dist/hooks/post-compact.js +84 -46
- package/dist/hooks/pre-compact.js +272 -147
- package/dist/hooks/pre-tool-use.js +104 -66
- package/dist/hooks/prompt-submit.js +126 -66
- package/dist/hooks/session-end.js +277 -152
- package/dist/hooks/session-start.js +70 -28
- package/dist/hooks/stop.js +90 -52
- package/dist/hooks/subagent-stop.js +84 -46
- package/dist/hooks/summary-worker.js +175 -114
- package/dist/index.js +296 -171
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +25 -2
- package/dist/lib/exe-daemon.js +338 -213
- package/dist/lib/hybrid-search.js +7 -2
- package/dist/lib/messaging.js +95 -39
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/tasks.js +242 -117
- package/dist/lib/tmux-routing.js +314 -189
- package/dist/mcp/server.js +573 -274
- package/dist/mcp/tools/create-task.js +260 -135
- package/dist/mcp/tools/list-tasks.js +68 -30
- package/dist/mcp/tools/send-message.js +100 -44
- package/dist/mcp/tools/update-task.js +123 -67
- package/dist/runtime/index.js +276 -151
- package/dist/tui/App.js +479 -354
- package/package.json +1 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
package/dist/index.js
CHANGED
|
@@ -757,18 +757,69 @@ var init_provider_table = __esm({
|
|
|
757
757
|
}
|
|
758
758
|
});
|
|
759
759
|
|
|
760
|
-
// src/lib/
|
|
761
|
-
|
|
760
|
+
// src/lib/runtime-table.ts
|
|
761
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
762
|
+
var init_runtime_table = __esm({
|
|
763
|
+
"src/lib/runtime-table.ts"() {
|
|
764
|
+
"use strict";
|
|
765
|
+
RUNTIME_TABLE = {
|
|
766
|
+
codex: {
|
|
767
|
+
binary: "codex",
|
|
768
|
+
launchMode: "exec",
|
|
769
|
+
autoApproveFlag: "--full-auto",
|
|
770
|
+
inlineFlag: "--no-alt-screen",
|
|
771
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
772
|
+
defaultModel: "gpt-5.4"
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
DEFAULT_RUNTIME = "claude";
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// src/lib/agent-config.ts
|
|
780
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
762
781
|
import path5 from "path";
|
|
782
|
+
function loadAgentConfig() {
|
|
783
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
784
|
+
try {
|
|
785
|
+
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
786
|
+
} catch {
|
|
787
|
+
return {};
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function getAgentRuntime(agentId) {
|
|
791
|
+
const config2 = loadAgentConfig();
|
|
792
|
+
const entry = config2[agentId];
|
|
793
|
+
if (entry) return entry;
|
|
794
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
795
|
+
}
|
|
796
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
797
|
+
var init_agent_config = __esm({
|
|
798
|
+
"src/lib/agent-config.ts"() {
|
|
799
|
+
"use strict";
|
|
800
|
+
init_config();
|
|
801
|
+
init_runtime_table();
|
|
802
|
+
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
803
|
+
DEFAULT_MODELS = {
|
|
804
|
+
claude: "claude-opus-4",
|
|
805
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
806
|
+
opencode: "minimax-m2.7"
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// src/lib/intercom-queue.ts
|
|
812
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
813
|
+
import path6 from "path";
|
|
763
814
|
import os5 from "os";
|
|
764
815
|
function ensureDir() {
|
|
765
|
-
const dir =
|
|
766
|
-
if (!
|
|
816
|
+
const dir = path6.dirname(QUEUE_PATH);
|
|
817
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
767
818
|
}
|
|
768
819
|
function readQueue() {
|
|
769
820
|
try {
|
|
770
|
-
if (!
|
|
771
|
-
return JSON.parse(
|
|
821
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
822
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
772
823
|
} catch {
|
|
773
824
|
return [];
|
|
774
825
|
}
|
|
@@ -776,7 +827,7 @@ function readQueue() {
|
|
|
776
827
|
function writeQueue(queue) {
|
|
777
828
|
ensureDir();
|
|
778
829
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
779
|
-
|
|
830
|
+
writeFileSync4(tmp, JSON.stringify(queue, null, 2));
|
|
780
831
|
renameSync3(tmp, QUEUE_PATH);
|
|
781
832
|
}
|
|
782
833
|
function queueIntercom(targetSession, reason) {
|
|
@@ -800,9 +851,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
800
851
|
var init_intercom_queue = __esm({
|
|
801
852
|
"src/lib/intercom-queue.ts"() {
|
|
802
853
|
"use strict";
|
|
803
|
-
QUEUE_PATH =
|
|
854
|
+
QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
804
855
|
TTL_MS = 60 * 60 * 1e3;
|
|
805
|
-
INTERCOM_LOG =
|
|
856
|
+
INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
806
857
|
}
|
|
807
858
|
});
|
|
808
859
|
|
|
@@ -865,8 +916,8 @@ var init_db_retry = __esm({
|
|
|
865
916
|
import net from "net";
|
|
866
917
|
import { spawn } from "child_process";
|
|
867
918
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
868
|
-
import { existsSync as
|
|
869
|
-
import
|
|
919
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
|
|
920
|
+
import path7 from "path";
|
|
870
921
|
import { fileURLToPath } from "url";
|
|
871
922
|
function handleData(chunk) {
|
|
872
923
|
_buffer += chunk.toString();
|
|
@@ -894,9 +945,9 @@ function handleData(chunk) {
|
|
|
894
945
|
}
|
|
895
946
|
}
|
|
896
947
|
function cleanupStaleFiles() {
|
|
897
|
-
if (
|
|
948
|
+
if (existsSync6(PID_PATH)) {
|
|
898
949
|
try {
|
|
899
|
-
const pid = parseInt(
|
|
950
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
900
951
|
if (pid > 0) {
|
|
901
952
|
try {
|
|
902
953
|
process.kill(pid, 0);
|
|
@@ -917,11 +968,11 @@ function cleanupStaleFiles() {
|
|
|
917
968
|
}
|
|
918
969
|
}
|
|
919
970
|
function findPackageRoot() {
|
|
920
|
-
let dir =
|
|
921
|
-
const { root } =
|
|
971
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
972
|
+
const { root } = path7.parse(dir);
|
|
922
973
|
while (dir !== root) {
|
|
923
|
-
if (
|
|
924
|
-
dir =
|
|
974
|
+
if (existsSync6(path7.join(dir, "package.json"))) return dir;
|
|
975
|
+
dir = path7.dirname(dir);
|
|
925
976
|
}
|
|
926
977
|
return null;
|
|
927
978
|
}
|
|
@@ -931,8 +982,8 @@ function spawnDaemon() {
|
|
|
931
982
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
932
983
|
return;
|
|
933
984
|
}
|
|
934
|
-
const daemonPath =
|
|
935
|
-
if (!
|
|
985
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
986
|
+
if (!existsSync6(daemonPath)) {
|
|
936
987
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
937
988
|
`);
|
|
938
989
|
return;
|
|
@@ -940,7 +991,7 @@ function spawnDaemon() {
|
|
|
940
991
|
const resolvedPath = daemonPath;
|
|
941
992
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
942
993
|
`);
|
|
943
|
-
const logPath =
|
|
994
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
944
995
|
let stderrFd = "ignore";
|
|
945
996
|
try {
|
|
946
997
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1090,9 +1141,9 @@ async function pingDaemon() {
|
|
|
1090
1141
|
}
|
|
1091
1142
|
function killAndRespawnDaemon() {
|
|
1092
1143
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1093
|
-
if (
|
|
1144
|
+
if (existsSync6(PID_PATH)) {
|
|
1094
1145
|
try {
|
|
1095
|
-
const pid = parseInt(
|
|
1146
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
1096
1147
|
if (pid > 0) {
|
|
1097
1148
|
try {
|
|
1098
1149
|
process.kill(pid, "SIGKILL");
|
|
@@ -1179,9 +1230,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1179
1230
|
"src/lib/exe-daemon-client.ts"() {
|
|
1180
1231
|
"use strict";
|
|
1181
1232
|
init_config();
|
|
1182
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1183
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1184
|
-
SPAWN_LOCK_PATH =
|
|
1233
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
1234
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
1235
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1185
1236
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1186
1237
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1187
1238
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -2408,18 +2459,18 @@ var init_database = __esm({
|
|
|
2408
2459
|
});
|
|
2409
2460
|
|
|
2410
2461
|
// src/lib/license.ts
|
|
2411
|
-
import { readFileSync as
|
|
2462
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2412
2463
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2413
|
-
import
|
|
2464
|
+
import path8 from "path";
|
|
2414
2465
|
import { jwtVerify, importSPKI } from "jose";
|
|
2415
2466
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2416
2467
|
var init_license = __esm({
|
|
2417
2468
|
"src/lib/license.ts"() {
|
|
2418
2469
|
"use strict";
|
|
2419
2470
|
init_config();
|
|
2420
|
-
LICENSE_PATH =
|
|
2421
|
-
CACHE_PATH =
|
|
2422
|
-
DEVICE_ID_PATH =
|
|
2471
|
+
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
2472
|
+
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2473
|
+
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
2423
2474
|
PLAN_LIMITS = {
|
|
2424
2475
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2425
2476
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2431,12 +2482,12 @@ var init_license = __esm({
|
|
|
2431
2482
|
});
|
|
2432
2483
|
|
|
2433
2484
|
// src/lib/plan-limits.ts
|
|
2434
|
-
import { readFileSync as
|
|
2435
|
-
import
|
|
2485
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
2486
|
+
import path9 from "path";
|
|
2436
2487
|
function getLicenseSync() {
|
|
2437
2488
|
try {
|
|
2438
|
-
if (!
|
|
2439
|
-
const raw = JSON.parse(
|
|
2489
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2490
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
2440
2491
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2441
2492
|
const parts = raw.token.split(".");
|
|
2442
2493
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2474,8 +2525,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2474
2525
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2475
2526
|
let count = 0;
|
|
2476
2527
|
try {
|
|
2477
|
-
if (
|
|
2478
|
-
const raw =
|
|
2528
|
+
if (existsSync8(filePath)) {
|
|
2529
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
2479
2530
|
const employees = JSON.parse(raw);
|
|
2480
2531
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2481
2532
|
}
|
|
@@ -2504,19 +2555,19 @@ var init_plan_limits = __esm({
|
|
|
2504
2555
|
this.name = "PlanLimitError";
|
|
2505
2556
|
}
|
|
2506
2557
|
};
|
|
2507
|
-
CACHE_PATH2 =
|
|
2558
|
+
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2508
2559
|
}
|
|
2509
2560
|
});
|
|
2510
2561
|
|
|
2511
2562
|
// src/lib/notifications.ts
|
|
2512
2563
|
import crypto from "crypto";
|
|
2513
|
-
import
|
|
2564
|
+
import path10 from "path";
|
|
2514
2565
|
import os6 from "os";
|
|
2515
2566
|
import {
|
|
2516
|
-
readFileSync as
|
|
2567
|
+
readFileSync as readFileSync9,
|
|
2517
2568
|
readdirSync,
|
|
2518
2569
|
unlinkSync as unlinkSync3,
|
|
2519
|
-
existsSync as
|
|
2570
|
+
existsSync as existsSync9,
|
|
2520
2571
|
rmdirSync
|
|
2521
2572
|
} from "fs";
|
|
2522
2573
|
async function writeNotification(notification) {
|
|
@@ -2680,11 +2731,11 @@ var init_state_bus = __esm({
|
|
|
2680
2731
|
|
|
2681
2732
|
// src/lib/tasks-crud.ts
|
|
2682
2733
|
import crypto3 from "crypto";
|
|
2683
|
-
import
|
|
2734
|
+
import path11 from "path";
|
|
2684
2735
|
import os7 from "os";
|
|
2685
2736
|
import { execSync as execSync5 } from "child_process";
|
|
2686
2737
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2687
|
-
import { existsSync as
|
|
2738
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
2688
2739
|
async function writeCheckpoint(input) {
|
|
2689
2740
|
const client = getClient();
|
|
2690
2741
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2859,8 +2910,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2859
2910
|
}
|
|
2860
2911
|
if (input.baseDir) {
|
|
2861
2912
|
try {
|
|
2862
|
-
await mkdir3(
|
|
2863
|
-
await mkdir3(
|
|
2913
|
+
await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2914
|
+
await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2864
2915
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2865
2916
|
await ensureGitignoreExe(input.baseDir);
|
|
2866
2917
|
} catch {
|
|
@@ -2896,10 +2947,10 @@ ${laneWarning}` : laneWarning;
|
|
|
2896
2947
|
});
|
|
2897
2948
|
if (input.baseDir) {
|
|
2898
2949
|
try {
|
|
2899
|
-
const EXE_OS_DIR =
|
|
2900
|
-
const mdPath =
|
|
2901
|
-
const mdDir =
|
|
2902
|
-
if (!
|
|
2950
|
+
const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
|
|
2951
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
2952
|
+
const mdDir = path11.dirname(mdPath);
|
|
2953
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2903
2954
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2904
2955
|
const mdContent = `# ${input.title}
|
|
2905
2956
|
|
|
@@ -2924,7 +2975,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
|
|
|
2924
2975
|
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2925
2976
|
`;
|
|
2926
2977
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2927
|
-
} catch {
|
|
2978
|
+
} catch (err) {
|
|
2979
|
+
process.stderr.write(
|
|
2980
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2981
|
+
`
|
|
2982
|
+
);
|
|
2928
2983
|
}
|
|
2929
2984
|
}
|
|
2930
2985
|
return {
|
|
@@ -3184,9 +3239,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3184
3239
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3185
3240
|
}
|
|
3186
3241
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3187
|
-
const archPath =
|
|
3242
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3188
3243
|
try {
|
|
3189
|
-
if (
|
|
3244
|
+
if (existsSync10(archPath)) return;
|
|
3190
3245
|
const template = [
|
|
3191
3246
|
`# ${projectName} \u2014 System Architecture`,
|
|
3192
3247
|
"",
|
|
@@ -3219,10 +3274,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3219
3274
|
}
|
|
3220
3275
|
}
|
|
3221
3276
|
async function ensureGitignoreExe(baseDir) {
|
|
3222
|
-
const gitignorePath =
|
|
3277
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
3223
3278
|
try {
|
|
3224
|
-
if (
|
|
3225
|
-
const content =
|
|
3279
|
+
if (existsSync10(gitignorePath)) {
|
|
3280
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
3226
3281
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3227
3282
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3228
3283
|
} else {
|
|
@@ -3253,8 +3308,8 @@ var init_tasks_crud = __esm({
|
|
|
3253
3308
|
});
|
|
3254
3309
|
|
|
3255
3310
|
// src/lib/tasks-review.ts
|
|
3256
|
-
import
|
|
3257
|
-
import { existsSync as
|
|
3311
|
+
import path12 from "path";
|
|
3312
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3258
3313
|
async function countPendingReviews(sessionScope) {
|
|
3259
3314
|
const client = getClient();
|
|
3260
3315
|
if (sessionScope) {
|
|
@@ -3435,11 +3490,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3435
3490
|
);
|
|
3436
3491
|
}
|
|
3437
3492
|
try {
|
|
3438
|
-
const cacheDir =
|
|
3439
|
-
if (
|
|
3493
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
3494
|
+
if (existsSync11(cacheDir)) {
|
|
3440
3495
|
for (const f of readdirSync2(cacheDir)) {
|
|
3441
3496
|
if (f.startsWith("review-notified-")) {
|
|
3442
|
-
unlinkSync4(
|
|
3497
|
+
unlinkSync4(path12.join(cacheDir, f));
|
|
3443
3498
|
}
|
|
3444
3499
|
}
|
|
3445
3500
|
}
|
|
@@ -3460,7 +3515,7 @@ var init_tasks_review = __esm({
|
|
|
3460
3515
|
});
|
|
3461
3516
|
|
|
3462
3517
|
// src/lib/tasks-chain.ts
|
|
3463
|
-
import
|
|
3518
|
+
import path13 from "path";
|
|
3464
3519
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3465
3520
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3466
3521
|
const client = getClient();
|
|
@@ -3477,7 +3532,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3477
3532
|
});
|
|
3478
3533
|
for (const ur of unblockedRows.rows) {
|
|
3479
3534
|
try {
|
|
3480
|
-
const ubFile =
|
|
3535
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
3481
3536
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3482
3537
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3483
3538
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3546,7 +3601,7 @@ var init_tasks_chain = __esm({
|
|
|
3546
3601
|
|
|
3547
3602
|
// src/lib/project-name.ts
|
|
3548
3603
|
import { execSync as execSync6 } from "child_process";
|
|
3549
|
-
import
|
|
3604
|
+
import path14 from "path";
|
|
3550
3605
|
function getProjectName(cwd) {
|
|
3551
3606
|
const dir = cwd ?? process.cwd();
|
|
3552
3607
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3559,7 +3614,7 @@ function getProjectName(cwd) {
|
|
|
3559
3614
|
timeout: 2e3,
|
|
3560
3615
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3561
3616
|
}).trim();
|
|
3562
|
-
repoRoot =
|
|
3617
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
3563
3618
|
} catch {
|
|
3564
3619
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3565
3620
|
cwd: dir,
|
|
@@ -3568,11 +3623,11 @@ function getProjectName(cwd) {
|
|
|
3568
3623
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3569
3624
|
}).trim();
|
|
3570
3625
|
}
|
|
3571
|
-
_cached2 =
|
|
3626
|
+
_cached2 = path14.basename(repoRoot);
|
|
3572
3627
|
_cachedCwd = dir;
|
|
3573
3628
|
return _cached2;
|
|
3574
3629
|
} catch {
|
|
3575
|
-
_cached2 =
|
|
3630
|
+
_cached2 = path14.basename(dir);
|
|
3576
3631
|
_cachedCwd = dir;
|
|
3577
3632
|
return _cached2;
|
|
3578
3633
|
}
|
|
@@ -4107,8 +4162,8 @@ __export(tasks_exports, {
|
|
|
4107
4162
|
updateTaskStatus: () => updateTaskStatus,
|
|
4108
4163
|
writeCheckpoint: () => writeCheckpoint
|
|
4109
4164
|
});
|
|
4110
|
-
import
|
|
4111
|
-
import { writeFileSync as
|
|
4165
|
+
import path15 from "path";
|
|
4166
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4112
4167
|
async function createTask(input) {
|
|
4113
4168
|
const result = await createTaskCore(input);
|
|
4114
4169
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4127,11 +4182,11 @@ async function updateTask(input) {
|
|
|
4127
4182
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4128
4183
|
try {
|
|
4129
4184
|
const agent = String(row.assigned_to);
|
|
4130
|
-
const cacheDir =
|
|
4131
|
-
const cachePath =
|
|
4185
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
4186
|
+
const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
|
|
4132
4187
|
if (input.status === "in_progress") {
|
|
4133
|
-
|
|
4134
|
-
|
|
4188
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
4189
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4135
4190
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4136
4191
|
try {
|
|
4137
4192
|
unlinkSync5(cachePath);
|
|
@@ -4598,13 +4653,13 @@ __export(tmux_routing_exports, {
|
|
|
4598
4653
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4599
4654
|
});
|
|
4600
4655
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
4601
|
-
import { readFileSync as
|
|
4602
|
-
import
|
|
4656
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
|
|
4657
|
+
import path16 from "path";
|
|
4603
4658
|
import os8 from "os";
|
|
4604
4659
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4605
4660
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
4606
4661
|
function spawnLockPath(sessionName) {
|
|
4607
|
-
return
|
|
4662
|
+
return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4608
4663
|
}
|
|
4609
4664
|
function isProcessAlive(pid) {
|
|
4610
4665
|
try {
|
|
@@ -4615,13 +4670,13 @@ function isProcessAlive(pid) {
|
|
|
4615
4670
|
}
|
|
4616
4671
|
}
|
|
4617
4672
|
function acquireSpawnLock2(sessionName) {
|
|
4618
|
-
if (!
|
|
4619
|
-
|
|
4673
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4674
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4620
4675
|
}
|
|
4621
4676
|
const lockFile = spawnLockPath(sessionName);
|
|
4622
|
-
if (
|
|
4677
|
+
if (existsSync12(lockFile)) {
|
|
4623
4678
|
try {
|
|
4624
|
-
const lock = JSON.parse(
|
|
4679
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
4625
4680
|
const age = Date.now() - lock.timestamp;
|
|
4626
4681
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4627
4682
|
return false;
|
|
@@ -4629,7 +4684,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
4629
4684
|
} catch {
|
|
4630
4685
|
}
|
|
4631
4686
|
}
|
|
4632
|
-
|
|
4687
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4633
4688
|
return true;
|
|
4634
4689
|
}
|
|
4635
4690
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -4641,13 +4696,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
4641
4696
|
function resolveBehaviorsExporterScript() {
|
|
4642
4697
|
try {
|
|
4643
4698
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4644
|
-
const scriptPath =
|
|
4645
|
-
|
|
4699
|
+
const scriptPath = path16.join(
|
|
4700
|
+
path16.dirname(thisFile),
|
|
4646
4701
|
"..",
|
|
4647
4702
|
"bin",
|
|
4648
4703
|
"exe-export-behaviors.js"
|
|
4649
4704
|
);
|
|
4650
|
-
return
|
|
4705
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
4651
4706
|
} catch {
|
|
4652
4707
|
return null;
|
|
4653
4708
|
}
|
|
@@ -4713,12 +4768,12 @@ function extractRootExe(name) {
|
|
|
4713
4768
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4714
4769
|
}
|
|
4715
4770
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4716
|
-
if (!
|
|
4717
|
-
|
|
4771
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4772
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4718
4773
|
}
|
|
4719
4774
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4720
|
-
const filePath =
|
|
4721
|
-
|
|
4775
|
+
const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4776
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
4722
4777
|
parentExe: rootExe,
|
|
4723
4778
|
dispatchedBy: dispatchedBy || rootExe,
|
|
4724
4779
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4726,7 +4781,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4726
4781
|
}
|
|
4727
4782
|
function getParentExe(sessionKey) {
|
|
4728
4783
|
try {
|
|
4729
|
-
const data = JSON.parse(
|
|
4784
|
+
const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4730
4785
|
return data.parentExe || null;
|
|
4731
4786
|
} catch {
|
|
4732
4787
|
return null;
|
|
@@ -4734,8 +4789,8 @@ function getParentExe(sessionKey) {
|
|
|
4734
4789
|
}
|
|
4735
4790
|
function getDispatchedBy(sessionKey) {
|
|
4736
4791
|
try {
|
|
4737
|
-
const data = JSON.parse(
|
|
4738
|
-
|
|
4792
|
+
const data = JSON.parse(readFileSync11(
|
|
4793
|
+
path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4739
4794
|
"utf8"
|
|
4740
4795
|
));
|
|
4741
4796
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4796,32 +4851,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4796
4851
|
}
|
|
4797
4852
|
function readDebounceState() {
|
|
4798
4853
|
try {
|
|
4799
|
-
if (!
|
|
4800
|
-
|
|
4854
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4855
|
+
const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
4856
|
+
const state = {};
|
|
4857
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
4858
|
+
if (typeof val === "number") {
|
|
4859
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
4860
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
4861
|
+
state[key] = val;
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
return state;
|
|
4801
4865
|
} catch {
|
|
4802
4866
|
return {};
|
|
4803
4867
|
}
|
|
4804
4868
|
}
|
|
4805
4869
|
function writeDebounceState(state) {
|
|
4806
4870
|
try {
|
|
4807
|
-
if (!
|
|
4808
|
-
|
|
4871
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4872
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4809
4873
|
} catch {
|
|
4810
4874
|
}
|
|
4811
4875
|
}
|
|
4812
4876
|
function isDebounced(targetSession) {
|
|
4813
4877
|
const state = readDebounceState();
|
|
4814
|
-
const
|
|
4815
|
-
|
|
4878
|
+
const entry = state[targetSession];
|
|
4879
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
4880
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
4881
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
4882
|
+
state[targetSession].pending++;
|
|
4883
|
+
writeDebounceState(state);
|
|
4884
|
+
return true;
|
|
4885
|
+
}
|
|
4886
|
+
return false;
|
|
4816
4887
|
}
|
|
4817
4888
|
function recordDebounce(targetSession) {
|
|
4818
4889
|
const state = readDebounceState();
|
|
4819
|
-
state[targetSession]
|
|
4890
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
4891
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
4820
4892
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
4821
4893
|
for (const key of Object.keys(state)) {
|
|
4822
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4894
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
4823
4895
|
}
|
|
4824
4896
|
writeDebounceState(state);
|
|
4897
|
+
return batched;
|
|
4825
4898
|
}
|
|
4826
4899
|
function logIntercom(msg) {
|
|
4827
4900
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -4866,7 +4939,7 @@ function sendIntercom(targetSession) {
|
|
|
4866
4939
|
return "skipped_exe";
|
|
4867
4940
|
}
|
|
4868
4941
|
if (isDebounced(targetSession)) {
|
|
4869
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
4942
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
4870
4943
|
return "debounced";
|
|
4871
4944
|
}
|
|
4872
4945
|
try {
|
|
@@ -4878,14 +4951,14 @@ function sendIntercom(targetSession) {
|
|
|
4878
4951
|
const sessionState = getSessionState(targetSession);
|
|
4879
4952
|
if (sessionState === "no_claude") {
|
|
4880
4953
|
queueIntercom(targetSession, "claude not running in session");
|
|
4881
|
-
recordDebounce(targetSession);
|
|
4882
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
4954
|
+
const batched2 = recordDebounce(targetSession);
|
|
4955
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
4883
4956
|
return "queued";
|
|
4884
4957
|
}
|
|
4885
4958
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
4886
4959
|
queueIntercom(targetSession, "session busy at send time");
|
|
4887
|
-
recordDebounce(targetSession);
|
|
4888
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
4960
|
+
const batched2 = recordDebounce(targetSession);
|
|
4961
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
4889
4962
|
return "queued";
|
|
4890
4963
|
}
|
|
4891
4964
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -4893,8 +4966,8 @@ function sendIntercom(targetSession) {
|
|
|
4893
4966
|
transport.sendKeys(targetSession, "q");
|
|
4894
4967
|
}
|
|
4895
4968
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
4896
|
-
recordDebounce(targetSession);
|
|
4897
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4969
|
+
const batched = recordDebounce(targetSession);
|
|
4970
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
4898
4971
|
return "delivered";
|
|
4899
4972
|
} catch {
|
|
4900
4973
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -4996,26 +5069,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4996
5069
|
const transport = getTransport();
|
|
4997
5070
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4998
5071
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4999
|
-
const logDir =
|
|
5000
|
-
const logFile =
|
|
5001
|
-
if (!
|
|
5002
|
-
|
|
5072
|
+
const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
|
|
5073
|
+
const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5074
|
+
if (!existsSync12(logDir)) {
|
|
5075
|
+
mkdirSync6(logDir, { recursive: true });
|
|
5003
5076
|
}
|
|
5004
5077
|
transport.kill(sessionName);
|
|
5005
5078
|
let cleanupSuffix = "";
|
|
5006
5079
|
try {
|
|
5007
5080
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5008
|
-
const cleanupScript =
|
|
5009
|
-
if (
|
|
5081
|
+
const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5082
|
+
if (existsSync12(cleanupScript)) {
|
|
5010
5083
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5011
5084
|
}
|
|
5012
5085
|
} catch {
|
|
5013
5086
|
}
|
|
5014
5087
|
try {
|
|
5015
|
-
const claudeJsonPath =
|
|
5088
|
+
const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
|
|
5016
5089
|
let claudeJson = {};
|
|
5017
5090
|
try {
|
|
5018
|
-
claudeJson = JSON.parse(
|
|
5091
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
5019
5092
|
} catch {
|
|
5020
5093
|
}
|
|
5021
5094
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5023,17 +5096,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5023
5096
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5024
5097
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5025
5098
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5026
|
-
|
|
5099
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5027
5100
|
} catch {
|
|
5028
5101
|
}
|
|
5029
5102
|
try {
|
|
5030
|
-
const settingsDir =
|
|
5103
|
+
const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
|
|
5031
5104
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5032
|
-
const projSettingsDir =
|
|
5033
|
-
const settingsPath =
|
|
5105
|
+
const projSettingsDir = path16.join(settingsDir, normalizedKey);
|
|
5106
|
+
const settingsPath = path16.join(projSettingsDir, "settings.json");
|
|
5034
5107
|
let settings = {};
|
|
5035
5108
|
try {
|
|
5036
|
-
settings = JSON.parse(
|
|
5109
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
5037
5110
|
} catch {
|
|
5038
5111
|
}
|
|
5039
5112
|
const perms = settings.permissions ?? {};
|
|
@@ -5061,20 +5134,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5061
5134
|
if (changed) {
|
|
5062
5135
|
perms.allow = allow;
|
|
5063
5136
|
settings.permissions = perms;
|
|
5064
|
-
|
|
5065
|
-
|
|
5137
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5138
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5066
5139
|
}
|
|
5067
5140
|
} catch {
|
|
5068
5141
|
}
|
|
5069
5142
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
5070
5143
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
5071
|
-
const
|
|
5144
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
5145
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
5146
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
5147
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
5072
5148
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
5073
5149
|
let identityFlag = "";
|
|
5074
5150
|
let behaviorsFlag = "";
|
|
5075
5151
|
let legacyFallbackWarned = false;
|
|
5076
5152
|
if (!useExeAgent && !useBinSymlink) {
|
|
5077
|
-
const identityPath =
|
|
5153
|
+
const identityPath = path16.join(
|
|
5078
5154
|
os8.homedir(),
|
|
5079
5155
|
".exe-os",
|
|
5080
5156
|
"identity",
|
|
@@ -5084,13 +5160,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5084
5160
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5085
5161
|
if (hasAgentFlag) {
|
|
5086
5162
|
identityFlag = ` --agent ${employeeName}`;
|
|
5087
|
-
} else if (
|
|
5163
|
+
} else if (existsSync12(identityPath)) {
|
|
5088
5164
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5089
5165
|
legacyFallbackWarned = true;
|
|
5090
5166
|
}
|
|
5091
5167
|
const behaviorsFile = exportBehaviorsSync(
|
|
5092
5168
|
employeeName,
|
|
5093
|
-
|
|
5169
|
+
path16.basename(spawnCwd),
|
|
5094
5170
|
sessionName
|
|
5095
5171
|
);
|
|
5096
5172
|
if (behaviorsFile) {
|
|
@@ -5105,16 +5181,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5105
5181
|
}
|
|
5106
5182
|
let sessionContextFlag = "";
|
|
5107
5183
|
try {
|
|
5108
|
-
const ctxDir =
|
|
5109
|
-
|
|
5110
|
-
const ctxFile =
|
|
5184
|
+
const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
|
|
5185
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
5186
|
+
const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5111
5187
|
const ctxContent = [
|
|
5112
5188
|
`## Session Context`,
|
|
5113
5189
|
`You are running in tmux session: ${sessionName}.`,
|
|
5114
5190
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5115
5191
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5116
5192
|
].join("\n");
|
|
5117
|
-
|
|
5193
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
5118
5194
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5119
5195
|
} catch {
|
|
5120
5196
|
}
|
|
@@ -5128,9 +5204,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5128
5204
|
}
|
|
5129
5205
|
}
|
|
5130
5206
|
}
|
|
5207
|
+
if (useCodex) {
|
|
5208
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
5209
|
+
if (codexCfg?.apiKeyEnv) {
|
|
5210
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
5211
|
+
if (keyVal) {
|
|
5212
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
5216
|
+
}
|
|
5217
|
+
if (useOpencode) {
|
|
5218
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
5219
|
+
if (ocCfg?.apiKeyEnv) {
|
|
5220
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
5221
|
+
if (keyVal) {
|
|
5222
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
5226
|
+
}
|
|
5227
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
5228
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
5229
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
5230
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5131
5233
|
let spawnCommand;
|
|
5132
5234
|
if (useExeAgent) {
|
|
5133
5235
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
5236
|
+
} else if (useCodex) {
|
|
5237
|
+
process.stderr.write(
|
|
5238
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
5239
|
+
`
|
|
5240
|
+
);
|
|
5241
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
5242
|
+
} else if (useOpencode) {
|
|
5243
|
+
const binName = `${employeeName}-opencode`;
|
|
5244
|
+
process.stderr.write(
|
|
5245
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
5246
|
+
`
|
|
5247
|
+
);
|
|
5248
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
5134
5249
|
} else if (useBinSymlink) {
|
|
5135
5250
|
const binName = `${employeeName}-${ccProvider}`;
|
|
5136
5251
|
process.stderr.write(
|
|
@@ -5152,11 +5267,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5152
5267
|
transport.pipeLog(sessionName, logFile);
|
|
5153
5268
|
try {
|
|
5154
5269
|
const mySession = getMySession();
|
|
5155
|
-
const dispatchInfo =
|
|
5156
|
-
|
|
5270
|
+
const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5271
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
5157
5272
|
dispatchedBy: mySession,
|
|
5158
5273
|
rootExe: exeSession,
|
|
5159
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
5274
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
5275
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
5276
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
5160
5277
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5161
5278
|
}));
|
|
5162
5279
|
} catch {
|
|
@@ -5174,6 +5291,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5174
5291
|
booted = true;
|
|
5175
5292
|
break;
|
|
5176
5293
|
}
|
|
5294
|
+
} else if (useCodex) {
|
|
5295
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
5296
|
+
booted = true;
|
|
5297
|
+
break;
|
|
5298
|
+
}
|
|
5177
5299
|
} else {
|
|
5178
5300
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
5179
5301
|
booted = true;
|
|
@@ -5185,9 +5307,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5185
5307
|
}
|
|
5186
5308
|
if (!booted) {
|
|
5187
5309
|
releaseSpawnLock2(sessionName);
|
|
5188
|
-
|
|
5310
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
5311
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
5189
5312
|
}
|
|
5190
|
-
if (!useExeAgent) {
|
|
5313
|
+
if (!useExeAgent && !useCodex) {
|
|
5191
5314
|
try {
|
|
5192
5315
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
5193
5316
|
} catch {
|
|
@@ -5214,17 +5337,19 @@ var init_tmux_routing = __esm({
|
|
|
5214
5337
|
init_cc_agent_support();
|
|
5215
5338
|
init_mcp_prefix();
|
|
5216
5339
|
init_provider_table();
|
|
5340
|
+
init_agent_config();
|
|
5341
|
+
init_runtime_table();
|
|
5217
5342
|
init_intercom_queue();
|
|
5218
5343
|
init_plan_limits();
|
|
5219
5344
|
init_employees();
|
|
5220
|
-
SPAWN_LOCK_DIR =
|
|
5221
|
-
SESSION_CACHE =
|
|
5345
|
+
SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
5346
|
+
SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
|
|
5222
5347
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5223
5348
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5224
5349
|
VERIFY_PANE_LINES = 200;
|
|
5225
5350
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5226
|
-
INTERCOM_LOG2 =
|
|
5227
|
-
DEBOUNCE_FILE =
|
|
5351
|
+
INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
5352
|
+
DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5228
5353
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5229
5354
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
5230
5355
|
}
|
|
@@ -5241,14 +5366,14 @@ var init_memory = __esm({
|
|
|
5241
5366
|
|
|
5242
5367
|
// src/lib/keychain.ts
|
|
5243
5368
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5244
|
-
import { existsSync as
|
|
5245
|
-
import
|
|
5369
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5370
|
+
import path17 from "path";
|
|
5246
5371
|
import os9 from "os";
|
|
5247
5372
|
function getKeyDir() {
|
|
5248
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5373
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os9.homedir(), ".exe-os");
|
|
5249
5374
|
}
|
|
5250
5375
|
function getKeyPath() {
|
|
5251
|
-
return
|
|
5376
|
+
return path17.join(getKeyDir(), "master.key");
|
|
5252
5377
|
}
|
|
5253
5378
|
async function tryKeytar() {
|
|
5254
5379
|
try {
|
|
@@ -5269,7 +5394,7 @@ async function getMasterKey() {
|
|
|
5269
5394
|
}
|
|
5270
5395
|
}
|
|
5271
5396
|
const keyPath = getKeyPath();
|
|
5272
|
-
if (!
|
|
5397
|
+
if (!existsSync13(keyPath)) {
|
|
5273
5398
|
process.stderr.write(
|
|
5274
5399
|
`[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5275
5400
|
`
|
|
@@ -5309,13 +5434,13 @@ __export(shard_manager_exports, {
|
|
|
5309
5434
|
listShards: () => listShards,
|
|
5310
5435
|
shardExists: () => shardExists
|
|
5311
5436
|
});
|
|
5312
|
-
import
|
|
5313
|
-
import { existsSync as
|
|
5437
|
+
import path18 from "path";
|
|
5438
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
|
|
5314
5439
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5315
5440
|
function initShardManager(encryptionKey) {
|
|
5316
5441
|
_encryptionKey = encryptionKey;
|
|
5317
|
-
if (!
|
|
5318
|
-
|
|
5442
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5443
|
+
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5319
5444
|
}
|
|
5320
5445
|
_shardingEnabled = true;
|
|
5321
5446
|
}
|
|
@@ -5335,7 +5460,7 @@ function getShardClient(projectName) {
|
|
|
5335
5460
|
}
|
|
5336
5461
|
const cached = _shards.get(safeName);
|
|
5337
5462
|
if (cached) return cached;
|
|
5338
|
-
const dbPath =
|
|
5463
|
+
const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
|
|
5339
5464
|
const client = createClient2({
|
|
5340
5465
|
url: `file:${dbPath}`,
|
|
5341
5466
|
encryptionKey: _encryptionKey
|
|
@@ -5345,10 +5470,10 @@ function getShardClient(projectName) {
|
|
|
5345
5470
|
}
|
|
5346
5471
|
function shardExists(projectName) {
|
|
5347
5472
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5348
|
-
return
|
|
5473
|
+
return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
|
|
5349
5474
|
}
|
|
5350
5475
|
function listShards() {
|
|
5351
|
-
if (!
|
|
5476
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
5352
5477
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5353
5478
|
}
|
|
5354
5479
|
async function ensureShardSchema(client) {
|
|
@@ -5534,7 +5659,7 @@ var init_shard_manager = __esm({
|
|
|
5534
5659
|
"src/lib/shard-manager.ts"() {
|
|
5535
5660
|
"use strict";
|
|
5536
5661
|
init_config();
|
|
5537
|
-
SHARDS_DIR =
|
|
5662
|
+
SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
|
|
5538
5663
|
_shards = /* @__PURE__ */ new Map();
|
|
5539
5664
|
_encryptionKey = null;
|
|
5540
5665
|
_shardingEnabled = false;
|
|
@@ -6323,8 +6448,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
6323
6448
|
try {
|
|
6324
6449
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
6325
6450
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
6326
|
-
const { readFileSync:
|
|
6327
|
-
const source =
|
|
6451
|
+
const { readFileSync: readFileSync14 } = __require("fs");
|
|
6452
|
+
const source = readFileSync14(filePath, "utf8");
|
|
6328
6453
|
const lines = source.split("\n");
|
|
6329
6454
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
6330
6455
|
let matchLine = -1;
|
|
@@ -6390,9 +6515,9 @@ function extractBash(input, response) {
|
|
|
6390
6515
|
}
|
|
6391
6516
|
function extractGrep(input, response) {
|
|
6392
6517
|
const pattern = String(input.pattern ?? "");
|
|
6393
|
-
const
|
|
6518
|
+
const path21 = input.path ? String(input.path) : "";
|
|
6394
6519
|
const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
|
|
6395
|
-
return `Searched for "${pattern}"${
|
|
6520
|
+
return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
|
|
6396
6521
|
${output.slice(0, MAX_OUTPUT)}`;
|
|
6397
6522
|
}
|
|
6398
6523
|
function extractGlob(input, response) {
|
|
@@ -6930,10 +7055,10 @@ async function disposeEmbedder() {
|
|
|
6930
7055
|
async function embedDirect(text) {
|
|
6931
7056
|
const llamaCpp = await import("node-llama-cpp");
|
|
6932
7057
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6933
|
-
const { existsSync:
|
|
6934
|
-
const
|
|
6935
|
-
const modelPath =
|
|
6936
|
-
if (!
|
|
7058
|
+
const { existsSync: existsSync16 } = await import("fs");
|
|
7059
|
+
const path21 = await import("path");
|
|
7060
|
+
const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
7061
|
+
if (!existsSync16(modelPath)) {
|
|
6937
7062
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
6938
7063
|
}
|
|
6939
7064
|
const llama = await llamaCpp.getLlama();
|
|
@@ -6970,8 +7095,8 @@ __export(wiki_client_exports, {
|
|
|
6970
7095
|
listDocuments: () => listDocuments,
|
|
6971
7096
|
listWorkspaces: () => listWorkspaces
|
|
6972
7097
|
});
|
|
6973
|
-
async function wikiFetch(config2,
|
|
6974
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
7098
|
+
async function wikiFetch(config2, path21, method = "GET", body) {
|
|
7099
|
+
const url = `${config2.baseUrl}/api/v1${path21}`;
|
|
6975
7100
|
const headers = {
|
|
6976
7101
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
6977
7102
|
"Content-Type": "application/json"
|
|
@@ -7004,7 +7129,7 @@ async function wikiFetch(config2, path20, method = "GET", body) {
|
|
|
7004
7129
|
}
|
|
7005
7130
|
}
|
|
7006
7131
|
if (!response.ok) {
|
|
7007
|
-
throw new Error(`Wiki API ${method} ${
|
|
7132
|
+
throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
|
|
7008
7133
|
}
|
|
7009
7134
|
return response.json();
|
|
7010
7135
|
} finally {
|
|
@@ -8025,13 +8150,13 @@ __export(whatsapp_accounts_exports, {
|
|
|
8025
8150
|
getDefaultAccount: () => getDefaultAccount,
|
|
8026
8151
|
loadAccounts: () => loadAccounts
|
|
8027
8152
|
});
|
|
8028
|
-
import { readFileSync as
|
|
8153
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
8029
8154
|
import { join as join2 } from "path";
|
|
8030
8155
|
import { homedir as homedir2 } from "os";
|
|
8031
8156
|
function loadAccounts() {
|
|
8032
8157
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
8033
8158
|
try {
|
|
8034
|
-
const raw =
|
|
8159
|
+
const raw = readFileSync12(CONFIG_PATH2, "utf8");
|
|
8035
8160
|
const parsed = JSON.parse(raw);
|
|
8036
8161
|
if (!Array.isArray(parsed)) {
|
|
8037
8162
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -12192,7 +12317,7 @@ var OllamaProvider = class {
|
|
|
12192
12317
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
12193
12318
|
import { homedir } from "os";
|
|
12194
12319
|
import { join } from "path";
|
|
12195
|
-
import { mkdirSync as
|
|
12320
|
+
import { mkdirSync as mkdirSync8 } from "fs";
|
|
12196
12321
|
var INITIAL_BACKOFF_MS = 1e3;
|
|
12197
12322
|
var MAX_BACKOFF_MS = 3e5;
|
|
12198
12323
|
var BACKOFF_MULTIPLIER = 2;
|
|
@@ -12218,7 +12343,7 @@ var WhatsAppAdapter = class {
|
|
|
12218
12343
|
disconnectedAt = 0;
|
|
12219
12344
|
async connect(config2) {
|
|
12220
12345
|
this.authDir = config2.credentials.authDir ?? AUTH_DIR;
|
|
12221
|
-
|
|
12346
|
+
mkdirSync8(this.authDir, { recursive: true });
|
|
12222
12347
|
const baileys = await import("@whiskeysockets/baileys");
|
|
12223
12348
|
const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason, makeCacheableSignalKeyStore } = baileys;
|
|
12224
12349
|
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
@@ -13521,10 +13646,10 @@ var SlackAdapter = class {
|
|
|
13521
13646
|
import { execFile } from "child_process";
|
|
13522
13647
|
import { promisify } from "util";
|
|
13523
13648
|
import os10 from "os";
|
|
13524
|
-
import
|
|
13649
|
+
import path19 from "path";
|
|
13525
13650
|
var execFileAsync = promisify(execFile);
|
|
13526
13651
|
var POLL_INTERVAL_MS = 5e3;
|
|
13527
|
-
var MESSAGES_DB_PATH =
|
|
13652
|
+
var MESSAGES_DB_PATH = path19.join(
|
|
13528
13653
|
process.env.HOME ?? os10.homedir(),
|
|
13529
13654
|
"Library/Messages/chat.db"
|
|
13530
13655
|
);
|
|
@@ -14368,11 +14493,11 @@ async function ensureCRMContact(info) {
|
|
|
14368
14493
|
}
|
|
14369
14494
|
|
|
14370
14495
|
// src/automation/trigger-engine.ts
|
|
14371
|
-
import { readFileSync as
|
|
14496
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
|
|
14372
14497
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
14373
|
-
import
|
|
14498
|
+
import path20 from "path";
|
|
14374
14499
|
import os11 from "os";
|
|
14375
|
-
var TRIGGERS_PATH =
|
|
14500
|
+
var TRIGGERS_PATH = path20.join(os11.homedir(), ".exe-os", "triggers.json");
|
|
14376
14501
|
var GRAPH_API_VERSION = "v21.0";
|
|
14377
14502
|
function substituteTemplate(template, record) {
|
|
14378
14503
|
return template.replace(
|
|
@@ -14426,9 +14551,9 @@ function evaluateConditions(conditions, record) {
|
|
|
14426
14551
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
14427
14552
|
}
|
|
14428
14553
|
function loadTriggers(project) {
|
|
14429
|
-
if (!
|
|
14554
|
+
if (!existsSync15(TRIGGERS_PATH)) return [];
|
|
14430
14555
|
try {
|
|
14431
|
-
const raw =
|
|
14556
|
+
const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
|
|
14432
14557
|
const all = JSON.parse(raw);
|
|
14433
14558
|
if (!Array.isArray(all)) return [];
|
|
14434
14559
|
if (project) {
|