@askexenow/exe-os 0.8.36 → 0.8.37
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 +9 -1
- package/dist/bin/backfill-responses.js +9 -1
- package/dist/bin/cli.js +255 -31
- package/dist/bin/exe-assign.js +9 -1
- package/dist/bin/exe-boot.js +554 -23
- package/dist/bin/exe-dispatch.js +43 -3
- package/dist/bin/exe-export-behaviors.js +7 -0
- package/dist/bin/exe-gateway.js +57 -9
- package/dist/bin/exe-heartbeat.js +2 -1
- package/dist/bin/exe-kill.js +7 -0
- package/dist/bin/exe-launch-agent.js +8 -1
- package/dist/bin/exe-link.js +503 -12
- package/dist/bin/exe-pending-messages.js +2 -1
- package/dist/bin/exe-pending-reviews.js +2 -1
- package/dist/bin/exe-review.js +9 -1
- package/dist/bin/exe-search.js +9 -1
- package/dist/bin/exe-session-cleanup.js +11 -2
- package/dist/bin/git-sweep.js +9 -1
- package/dist/bin/graph-backfill.js +7 -0
- package/dist/bin/graph-export.js +7 -0
- package/dist/bin/install.js +35 -5
- package/dist/bin/scan-tasks.js +9 -1
- package/dist/bin/shard-migrate.js +7 -0
- package/dist/bin/wiki-sync.js +7 -0
- package/dist/gateway/index.js +57 -9
- package/dist/hooks/bug-report-worker.js +45 -5
- package/dist/hooks/commit-complete.js +9 -1
- package/dist/hooks/error-recall.js +10 -2
- package/dist/hooks/exe-heartbeat-hook.js +1 -1
- package/dist/hooks/ingest-worker.js +56 -8
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +10 -2
- package/dist/hooks/notification.js +10 -2
- package/dist/hooks/post-compact.js +10 -2
- package/dist/hooks/pre-compact.js +10 -2
- package/dist/hooks/pre-tool-use.js +10 -2
- package/dist/hooks/prompt-ingest-worker.js +9 -1
- package/dist/hooks/prompt-submit.js +56 -8
- package/dist/hooks/response-ingest-worker.js +9 -1
- package/dist/hooks/session-end.js +10 -2
- package/dist/hooks/session-start.js +10 -2
- package/dist/hooks/stop.js +10 -2
- package/dist/hooks/subagent-stop.js +10 -2
- package/dist/hooks/summary-worker.js +512 -13
- package/dist/index.js +65 -15
- package/dist/lib/cloud-sync.js +502 -11
- package/dist/lib/exe-daemon.js +73 -23
- package/dist/lib/hybrid-search.js +9 -1
- package/dist/lib/messaging.js +43 -3
- package/dist/lib/store.js +9 -1
- package/dist/lib/tasks.js +47 -7
- package/dist/lib/tmux-routing.js +45 -3
- package/dist/mcp/server.js +73 -16
- package/dist/mcp/tools/create-task.js +48 -8
- package/dist/mcp/tools/deactivate-behavior.js +1 -1
- package/dist/mcp/tools/list-tasks.js +2 -1
- package/dist/mcp/tools/send-message.js +46 -6
- package/dist/mcp/tools/update-task.js +3 -2
- package/dist/runtime/index.js +54 -4
- package/dist/tui/App.js +54 -4
- package/package.json +2 -2
- package/src/commands/exe/afk.md +116 -0
package/dist/mcp/server.js
CHANGED
|
@@ -1947,7 +1947,8 @@ async function writeMemory(record) {
|
|
|
1947
1947
|
has_error: record.has_error ? 1 : 0,
|
|
1948
1948
|
raw_text: record.raw_text,
|
|
1949
1949
|
vector: record.vector,
|
|
1950
|
-
version:
|
|
1950
|
+
version: 0,
|
|
1951
|
+
// Placeholder — assigned atomically at flush time
|
|
1951
1952
|
task_id: record.task_id ?? null,
|
|
1952
1953
|
importance: record.importance ?? 5,
|
|
1953
1954
|
status: record.status ?? "active",
|
|
@@ -1981,6 +1982,13 @@ async function flushBatch() {
|
|
|
1981
1982
|
_flushing = true;
|
|
1982
1983
|
try {
|
|
1983
1984
|
const batch = _pendingRecords.slice(0);
|
|
1985
|
+
const client = getClient();
|
|
1986
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1987
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1988
|
+
for (const row of batch) {
|
|
1989
|
+
row.version = baseVersion++;
|
|
1990
|
+
}
|
|
1991
|
+
_nextVersion = baseVersion;
|
|
1984
1992
|
const buildStmt = (row) => {
|
|
1985
1993
|
const hasVector = row.vector !== null;
|
|
1986
1994
|
const taskId = row.task_id ?? null;
|
|
@@ -2883,7 +2891,7 @@ function getActiveAgent() {
|
|
|
2883
2891
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
2884
2892
|
{ encoding: "utf8", timeout: 2e3 }
|
|
2885
2893
|
).trim();
|
|
2886
|
-
const empMatch = sessionName.match(/^(
|
|
2894
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
2887
2895
|
if (empMatch && empMatch[1] !== "exe") {
|
|
2888
2896
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
2889
2897
|
}
|
|
@@ -4232,6 +4240,42 @@ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirS
|
|
|
4232
4240
|
import path18 from "path";
|
|
4233
4241
|
import os5 from "os";
|
|
4234
4242
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4243
|
+
import { unlinkSync as unlinkSync4 } from "fs";
|
|
4244
|
+
function spawnLockPath(sessionName) {
|
|
4245
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4246
|
+
}
|
|
4247
|
+
function isProcessAlive(pid) {
|
|
4248
|
+
try {
|
|
4249
|
+
process.kill(pid, 0);
|
|
4250
|
+
return true;
|
|
4251
|
+
} catch {
|
|
4252
|
+
return false;
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
function acquireSpawnLock2(sessionName) {
|
|
4256
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4257
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4258
|
+
}
|
|
4259
|
+
const lockFile = spawnLockPath(sessionName);
|
|
4260
|
+
if (existsSync14(lockFile)) {
|
|
4261
|
+
try {
|
|
4262
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
4263
|
+
const age = Date.now() - lock.timestamp;
|
|
4264
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4265
|
+
return false;
|
|
4266
|
+
}
|
|
4267
|
+
} catch {
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4271
|
+
return true;
|
|
4272
|
+
}
|
|
4273
|
+
function releaseSpawnLock2(sessionName) {
|
|
4274
|
+
try {
|
|
4275
|
+
unlinkSync4(spawnLockPath(sessionName));
|
|
4276
|
+
} catch {
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4235
4279
|
function resolveBehaviorsExporterScript() {
|
|
4236
4280
|
try {
|
|
4237
4281
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
@@ -4312,10 +4356,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
4312
4356
|
}
|
|
4313
4357
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
4314
4358
|
const base = employeeSessionName(employeeName, exeSession);
|
|
4315
|
-
if (!isAlive(base)) return 0;
|
|
4359
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
4316
4360
|
for (let i = 2; i <= maxInstances; i++) {
|
|
4317
4361
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
4318
|
-
if (!isAlive(candidate)) return i;
|
|
4362
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
4319
4363
|
}
|
|
4320
4364
|
return null;
|
|
4321
4365
|
}
|
|
@@ -4649,6 +4693,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4649
4693
|
command: spawnCommand
|
|
4650
4694
|
});
|
|
4651
4695
|
if (spawnResult.error) {
|
|
4696
|
+
releaseSpawnLock2(sessionName);
|
|
4652
4697
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4653
4698
|
}
|
|
4654
4699
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -4686,6 +4731,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4686
4731
|
}
|
|
4687
4732
|
}
|
|
4688
4733
|
if (!booted) {
|
|
4734
|
+
releaseSpawnLock2(sessionName);
|
|
4689
4735
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4690
4736
|
}
|
|
4691
4737
|
if (!useExeAgent) {
|
|
@@ -4702,9 +4748,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4702
4748
|
pid: 0,
|
|
4703
4749
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4704
4750
|
});
|
|
4751
|
+
releaseSpawnLock2(sessionName);
|
|
4705
4752
|
return { sessionName };
|
|
4706
4753
|
}
|
|
4707
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
4754
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
4708
4755
|
var init_tmux_routing = __esm({
|
|
4709
4756
|
"src/lib/tmux-routing.ts"() {
|
|
4710
4757
|
"use strict";
|
|
@@ -4716,6 +4763,7 @@ var init_tmux_routing = __esm({
|
|
|
4716
4763
|
init_provider_table();
|
|
4717
4764
|
init_intercom_queue();
|
|
4718
4765
|
init_plan_limits();
|
|
4766
|
+
SPAWN_LOCK_DIR = path18.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
4719
4767
|
SESSION_CACHE = path18.join(os5.homedir(), ".exe-os", "session-cache");
|
|
4720
4768
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4721
4769
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -4728,7 +4776,7 @@ var init_tmux_routing = __esm({
|
|
|
4728
4776
|
|
|
4729
4777
|
// src/lib/tasks-review.ts
|
|
4730
4778
|
import path19 from "path";
|
|
4731
|
-
import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as
|
|
4779
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
4732
4780
|
async function countPendingReviews() {
|
|
4733
4781
|
const client = getClient();
|
|
4734
4782
|
const result = await client.execute({
|
|
@@ -4852,7 +4900,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4852
4900
|
if (existsSync15(cacheDir)) {
|
|
4853
4901
|
for (const f of readdirSync4(cacheDir)) {
|
|
4854
4902
|
if (f.startsWith("review-notified-")) {
|
|
4855
|
-
|
|
4903
|
+
unlinkSync5(path19.join(cacheDir, f));
|
|
4856
4904
|
}
|
|
4857
4905
|
}
|
|
4858
4906
|
}
|
|
@@ -5439,7 +5487,7 @@ __export(tasks_exports, {
|
|
|
5439
5487
|
writeCheckpoint: () => writeCheckpoint
|
|
5440
5488
|
});
|
|
5441
5489
|
import path21 from "path";
|
|
5442
|
-
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as
|
|
5490
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
|
|
5443
5491
|
async function createTask(input) {
|
|
5444
5492
|
const result = await createTaskCore(input);
|
|
5445
5493
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5465,7 +5513,7 @@ async function updateTask(input) {
|
|
|
5465
5513
|
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5466
5514
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5467
5515
|
try {
|
|
5468
|
-
|
|
5516
|
+
unlinkSync6(cachePath);
|
|
5469
5517
|
} catch {
|
|
5470
5518
|
}
|
|
5471
5519
|
}
|
|
@@ -6762,7 +6810,8 @@ function registerGetSessionContext(server2) {
|
|
|
6762
6810
|
has_error, raw_text, vector, task_id
|
|
6763
6811
|
FROM memories
|
|
6764
6812
|
WHERE session_id = ?
|
|
6765
|
-
ORDER BY timestamp ASC
|
|
6813
|
+
ORDER BY timestamp ASC
|
|
6814
|
+
LIMIT 500`,
|
|
6766
6815
|
args: [session_id]
|
|
6767
6816
|
});
|
|
6768
6817
|
if (result.rows.length === 0) {
|
|
@@ -8465,12 +8514,20 @@ async function resolveWorkspace(slug_or_id, name) {
|
|
|
8465
8514
|
};
|
|
8466
8515
|
}
|
|
8467
8516
|
async function embedTexts(texts) {
|
|
8468
|
-
const
|
|
8469
|
-
|
|
8470
|
-
)
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8517
|
+
const BATCH_SIZE = 50;
|
|
8518
|
+
const results = [];
|
|
8519
|
+
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
8520
|
+
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
8521
|
+
const settled = await Promise.allSettled(
|
|
8522
|
+
batch.map((text) => embed(text))
|
|
8523
|
+
);
|
|
8524
|
+
results.push(
|
|
8525
|
+
...settled.map(
|
|
8526
|
+
(result) => result.status === "fulfilled" ? result.value : null
|
|
8527
|
+
)
|
|
8528
|
+
);
|
|
8529
|
+
}
|
|
8530
|
+
return results;
|
|
8474
8531
|
}
|
|
8475
8532
|
function buildChunkStatement(input) {
|
|
8476
8533
|
const hasVector = input.vector !== null;
|
|
@@ -1052,6 +1052,42 @@ import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSy
|
|
|
1052
1052
|
import path9 from "path";
|
|
1053
1053
|
import os5 from "os";
|
|
1054
1054
|
import { fileURLToPath } from "url";
|
|
1055
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
1056
|
+
function spawnLockPath(sessionName) {
|
|
1057
|
+
return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
1058
|
+
}
|
|
1059
|
+
function isProcessAlive(pid) {
|
|
1060
|
+
try {
|
|
1061
|
+
process.kill(pid, 0);
|
|
1062
|
+
return true;
|
|
1063
|
+
} catch {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function acquireSpawnLock(sessionName) {
|
|
1068
|
+
if (!existsSync9(SPAWN_LOCK_DIR)) {
|
|
1069
|
+
mkdirSync4(SPAWN_LOCK_DIR, { recursive: true });
|
|
1070
|
+
}
|
|
1071
|
+
const lockFile = spawnLockPath(sessionName);
|
|
1072
|
+
if (existsSync9(lockFile)) {
|
|
1073
|
+
try {
|
|
1074
|
+
const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
|
|
1075
|
+
const age = Date.now() - lock.timestamp;
|
|
1076
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
function releaseSpawnLock(sessionName) {
|
|
1086
|
+
try {
|
|
1087
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
1088
|
+
} catch {
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1055
1091
|
function resolveBehaviorsExporterScript() {
|
|
1056
1092
|
try {
|
|
1057
1093
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -1121,10 +1157,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
1121
1157
|
}
|
|
1122
1158
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
1123
1159
|
const base = employeeSessionName(employeeName, exeSession);
|
|
1124
|
-
if (!isAlive(base)) return 0;
|
|
1160
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
1125
1161
|
for (let i = 2; i <= maxInstances; i++) {
|
|
1126
1162
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
1127
|
-
if (!isAlive(candidate)) return i;
|
|
1163
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
1128
1164
|
}
|
|
1129
1165
|
return null;
|
|
1130
1166
|
}
|
|
@@ -1436,6 +1472,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1436
1472
|
command: spawnCommand
|
|
1437
1473
|
});
|
|
1438
1474
|
if (spawnResult.error) {
|
|
1475
|
+
releaseSpawnLock(sessionName);
|
|
1439
1476
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
1440
1477
|
}
|
|
1441
1478
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -1473,6 +1510,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1473
1510
|
}
|
|
1474
1511
|
}
|
|
1475
1512
|
if (!booted) {
|
|
1513
|
+
releaseSpawnLock(sessionName);
|
|
1476
1514
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
1477
1515
|
}
|
|
1478
1516
|
if (!useExeAgent) {
|
|
@@ -1489,9 +1527,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1489
1527
|
pid: 0,
|
|
1490
1528
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1491
1529
|
});
|
|
1530
|
+
releaseSpawnLock(sessionName);
|
|
1492
1531
|
return { sessionName };
|
|
1493
1532
|
}
|
|
1494
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
1533
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
1495
1534
|
var init_tmux_routing = __esm({
|
|
1496
1535
|
"src/lib/tmux-routing.ts"() {
|
|
1497
1536
|
"use strict";
|
|
@@ -1503,6 +1542,7 @@ var init_tmux_routing = __esm({
|
|
|
1503
1542
|
init_provider_table();
|
|
1504
1543
|
init_intercom_queue();
|
|
1505
1544
|
init_plan_limits();
|
|
1545
|
+
SPAWN_LOCK_DIR = path9.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
1506
1546
|
SESSION_CACHE = path9.join(os5.homedir(), ".exe-os", "session-cache");
|
|
1507
1547
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
1508
1548
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -1515,7 +1555,7 @@ var init_tmux_routing = __esm({
|
|
|
1515
1555
|
|
|
1516
1556
|
// src/lib/tasks-review.ts
|
|
1517
1557
|
import path10 from "path";
|
|
1518
|
-
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as
|
|
1558
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1519
1559
|
var init_tasks_review = __esm({
|
|
1520
1560
|
"src/lib/tasks-review.ts"() {
|
|
1521
1561
|
"use strict";
|
|
@@ -1698,7 +1738,7 @@ var init_tasks_notify = __esm({
|
|
|
1698
1738
|
|
|
1699
1739
|
// src/lib/tasks.ts
|
|
1700
1740
|
import path13 from "path";
|
|
1701
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as
|
|
1741
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
1702
1742
|
async function createTask(input) {
|
|
1703
1743
|
const result = await createTaskCore(input);
|
|
1704
1744
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -1734,7 +1774,7 @@ import { z } from "zod";
|
|
|
1734
1774
|
|
|
1735
1775
|
// src/adapters/claude/active-agent.ts
|
|
1736
1776
|
init_config();
|
|
1737
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as
|
|
1777
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5, readdirSync as readdirSync3 } from "fs";
|
|
1738
1778
|
import { execSync as execSync7 } from "child_process";
|
|
1739
1779
|
import path14 from "path";
|
|
1740
1780
|
|
|
@@ -1757,7 +1797,7 @@ function getActiveAgent() {
|
|
|
1757
1797
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
1758
1798
|
if (age > STALE_MS) {
|
|
1759
1799
|
try {
|
|
1760
|
-
|
|
1800
|
+
unlinkSync5(markerPath);
|
|
1761
1801
|
} catch {
|
|
1762
1802
|
}
|
|
1763
1803
|
} else {
|
|
@@ -1780,7 +1820,7 @@ function getActiveAgent() {
|
|
|
1780
1820
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1781
1821
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1782
1822
|
).trim();
|
|
1783
|
-
const empMatch = sessionName.match(/^(
|
|
1823
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1784
1824
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1785
1825
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1786
1826
|
}
|
|
@@ -188,7 +188,7 @@ function getActiveAgent() {
|
|
|
188
188
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
189
189
|
{ encoding: "utf8", timeout: 2e3 }
|
|
190
190
|
).trim();
|
|
191
|
-
const empMatch = sessionName.match(/^(
|
|
191
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
192
192
|
if (empMatch && empMatch[1] !== "exe") {
|
|
193
193
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
194
194
|
}
|
|
@@ -318,7 +318,7 @@ var init_plan_limits = __esm({
|
|
|
318
318
|
import path9 from "path";
|
|
319
319
|
import os5 from "os";
|
|
320
320
|
import { fileURLToPath } from "url";
|
|
321
|
-
var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
321
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
322
322
|
var init_tmux_routing = __esm({
|
|
323
323
|
"src/lib/tmux-routing.ts"() {
|
|
324
324
|
"use strict";
|
|
@@ -330,6 +330,7 @@ var init_tmux_routing = __esm({
|
|
|
330
330
|
init_provider_table();
|
|
331
331
|
init_intercom_queue();
|
|
332
332
|
init_plan_limits();
|
|
333
|
+
SPAWN_LOCK_DIR = path9.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
333
334
|
SESSION_CACHE = path9.join(os5.homedir(), ".exe-os", "session-cache");
|
|
334
335
|
INTERCOM_LOG2 = path9.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
335
336
|
DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
|
|
@@ -559,6 +559,42 @@ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSy
|
|
|
559
559
|
import path7 from "path";
|
|
560
560
|
import os4 from "os";
|
|
561
561
|
import { fileURLToPath } from "url";
|
|
562
|
+
import { unlinkSync } from "fs";
|
|
563
|
+
function spawnLockPath(sessionName) {
|
|
564
|
+
return path7.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
565
|
+
}
|
|
566
|
+
function isProcessAlive(pid) {
|
|
567
|
+
try {
|
|
568
|
+
process.kill(pid, 0);
|
|
569
|
+
return true;
|
|
570
|
+
} catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function acquireSpawnLock(sessionName) {
|
|
575
|
+
if (!existsSync7(SPAWN_LOCK_DIR)) {
|
|
576
|
+
mkdirSync4(SPAWN_LOCK_DIR, { recursive: true });
|
|
577
|
+
}
|
|
578
|
+
const lockFile = spawnLockPath(sessionName);
|
|
579
|
+
if (existsSync7(lockFile)) {
|
|
580
|
+
try {
|
|
581
|
+
const lock = JSON.parse(readFileSync7(lockFile, "utf8"));
|
|
582
|
+
const age = Date.now() - lock.timestamp;
|
|
583
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
function releaseSpawnLock(sessionName) {
|
|
593
|
+
try {
|
|
594
|
+
unlinkSync(spawnLockPath(sessionName));
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
}
|
|
562
598
|
function resolveBehaviorsExporterScript() {
|
|
563
599
|
try {
|
|
564
600
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -628,10 +664,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
628
664
|
}
|
|
629
665
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
630
666
|
const base = employeeSessionName(employeeName, exeSession);
|
|
631
|
-
if (!isAlive(base)) return 0;
|
|
667
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
632
668
|
for (let i = 2; i <= maxInstances; i++) {
|
|
633
669
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
634
|
-
if (!isAlive(candidate)) return i;
|
|
670
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
635
671
|
}
|
|
636
672
|
return null;
|
|
637
673
|
}
|
|
@@ -943,6 +979,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
943
979
|
command: spawnCommand
|
|
944
980
|
});
|
|
945
981
|
if (spawnResult.error) {
|
|
982
|
+
releaseSpawnLock(sessionName);
|
|
946
983
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
947
984
|
}
|
|
948
985
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -980,6 +1017,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
980
1017
|
}
|
|
981
1018
|
}
|
|
982
1019
|
if (!booted) {
|
|
1020
|
+
releaseSpawnLock(sessionName);
|
|
983
1021
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
984
1022
|
}
|
|
985
1023
|
if (!useExeAgent) {
|
|
@@ -996,9 +1034,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
996
1034
|
pid: 0,
|
|
997
1035
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
998
1036
|
});
|
|
1037
|
+
releaseSpawnLock(sessionName);
|
|
999
1038
|
return { sessionName };
|
|
1000
1039
|
}
|
|
1001
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
1040
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
1002
1041
|
var init_tmux_routing = __esm({
|
|
1003
1042
|
"src/lib/tmux-routing.ts"() {
|
|
1004
1043
|
"use strict";
|
|
@@ -1010,6 +1049,7 @@ var init_tmux_routing = __esm({
|
|
|
1010
1049
|
init_provider_table();
|
|
1011
1050
|
init_intercom_queue();
|
|
1012
1051
|
init_plan_limits();
|
|
1052
|
+
SPAWN_LOCK_DIR = path7.join(os4.homedir(), ".exe-os", "spawn-locks");
|
|
1013
1053
|
SESSION_CACHE = path7.join(os4.homedir(), ".exe-os", "session-cache");
|
|
1014
1054
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
1015
1055
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -1166,7 +1206,7 @@ async function markFailed(messageId, reason) {
|
|
|
1166
1206
|
|
|
1167
1207
|
// src/adapters/claude/active-agent.ts
|
|
1168
1208
|
init_config();
|
|
1169
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync, readdirSync } from "fs";
|
|
1209
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
1170
1210
|
import { execSync as execSync5 } from "child_process";
|
|
1171
1211
|
import path8 from "path";
|
|
1172
1212
|
|
|
@@ -1189,7 +1229,7 @@ function getActiveAgent() {
|
|
|
1189
1229
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
1190
1230
|
if (age > STALE_MS) {
|
|
1191
1231
|
try {
|
|
1192
|
-
|
|
1232
|
+
unlinkSync2(markerPath);
|
|
1193
1233
|
} catch {
|
|
1194
1234
|
}
|
|
1195
1235
|
} else {
|
|
@@ -1212,7 +1252,7 @@ function getActiveAgent() {
|
|
|
1212
1252
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1213
1253
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1214
1254
|
).trim();
|
|
1215
|
-
const empMatch = sessionName.match(/^(
|
|
1255
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1216
1256
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1217
1257
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1218
1258
|
}
|
|
@@ -917,7 +917,7 @@ function notifyParentExe(sessionKey) {
|
|
|
917
917
|
}
|
|
918
918
|
return true;
|
|
919
919
|
}
|
|
920
|
-
var SESSION_CACHE, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
920
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
921
921
|
var init_tmux_routing = __esm({
|
|
922
922
|
"src/lib/tmux-routing.ts"() {
|
|
923
923
|
"use strict";
|
|
@@ -929,6 +929,7 @@ var init_tmux_routing = __esm({
|
|
|
929
929
|
init_provider_table();
|
|
930
930
|
init_intercom_queue();
|
|
931
931
|
init_plan_limits();
|
|
932
|
+
SPAWN_LOCK_DIR = path9.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
932
933
|
SESSION_CACHE = path9.join(os5.homedir(), ".exe-os", "session-cache");
|
|
933
934
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
934
935
|
INTERCOM_LOG2 = path9.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
@@ -1608,7 +1609,7 @@ function getActiveAgent() {
|
|
|
1608
1609
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1609
1610
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1610
1611
|
).trim();
|
|
1611
|
-
const empMatch = sessionName.match(/^(
|
|
1612
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1612
1613
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1613
1614
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1614
1615
|
}
|
package/dist/runtime/index.js
CHANGED
|
@@ -3285,6 +3285,7 @@ var init_capacity_monitor = __esm({
|
|
|
3285
3285
|
// src/lib/tmux-routing.ts
|
|
3286
3286
|
var tmux_routing_exports = {};
|
|
3287
3287
|
__export(tmux_routing_exports, {
|
|
3288
|
+
acquireSpawnLock: () => acquireSpawnLock,
|
|
3288
3289
|
employeeSessionName: () => employeeSessionName,
|
|
3289
3290
|
ensureEmployee: () => ensureEmployee,
|
|
3290
3291
|
extractRootExe: () => extractRootExe,
|
|
@@ -3299,6 +3300,7 @@ __export(tmux_routing_exports, {
|
|
|
3299
3300
|
notifyParentExe: () => notifyParentExe,
|
|
3300
3301
|
parseParentExe: () => parseParentExe,
|
|
3301
3302
|
registerParentExe: () => registerParentExe,
|
|
3303
|
+
releaseSpawnLock: () => releaseSpawnLock,
|
|
3302
3304
|
resolveExeSession: () => resolveExeSession,
|
|
3303
3305
|
sendIntercom: () => sendIntercom,
|
|
3304
3306
|
spawnEmployee: () => spawnEmployee,
|
|
@@ -3309,6 +3311,42 @@ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSy
|
|
|
3309
3311
|
import path14 from "path";
|
|
3310
3312
|
import os6 from "os";
|
|
3311
3313
|
import { fileURLToPath } from "url";
|
|
3314
|
+
import { unlinkSync as unlinkSync4 } from "fs";
|
|
3315
|
+
function spawnLockPath(sessionName) {
|
|
3316
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3317
|
+
}
|
|
3318
|
+
function isProcessAlive(pid) {
|
|
3319
|
+
try {
|
|
3320
|
+
process.kill(pid, 0);
|
|
3321
|
+
return true;
|
|
3322
|
+
} catch {
|
|
3323
|
+
return false;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
function acquireSpawnLock(sessionName) {
|
|
3327
|
+
if (!existsSync10(SPAWN_LOCK_DIR)) {
|
|
3328
|
+
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
3329
|
+
}
|
|
3330
|
+
const lockFile = spawnLockPath(sessionName);
|
|
3331
|
+
if (existsSync10(lockFile)) {
|
|
3332
|
+
try {
|
|
3333
|
+
const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
|
|
3334
|
+
const age = Date.now() - lock.timestamp;
|
|
3335
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3336
|
+
return false;
|
|
3337
|
+
}
|
|
3338
|
+
} catch {
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3342
|
+
return true;
|
|
3343
|
+
}
|
|
3344
|
+
function releaseSpawnLock(sessionName) {
|
|
3345
|
+
try {
|
|
3346
|
+
unlinkSync4(spawnLockPath(sessionName));
|
|
3347
|
+
} catch {
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3312
3350
|
function resolveBehaviorsExporterScript() {
|
|
3313
3351
|
try {
|
|
3314
3352
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -3407,10 +3445,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3407
3445
|
}
|
|
3408
3446
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3409
3447
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3410
|
-
if (!isAlive(base)) return 0;
|
|
3448
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
3411
3449
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3412
3450
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3413
|
-
if (!isAlive(candidate)) return i;
|
|
3451
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
3414
3452
|
}
|
|
3415
3453
|
return null;
|
|
3416
3454
|
}
|
|
@@ -3774,6 +3812,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3774
3812
|
command: spawnCommand
|
|
3775
3813
|
});
|
|
3776
3814
|
if (spawnResult.error) {
|
|
3815
|
+
releaseSpawnLock(sessionName);
|
|
3777
3816
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
3778
3817
|
}
|
|
3779
3818
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -3811,6 +3850,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3811
3850
|
}
|
|
3812
3851
|
}
|
|
3813
3852
|
if (!booted) {
|
|
3853
|
+
releaseSpawnLock(sessionName);
|
|
3814
3854
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3815
3855
|
}
|
|
3816
3856
|
if (!useExeAgent) {
|
|
@@ -3827,9 +3867,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3827
3867
|
pid: 0,
|
|
3828
3868
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3829
3869
|
});
|
|
3870
|
+
releaseSpawnLock(sessionName);
|
|
3830
3871
|
return { sessionName };
|
|
3831
3872
|
}
|
|
3832
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3873
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3833
3874
|
var init_tmux_routing = __esm({
|
|
3834
3875
|
"src/lib/tmux-routing.ts"() {
|
|
3835
3876
|
"use strict";
|
|
@@ -3841,6 +3882,7 @@ var init_tmux_routing = __esm({
|
|
|
3841
3882
|
init_provider_table();
|
|
3842
3883
|
init_intercom_queue();
|
|
3843
3884
|
init_plan_limits();
|
|
3885
|
+
SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
3844
3886
|
SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
|
|
3845
3887
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3846
3888
|
VERIFY_PANE_LINES = 200;
|
|
@@ -4229,7 +4271,8 @@ async function writeMemory(record) {
|
|
|
4229
4271
|
has_error: record.has_error ? 1 : 0,
|
|
4230
4272
|
raw_text: record.raw_text,
|
|
4231
4273
|
vector: record.vector,
|
|
4232
|
-
version:
|
|
4274
|
+
version: 0,
|
|
4275
|
+
// Placeholder — assigned atomically at flush time
|
|
4233
4276
|
task_id: record.task_id ?? null,
|
|
4234
4277
|
importance: record.importance ?? 5,
|
|
4235
4278
|
status: record.status ?? "active",
|
|
@@ -4263,6 +4306,13 @@ async function flushBatch() {
|
|
|
4263
4306
|
_flushing = true;
|
|
4264
4307
|
try {
|
|
4265
4308
|
const batch = _pendingRecords.slice(0);
|
|
4309
|
+
const client = getClient();
|
|
4310
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
4311
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
4312
|
+
for (const row of batch) {
|
|
4313
|
+
row.version = baseVersion++;
|
|
4314
|
+
}
|
|
4315
|
+
_nextVersion = baseVersion;
|
|
4266
4316
|
const buildStmt = (row) => {
|
|
4267
4317
|
const hasVector = row.vector !== null;
|
|
4268
4318
|
const taskId = row.task_id ?? null;
|