@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
|
@@ -2924,6 +2924,42 @@ import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirS
|
|
|
2924
2924
|
import path14 from "path";
|
|
2925
2925
|
import os5 from "os";
|
|
2926
2926
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2927
|
+
import { unlinkSync as unlinkSync3 } from "fs";
|
|
2928
|
+
function spawnLockPath(sessionName) {
|
|
2929
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2930
|
+
}
|
|
2931
|
+
function isProcessAlive(pid) {
|
|
2932
|
+
try {
|
|
2933
|
+
process.kill(pid, 0);
|
|
2934
|
+
return true;
|
|
2935
|
+
} catch {
|
|
2936
|
+
return false;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function acquireSpawnLock2(sessionName) {
|
|
2940
|
+
if (!existsSync13(SPAWN_LOCK_DIR)) {
|
|
2941
|
+
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
2942
|
+
}
|
|
2943
|
+
const lockFile = spawnLockPath(sessionName);
|
|
2944
|
+
if (existsSync13(lockFile)) {
|
|
2945
|
+
try {
|
|
2946
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
2947
|
+
const age = Date.now() - lock.timestamp;
|
|
2948
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2949
|
+
return false;
|
|
2950
|
+
}
|
|
2951
|
+
} catch {
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
2955
|
+
return true;
|
|
2956
|
+
}
|
|
2957
|
+
function releaseSpawnLock2(sessionName) {
|
|
2958
|
+
try {
|
|
2959
|
+
unlinkSync3(spawnLockPath(sessionName));
|
|
2960
|
+
} catch {
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2927
2963
|
function resolveBehaviorsExporterScript() {
|
|
2928
2964
|
try {
|
|
2929
2965
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
@@ -3004,10 +3040,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3004
3040
|
}
|
|
3005
3041
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3006
3042
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3007
|
-
if (!isAlive(base)) return 0;
|
|
3043
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3008
3044
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3009
3045
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3010
|
-
if (!isAlive(candidate)) return i;
|
|
3046
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3011
3047
|
}
|
|
3012
3048
|
return null;
|
|
3013
3049
|
}
|
|
@@ -3341,6 +3377,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3341
3377
|
command: spawnCommand
|
|
3342
3378
|
});
|
|
3343
3379
|
if (spawnResult.error) {
|
|
3380
|
+
releaseSpawnLock2(sessionName);
|
|
3344
3381
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
3345
3382
|
}
|
|
3346
3383
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -3378,6 +3415,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3378
3415
|
}
|
|
3379
3416
|
}
|
|
3380
3417
|
if (!booted) {
|
|
3418
|
+
releaseSpawnLock2(sessionName);
|
|
3381
3419
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3382
3420
|
}
|
|
3383
3421
|
if (!useExeAgent) {
|
|
@@ -3394,9 +3432,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3394
3432
|
pid: 0,
|
|
3395
3433
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3396
3434
|
});
|
|
3435
|
+
releaseSpawnLock2(sessionName);
|
|
3397
3436
|
return { sessionName };
|
|
3398
3437
|
}
|
|
3399
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3438
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3400
3439
|
var init_tmux_routing = __esm({
|
|
3401
3440
|
"src/lib/tmux-routing.ts"() {
|
|
3402
3441
|
"use strict";
|
|
@@ -3408,6 +3447,7 @@ var init_tmux_routing = __esm({
|
|
|
3408
3447
|
init_provider_table();
|
|
3409
3448
|
init_intercom_queue();
|
|
3410
3449
|
init_plan_limits();
|
|
3450
|
+
SPAWN_LOCK_DIR = path14.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
3411
3451
|
SESSION_CACHE = path14.join(os5.homedir(), ".exe-os", "session-cache");
|
|
3412
3452
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3413
3453
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -3420,7 +3460,7 @@ var init_tmux_routing = __esm({
|
|
|
3420
3460
|
|
|
3421
3461
|
// src/lib/tasks-review.ts
|
|
3422
3462
|
import path15 from "path";
|
|
3423
|
-
import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as
|
|
3463
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
3424
3464
|
async function countPendingReviews() {
|
|
3425
3465
|
const client = getClient();
|
|
3426
3466
|
const result = await client.execute({
|
|
@@ -3544,7 +3584,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3544
3584
|
if (existsSync14(cacheDir)) {
|
|
3545
3585
|
for (const f of readdirSync3(cacheDir)) {
|
|
3546
3586
|
if (f.startsWith("review-notified-")) {
|
|
3547
|
-
|
|
3587
|
+
unlinkSync4(path15.join(cacheDir, f));
|
|
3548
3588
|
}
|
|
3549
3589
|
}
|
|
3550
3590
|
}
|
|
@@ -4103,7 +4143,7 @@ __export(tasks_exports, {
|
|
|
4103
4143
|
writeCheckpoint: () => writeCheckpoint
|
|
4104
4144
|
});
|
|
4105
4145
|
import path17 from "path";
|
|
4106
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as
|
|
4146
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
4107
4147
|
async function createTask(input2) {
|
|
4108
4148
|
const result = await createTaskCore(input2);
|
|
4109
4149
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4129,7 +4169,7 @@ async function updateTask(input2) {
|
|
|
4129
4169
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4130
4170
|
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
4131
4171
|
try {
|
|
4132
|
-
|
|
4172
|
+
unlinkSync5(cachePath);
|
|
4133
4173
|
} catch {
|
|
4134
4174
|
}
|
|
4135
4175
|
}
|
|
@@ -4464,7 +4504,8 @@ async function writeMemory(record) {
|
|
|
4464
4504
|
has_error: record.has_error ? 1 : 0,
|
|
4465
4505
|
raw_text: record.raw_text,
|
|
4466
4506
|
vector: record.vector,
|
|
4467
|
-
version:
|
|
4507
|
+
version: 0,
|
|
4508
|
+
// Placeholder — assigned atomically at flush time
|
|
4468
4509
|
task_id: record.task_id ?? null,
|
|
4469
4510
|
importance: record.importance ?? 5,
|
|
4470
4511
|
status: record.status ?? "active",
|
|
@@ -4498,6 +4539,13 @@ async function flushBatch() {
|
|
|
4498
4539
|
_flushing = true;
|
|
4499
4540
|
try {
|
|
4500
4541
|
const batch = _pendingRecords.slice(0);
|
|
4542
|
+
const client = getClient();
|
|
4543
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
4544
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
4545
|
+
for (const row of batch) {
|
|
4546
|
+
row.version = baseVersion++;
|
|
4547
|
+
}
|
|
4548
|
+
_nextVersion = baseVersion;
|
|
4501
4549
|
const buildStmt = (row) => {
|
|
4502
4550
|
const hasVector = row.vector !== null;
|
|
4503
4551
|
const taskId = row.task_id ?? null;
|
package/dist/hooks/ingest.js
CHANGED
|
@@ -247,7 +247,7 @@ function getActiveAgent() {
|
|
|
247
247
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
248
248
|
{ encoding: "utf8", timeout: 2e3 }
|
|
249
249
|
).trim();
|
|
250
|
-
const empMatch = sessionName.match(/^(
|
|
250
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
251
251
|
if (empMatch && empMatch[1] !== "exe") {
|
|
252
252
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
253
253
|
}
|
|
@@ -1489,7 +1489,8 @@ async function writeMemory(record) {
|
|
|
1489
1489
|
has_error: record.has_error ? 1 : 0,
|
|
1490
1490
|
raw_text: record.raw_text,
|
|
1491
1491
|
vector: record.vector,
|
|
1492
|
-
version:
|
|
1492
|
+
version: 0,
|
|
1493
|
+
// Placeholder — assigned atomically at flush time
|
|
1493
1494
|
task_id: record.task_id ?? null,
|
|
1494
1495
|
importance: record.importance ?? 5,
|
|
1495
1496
|
status: record.status ?? "active",
|
|
@@ -1523,6 +1524,13 @@ async function flushBatch() {
|
|
|
1523
1524
|
_flushing = true;
|
|
1524
1525
|
try {
|
|
1525
1526
|
const batch = _pendingRecords.slice(0);
|
|
1527
|
+
const client = getClient();
|
|
1528
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1529
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1530
|
+
for (const row of batch) {
|
|
1531
|
+
row.version = baseVersion++;
|
|
1532
|
+
}
|
|
1533
|
+
_nextVersion = baseVersion;
|
|
1526
1534
|
const buildStmt = (row) => {
|
|
1527
1535
|
const hasVector = row.vector !== null;
|
|
1528
1536
|
const taskId = row.task_id ?? null;
|
|
@@ -1921,7 +1929,7 @@ function getActiveAgent() {
|
|
|
1921
1929
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1922
1930
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1923
1931
|
).trim();
|
|
1924
|
-
const empMatch = sessionName.match(/^(
|
|
1932
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1925
1933
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1926
1934
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1927
1935
|
}
|
|
@@ -1489,7 +1489,8 @@ async function writeMemory(record) {
|
|
|
1489
1489
|
has_error: record.has_error ? 1 : 0,
|
|
1490
1490
|
raw_text: record.raw_text,
|
|
1491
1491
|
vector: record.vector,
|
|
1492
|
-
version:
|
|
1492
|
+
version: 0,
|
|
1493
|
+
// Placeholder — assigned atomically at flush time
|
|
1493
1494
|
task_id: record.task_id ?? null,
|
|
1494
1495
|
importance: record.importance ?? 5,
|
|
1495
1496
|
status: record.status ?? "active",
|
|
@@ -1523,6 +1524,13 @@ async function flushBatch() {
|
|
|
1523
1524
|
_flushing = true;
|
|
1524
1525
|
try {
|
|
1525
1526
|
const batch = _pendingRecords.slice(0);
|
|
1527
|
+
const client = getClient();
|
|
1528
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1529
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1530
|
+
for (const row of batch) {
|
|
1531
|
+
row.version = baseVersion++;
|
|
1532
|
+
}
|
|
1533
|
+
_nextVersion = baseVersion;
|
|
1526
1534
|
const buildStmt = (row) => {
|
|
1527
1535
|
const hasVector = row.vector !== null;
|
|
1528
1536
|
const taskId = row.task_id ?? null;
|
|
@@ -1921,7 +1929,7 @@ function getActiveAgent() {
|
|
|
1921
1929
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1922
1930
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1923
1931
|
).trim();
|
|
1924
|
-
const empMatch = sessionName.match(/^(
|
|
1932
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1925
1933
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1926
1934
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1927
1935
|
}
|
|
@@ -1489,7 +1489,8 @@ async function writeMemory(record) {
|
|
|
1489
1489
|
has_error: record.has_error ? 1 : 0,
|
|
1490
1490
|
raw_text: record.raw_text,
|
|
1491
1491
|
vector: record.vector,
|
|
1492
|
-
version:
|
|
1492
|
+
version: 0,
|
|
1493
|
+
// Placeholder — assigned atomically at flush time
|
|
1493
1494
|
task_id: record.task_id ?? null,
|
|
1494
1495
|
importance: record.importance ?? 5,
|
|
1495
1496
|
status: record.status ?? "active",
|
|
@@ -1523,6 +1524,13 @@ async function flushBatch() {
|
|
|
1523
1524
|
_flushing = true;
|
|
1524
1525
|
try {
|
|
1525
1526
|
const batch = _pendingRecords.slice(0);
|
|
1527
|
+
const client = getClient();
|
|
1528
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1529
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1530
|
+
for (const row of batch) {
|
|
1531
|
+
row.version = baseVersion++;
|
|
1532
|
+
}
|
|
1533
|
+
_nextVersion = baseVersion;
|
|
1526
1534
|
const buildStmt = (row) => {
|
|
1527
1535
|
const hasVector = row.vector !== null;
|
|
1528
1536
|
const taskId = row.task_id ?? null;
|
|
@@ -1921,7 +1929,7 @@ function getActiveAgent() {
|
|
|
1921
1929
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1922
1930
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1923
1931
|
).trim();
|
|
1924
|
-
const empMatch = sessionName.match(/^(
|
|
1932
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1925
1933
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1926
1934
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1927
1935
|
}
|
|
@@ -1489,7 +1489,8 @@ async function writeMemory(record) {
|
|
|
1489
1489
|
has_error: record.has_error ? 1 : 0,
|
|
1490
1490
|
raw_text: record.raw_text,
|
|
1491
1491
|
vector: record.vector,
|
|
1492
|
-
version:
|
|
1492
|
+
version: 0,
|
|
1493
|
+
// Placeholder — assigned atomically at flush time
|
|
1493
1494
|
task_id: record.task_id ?? null,
|
|
1494
1495
|
importance: record.importance ?? 5,
|
|
1495
1496
|
status: record.status ?? "active",
|
|
@@ -1523,6 +1524,13 @@ async function flushBatch() {
|
|
|
1523
1524
|
_flushing = true;
|
|
1524
1525
|
try {
|
|
1525
1526
|
const batch = _pendingRecords.slice(0);
|
|
1527
|
+
const client = getClient();
|
|
1528
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1529
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1530
|
+
for (const row of batch) {
|
|
1531
|
+
row.version = baseVersion++;
|
|
1532
|
+
}
|
|
1533
|
+
_nextVersion = baseVersion;
|
|
1526
1534
|
const buildStmt = (row) => {
|
|
1527
1535
|
const hasVector = row.vector !== null;
|
|
1528
1536
|
const taskId = row.task_id ?? null;
|
|
@@ -1921,7 +1929,7 @@ function getActiveAgent() {
|
|
|
1921
1929
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1922
1930
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1923
1931
|
).trim();
|
|
1924
|
-
const empMatch = sessionName.match(/^(
|
|
1932
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1925
1933
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1926
1934
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1927
1935
|
}
|
|
@@ -1793,7 +1793,8 @@ async function writeMemory(record) {
|
|
|
1793
1793
|
has_error: record.has_error ? 1 : 0,
|
|
1794
1794
|
raw_text: record.raw_text,
|
|
1795
1795
|
vector: record.vector,
|
|
1796
|
-
version:
|
|
1796
|
+
version: 0,
|
|
1797
|
+
// Placeholder — assigned atomically at flush time
|
|
1797
1798
|
task_id: record.task_id ?? null,
|
|
1798
1799
|
importance: record.importance ?? 5,
|
|
1799
1800
|
status: record.status ?? "active",
|
|
@@ -1827,6 +1828,13 @@ async function flushBatch() {
|
|
|
1827
1828
|
_flushing = true;
|
|
1828
1829
|
try {
|
|
1829
1830
|
const batch = _pendingRecords.slice(0);
|
|
1831
|
+
const client = getClient();
|
|
1832
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1833
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1834
|
+
for (const row of batch) {
|
|
1835
|
+
row.version = baseVersion++;
|
|
1836
|
+
}
|
|
1837
|
+
_nextVersion = baseVersion;
|
|
1830
1838
|
const buildStmt = (row) => {
|
|
1831
1839
|
const hasVector = row.vector !== null;
|
|
1832
1840
|
const taskId = row.task_id ?? null;
|
|
@@ -2356,7 +2364,7 @@ function getActiveAgent() {
|
|
|
2356
2364
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
2357
2365
|
{ encoding: "utf8", timeout: 2e3 }
|
|
2358
2366
|
).trim();
|
|
2359
|
-
const empMatch = sessionName.match(/^(
|
|
2367
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
2360
2368
|
if (empMatch && empMatch[1] !== "exe") {
|
|
2361
2369
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
2362
2370
|
}
|
|
@@ -1946,7 +1946,8 @@ async function writeMemory(record) {
|
|
|
1946
1946
|
has_error: record.has_error ? 1 : 0,
|
|
1947
1947
|
raw_text: record.raw_text,
|
|
1948
1948
|
vector: record.vector,
|
|
1949
|
-
version:
|
|
1949
|
+
version: 0,
|
|
1950
|
+
// Placeholder — assigned atomically at flush time
|
|
1950
1951
|
task_id: record.task_id ?? null,
|
|
1951
1952
|
importance: record.importance ?? 5,
|
|
1952
1953
|
status: record.status ?? "active",
|
|
@@ -1980,6 +1981,13 @@ async function flushBatch() {
|
|
|
1980
1981
|
_flushing = true;
|
|
1981
1982
|
try {
|
|
1982
1983
|
const batch = _pendingRecords.slice(0);
|
|
1984
|
+
const client = getClient();
|
|
1985
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1986
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1987
|
+
for (const row of batch) {
|
|
1988
|
+
row.version = baseVersion++;
|
|
1989
|
+
}
|
|
1990
|
+
_nextVersion = baseVersion;
|
|
1983
1991
|
const buildStmt = (row) => {
|
|
1984
1992
|
const hasVector = row.vector !== null;
|
|
1985
1993
|
const taskId = row.task_id ?? null;
|
|
@@ -1540,7 +1540,8 @@ async function writeMemory(record) {
|
|
|
1540
1540
|
has_error: record.has_error ? 1 : 0,
|
|
1541
1541
|
raw_text: record.raw_text,
|
|
1542
1542
|
vector: record.vector,
|
|
1543
|
-
version:
|
|
1543
|
+
version: 0,
|
|
1544
|
+
// Placeholder — assigned atomically at flush time
|
|
1544
1545
|
task_id: record.task_id ?? null,
|
|
1545
1546
|
importance: record.importance ?? 5,
|
|
1546
1547
|
status: record.status ?? "active",
|
|
@@ -1574,6 +1575,13 @@ async function flushBatch() {
|
|
|
1574
1575
|
_flushing = true;
|
|
1575
1576
|
try {
|
|
1576
1577
|
const batch = _pendingRecords.slice(0);
|
|
1578
|
+
const client = getClient();
|
|
1579
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1580
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1581
|
+
for (const row of batch) {
|
|
1582
|
+
row.version = baseVersion++;
|
|
1583
|
+
}
|
|
1584
|
+
_nextVersion = baseVersion;
|
|
1577
1585
|
const buildStmt = (row) => {
|
|
1578
1586
|
const hasVector = row.vector !== null;
|
|
1579
1587
|
const taskId = row.task_id ?? null;
|
|
@@ -3281,6 +3289,42 @@ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirS
|
|
|
3281
3289
|
import path14 from "path";
|
|
3282
3290
|
import os4 from "os";
|
|
3283
3291
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3292
|
+
import { unlinkSync as unlinkSync3 } from "fs";
|
|
3293
|
+
function spawnLockPath(sessionName) {
|
|
3294
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3295
|
+
}
|
|
3296
|
+
function isProcessAlive(pid) {
|
|
3297
|
+
try {
|
|
3298
|
+
process.kill(pid, 0);
|
|
3299
|
+
return true;
|
|
3300
|
+
} catch {
|
|
3301
|
+
return false;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function acquireSpawnLock2(sessionName) {
|
|
3305
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
3306
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3307
|
+
}
|
|
3308
|
+
const lockFile = spawnLockPath(sessionName);
|
|
3309
|
+
if (existsSync12(lockFile)) {
|
|
3310
|
+
try {
|
|
3311
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3312
|
+
const age = Date.now() - lock.timestamp;
|
|
3313
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3314
|
+
return false;
|
|
3315
|
+
}
|
|
3316
|
+
} catch {
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3320
|
+
return true;
|
|
3321
|
+
}
|
|
3322
|
+
function releaseSpawnLock2(sessionName) {
|
|
3323
|
+
try {
|
|
3324
|
+
unlinkSync3(spawnLockPath(sessionName));
|
|
3325
|
+
} catch {
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3284
3328
|
function resolveBehaviorsExporterScript() {
|
|
3285
3329
|
try {
|
|
3286
3330
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
@@ -3350,10 +3394,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3350
3394
|
}
|
|
3351
3395
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3352
3396
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3353
|
-
if (!isAlive(base)) return 0;
|
|
3397
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3354
3398
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3355
3399
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3356
|
-
if (!isAlive(candidate)) return i;
|
|
3400
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3357
3401
|
}
|
|
3358
3402
|
return null;
|
|
3359
3403
|
}
|
|
@@ -3665,6 +3709,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3665
3709
|
command: spawnCommand
|
|
3666
3710
|
});
|
|
3667
3711
|
if (spawnResult.error) {
|
|
3712
|
+
releaseSpawnLock2(sessionName);
|
|
3668
3713
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
3669
3714
|
}
|
|
3670
3715
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -3702,6 +3747,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3702
3747
|
}
|
|
3703
3748
|
}
|
|
3704
3749
|
if (!booted) {
|
|
3750
|
+
releaseSpawnLock2(sessionName);
|
|
3705
3751
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3706
3752
|
}
|
|
3707
3753
|
if (!useExeAgent) {
|
|
@@ -3718,9 +3764,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3718
3764
|
pid: 0,
|
|
3719
3765
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3720
3766
|
});
|
|
3767
|
+
releaseSpawnLock2(sessionName);
|
|
3721
3768
|
return { sessionName };
|
|
3722
3769
|
}
|
|
3723
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3770
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3724
3771
|
var init_tmux_routing = __esm({
|
|
3725
3772
|
"src/lib/tmux-routing.ts"() {
|
|
3726
3773
|
"use strict";
|
|
@@ -3732,6 +3779,7 @@ var init_tmux_routing = __esm({
|
|
|
3732
3779
|
init_provider_table();
|
|
3733
3780
|
init_intercom_queue();
|
|
3734
3781
|
init_plan_limits();
|
|
3782
|
+
SPAWN_LOCK_DIR = path14.join(os4.homedir(), ".exe-os", "spawn-locks");
|
|
3735
3783
|
SESSION_CACHE = path14.join(os4.homedir(), ".exe-os", "session-cache");
|
|
3736
3784
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3737
3785
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -3999,7 +4047,7 @@ import os5 from "os";
|
|
|
3999
4047
|
import {
|
|
4000
4048
|
readFileSync as readFileSync11,
|
|
4001
4049
|
readdirSync as readdirSync3,
|
|
4002
|
-
unlinkSync as
|
|
4050
|
+
unlinkSync as unlinkSync4,
|
|
4003
4051
|
existsSync as existsSync13,
|
|
4004
4052
|
rmdirSync
|
|
4005
4053
|
} from "fs";
|
|
@@ -4256,7 +4304,7 @@ __export(tasks_review_exports, {
|
|
|
4256
4304
|
listPendingReviews: () => listPendingReviews
|
|
4257
4305
|
});
|
|
4258
4306
|
import path17 from "path";
|
|
4259
|
-
import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as
|
|
4307
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
4260
4308
|
async function countPendingReviews() {
|
|
4261
4309
|
const client = getClient();
|
|
4262
4310
|
const result = await client.execute({
|
|
@@ -4470,7 +4518,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4470
4518
|
if (existsSync15(cacheDir)) {
|
|
4471
4519
|
for (const f of readdirSync4(cacheDir)) {
|
|
4472
4520
|
if (f.startsWith("review-notified-")) {
|
|
4473
|
-
|
|
4521
|
+
unlinkSync5(path17.join(cacheDir, f));
|
|
4474
4522
|
}
|
|
4475
4523
|
}
|
|
4476
4524
|
}
|
|
@@ -4904,7 +4952,7 @@ function getActiveAgent() {
|
|
|
4904
4952
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
4905
4953
|
{ encoding: "utf8", timeout: 2e3 }
|
|
4906
4954
|
).trim();
|
|
4907
|
-
const empMatch = sessionName.match(/^(
|
|
4955
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
4908
4956
|
if (empMatch && empMatch[1] !== "exe") {
|
|
4909
4957
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
4910
4958
|
}
|
|
@@ -2272,7 +2272,8 @@ async function writeMemory(record) {
|
|
|
2272
2272
|
has_error: record.has_error ? 1 : 0,
|
|
2273
2273
|
raw_text: record.raw_text,
|
|
2274
2274
|
vector: record.vector,
|
|
2275
|
-
version:
|
|
2275
|
+
version: 0,
|
|
2276
|
+
// Placeholder — assigned atomically at flush time
|
|
2276
2277
|
task_id: record.task_id ?? null,
|
|
2277
2278
|
importance: record.importance ?? 5,
|
|
2278
2279
|
status: record.status ?? "active",
|
|
@@ -2306,6 +2307,13 @@ async function flushBatch() {
|
|
|
2306
2307
|
_flushing = true;
|
|
2307
2308
|
try {
|
|
2308
2309
|
const batch = _pendingRecords.slice(0);
|
|
2310
|
+
const client = getClient();
|
|
2311
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
2312
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
2313
|
+
for (const row of batch) {
|
|
2314
|
+
row.version = baseVersion++;
|
|
2315
|
+
}
|
|
2316
|
+
_nextVersion = baseVersion;
|
|
2309
2317
|
const buildStmt = (row) => {
|
|
2310
2318
|
const hasVector = row.vector !== null;
|
|
2311
2319
|
const taskId = row.task_id ?? null;
|
|
@@ -1489,7 +1489,8 @@ async function writeMemory(record) {
|
|
|
1489
1489
|
has_error: record.has_error ? 1 : 0,
|
|
1490
1490
|
raw_text: record.raw_text,
|
|
1491
1491
|
vector: record.vector,
|
|
1492
|
-
version:
|
|
1492
|
+
version: 0,
|
|
1493
|
+
// Placeholder — assigned atomically at flush time
|
|
1493
1494
|
task_id: record.task_id ?? null,
|
|
1494
1495
|
importance: record.importance ?? 5,
|
|
1495
1496
|
status: record.status ?? "active",
|
|
@@ -1523,6 +1524,13 @@ async function flushBatch() {
|
|
|
1523
1524
|
_flushing = true;
|
|
1524
1525
|
try {
|
|
1525
1526
|
const batch = _pendingRecords.slice(0);
|
|
1527
|
+
const client = getClient();
|
|
1528
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1529
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1530
|
+
for (const row of batch) {
|
|
1531
|
+
row.version = baseVersion++;
|
|
1532
|
+
}
|
|
1533
|
+
_nextVersion = baseVersion;
|
|
1526
1534
|
const buildStmt = (row) => {
|
|
1527
1535
|
const hasVector = row.vector !== null;
|
|
1528
1536
|
const taskId = row.task_id ?? null;
|
|
@@ -2162,7 +2170,7 @@ function getActiveAgent() {
|
|
|
2162
2170
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
2163
2171
|
{ encoding: "utf8", timeout: 2e3 }
|
|
2164
2172
|
).trim();
|
|
2165
|
-
const empMatch = sessionName.match(/^(
|
|
2173
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
2166
2174
|
if (empMatch && empMatch[1] !== "exe") {
|
|
2167
2175
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
2168
2176
|
}
|
|
@@ -1529,7 +1529,8 @@ async function writeMemory(record) {
|
|
|
1529
1529
|
has_error: record.has_error ? 1 : 0,
|
|
1530
1530
|
raw_text: record.raw_text,
|
|
1531
1531
|
vector: record.vector,
|
|
1532
|
-
version:
|
|
1532
|
+
version: 0,
|
|
1533
|
+
// Placeholder — assigned atomically at flush time
|
|
1533
1534
|
task_id: record.task_id ?? null,
|
|
1534
1535
|
importance: record.importance ?? 5,
|
|
1535
1536
|
status: record.status ?? "active",
|
|
@@ -1563,6 +1564,13 @@ async function flushBatch() {
|
|
|
1563
1564
|
_flushing = true;
|
|
1564
1565
|
try {
|
|
1565
1566
|
const batch = _pendingRecords.slice(0);
|
|
1567
|
+
const client = getClient();
|
|
1568
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1569
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1570
|
+
for (const row of batch) {
|
|
1571
|
+
row.version = baseVersion++;
|
|
1572
|
+
}
|
|
1573
|
+
_nextVersion = baseVersion;
|
|
1566
1574
|
const buildStmt = (row) => {
|
|
1567
1575
|
const hasVector = row.vector !== null;
|
|
1568
1576
|
const taskId = row.task_id ?? null;
|
|
@@ -3201,7 +3209,7 @@ function getActiveAgent() {
|
|
|
3201
3209
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
3202
3210
|
{ encoding: "utf8", timeout: 2e3 }
|
|
3203
3211
|
).trim();
|
|
3204
|
-
const empMatch = sessionName.match(/^(
|
|
3212
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
3205
3213
|
if (empMatch && empMatch[1] !== "exe") {
|
|
3206
3214
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
3207
3215
|
}
|
package/dist/hooks/stop.js
CHANGED
|
@@ -1508,7 +1508,8 @@ async function writeMemory(record) {
|
|
|
1508
1508
|
has_error: record.has_error ? 1 : 0,
|
|
1509
1509
|
raw_text: record.raw_text,
|
|
1510
1510
|
vector: record.vector,
|
|
1511
|
-
version:
|
|
1511
|
+
version: 0,
|
|
1512
|
+
// Placeholder — assigned atomically at flush time
|
|
1512
1513
|
task_id: record.task_id ?? null,
|
|
1513
1514
|
importance: record.importance ?? 5,
|
|
1514
1515
|
status: record.status ?? "active",
|
|
@@ -1542,6 +1543,13 @@ async function flushBatch() {
|
|
|
1542
1543
|
_flushing = true;
|
|
1543
1544
|
try {
|
|
1544
1545
|
const batch = _pendingRecords.slice(0);
|
|
1546
|
+
const client = getClient();
|
|
1547
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1548
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1549
|
+
for (const row of batch) {
|
|
1550
|
+
row.version = baseVersion++;
|
|
1551
|
+
}
|
|
1552
|
+
_nextVersion = baseVersion;
|
|
1545
1553
|
const buildStmt = (row) => {
|
|
1546
1554
|
const hasVector = row.vector !== null;
|
|
1547
1555
|
const taskId = row.task_id ?? null;
|
|
@@ -1948,7 +1956,7 @@ function getActiveAgent() {
|
|
|
1948
1956
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1949
1957
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1950
1958
|
).trim();
|
|
1951
|
-
const empMatch = sessionName.match(/^(
|
|
1959
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1952
1960
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1953
1961
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1954
1962
|
}
|
|
@@ -1489,7 +1489,8 @@ async function writeMemory(record) {
|
|
|
1489
1489
|
has_error: record.has_error ? 1 : 0,
|
|
1490
1490
|
raw_text: record.raw_text,
|
|
1491
1491
|
vector: record.vector,
|
|
1492
|
-
version:
|
|
1492
|
+
version: 0,
|
|
1493
|
+
// Placeholder — assigned atomically at flush time
|
|
1493
1494
|
task_id: record.task_id ?? null,
|
|
1494
1495
|
importance: record.importance ?? 5,
|
|
1495
1496
|
status: record.status ?? "active",
|
|
@@ -1523,6 +1524,13 @@ async function flushBatch() {
|
|
|
1523
1524
|
_flushing = true;
|
|
1524
1525
|
try {
|
|
1525
1526
|
const batch = _pendingRecords.slice(0);
|
|
1527
|
+
const client = getClient();
|
|
1528
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1529
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1530
|
+
for (const row of batch) {
|
|
1531
|
+
row.version = baseVersion++;
|
|
1532
|
+
}
|
|
1533
|
+
_nextVersion = baseVersion;
|
|
1526
1534
|
const buildStmt = (row) => {
|
|
1527
1535
|
const hasVector = row.vector !== null;
|
|
1528
1536
|
const taskId = row.task_id ?? null;
|
|
@@ -1921,7 +1929,7 @@ function getActiveAgent() {
|
|
|
1921
1929
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1922
1930
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1923
1931
|
).trim();
|
|
1924
|
-
const empMatch = sessionName.match(/^(
|
|
1932
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1925
1933
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1926
1934
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1927
1935
|
}
|