@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/tui/App.js
CHANGED
|
@@ -39,6 +39,61 @@ var init_devtools = __esm({
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
// src/lib/db-retry.ts
|
|
43
|
+
function isBusyError(err) {
|
|
44
|
+
if (err instanceof Error) {
|
|
45
|
+
const msg = err.message.toLowerCase();
|
|
46
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function delay(ms) {
|
|
51
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
|
+
}
|
|
53
|
+
async function retryOnBusy(fn, label) {
|
|
54
|
+
let lastError;
|
|
55
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
56
|
+
try {
|
|
57
|
+
return await fn();
|
|
58
|
+
} catch (err) {
|
|
59
|
+
lastError = err;
|
|
60
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
64
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
65
|
+
process.stderr.write(
|
|
66
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
67
|
+
`
|
|
68
|
+
);
|
|
69
|
+
await delay(backoff + jitter);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw lastError;
|
|
73
|
+
}
|
|
74
|
+
function wrapWithRetry(client) {
|
|
75
|
+
return new Proxy(client, {
|
|
76
|
+
get(target, prop, receiver) {
|
|
77
|
+
if (prop === "execute") {
|
|
78
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
79
|
+
}
|
|
80
|
+
if (prop === "batch") {
|
|
81
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
82
|
+
}
|
|
83
|
+
return Reflect.get(target, prop, receiver);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
88
|
+
var init_db_retry = __esm({
|
|
89
|
+
"src/lib/db-retry.ts"() {
|
|
90
|
+
"use strict";
|
|
91
|
+
MAX_RETRIES = 3;
|
|
92
|
+
BASE_DELAY_MS = 200;
|
|
93
|
+
MAX_JITTER_MS = 300;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
42
97
|
// src/lib/database.ts
|
|
43
98
|
var database_exports = {};
|
|
44
99
|
__export(database_exports, {
|
|
@@ -46,6 +101,7 @@ __export(database_exports, {
|
|
|
46
101
|
disposeTurso: () => disposeTurso,
|
|
47
102
|
ensureSchema: () => ensureSchema,
|
|
48
103
|
getClient: () => getClient,
|
|
104
|
+
getRawClient: () => getRawClient,
|
|
49
105
|
initDatabase: () => initDatabase,
|
|
50
106
|
initTurso: () => initTurso,
|
|
51
107
|
isInitialized: () => isInitialized
|
|
@@ -55,6 +111,7 @@ async function initDatabase(config) {
|
|
|
55
111
|
if (_client) {
|
|
56
112
|
_client.close();
|
|
57
113
|
_client = null;
|
|
114
|
+
_resilientClient = null;
|
|
58
115
|
}
|
|
59
116
|
const opts = {
|
|
60
117
|
url: `file:${config.dbPath}`
|
|
@@ -63,20 +120,27 @@ async function initDatabase(config) {
|
|
|
63
120
|
opts.encryptionKey = config.encryptionKey;
|
|
64
121
|
}
|
|
65
122
|
_client = createClient(opts);
|
|
123
|
+
_resilientClient = wrapWithRetry(_client);
|
|
66
124
|
}
|
|
67
125
|
function isInitialized() {
|
|
68
126
|
return _client !== null;
|
|
69
127
|
}
|
|
70
128
|
function getClient() {
|
|
129
|
+
if (!_resilientClient) {
|
|
130
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
131
|
+
}
|
|
132
|
+
return _resilientClient;
|
|
133
|
+
}
|
|
134
|
+
function getRawClient() {
|
|
71
135
|
if (!_client) {
|
|
72
136
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
73
137
|
}
|
|
74
138
|
return _client;
|
|
75
139
|
}
|
|
76
140
|
async function ensureSchema() {
|
|
77
|
-
const client =
|
|
141
|
+
const client = getRawClient();
|
|
78
142
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
79
|
-
await client.execute("PRAGMA busy_timeout =
|
|
143
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
80
144
|
try {
|
|
81
145
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
82
146
|
} catch {
|
|
@@ -869,13 +933,16 @@ async function disposeDatabase() {
|
|
|
869
933
|
if (_client) {
|
|
870
934
|
_client.close();
|
|
871
935
|
_client = null;
|
|
936
|
+
_resilientClient = null;
|
|
872
937
|
}
|
|
873
938
|
}
|
|
874
|
-
var _client, initTurso, disposeTurso;
|
|
939
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
875
940
|
var init_database = __esm({
|
|
876
941
|
"src/lib/database.ts"() {
|
|
877
942
|
"use strict";
|
|
943
|
+
init_db_retry();
|
|
878
944
|
_client = null;
|
|
945
|
+
_resilientClient = null;
|
|
879
946
|
initTurso = initDatabase;
|
|
880
947
|
disposeTurso = disposeDatabase;
|
|
881
948
|
}
|
|
@@ -3755,13 +3822,18 @@ __export(employees_exports, {
|
|
|
3755
3822
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
3756
3823
|
addEmployee: () => addEmployee,
|
|
3757
3824
|
getEmployee: () => getEmployee,
|
|
3825
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
3826
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
3827
|
+
hasRole: () => hasRole,
|
|
3828
|
+
isMultiInstance: () => isMultiInstance,
|
|
3758
3829
|
loadEmployees: () => loadEmployees,
|
|
3830
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
3759
3831
|
registerBinSymlinks: () => registerBinSymlinks,
|
|
3760
3832
|
saveEmployees: () => saveEmployees,
|
|
3761
3833
|
validateEmployeeName: () => validateEmployeeName
|
|
3762
3834
|
});
|
|
3763
3835
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
3764
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
|
|
3836
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5 } from "fs";
|
|
3765
3837
|
import { execSync as execSync3 } from "child_process";
|
|
3766
3838
|
import path10 from "path";
|
|
3767
3839
|
function validateEmployeeName(name) {
|
|
@@ -3794,9 +3866,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
3794
3866
|
await mkdir2(path10.dirname(employeesPath), { recursive: true });
|
|
3795
3867
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
3796
3868
|
}
|
|
3869
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
3870
|
+
if (!existsSync5(employeesPath)) return [];
|
|
3871
|
+
try {
|
|
3872
|
+
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
3873
|
+
} catch {
|
|
3874
|
+
return [];
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3797
3877
|
function getEmployee(employees, name) {
|
|
3798
3878
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
3799
3879
|
}
|
|
3880
|
+
function getEmployeeByRole(employees, role) {
|
|
3881
|
+
const lower = role.toLowerCase();
|
|
3882
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
3883
|
+
}
|
|
3884
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
3885
|
+
const lower = role.toLowerCase();
|
|
3886
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
3887
|
+
}
|
|
3888
|
+
function hasRole(agentName, role) {
|
|
3889
|
+
const employees = loadEmployeesSync();
|
|
3890
|
+
const emp = getEmployee(employees, agentName);
|
|
3891
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
3892
|
+
}
|
|
3893
|
+
function isMultiInstance(agentName, employees) {
|
|
3894
|
+
const roster = employees ?? loadEmployeesSync();
|
|
3895
|
+
const emp = getEmployee(roster, agentName);
|
|
3896
|
+
if (!emp) return false;
|
|
3897
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
3898
|
+
}
|
|
3800
3899
|
function addEmployee(employees, employee) {
|
|
3801
3900
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
3802
3901
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -3839,12 +3938,13 @@ function registerBinSymlinks(name) {
|
|
|
3839
3938
|
}
|
|
3840
3939
|
return { created, skipped, errors };
|
|
3841
3940
|
}
|
|
3842
|
-
var EMPLOYEES_PATH;
|
|
3941
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
3843
3942
|
var init_employees = __esm({
|
|
3844
3943
|
"src/lib/employees.ts"() {
|
|
3845
3944
|
"use strict";
|
|
3846
3945
|
init_config();
|
|
3847
3946
|
EMPLOYEES_PATH = path10.join(EXE_AI_DIR, "exe-employees.json");
|
|
3947
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
3848
3948
|
}
|
|
3849
3949
|
});
|
|
3850
3950
|
|
|
@@ -3918,12 +4018,14 @@ var init_task_router = __esm({
|
|
|
3918
4018
|
},
|
|
3919
4019
|
tierRules: {
|
|
3920
4020
|
junior: {
|
|
3921
|
-
eligible: [
|
|
4021
|
+
eligible: [],
|
|
4022
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
3922
4023
|
reviewRequired: false,
|
|
3923
4024
|
manualOnly: false
|
|
3924
4025
|
},
|
|
3925
4026
|
standard: {
|
|
3926
|
-
eligible: [
|
|
4027
|
+
eligible: [],
|
|
4028
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
3927
4029
|
reviewRequired: false,
|
|
3928
4030
|
manualOnly: false
|
|
3929
4031
|
},
|
|
@@ -4167,7 +4269,7 @@ var init_provider_table = __esm({
|
|
|
4167
4269
|
});
|
|
4168
4270
|
|
|
4169
4271
|
// src/lib/intercom-queue.ts
|
|
4170
|
-
import { readFileSync as
|
|
4272
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
4171
4273
|
import path11 from "path";
|
|
4172
4274
|
import os4 from "os";
|
|
4173
4275
|
function ensureDir() {
|
|
@@ -4177,7 +4279,7 @@ function ensureDir() {
|
|
|
4177
4279
|
function readQueue() {
|
|
4178
4280
|
try {
|
|
4179
4281
|
if (!existsSync6(QUEUE_PATH)) return [];
|
|
4180
|
-
return JSON.parse(
|
|
4282
|
+
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
4181
4283
|
} catch {
|
|
4182
4284
|
return [];
|
|
4183
4285
|
}
|
|
@@ -4216,12 +4318,12 @@ var init_intercom_queue = __esm({
|
|
|
4216
4318
|
});
|
|
4217
4319
|
|
|
4218
4320
|
// src/lib/plan-limits.ts
|
|
4219
|
-
import { readFileSync as
|
|
4321
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
4220
4322
|
import path12 from "path";
|
|
4221
4323
|
function getLicenseSync() {
|
|
4222
4324
|
try {
|
|
4223
4325
|
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
4224
|
-
const raw = JSON.parse(
|
|
4326
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
4225
4327
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
4226
4328
|
const parts = raw.token.split(".");
|
|
4227
4329
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -4260,7 +4362,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
4260
4362
|
let count = 0;
|
|
4261
4363
|
try {
|
|
4262
4364
|
if (existsSync7(filePath)) {
|
|
4263
|
-
const raw =
|
|
4365
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
4264
4366
|
const employees = JSON.parse(raw);
|
|
4265
4367
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
4266
4368
|
}
|
|
@@ -4298,7 +4400,7 @@ import crypto from "crypto";
|
|
|
4298
4400
|
import path13 from "path";
|
|
4299
4401
|
import os5 from "os";
|
|
4300
4402
|
import {
|
|
4301
|
-
readFileSync as
|
|
4403
|
+
readFileSync as readFileSync8,
|
|
4302
4404
|
readdirSync,
|
|
4303
4405
|
unlinkSync,
|
|
4304
4406
|
existsSync as existsSync8,
|
|
@@ -4384,7 +4486,7 @@ import crypto3 from "crypto";
|
|
|
4384
4486
|
import path14 from "path";
|
|
4385
4487
|
import { execSync as execSync6 } from "child_process";
|
|
4386
4488
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
4387
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4489
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
4388
4490
|
async function writeCheckpoint(input) {
|
|
4389
4491
|
const client = getClient();
|
|
4390
4492
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -4761,7 +4863,7 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
4761
4863
|
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
4762
4864
|
try {
|
|
4763
4865
|
if (existsSync9(gitignorePath)) {
|
|
4764
|
-
const content =
|
|
4866
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
4765
4867
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4766
4868
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
4767
4869
|
} else {
|
|
@@ -5137,7 +5239,7 @@ async function dispatchTaskToEmployee(input) {
|
|
|
5137
5239
|
} else {
|
|
5138
5240
|
const projectDir = input.projectDir ?? process.cwd();
|
|
5139
5241
|
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
5140
|
-
autoInstance: input.assignedTo
|
|
5242
|
+
autoInstance: isMultiInstance(input.assignedTo)
|
|
5141
5243
|
});
|
|
5142
5244
|
if (result.status === "failed") {
|
|
5143
5245
|
process.stderr.write(
|
|
@@ -5172,6 +5274,7 @@ var init_tasks_notify = __esm({
|
|
|
5172
5274
|
init_session_key();
|
|
5173
5275
|
init_notifications();
|
|
5174
5276
|
init_transport();
|
|
5277
|
+
init_employees();
|
|
5175
5278
|
}
|
|
5176
5279
|
});
|
|
5177
5280
|
|
|
@@ -5925,6 +6028,7 @@ var init_capacity_monitor = __esm({
|
|
|
5925
6028
|
// src/lib/tmux-routing.ts
|
|
5926
6029
|
var tmux_routing_exports = {};
|
|
5927
6030
|
__export(tmux_routing_exports, {
|
|
6031
|
+
acquireSpawnLock: () => acquireSpawnLock,
|
|
5928
6032
|
employeeSessionName: () => employeeSessionName,
|
|
5929
6033
|
ensureEmployee: () => ensureEmployee,
|
|
5930
6034
|
extractRootExe: () => extractRootExe,
|
|
@@ -5939,16 +6043,53 @@ __export(tmux_routing_exports, {
|
|
|
5939
6043
|
notifyParentExe: () => notifyParentExe,
|
|
5940
6044
|
parseParentExe: () => parseParentExe,
|
|
5941
6045
|
registerParentExe: () => registerParentExe,
|
|
6046
|
+
releaseSpawnLock: () => releaseSpawnLock,
|
|
5942
6047
|
resolveExeSession: () => resolveExeSession,
|
|
5943
6048
|
sendIntercom: () => sendIntercom,
|
|
5944
6049
|
spawnEmployee: () => spawnEmployee,
|
|
5945
6050
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5946
6051
|
});
|
|
5947
6052
|
import { execFileSync as execFileSync3, execSync as execSync8 } from "child_process";
|
|
5948
|
-
import { readFileSync as
|
|
6053
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
5949
6054
|
import path19 from "path";
|
|
5950
6055
|
import os6 from "os";
|
|
5951
6056
|
import { fileURLToPath } from "url";
|
|
6057
|
+
import { unlinkSync as unlinkSync4 } from "fs";
|
|
6058
|
+
function spawnLockPath(sessionName) {
|
|
6059
|
+
return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
6060
|
+
}
|
|
6061
|
+
function isProcessAlive(pid) {
|
|
6062
|
+
try {
|
|
6063
|
+
process.kill(pid, 0);
|
|
6064
|
+
return true;
|
|
6065
|
+
} catch {
|
|
6066
|
+
return false;
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
6069
|
+
function acquireSpawnLock(sessionName) {
|
|
6070
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
6071
|
+
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
6072
|
+
}
|
|
6073
|
+
const lockFile = spawnLockPath(sessionName);
|
|
6074
|
+
if (existsSync11(lockFile)) {
|
|
6075
|
+
try {
|
|
6076
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
6077
|
+
const age = Date.now() - lock.timestamp;
|
|
6078
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
6079
|
+
return false;
|
|
6080
|
+
}
|
|
6081
|
+
} catch {
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
6085
|
+
return true;
|
|
6086
|
+
}
|
|
6087
|
+
function releaseSpawnLock(sessionName) {
|
|
6088
|
+
try {
|
|
6089
|
+
unlinkSync4(spawnLockPath(sessionName));
|
|
6090
|
+
} catch {
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
5952
6093
|
function resolveBehaviorsExporterScript() {
|
|
5953
6094
|
try {
|
|
5954
6095
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -6012,7 +6153,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
6012
6153
|
}
|
|
6013
6154
|
function getParentExe(sessionKey) {
|
|
6014
6155
|
try {
|
|
6015
|
-
const data = JSON.parse(
|
|
6156
|
+
const data = JSON.parse(readFileSync10(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
6016
6157
|
return data.parentExe || null;
|
|
6017
6158
|
} catch {
|
|
6018
6159
|
return null;
|
|
@@ -6020,7 +6161,7 @@ function getParentExe(sessionKey) {
|
|
|
6020
6161
|
}
|
|
6021
6162
|
function getDispatchedBy(sessionKey) {
|
|
6022
6163
|
try {
|
|
6023
|
-
const data = JSON.parse(
|
|
6164
|
+
const data = JSON.parse(readFileSync10(
|
|
6024
6165
|
path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
6025
6166
|
"utf8"
|
|
6026
6167
|
));
|
|
@@ -6047,10 +6188,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
6047
6188
|
}
|
|
6048
6189
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
6049
6190
|
const base = employeeSessionName(employeeName, exeSession);
|
|
6050
|
-
if (!isAlive(base)) return 0;
|
|
6191
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
6051
6192
|
for (let i = 2; i <= maxInstances; i++) {
|
|
6052
6193
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
6053
|
-
if (!isAlive(candidate)) return i;
|
|
6194
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
6054
6195
|
}
|
|
6055
6196
|
return null;
|
|
6056
6197
|
}
|
|
@@ -6083,7 +6224,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
6083
6224
|
function readDebounceState() {
|
|
6084
6225
|
try {
|
|
6085
6226
|
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
6086
|
-
return JSON.parse(
|
|
6227
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
6087
6228
|
} catch {
|
|
6088
6229
|
return {};
|
|
6089
6230
|
}
|
|
@@ -6283,7 +6424,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6283
6424
|
const claudeJsonPath = path19.join(os6.homedir(), ".claude.json");
|
|
6284
6425
|
let claudeJson = {};
|
|
6285
6426
|
try {
|
|
6286
|
-
claudeJson = JSON.parse(
|
|
6427
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
6287
6428
|
} catch {
|
|
6288
6429
|
}
|
|
6289
6430
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6301,7 +6442,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6301
6442
|
const settingsPath = path19.join(projSettingsDir, "settings.json");
|
|
6302
6443
|
let settings = {};
|
|
6303
6444
|
try {
|
|
6304
|
-
settings = JSON.parse(
|
|
6445
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
6305
6446
|
} catch {
|
|
6306
6447
|
}
|
|
6307
6448
|
const perms = settings.permissions ?? {};
|
|
@@ -6414,6 +6555,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6414
6555
|
command: spawnCommand
|
|
6415
6556
|
});
|
|
6416
6557
|
if (spawnResult.error) {
|
|
6558
|
+
releaseSpawnLock(sessionName);
|
|
6417
6559
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
6418
6560
|
}
|
|
6419
6561
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -6451,6 +6593,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6451
6593
|
}
|
|
6452
6594
|
}
|
|
6453
6595
|
if (!booted) {
|
|
6596
|
+
releaseSpawnLock(sessionName);
|
|
6454
6597
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
6455
6598
|
}
|
|
6456
6599
|
if (!useExeAgent) {
|
|
@@ -6467,9 +6610,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6467
6610
|
pid: 0,
|
|
6468
6611
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6469
6612
|
});
|
|
6613
|
+
releaseSpawnLock(sessionName);
|
|
6470
6614
|
return { sessionName };
|
|
6471
6615
|
}
|
|
6472
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
6616
|
+
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;
|
|
6473
6617
|
var init_tmux_routing = __esm({
|
|
6474
6618
|
"src/lib/tmux-routing.ts"() {
|
|
6475
6619
|
"use strict";
|
|
@@ -6481,6 +6625,7 @@ var init_tmux_routing = __esm({
|
|
|
6481
6625
|
init_provider_table();
|
|
6482
6626
|
init_intercom_queue();
|
|
6483
6627
|
init_plan_limits();
|
|
6628
|
+
SPAWN_LOCK_DIR = path19.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
6484
6629
|
SESSION_CACHE = path19.join(os6.homedir(), ".exe-os", "session-cache");
|
|
6485
6630
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
6486
6631
|
VERIFY_PANE_LINES = 200;
|
|
@@ -7370,7 +7515,7 @@ function listShards() {
|
|
|
7370
7515
|
}
|
|
7371
7516
|
async function ensureShardSchema(client) {
|
|
7372
7517
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
7373
|
-
await client.execute("PRAGMA busy_timeout =
|
|
7518
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
7374
7519
|
try {
|
|
7375
7520
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
7376
7521
|
} catch {
|
|
@@ -7631,7 +7776,8 @@ async function writeMemory(record) {
|
|
|
7631
7776
|
has_error: record.has_error ? 1 : 0,
|
|
7632
7777
|
raw_text: record.raw_text,
|
|
7633
7778
|
vector: record.vector,
|
|
7634
|
-
version:
|
|
7779
|
+
version: 0,
|
|
7780
|
+
// Placeholder — assigned atomically at flush time
|
|
7635
7781
|
task_id: record.task_id ?? null,
|
|
7636
7782
|
importance: record.importance ?? 5,
|
|
7637
7783
|
status: record.status ?? "active",
|
|
@@ -7665,6 +7811,13 @@ async function flushBatch() {
|
|
|
7665
7811
|
_flushing = true;
|
|
7666
7812
|
try {
|
|
7667
7813
|
const batch = _pendingRecords.slice(0);
|
|
7814
|
+
const client = getClient();
|
|
7815
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
7816
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
7817
|
+
for (const row of batch) {
|
|
7818
|
+
row.version = baseVersion++;
|
|
7819
|
+
}
|
|
7820
|
+
_nextVersion = baseVersion;
|
|
7668
7821
|
const buildStmt = (row) => {
|
|
7669
7822
|
const hasVector = row.vector !== null;
|
|
7670
7823
|
const taskId = row.task_id ?? null;
|
|
@@ -14635,7 +14788,7 @@ function CommandCenterView({
|
|
|
14635
14788
|
const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
|
|
14636
14789
|
const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
|
|
14637
14790
|
const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
|
|
14638
|
-
const { readFileSync:
|
|
14791
|
+
const { readFileSync: readFileSync11, existsSync: existsSync14 } = await import("fs");
|
|
14639
14792
|
const { join } = await import("path");
|
|
14640
14793
|
const { homedir } = await import("os");
|
|
14641
14794
|
const configPath = join(homedir(), ".exe-os", "config.json");
|
|
@@ -14643,7 +14796,7 @@ function CommandCenterView({
|
|
|
14643
14796
|
let providerConfigs = {};
|
|
14644
14797
|
if (existsSync14(configPath)) {
|
|
14645
14798
|
try {
|
|
14646
|
-
const raw = JSON.parse(
|
|
14799
|
+
const raw = JSON.parse(readFileSync11(configPath, "utf8"));
|
|
14647
14800
|
if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
|
|
14648
14801
|
if (raw.providers && typeof raw.providers === "object") {
|
|
14649
14802
|
providerConfigs = raw.providers;
|
|
@@ -14704,7 +14857,7 @@ function CommandCenterView({
|
|
|
14704
14857
|
const markerDir = join(homedir(), ".exe-os", "session-cache");
|
|
14705
14858
|
const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
|
|
14706
14859
|
for (const f of agentFiles) {
|
|
14707
|
-
const data = JSON.parse(
|
|
14860
|
+
const data = JSON.parse(readFileSync11(join(markerDir, f), "utf8"));
|
|
14708
14861
|
if (data.agentRole) {
|
|
14709
14862
|
agentRole = data.agentRole;
|
|
14710
14863
|
break;
|
|
@@ -16397,12 +16550,12 @@ async function loadGatewayConfig() {
|
|
|
16397
16550
|
state.running = false;
|
|
16398
16551
|
}
|
|
16399
16552
|
try {
|
|
16400
|
-
const { existsSync: existsSync14, readFileSync:
|
|
16553
|
+
const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
|
|
16401
16554
|
const { join } = await import("path");
|
|
16402
16555
|
const home = process.env.HOME ?? "";
|
|
16403
16556
|
const configPath = join(home, ".exe-os", "gateway.json");
|
|
16404
16557
|
if (existsSync14(configPath)) {
|
|
16405
|
-
const raw = JSON.parse(
|
|
16558
|
+
const raw = JSON.parse(readFileSync11(configPath, "utf8"));
|
|
16406
16559
|
state.port = raw.port ?? 3100;
|
|
16407
16560
|
state.gatewayUrl = raw.gatewayUrl ?? "";
|
|
16408
16561
|
if (raw.adapters) {
|
|
@@ -16872,12 +17025,12 @@ function TeamView({ onBack }) {
|
|
|
16872
17025
|
setMembers(teamData);
|
|
16873
17026
|
setDbError(false);
|
|
16874
17027
|
try {
|
|
16875
|
-
const { existsSync: existsSync14, readFileSync:
|
|
17028
|
+
const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
|
|
16876
17029
|
const { join } = await import("path");
|
|
16877
17030
|
const home = process.env.HOME ?? "";
|
|
16878
17031
|
const gatewayConfig = join(home, ".exe-os", "gateway.json");
|
|
16879
17032
|
if (existsSync14(gatewayConfig)) {
|
|
16880
|
-
const raw = JSON.parse(
|
|
17033
|
+
const raw = JSON.parse(readFileSync11(gatewayConfig, "utf8"));
|
|
16881
17034
|
if (raw.agents && raw.agents.length > 0) {
|
|
16882
17035
|
setExternals(raw.agents.map((a) => ({
|
|
16883
17036
|
name: a.name,
|
|
@@ -17413,14 +17566,14 @@ function SettingsView({ onBack }) {
|
|
|
17413
17566
|
try {
|
|
17414
17567
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
17415
17568
|
const roster = await loadEmployees2();
|
|
17416
|
-
const { existsSync: existsSync14, readFileSync:
|
|
17569
|
+
const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
|
|
17417
17570
|
const { join } = await import("path");
|
|
17418
17571
|
const home = process.env.HOME ?? "";
|
|
17419
17572
|
const configPath = join(home, ".exe-os", "config.json");
|
|
17420
17573
|
let empConfig = {};
|
|
17421
17574
|
if (existsSync14(configPath)) {
|
|
17422
17575
|
try {
|
|
17423
|
-
const raw = JSON.parse(
|
|
17576
|
+
const raw = JSON.parse(readFileSync11(configPath, "utf8"));
|
|
17424
17577
|
if (raw.employees && typeof raw.employees === "object") {
|
|
17425
17578
|
empConfig = raw.employees;
|
|
17426
17579
|
}
|
|
@@ -17435,7 +17588,7 @@ function SettingsView({ onBack }) {
|
|
|
17435
17588
|
} catch {
|
|
17436
17589
|
}
|
|
17437
17590
|
try {
|
|
17438
|
-
const { existsSync: existsSync14, readFileSync:
|
|
17591
|
+
const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
|
|
17439
17592
|
const { join } = await import("path");
|
|
17440
17593
|
const home = process.env.HOME ?? "";
|
|
17441
17594
|
const ccSettingsPath = join(home, ".claude", "settings.json");
|
|
@@ -17443,7 +17596,7 @@ function SettingsView({ onBack }) {
|
|
|
17443
17596
|
let hooksWired = false;
|
|
17444
17597
|
if (installed) {
|
|
17445
17598
|
try {
|
|
17446
|
-
const settings = JSON.parse(
|
|
17599
|
+
const settings = JSON.parse(readFileSync11(ccSettingsPath, "utf8"));
|
|
17447
17600
|
const hooks = settings.hooks;
|
|
17448
17601
|
if (hooks) {
|
|
17449
17602
|
hooksWired = Object.values(hooks).flat().some((h) => h.command?.includes("exe-os") || h.command?.includes("exe-mem"));
|
|
@@ -17455,13 +17608,13 @@ function SettingsView({ onBack }) {
|
|
|
17455
17608
|
} catch {
|
|
17456
17609
|
}
|
|
17457
17610
|
try {
|
|
17458
|
-
const { existsSync: existsSync14, readFileSync:
|
|
17611
|
+
const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
|
|
17459
17612
|
const { join } = await import("path");
|
|
17460
17613
|
const home = process.env.HOME ?? "";
|
|
17461
17614
|
const licensePath = join(home, ".exe-os", "license.json");
|
|
17462
17615
|
if (existsSync14(licensePath)) {
|
|
17463
17616
|
try {
|
|
17464
|
-
const lic = JSON.parse(
|
|
17617
|
+
const lic = JSON.parse(readFileSync11(licensePath, "utf8"));
|
|
17465
17618
|
setLicense({ valid: lic.valid !== false, detail: lic.plan ?? "licensed" });
|
|
17466
17619
|
} catch {
|
|
17467
17620
|
setLicense({ valid: false, detail: "invalid license file" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.37",
|
|
4
4
|
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "CC-BY-NC-4.0",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"exe-os-install": "./dist/bin/install.js",
|
|
28
28
|
"exe-agent": "./dist/bin/exe-agent.js",
|
|
29
29
|
"exe-os-backfill-responses": "./dist/bin/backfill-responses.js",
|
|
30
|
+
"exe-os-backfill-conversations": "./dist/bin/backfill-conversations.js",
|
|
30
31
|
"exe-os-repo-drift": "./dist/bin/exe-repo-drift.js",
|
|
31
32
|
"exe-kill": "./dist/bin/exe-kill.js",
|
|
32
33
|
"exe-os-list-providers": "./dist/bin/list-providers.js",
|
|
@@ -58,14 +59,14 @@
|
|
|
58
59
|
"./mcp/server": "./dist/mcp/server.js"
|
|
59
60
|
},
|
|
60
61
|
"engines": {
|
|
61
|
-
"node": ">=
|
|
62
|
+
"node": ">=20.0.0"
|
|
62
63
|
},
|
|
63
64
|
"scripts": {
|
|
64
65
|
"test": "vitest run",
|
|
65
66
|
"test:watch": "vitest",
|
|
66
67
|
"typecheck": "tsc --noEmit",
|
|
67
68
|
"build": "tsup",
|
|
68
|
-
"deploy": "tsup && npm install -g . && node dist/bin/install.js --global",
|
|
69
|
+
"deploy": "node dist/bin/pre-build-guard.js 2>/dev/null; tsup && npm install -g . && node dist/bin/install.js --global && echo '[exe-os] Deploy complete. MCP servers will auto-reconnect on next tool call.'",
|
|
69
70
|
"postinstall": "node dist/bin/install.js --global 2>/dev/null || true",
|
|
70
71
|
"prepublishOnly": "npm run typecheck && npm run build && npx vitest run --exclude 'tests/tui/**' --exclude 'tests/lib/tmux-routing.test.ts' --exclude 'tests/lib/intercom-routing.test.ts' --exclude 'tests/gateway/**' --exclude 'tests/installer/setup-wizard.test.ts' --exclude 'tests/mcp/ingest-document.test.ts' --exclude 'tests/lib/hybrid-search.test.ts'"
|
|
71
72
|
},
|
|
@@ -74,6 +75,9 @@
|
|
|
74
75
|
"@discordjs/voice": "^0.19.2",
|
|
75
76
|
"@libsql/client": "^0.14.0",
|
|
76
77
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
78
|
+
"@opentelemetry/api": "^1.9.1",
|
|
79
|
+
"@opentelemetry/sdk-node": "^0.215.0",
|
|
80
|
+
"@opentelemetry/sdk-trace-base": "^2.7.0",
|
|
77
81
|
"@slack/bolt": "^4.7.0",
|
|
78
82
|
"@slack/web-api": "^7.15.1",
|
|
79
83
|
"@types/react": "^19.2.14",
|