@askexenow/exe-os 0.8.33 → 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 +341 -349
- package/dist/bin/backfill-responses.js +81 -13
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1737 -1117
- package/dist/bin/exe-assign.js +89 -19
- package/dist/bin/exe-boot.js +951 -101
- package/dist/bin/exe-call.js +61 -2
- package/dist/bin/exe-dispatch.js +61 -13
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +71 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +178 -45
- package/dist/bin/exe-heartbeat.js +79 -14
- package/dist/bin/exe-kill.js +71 -3
- package/dist/bin/exe-launch-agent.js +148 -14
- package/dist/bin/exe-link.js +1437 -0
- package/dist/bin/exe-new-employee.js +98 -13
- package/dist/bin/exe-pending-messages.js +74 -8
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +77 -11
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +73 -5
- package/dist/bin/exe-search.js +88 -14
- package/dist/bin/exe-session-cleanup.js +102 -28
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +80 -5
- package/dist/bin/graph-backfill.js +71 -3
- package/dist/bin/graph-export.js +71 -3
- package/dist/bin/install.js +38 -8
- package/dist/bin/scan-tasks.js +80 -5
- package/dist/bin/setup.js +128 -10
- package/dist/bin/shard-migrate.js +71 -3
- package/dist/bin/wiki-sync.js +71 -3
- package/dist/gateway/index.js +179 -46
- package/dist/hooks/bug-report-worker.js +254 -28
- package/dist/hooks/commit-complete.js +80 -5
- package/dist/hooks/error-recall.js +89 -15
- package/dist/hooks/exe-heartbeat-hook.js +1 -1
- package/dist/hooks/ingest-worker.js +185 -51
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +81 -6
- package/dist/hooks/notification.js +81 -6
- package/dist/hooks/post-compact.js +81 -6
- package/dist/hooks/pre-compact.js +81 -6
- package/dist/hooks/pre-tool-use.js +423 -196
- package/dist/hooks/prompt-ingest-worker.js +91 -23
- package/dist/hooks/prompt-submit.js +159 -45
- package/dist/hooks/response-ingest-worker.js +96 -23
- package/dist/hooks/session-end.js +81 -6
- package/dist/hooks/session-start.js +89 -15
- package/dist/hooks/stop.js +81 -6
- package/dist/hooks/subagent-stop.js +81 -6
- package/dist/hooks/summary-worker.js +807 -55
- package/dist/index.js +198 -60
- package/dist/lib/cloud-sync.js +703 -18
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +26 -0
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +207 -74
- package/dist/lib/hybrid-search.js +88 -14
- package/dist/lib/identity-templates.js +51 -0
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +65 -17
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +73 -4
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +95 -28
- package/dist/lib/tmux-routing.js +92 -23
- package/dist/mcp/server.js +800 -74
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +198 -31
- package/dist/mcp/tools/deactivate-behavior.js +4 -4
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +19 -9
- package/dist/mcp/tools/send-message.js +69 -21
- package/dist/mcp/tools/update-task.js +28 -18
- package/dist/runtime/index.js +166 -28
- package/dist/tui/App.js +193 -40
- package/package.json +7 -3
- package/src/commands/exe/afk.md +116 -0
- package/src/commands/exe/rename.md +12 -0
package/dist/runtime/index.js
CHANGED
|
@@ -359,6 +359,61 @@ var init_intercom_queue = __esm({
|
|
|
359
359
|
}
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
+
// src/lib/db-retry.ts
|
|
363
|
+
function isBusyError(err) {
|
|
364
|
+
if (err instanceof Error) {
|
|
365
|
+
const msg = err.message.toLowerCase();
|
|
366
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
367
|
+
}
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
function delay(ms) {
|
|
371
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
372
|
+
}
|
|
373
|
+
async function retryOnBusy(fn, label) {
|
|
374
|
+
let lastError;
|
|
375
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
376
|
+
try {
|
|
377
|
+
return await fn();
|
|
378
|
+
} catch (err) {
|
|
379
|
+
lastError = err;
|
|
380
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
381
|
+
throw err;
|
|
382
|
+
}
|
|
383
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
384
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
385
|
+
process.stderr.write(
|
|
386
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
387
|
+
`
|
|
388
|
+
);
|
|
389
|
+
await delay(backoff + jitter);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
throw lastError;
|
|
393
|
+
}
|
|
394
|
+
function wrapWithRetry(client) {
|
|
395
|
+
return new Proxy(client, {
|
|
396
|
+
get(target, prop, receiver) {
|
|
397
|
+
if (prop === "execute") {
|
|
398
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
399
|
+
}
|
|
400
|
+
if (prop === "batch") {
|
|
401
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
402
|
+
}
|
|
403
|
+
return Reflect.get(target, prop, receiver);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
408
|
+
var init_db_retry = __esm({
|
|
409
|
+
"src/lib/db-retry.ts"() {
|
|
410
|
+
"use strict";
|
|
411
|
+
MAX_RETRIES = 3;
|
|
412
|
+
BASE_DELAY_MS = 200;
|
|
413
|
+
MAX_JITTER_MS = 300;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
362
417
|
// src/lib/database.ts
|
|
363
418
|
var database_exports = {};
|
|
364
419
|
__export(database_exports, {
|
|
@@ -366,6 +421,7 @@ __export(database_exports, {
|
|
|
366
421
|
disposeTurso: () => disposeTurso,
|
|
367
422
|
ensureSchema: () => ensureSchema,
|
|
368
423
|
getClient: () => getClient,
|
|
424
|
+
getRawClient: () => getRawClient,
|
|
369
425
|
initDatabase: () => initDatabase,
|
|
370
426
|
initTurso: () => initTurso,
|
|
371
427
|
isInitialized: () => isInitialized
|
|
@@ -375,6 +431,7 @@ async function initDatabase(config) {
|
|
|
375
431
|
if (_client) {
|
|
376
432
|
_client.close();
|
|
377
433
|
_client = null;
|
|
434
|
+
_resilientClient = null;
|
|
378
435
|
}
|
|
379
436
|
const opts = {
|
|
380
437
|
url: `file:${config.dbPath}`
|
|
@@ -383,20 +440,27 @@ async function initDatabase(config) {
|
|
|
383
440
|
opts.encryptionKey = config.encryptionKey;
|
|
384
441
|
}
|
|
385
442
|
_client = createClient(opts);
|
|
443
|
+
_resilientClient = wrapWithRetry(_client);
|
|
386
444
|
}
|
|
387
445
|
function isInitialized() {
|
|
388
446
|
return _client !== null;
|
|
389
447
|
}
|
|
390
448
|
function getClient() {
|
|
449
|
+
if (!_resilientClient) {
|
|
450
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
451
|
+
}
|
|
452
|
+
return _resilientClient;
|
|
453
|
+
}
|
|
454
|
+
function getRawClient() {
|
|
391
455
|
if (!_client) {
|
|
392
456
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
393
457
|
}
|
|
394
458
|
return _client;
|
|
395
459
|
}
|
|
396
460
|
async function ensureSchema() {
|
|
397
|
-
const client =
|
|
461
|
+
const client = getRawClient();
|
|
398
462
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
399
|
-
await client.execute("PRAGMA busy_timeout =
|
|
463
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
400
464
|
try {
|
|
401
465
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
402
466
|
} catch {
|
|
@@ -1189,13 +1253,16 @@ async function disposeDatabase() {
|
|
|
1189
1253
|
if (_client) {
|
|
1190
1254
|
_client.close();
|
|
1191
1255
|
_client = null;
|
|
1256
|
+
_resilientClient = null;
|
|
1192
1257
|
}
|
|
1193
1258
|
}
|
|
1194
|
-
var _client, initTurso, disposeTurso;
|
|
1259
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1195
1260
|
var init_database = __esm({
|
|
1196
1261
|
"src/lib/database.ts"() {
|
|
1197
1262
|
"use strict";
|
|
1263
|
+
init_db_retry();
|
|
1198
1264
|
_client = null;
|
|
1265
|
+
_resilientClient = null;
|
|
1199
1266
|
initTurso = initDatabase;
|
|
1200
1267
|
disposeTurso = disposeDatabase;
|
|
1201
1268
|
}
|
|
@@ -1392,20 +1459,38 @@ var init_config = __esm({
|
|
|
1392
1459
|
|
|
1393
1460
|
// src/lib/employees.ts
|
|
1394
1461
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1395
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
|
|
1462
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
|
|
1396
1463
|
import { execSync as execSync4 } from "child_process";
|
|
1397
1464
|
import path5 from "path";
|
|
1398
|
-
|
|
1465
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1466
|
+
if (!existsSync4(employeesPath)) return [];
|
|
1467
|
+
try {
|
|
1468
|
+
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
1469
|
+
} catch {
|
|
1470
|
+
return [];
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function getEmployee(employees, name) {
|
|
1474
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
1475
|
+
}
|
|
1476
|
+
function isMultiInstance(agentName, employees) {
|
|
1477
|
+
const roster = employees ?? loadEmployeesSync();
|
|
1478
|
+
const emp = getEmployee(roster, agentName);
|
|
1479
|
+
if (!emp) return false;
|
|
1480
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1481
|
+
}
|
|
1482
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1399
1483
|
var init_employees = __esm({
|
|
1400
1484
|
"src/lib/employees.ts"() {
|
|
1401
1485
|
"use strict";
|
|
1402
1486
|
init_config();
|
|
1403
1487
|
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
1488
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1404
1489
|
}
|
|
1405
1490
|
});
|
|
1406
1491
|
|
|
1407
1492
|
// src/lib/license.ts
|
|
1408
|
-
import { readFileSync as
|
|
1493
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
1409
1494
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1410
1495
|
import path6 from "path";
|
|
1411
1496
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -1428,12 +1513,12 @@ var init_license = __esm({
|
|
|
1428
1513
|
});
|
|
1429
1514
|
|
|
1430
1515
|
// src/lib/plan-limits.ts
|
|
1431
|
-
import { readFileSync as
|
|
1516
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
1432
1517
|
import path7 from "path";
|
|
1433
1518
|
function getLicenseSync() {
|
|
1434
1519
|
try {
|
|
1435
1520
|
if (!existsSync6(CACHE_PATH2)) return freeLicense();
|
|
1436
|
-
const raw = JSON.parse(
|
|
1521
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
1437
1522
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1438
1523
|
const parts = raw.token.split(".");
|
|
1439
1524
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1472,7 +1557,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1472
1557
|
let count = 0;
|
|
1473
1558
|
try {
|
|
1474
1559
|
if (existsSync6(filePath)) {
|
|
1475
|
-
const raw =
|
|
1560
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
1476
1561
|
const employees = JSON.parse(raw);
|
|
1477
1562
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1478
1563
|
}
|
|
@@ -1510,7 +1595,7 @@ import crypto from "crypto";
|
|
|
1510
1595
|
import path8 from "path";
|
|
1511
1596
|
import os5 from "os";
|
|
1512
1597
|
import {
|
|
1513
|
-
readFileSync as
|
|
1598
|
+
readFileSync as readFileSync7,
|
|
1514
1599
|
readdirSync,
|
|
1515
1600
|
unlinkSync,
|
|
1516
1601
|
existsSync as existsSync7,
|
|
@@ -1596,7 +1681,7 @@ import crypto3 from "crypto";
|
|
|
1596
1681
|
import path9 from "path";
|
|
1597
1682
|
import { execSync as execSync5 } from "child_process";
|
|
1598
1683
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1599
|
-
import { existsSync as existsSync8, readFileSync as
|
|
1684
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
1600
1685
|
async function writeCheckpoint(input) {
|
|
1601
1686
|
const client = getClient();
|
|
1602
1687
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1973,7 +2058,7 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
1973
2058
|
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
1974
2059
|
try {
|
|
1975
2060
|
if (existsSync8(gitignorePath)) {
|
|
1976
|
-
const content =
|
|
2061
|
+
const content = readFileSync8(gitignorePath, "utf-8");
|
|
1977
2062
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
1978
2063
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
1979
2064
|
} else {
|
|
@@ -2349,7 +2434,7 @@ async function dispatchTaskToEmployee(input) {
|
|
|
2349
2434
|
} else {
|
|
2350
2435
|
const projectDir = input.projectDir ?? process.cwd();
|
|
2351
2436
|
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
2352
|
-
autoInstance: input.assignedTo
|
|
2437
|
+
autoInstance: isMultiInstance(input.assignedTo)
|
|
2353
2438
|
});
|
|
2354
2439
|
if (result.status === "failed") {
|
|
2355
2440
|
process.stderr.write(
|
|
@@ -2384,6 +2469,7 @@ var init_tasks_notify = __esm({
|
|
|
2384
2469
|
init_session_key();
|
|
2385
2470
|
init_notifications();
|
|
2386
2471
|
init_transport();
|
|
2472
|
+
init_employees();
|
|
2387
2473
|
}
|
|
2388
2474
|
});
|
|
2389
2475
|
|
|
@@ -3199,6 +3285,7 @@ var init_capacity_monitor = __esm({
|
|
|
3199
3285
|
// src/lib/tmux-routing.ts
|
|
3200
3286
|
var tmux_routing_exports = {};
|
|
3201
3287
|
__export(tmux_routing_exports, {
|
|
3288
|
+
acquireSpawnLock: () => acquireSpawnLock,
|
|
3202
3289
|
employeeSessionName: () => employeeSessionName,
|
|
3203
3290
|
ensureEmployee: () => ensureEmployee,
|
|
3204
3291
|
extractRootExe: () => extractRootExe,
|
|
@@ -3213,16 +3300,53 @@ __export(tmux_routing_exports, {
|
|
|
3213
3300
|
notifyParentExe: () => notifyParentExe,
|
|
3214
3301
|
parseParentExe: () => parseParentExe,
|
|
3215
3302
|
registerParentExe: () => registerParentExe,
|
|
3303
|
+
releaseSpawnLock: () => releaseSpawnLock,
|
|
3216
3304
|
resolveExeSession: () => resolveExeSession,
|
|
3217
3305
|
sendIntercom: () => sendIntercom,
|
|
3218
3306
|
spawnEmployee: () => spawnEmployee,
|
|
3219
3307
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3220
3308
|
});
|
|
3221
3309
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
3222
|
-
import { readFileSync as
|
|
3310
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
|
|
3223
3311
|
import path14 from "path";
|
|
3224
3312
|
import os6 from "os";
|
|
3225
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
|
+
}
|
|
3226
3350
|
function resolveBehaviorsExporterScript() {
|
|
3227
3351
|
try {
|
|
3228
3352
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -3286,7 +3410,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3286
3410
|
}
|
|
3287
3411
|
function getParentExe(sessionKey) {
|
|
3288
3412
|
try {
|
|
3289
|
-
const data = JSON.parse(
|
|
3413
|
+
const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3290
3414
|
return data.parentExe || null;
|
|
3291
3415
|
} catch {
|
|
3292
3416
|
return null;
|
|
@@ -3294,7 +3418,7 @@ function getParentExe(sessionKey) {
|
|
|
3294
3418
|
}
|
|
3295
3419
|
function getDispatchedBy(sessionKey) {
|
|
3296
3420
|
try {
|
|
3297
|
-
const data = JSON.parse(
|
|
3421
|
+
const data = JSON.parse(readFileSync9(
|
|
3298
3422
|
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3299
3423
|
"utf8"
|
|
3300
3424
|
));
|
|
@@ -3321,10 +3445,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3321
3445
|
}
|
|
3322
3446
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3323
3447
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3324
|
-
if (!isAlive(base)) return 0;
|
|
3448
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
3325
3449
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3326
3450
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3327
|
-
if (!isAlive(candidate)) return i;
|
|
3451
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
3328
3452
|
}
|
|
3329
3453
|
return null;
|
|
3330
3454
|
}
|
|
@@ -3357,7 +3481,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3357
3481
|
function readDebounceState() {
|
|
3358
3482
|
try {
|
|
3359
3483
|
if (!existsSync10(DEBOUNCE_FILE)) return {};
|
|
3360
|
-
return JSON.parse(
|
|
3484
|
+
return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
|
|
3361
3485
|
} catch {
|
|
3362
3486
|
return {};
|
|
3363
3487
|
}
|
|
@@ -3557,7 +3681,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3557
3681
|
const claudeJsonPath = path14.join(os6.homedir(), ".claude.json");
|
|
3558
3682
|
let claudeJson = {};
|
|
3559
3683
|
try {
|
|
3560
|
-
claudeJson = JSON.parse(
|
|
3684
|
+
claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
|
|
3561
3685
|
} catch {
|
|
3562
3686
|
}
|
|
3563
3687
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3575,7 +3699,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3575
3699
|
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3576
3700
|
let settings = {};
|
|
3577
3701
|
try {
|
|
3578
|
-
settings = JSON.parse(
|
|
3702
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
3579
3703
|
} catch {
|
|
3580
3704
|
}
|
|
3581
3705
|
const perms = settings.permissions ?? {};
|
|
@@ -3688,6 +3812,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3688
3812
|
command: spawnCommand
|
|
3689
3813
|
});
|
|
3690
3814
|
if (spawnResult.error) {
|
|
3815
|
+
releaseSpawnLock(sessionName);
|
|
3691
3816
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
3692
3817
|
}
|
|
3693
3818
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -3725,6 +3850,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3725
3850
|
}
|
|
3726
3851
|
}
|
|
3727
3852
|
if (!booted) {
|
|
3853
|
+
releaseSpawnLock(sessionName);
|
|
3728
3854
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3729
3855
|
}
|
|
3730
3856
|
if (!useExeAgent) {
|
|
@@ -3741,9 +3867,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3741
3867
|
pid: 0,
|
|
3742
3868
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3743
3869
|
});
|
|
3870
|
+
releaseSpawnLock(sessionName);
|
|
3744
3871
|
return { sessionName };
|
|
3745
3872
|
}
|
|
3746
|
-
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;
|
|
3747
3874
|
var init_tmux_routing = __esm({
|
|
3748
3875
|
"src/lib/tmux-routing.ts"() {
|
|
3749
3876
|
"use strict";
|
|
@@ -3755,6 +3882,7 @@ var init_tmux_routing = __esm({
|
|
|
3755
3882
|
init_provider_table();
|
|
3756
3883
|
init_intercom_queue();
|
|
3757
3884
|
init_plan_limits();
|
|
3885
|
+
SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
3758
3886
|
SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
|
|
3759
3887
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3760
3888
|
VERIFY_PANE_LINES = 200;
|
|
@@ -3882,7 +4010,7 @@ function listShards() {
|
|
|
3882
4010
|
}
|
|
3883
4011
|
async function ensureShardSchema(client) {
|
|
3884
4012
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3885
|
-
await client.execute("PRAGMA busy_timeout =
|
|
4013
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
3886
4014
|
try {
|
|
3887
4015
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
3888
4016
|
} catch {
|
|
@@ -4143,7 +4271,8 @@ async function writeMemory(record) {
|
|
|
4143
4271
|
has_error: record.has_error ? 1 : 0,
|
|
4144
4272
|
raw_text: record.raw_text,
|
|
4145
4273
|
vector: record.vector,
|
|
4146
|
-
version:
|
|
4274
|
+
version: 0,
|
|
4275
|
+
// Placeholder — assigned atomically at flush time
|
|
4147
4276
|
task_id: record.task_id ?? null,
|
|
4148
4277
|
importance: record.importance ?? 5,
|
|
4149
4278
|
status: record.status ?? "active",
|
|
@@ -4177,6 +4306,13 @@ async function flushBatch() {
|
|
|
4177
4306
|
_flushing = true;
|
|
4178
4307
|
try {
|
|
4179
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;
|
|
4180
4316
|
const buildStmt = (row) => {
|
|
4181
4317
|
const hasVector = row.vector !== null;
|
|
4182
4318
|
const taskId = row.task_id ?? null;
|
|
@@ -4534,8 +4670,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
4534
4670
|
try {
|
|
4535
4671
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
4536
4672
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
4537
|
-
const { readFileSync:
|
|
4538
|
-
const source =
|
|
4673
|
+
const { readFileSync: readFileSync10 } = __require("fs");
|
|
4674
|
+
const source = readFileSync10(filePath, "utf8");
|
|
4539
4675
|
const lines = source.split("\n");
|
|
4540
4676
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
4541
4677
|
let matchLine = -1;
|
|
@@ -6040,12 +6176,14 @@ var DEFAULT_BLOOM_CONFIG = {
|
|
|
6040
6176
|
},
|
|
6041
6177
|
tierRules: {
|
|
6042
6178
|
junior: {
|
|
6043
|
-
eligible: [
|
|
6179
|
+
eligible: [],
|
|
6180
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
6044
6181
|
reviewRequired: false,
|
|
6045
6182
|
manualOnly: false
|
|
6046
6183
|
},
|
|
6047
6184
|
standard: {
|
|
6048
|
-
eligible: [
|
|
6185
|
+
eligible: [],
|
|
6186
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
6049
6187
|
reviewRequired: false,
|
|
6050
6188
|
manualOnly: false
|
|
6051
6189
|
},
|