@askexenow/exe-os 0.8.32 → 0.8.36
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 +332 -348
- package/dist/bin/backfill-responses.js +72 -12
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1518 -1122
- package/dist/bin/exe-agent.js +4 -4
- package/dist/bin/exe-assign.js +80 -18
- package/dist/bin/exe-boot.js +408 -89
- package/dist/bin/exe-call.js +83 -24
- package/dist/bin/exe-dispatch.js +18 -10
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +64 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +121 -36
- package/dist/bin/exe-heartbeat.js +77 -13
- package/dist/bin/exe-kill.js +64 -3
- package/dist/bin/exe-launch-agent.js +162 -35
- package/dist/bin/exe-link.js +946 -0
- package/dist/bin/exe-new-employee.js +121 -36
- package/dist/bin/exe-pending-messages.js +72 -7
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +75 -10
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +64 -4
- package/dist/bin/exe-search.js +79 -13
- package/dist/bin/exe-session-cleanup.js +91 -26
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +71 -4
- package/dist/bin/graph-backfill.js +64 -3
- package/dist/bin/graph-export.js +64 -3
- package/dist/bin/install.js +3 -3
- package/dist/bin/scan-tasks.js +71 -4
- package/dist/bin/setup.js +156 -38
- package/dist/bin/shard-migrate.js +64 -3
- package/dist/bin/wiki-sync.js +64 -3
- package/dist/gateway/index.js +122 -37
- package/dist/hooks/bug-report-worker.js +209 -23
- package/dist/hooks/commit-complete.js +71 -4
- package/dist/hooks/error-recall.js +79 -13
- package/dist/hooks/ingest-worker.js +129 -43
- package/dist/hooks/instructions-loaded.js +71 -4
- package/dist/hooks/notification.js +71 -4
- package/dist/hooks/post-compact.js +71 -4
- package/dist/hooks/pre-compact.js +71 -4
- package/dist/hooks/pre-tool-use.js +413 -194
- package/dist/hooks/prompt-ingest-worker.js +82 -22
- package/dist/hooks/prompt-submit.js +103 -37
- package/dist/hooks/response-ingest-worker.js +87 -22
- package/dist/hooks/session-end.js +71 -4
- package/dist/hooks/session-start.js +79 -13
- package/dist/hooks/stop.js +71 -4
- package/dist/hooks/subagent-stop.js +71 -4
- package/dist/hooks/summary-worker.js +303 -50
- package/dist/index.js +134 -46
- package/dist/lib/cloud-sync.js +209 -15
- 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 +48 -22
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +136 -53
- package/dist/lib/hybrid-search.js +79 -13
- package/dist/lib/identity-templates.js +57 -6
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +22 -14
- 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 +64 -3
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +48 -21
- package/dist/lib/tmux-routing.js +47 -20
- package/dist/mcp/server.js +727 -58
- 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 +151 -24
- package/dist/mcp/tools/deactivate-behavior.js +3 -3
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +17 -8
- package/dist/mcp/tools/send-message.js +24 -16
- package/dist/mcp/tools/update-task.js +25 -16
- package/dist/runtime/index.js +112 -24
- package/dist/tui/App.js +139 -36
- package/package.json +6 -2
- package/src/commands/exe/rename.md +12 -0
package/dist/bin/exe-boot.js
CHANGED
|
@@ -271,7 +271,7 @@ var init_config = __esm({
|
|
|
271
271
|
|
|
272
272
|
// src/lib/employees.ts
|
|
273
273
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
274
|
-
import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
|
|
274
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
275
275
|
import { execSync } from "child_process";
|
|
276
276
|
import path2 from "path";
|
|
277
277
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
@@ -289,12 +289,65 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
289
289
|
await mkdir2(path2.dirname(employeesPath), { recursive: true });
|
|
290
290
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
291
291
|
}
|
|
292
|
-
|
|
292
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
293
|
+
if (!existsSync2(employeesPath)) return [];
|
|
294
|
+
try {
|
|
295
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
296
|
+
} catch {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function getEmployee(employees, name) {
|
|
301
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
302
|
+
}
|
|
303
|
+
function isMultiInstance(agentName, employees) {
|
|
304
|
+
const roster = employees ?? loadEmployeesSync();
|
|
305
|
+
const emp = getEmployee(roster, agentName);
|
|
306
|
+
if (!emp) return false;
|
|
307
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
308
|
+
}
|
|
309
|
+
function registerBinSymlinks(name) {
|
|
310
|
+
const created = [];
|
|
311
|
+
const skipped = [];
|
|
312
|
+
const errors = [];
|
|
313
|
+
let exeBinPath;
|
|
314
|
+
try {
|
|
315
|
+
exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
|
|
316
|
+
} catch {
|
|
317
|
+
errors.push("Could not find 'exe' in PATH");
|
|
318
|
+
return { created, skipped, errors };
|
|
319
|
+
}
|
|
320
|
+
const binDir = path2.dirname(exeBinPath);
|
|
321
|
+
let target;
|
|
322
|
+
try {
|
|
323
|
+
target = readlinkSync(exeBinPath);
|
|
324
|
+
} catch {
|
|
325
|
+
errors.push("Could not read 'exe' symlink");
|
|
326
|
+
return { created, skipped, errors };
|
|
327
|
+
}
|
|
328
|
+
for (const suffix of ["", "-opencode"]) {
|
|
329
|
+
const linkName = `${name}${suffix}`;
|
|
330
|
+
const linkPath = path2.join(binDir, linkName);
|
|
331
|
+
if (existsSync2(linkPath)) {
|
|
332
|
+
skipped.push(linkName);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
symlinkSync(target, linkPath);
|
|
337
|
+
created.push(linkName);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return { created, skipped, errors };
|
|
343
|
+
}
|
|
344
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
293
345
|
var init_employees = __esm({
|
|
294
346
|
"src/lib/employees.ts"() {
|
|
295
347
|
"use strict";
|
|
296
348
|
init_config();
|
|
297
349
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
350
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
298
351
|
}
|
|
299
352
|
});
|
|
300
353
|
|
|
@@ -305,12 +358,68 @@ var init_memory = __esm({
|
|
|
305
358
|
}
|
|
306
359
|
});
|
|
307
360
|
|
|
361
|
+
// src/lib/db-retry.ts
|
|
362
|
+
function isBusyError(err) {
|
|
363
|
+
if (err instanceof Error) {
|
|
364
|
+
const msg = err.message.toLowerCase();
|
|
365
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
function delay(ms) {
|
|
370
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
371
|
+
}
|
|
372
|
+
async function retryOnBusy(fn, label) {
|
|
373
|
+
let lastError;
|
|
374
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
375
|
+
try {
|
|
376
|
+
return await fn();
|
|
377
|
+
} catch (err) {
|
|
378
|
+
lastError = err;
|
|
379
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
380
|
+
throw err;
|
|
381
|
+
}
|
|
382
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
383
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
384
|
+
process.stderr.write(
|
|
385
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
386
|
+
`
|
|
387
|
+
);
|
|
388
|
+
await delay(backoff + jitter);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
throw lastError;
|
|
392
|
+
}
|
|
393
|
+
function wrapWithRetry(client) {
|
|
394
|
+
return new Proxy(client, {
|
|
395
|
+
get(target, prop, receiver) {
|
|
396
|
+
if (prop === "execute") {
|
|
397
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
398
|
+
}
|
|
399
|
+
if (prop === "batch") {
|
|
400
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
401
|
+
}
|
|
402
|
+
return Reflect.get(target, prop, receiver);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
407
|
+
var init_db_retry = __esm({
|
|
408
|
+
"src/lib/db-retry.ts"() {
|
|
409
|
+
"use strict";
|
|
410
|
+
MAX_RETRIES = 3;
|
|
411
|
+
BASE_DELAY_MS = 200;
|
|
412
|
+
MAX_JITTER_MS = 300;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
308
416
|
// src/lib/database.ts
|
|
309
417
|
import { createClient } from "@libsql/client";
|
|
310
418
|
async function initDatabase(config) {
|
|
311
419
|
if (_client) {
|
|
312
420
|
_client.close();
|
|
313
421
|
_client = null;
|
|
422
|
+
_resilientClient = null;
|
|
314
423
|
}
|
|
315
424
|
const opts = {
|
|
316
425
|
url: `file:${config.dbPath}`
|
|
@@ -319,20 +428,27 @@ async function initDatabase(config) {
|
|
|
319
428
|
opts.encryptionKey = config.encryptionKey;
|
|
320
429
|
}
|
|
321
430
|
_client = createClient(opts);
|
|
431
|
+
_resilientClient = wrapWithRetry(_client);
|
|
322
432
|
}
|
|
323
433
|
function isInitialized() {
|
|
324
434
|
return _client !== null;
|
|
325
435
|
}
|
|
326
436
|
function getClient() {
|
|
437
|
+
if (!_resilientClient) {
|
|
438
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
439
|
+
}
|
|
440
|
+
return _resilientClient;
|
|
441
|
+
}
|
|
442
|
+
function getRawClient() {
|
|
327
443
|
if (!_client) {
|
|
328
444
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
329
445
|
}
|
|
330
446
|
return _client;
|
|
331
447
|
}
|
|
332
448
|
async function ensureSchema() {
|
|
333
|
-
const client =
|
|
449
|
+
const client = getRawClient();
|
|
334
450
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
335
|
-
await client.execute("PRAGMA busy_timeout =
|
|
451
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
336
452
|
try {
|
|
337
453
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
338
454
|
} catch {
|
|
@@ -1121,11 +1237,13 @@ async function ensureSchema() {
|
|
|
1121
1237
|
}
|
|
1122
1238
|
}
|
|
1123
1239
|
}
|
|
1124
|
-
var _client, initTurso;
|
|
1240
|
+
var _client, _resilientClient, initTurso;
|
|
1125
1241
|
var init_database = __esm({
|
|
1126
1242
|
"src/lib/database.ts"() {
|
|
1127
1243
|
"use strict";
|
|
1244
|
+
init_db_retry();
|
|
1128
1245
|
_client = null;
|
|
1246
|
+
_resilientClient = null;
|
|
1129
1247
|
initTurso = initDatabase;
|
|
1130
1248
|
}
|
|
1131
1249
|
});
|
|
@@ -1329,12 +1447,12 @@ function shardExists(projectName) {
|
|
|
1329
1447
|
}
|
|
1330
1448
|
function listShards() {
|
|
1331
1449
|
if (!existsSync4(SHARDS_DIR)) return [];
|
|
1332
|
-
const { readdirSync:
|
|
1333
|
-
return
|
|
1450
|
+
const { readdirSync: readdirSync7 } = __require("fs");
|
|
1451
|
+
return readdirSync7(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1334
1452
|
}
|
|
1335
1453
|
async function ensureShardSchema(client) {
|
|
1336
1454
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1337
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1455
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1338
1456
|
try {
|
|
1339
1457
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1340
1458
|
} catch {
|
|
@@ -1579,7 +1697,7 @@ import crypto2 from "crypto";
|
|
|
1579
1697
|
import path5 from "path";
|
|
1580
1698
|
import os2 from "os";
|
|
1581
1699
|
import {
|
|
1582
|
-
readFileSync as
|
|
1700
|
+
readFileSync as readFileSync3,
|
|
1583
1701
|
readdirSync,
|
|
1584
1702
|
unlinkSync,
|
|
1585
1703
|
existsSync as existsSync5,
|
|
@@ -1706,7 +1824,7 @@ async function migrateJsonNotifications() {
|
|
|
1706
1824
|
for (const file of files) {
|
|
1707
1825
|
try {
|
|
1708
1826
|
const filePath = path5.join(notifDir, file);
|
|
1709
|
-
const data = JSON.parse(
|
|
1827
|
+
const data = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
1710
1828
|
await client.execute({
|
|
1711
1829
|
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
1712
1830
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1789,7 +1907,7 @@ __export(session_registry_exports, {
|
|
|
1789
1907
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
1790
1908
|
registerSession: () => registerSession
|
|
1791
1909
|
});
|
|
1792
|
-
import { readFileSync as
|
|
1910
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
|
|
1793
1911
|
import { execSync as execSync4 } from "child_process";
|
|
1794
1912
|
import path7 from "path";
|
|
1795
1913
|
import os3 from "os";
|
|
@@ -1809,7 +1927,7 @@ function registerSession(entry) {
|
|
|
1809
1927
|
}
|
|
1810
1928
|
function listSessions() {
|
|
1811
1929
|
try {
|
|
1812
|
-
const raw =
|
|
1930
|
+
const raw = readFileSync5(REGISTRY_PATH, "utf8");
|
|
1813
1931
|
return JSON.parse(raw);
|
|
1814
1932
|
} catch {
|
|
1815
1933
|
return [];
|
|
@@ -2023,7 +2141,7 @@ var init_provider_table = __esm({
|
|
|
2023
2141
|
});
|
|
2024
2142
|
|
|
2025
2143
|
// src/lib/intercom-queue.ts
|
|
2026
|
-
import { readFileSync as
|
|
2144
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2027
2145
|
import path8 from "path";
|
|
2028
2146
|
import os4 from "os";
|
|
2029
2147
|
function ensureDir() {
|
|
@@ -2033,7 +2151,7 @@ function ensureDir() {
|
|
|
2033
2151
|
function readQueue() {
|
|
2034
2152
|
try {
|
|
2035
2153
|
if (!existsSync7(QUEUE_PATH)) return [];
|
|
2036
|
-
return JSON.parse(
|
|
2154
|
+
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
2037
2155
|
} catch {
|
|
2038
2156
|
return [];
|
|
2039
2157
|
}
|
|
@@ -2086,7 +2204,7 @@ __export(license_exports, {
|
|
|
2086
2204
|
saveLicense: () => saveLicense,
|
|
2087
2205
|
validateLicense: () => validateLicense
|
|
2088
2206
|
});
|
|
2089
|
-
import { readFileSync as
|
|
2207
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
2090
2208
|
import { randomUUID } from "crypto";
|
|
2091
2209
|
import path9 from "path";
|
|
2092
2210
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -2094,14 +2212,14 @@ function loadDeviceId() {
|
|
|
2094
2212
|
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
2095
2213
|
try {
|
|
2096
2214
|
if (existsSync8(deviceJsonPath)) {
|
|
2097
|
-
const data = JSON.parse(
|
|
2215
|
+
const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
|
|
2098
2216
|
if (data.deviceId) return data.deviceId;
|
|
2099
2217
|
}
|
|
2100
2218
|
} catch {
|
|
2101
2219
|
}
|
|
2102
2220
|
try {
|
|
2103
2221
|
if (existsSync8(DEVICE_ID_PATH)) {
|
|
2104
|
-
const id2 =
|
|
2222
|
+
const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
|
|
2105
2223
|
if (id2) return id2;
|
|
2106
2224
|
}
|
|
2107
2225
|
} catch {
|
|
@@ -2114,7 +2232,7 @@ function loadDeviceId() {
|
|
|
2114
2232
|
function loadLicense() {
|
|
2115
2233
|
try {
|
|
2116
2234
|
if (!existsSync8(LICENSE_PATH)) return null;
|
|
2117
|
-
return
|
|
2235
|
+
return readFileSync7(LICENSE_PATH, "utf8").trim();
|
|
2118
2236
|
} catch {
|
|
2119
2237
|
return null;
|
|
2120
2238
|
}
|
|
@@ -2148,7 +2266,7 @@ async function verifyLicenseJwt(token) {
|
|
|
2148
2266
|
async function getCachedLicense() {
|
|
2149
2267
|
try {
|
|
2150
2268
|
if (!existsSync8(CACHE_PATH)) return null;
|
|
2151
|
-
const raw = JSON.parse(
|
|
2269
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
2152
2270
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2153
2271
|
return await verifyLicenseJwt(raw.token);
|
|
2154
2272
|
} catch {
|
|
@@ -2158,7 +2276,7 @@ async function getCachedLicense() {
|
|
|
2158
2276
|
function readCachedToken() {
|
|
2159
2277
|
try {
|
|
2160
2278
|
if (!existsSync8(CACHE_PATH)) return null;
|
|
2161
|
-
const raw = JSON.parse(
|
|
2279
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
2162
2280
|
return typeof raw.token === "string" ? raw.token : null;
|
|
2163
2281
|
} catch {
|
|
2164
2282
|
return null;
|
|
@@ -2370,12 +2488,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
2370
2488
|
});
|
|
2371
2489
|
|
|
2372
2490
|
// src/lib/plan-limits.ts
|
|
2373
|
-
import { readFileSync as
|
|
2491
|
+
import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
|
|
2374
2492
|
import path10 from "path";
|
|
2375
2493
|
function getLicenseSync() {
|
|
2376
2494
|
try {
|
|
2377
2495
|
if (!existsSync9(CACHE_PATH2)) return freeLicense();
|
|
2378
|
-
const raw = JSON.parse(
|
|
2496
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
2379
2497
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2380
2498
|
const parts = raw.token.split(".");
|
|
2381
2499
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2414,7 +2532,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2414
2532
|
let count = 0;
|
|
2415
2533
|
try {
|
|
2416
2534
|
if (existsSync9(filePath)) {
|
|
2417
|
-
const raw =
|
|
2535
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
2418
2536
|
const employees = JSON.parse(raw);
|
|
2419
2537
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2420
2538
|
}
|
|
@@ -2457,7 +2575,7 @@ var init_plan_limits = __esm({
|
|
|
2457
2575
|
|
|
2458
2576
|
// src/lib/tmux-routing.ts
|
|
2459
2577
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
2460
|
-
import { readFileSync as
|
|
2578
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
|
|
2461
2579
|
import path11 from "path";
|
|
2462
2580
|
import os5 from "os";
|
|
2463
2581
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -2506,7 +2624,7 @@ function extractRootExe(name) {
|
|
|
2506
2624
|
}
|
|
2507
2625
|
function getParentExe(sessionKey) {
|
|
2508
2626
|
try {
|
|
2509
|
-
const data = JSON.parse(
|
|
2627
|
+
const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2510
2628
|
return data.parentExe || null;
|
|
2511
2629
|
} catch {
|
|
2512
2630
|
return null;
|
|
@@ -2514,7 +2632,7 @@ function getParentExe(sessionKey) {
|
|
|
2514
2632
|
}
|
|
2515
2633
|
function getDispatchedBy(sessionKey) {
|
|
2516
2634
|
try {
|
|
2517
|
-
const data = JSON.parse(
|
|
2635
|
+
const data = JSON.parse(readFileSync9(
|
|
2518
2636
|
path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
2519
2637
|
"utf8"
|
|
2520
2638
|
));
|
|
@@ -2551,7 +2669,7 @@ function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive =
|
|
|
2551
2669
|
function readDebounceState() {
|
|
2552
2670
|
try {
|
|
2553
2671
|
if (!existsSync10(DEBOUNCE_FILE)) return {};
|
|
2554
|
-
return JSON.parse(
|
|
2672
|
+
return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
|
|
2555
2673
|
} catch {
|
|
2556
2674
|
return {};
|
|
2557
2675
|
}
|
|
@@ -2747,7 +2865,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2747
2865
|
const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
|
|
2748
2866
|
let claudeJson = {};
|
|
2749
2867
|
try {
|
|
2750
|
-
claudeJson = JSON.parse(
|
|
2868
|
+
claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
|
|
2751
2869
|
} catch {
|
|
2752
2870
|
}
|
|
2753
2871
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -2765,7 +2883,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2765
2883
|
const settingsPath = path11.join(projSettingsDir, "settings.json");
|
|
2766
2884
|
let settings = {};
|
|
2767
2885
|
try {
|
|
2768
|
-
settings = JSON.parse(
|
|
2886
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
2769
2887
|
} catch {
|
|
2770
2888
|
}
|
|
2771
2889
|
const perms = settings.permissions ?? {};
|
|
@@ -2966,7 +3084,7 @@ __export(task_scanner_exports, {
|
|
|
2966
3084
|
formatText: () => formatText,
|
|
2967
3085
|
scanAgentTasks: () => scanAgentTasks
|
|
2968
3086
|
});
|
|
2969
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
3087
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync10, existsSync as existsSync11, statSync } from "fs";
|
|
2970
3088
|
import { execSync as execSync7 } from "child_process";
|
|
2971
3089
|
import path12 from "path";
|
|
2972
3090
|
function getProjectRoot() {
|
|
@@ -2992,7 +3110,7 @@ function scanAgentTasks(agentId) {
|
|
|
2992
3110
|
total = files.length;
|
|
2993
3111
|
for (const f of files) {
|
|
2994
3112
|
try {
|
|
2995
|
-
const content =
|
|
3113
|
+
const content = readFileSync10(path12.join(taskDir, f), "utf8");
|
|
2996
3114
|
const statusMatch = content.match(STATUS_RE);
|
|
2997
3115
|
const status = statusMatch ? statusMatch[1].toLowerCase() : null;
|
|
2998
3116
|
if (status === "done") {
|
|
@@ -3146,7 +3264,7 @@ import crypto3 from "crypto";
|
|
|
3146
3264
|
import path14 from "path";
|
|
3147
3265
|
import { execSync as execSync9 } from "child_process";
|
|
3148
3266
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3149
|
-
import { existsSync as existsSync12, readFileSync as
|
|
3267
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3150
3268
|
async function writeCheckpoint(input) {
|
|
3151
3269
|
const client = getClient();
|
|
3152
3270
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3523,7 +3641,7 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
3523
3641
|
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
3524
3642
|
try {
|
|
3525
3643
|
if (existsSync12(gitignorePath)) {
|
|
3526
|
-
const content =
|
|
3644
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3527
3645
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3528
3646
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3529
3647
|
} else {
|
|
@@ -3857,7 +3975,7 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3857
3975
|
} else {
|
|
3858
3976
|
const projectDir = input.projectDir ?? process.cwd();
|
|
3859
3977
|
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
3860
|
-
autoInstance: input.assignedTo
|
|
3978
|
+
autoInstance: isMultiInstance(input.assignedTo)
|
|
3861
3979
|
});
|
|
3862
3980
|
if (result.status === "failed") {
|
|
3863
3981
|
process.stderr.write(
|
|
@@ -3892,6 +4010,7 @@ var init_tasks_notify = __esm({
|
|
|
3892
4010
|
init_session_key();
|
|
3893
4011
|
init_notifications();
|
|
3894
4012
|
init_transport();
|
|
4013
|
+
init_employees();
|
|
3895
4014
|
}
|
|
3896
4015
|
});
|
|
3897
4016
|
|
|
@@ -4548,10 +4667,25 @@ var init_compress = __esm({
|
|
|
4548
4667
|
// src/lib/cloud-sync.ts
|
|
4549
4668
|
var cloud_sync_exports = {};
|
|
4550
4669
|
__export(cloud_sync_exports, {
|
|
4670
|
+
buildRosterBlob: () => buildRosterBlob,
|
|
4551
4671
|
cloudPull: () => cloudPull,
|
|
4672
|
+
cloudPullRoster: () => cloudPullRoster,
|
|
4552
4673
|
cloudPush: () => cloudPush,
|
|
4553
|
-
|
|
4674
|
+
cloudPushRoster: () => cloudPushRoster,
|
|
4675
|
+
cloudSync: () => cloudSync,
|
|
4676
|
+
mergeRosterFromRemote: () => mergeRosterFromRemote
|
|
4554
4677
|
});
|
|
4678
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
|
|
4679
|
+
import path18 from "path";
|
|
4680
|
+
import { homedir } from "os";
|
|
4681
|
+
function logError(msg) {
|
|
4682
|
+
try {
|
|
4683
|
+
const logPath = path18.join(homedir(), ".exe-os", "workers.log");
|
|
4684
|
+
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
4685
|
+
`);
|
|
4686
|
+
} catch {
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4555
4689
|
function assertSecureEndpoint(endpoint) {
|
|
4556
4690
|
if (endpoint.startsWith("https://")) return;
|
|
4557
4691
|
if (endpoint.startsWith("http://")) {
|
|
@@ -4584,8 +4718,7 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
4584
4718
|
});
|
|
4585
4719
|
return resp.ok;
|
|
4586
4720
|
} catch (err) {
|
|
4587
|
-
|
|
4588
|
-
`);
|
|
4721
|
+
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
4589
4722
|
return false;
|
|
4590
4723
|
}
|
|
4591
4724
|
}
|
|
@@ -4616,8 +4749,7 @@ async function cloudPull(sinceVersion, config) {
|
|
|
4616
4749
|
}
|
|
4617
4750
|
return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
|
|
4618
4751
|
} catch (err) {
|
|
4619
|
-
|
|
4620
|
-
`);
|
|
4752
|
+
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
4621
4753
|
return { records: [], maxVersion: sinceVersion };
|
|
4622
4754
|
}
|
|
4623
4755
|
}
|
|
@@ -4706,8 +4838,137 @@ async function cloudSync(config) {
|
|
|
4706
4838
|
pushed = records.length;
|
|
4707
4839
|
}
|
|
4708
4840
|
}
|
|
4841
|
+
try {
|
|
4842
|
+
await cloudPushRoster(config);
|
|
4843
|
+
} catch (err) {
|
|
4844
|
+
process.stderr.write(`[cloud-sync] Roster push warning: ${err instanceof Error ? err.message : String(err)}
|
|
4845
|
+
`);
|
|
4846
|
+
}
|
|
4847
|
+
try {
|
|
4848
|
+
await cloudPullRoster(config);
|
|
4849
|
+
} catch (err) {
|
|
4850
|
+
process.stderr.write(`[cloud-sync] Roster pull warning: ${err instanceof Error ? err.message : String(err)}
|
|
4851
|
+
`);
|
|
4852
|
+
}
|
|
4709
4853
|
return { pushed, pulled };
|
|
4710
4854
|
}
|
|
4855
|
+
function buildRosterBlob(paths) {
|
|
4856
|
+
const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
|
|
4857
|
+
const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
|
|
4858
|
+
let roster = [];
|
|
4859
|
+
if (existsSync14(rosterPath)) {
|
|
4860
|
+
try {
|
|
4861
|
+
roster = JSON.parse(readFileSync12(rosterPath, "utf-8"));
|
|
4862
|
+
} catch {
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
const identities = {};
|
|
4866
|
+
if (existsSync14(identityDir)) {
|
|
4867
|
+
for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
4868
|
+
try {
|
|
4869
|
+
identities[file] = readFileSync12(path18.join(identityDir, file), "utf-8");
|
|
4870
|
+
} catch {
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
const content = JSON.stringify({ roster, identities });
|
|
4875
|
+
const hash = Buffer.from(content).length;
|
|
4876
|
+
return { roster, identities, version: hash };
|
|
4877
|
+
}
|
|
4878
|
+
async function cloudPushRoster(config) {
|
|
4879
|
+
assertSecureEndpoint(config.endpoint);
|
|
4880
|
+
const blob = buildRosterBlob();
|
|
4881
|
+
if (blob.roster.length === 0) return true;
|
|
4882
|
+
try {
|
|
4883
|
+
const client = getClient();
|
|
4884
|
+
const meta = await client.execute(
|
|
4885
|
+
"SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
|
|
4886
|
+
);
|
|
4887
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
4888
|
+
if (blob.version === lastVersion) return true;
|
|
4889
|
+
} catch {
|
|
4890
|
+
}
|
|
4891
|
+
try {
|
|
4892
|
+
const json = JSON.stringify(blob);
|
|
4893
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
4894
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
4895
|
+
const resp = await fetch(`${config.endpoint}/sync/push-roster`, {
|
|
4896
|
+
method: "POST",
|
|
4897
|
+
headers: {
|
|
4898
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
4899
|
+
"Content-Type": "application/json",
|
|
4900
|
+
"X-Device-Id": loadDeviceId()
|
|
4901
|
+
},
|
|
4902
|
+
body: JSON.stringify({ blob: encrypted })
|
|
4903
|
+
});
|
|
4904
|
+
if (resp.ok) {
|
|
4905
|
+
try {
|
|
4906
|
+
const client = getClient();
|
|
4907
|
+
await client.execute({
|
|
4908
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
|
|
4909
|
+
args: [String(blob.version)]
|
|
4910
|
+
});
|
|
4911
|
+
} catch {
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
return resp.ok;
|
|
4915
|
+
} catch (err) {
|
|
4916
|
+
process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
4917
|
+
`);
|
|
4918
|
+
return false;
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
async function cloudPullRoster(config) {
|
|
4922
|
+
assertSecureEndpoint(config.endpoint);
|
|
4923
|
+
try {
|
|
4924
|
+
const resp = await fetch(`${config.endpoint}/sync/pull-roster`, {
|
|
4925
|
+
method: "GET",
|
|
4926
|
+
headers: {
|
|
4927
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
4928
|
+
"X-Device-Id": loadDeviceId()
|
|
4929
|
+
}
|
|
4930
|
+
});
|
|
4931
|
+
if (!resp.ok) return { added: 0 };
|
|
4932
|
+
const data = await resp.json();
|
|
4933
|
+
if (!data.blob) return { added: 0 };
|
|
4934
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
4935
|
+
const json = decompress(compressed).toString("utf8");
|
|
4936
|
+
const remote = JSON.parse(json);
|
|
4937
|
+
return mergeRosterFromRemote(remote);
|
|
4938
|
+
} catch (err) {
|
|
4939
|
+
process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
4940
|
+
`);
|
|
4941
|
+
return { added: 0 };
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
async function mergeRosterFromRemote(remote, paths) {
|
|
4945
|
+
const rosterPath = paths?.rosterPath ?? void 0;
|
|
4946
|
+
const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
|
|
4947
|
+
const localEmployees = await loadEmployees(rosterPath);
|
|
4948
|
+
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
4949
|
+
let added = 0;
|
|
4950
|
+
for (const remoteEmp of remote.roster) {
|
|
4951
|
+
if (localNames.has(remoteEmp.name)) continue;
|
|
4952
|
+
localEmployees.push(remoteEmp);
|
|
4953
|
+
localNames.add(remoteEmp.name);
|
|
4954
|
+
added++;
|
|
4955
|
+
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
4956
|
+
if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
|
|
4957
|
+
const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
|
|
4958
|
+
if (!existsSync14(idPath)) {
|
|
4959
|
+
writeFileSync7(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
try {
|
|
4963
|
+
registerBinSymlinks(remoteEmp.name);
|
|
4964
|
+
} catch {
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
if (added > 0) {
|
|
4968
|
+
await saveEmployees(localEmployees, rosterPath);
|
|
4969
|
+
}
|
|
4970
|
+
return { added };
|
|
4971
|
+
}
|
|
4711
4972
|
var LOCALHOST_PATTERNS;
|
|
4712
4973
|
var init_cloud_sync = __esm({
|
|
4713
4974
|
"src/lib/cloud-sync.ts"() {
|
|
@@ -4717,6 +4978,8 @@ var init_cloud_sync = __esm({
|
|
|
4717
4978
|
init_compress();
|
|
4718
4979
|
init_plan_limits();
|
|
4719
4980
|
init_license();
|
|
4981
|
+
init_config();
|
|
4982
|
+
init_employees();
|
|
4720
4983
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4721
4984
|
}
|
|
4722
4985
|
});
|
|
@@ -4899,9 +5162,9 @@ var init_schedules = __esm({
|
|
|
4899
5162
|
|
|
4900
5163
|
// src/bin/exe-boot.ts
|
|
4901
5164
|
init_employees();
|
|
4902
|
-
import
|
|
5165
|
+
import path19 from "path";
|
|
4903
5166
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
4904
|
-
import { existsSync as
|
|
5167
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
4905
5168
|
import os6 from "os";
|
|
4906
5169
|
|
|
4907
5170
|
// src/lib/employee-templates.ts
|
|
@@ -4912,7 +5175,7 @@ var DEFAULT_EXE = {
|
|
|
4912
5175
|
|
|
4913
5176
|
Character: No bullshit. Precise. Accountable. Direct but never offensive. Calm foresight. You see problems before they arrive and propose solutions. If the founder decides differently, you commit fully.
|
|
4914
5177
|
|
|
4915
|
-
You are the single interface. The founder talks to you \u2014 only you. When they ask for technical work, you delegate to
|
|
5178
|
+
You are the single interface. The founder talks to you \u2014 only you. When they ask for technical work, you delegate to the CTO via sub-agent and review their output before presenting. When they ask for status, you synthesize across all projects. You never tell the founder to run commands or talk to someone else.
|
|
4916
5179
|
|
|
4917
5180
|
After every specialist task: verify tests ran, behavior was checked, and a memory summary was stored. If not, flag it.
|
|
4918
5181
|
|
|
@@ -4955,8 +5218,22 @@ function boxMid(w) {
|
|
|
4955
5218
|
function boxBot(w) {
|
|
4956
5219
|
return `\u2514${"\u2500".repeat(w)}\u2518`;
|
|
4957
5220
|
}
|
|
5221
|
+
function truncateToWidth(str, maxW) {
|
|
5222
|
+
if (displayWidth(str) <= maxW) return str;
|
|
5223
|
+
let w = 0;
|
|
5224
|
+
let i = 0;
|
|
5225
|
+
for (const ch of str) {
|
|
5226
|
+
const cw = ch.codePointAt(0) > 255 ? 2 : 1;
|
|
5227
|
+
if (w + cw + 1 > maxW) break;
|
|
5228
|
+
w += cw;
|
|
5229
|
+
i += ch.length;
|
|
5230
|
+
}
|
|
5231
|
+
return str.slice(0, i) + "\u2026";
|
|
5232
|
+
}
|
|
4958
5233
|
function boxRow(content, w) {
|
|
4959
|
-
|
|
5234
|
+
const maxContent = w - 2;
|
|
5235
|
+
const truncated = truncateToWidth(content, maxContent);
|
|
5236
|
+
return `\u2502 ${padRight(truncated, maxContent)} \u2502`;
|
|
4960
5237
|
}
|
|
4961
5238
|
function formatUptime(seconds) {
|
|
4962
5239
|
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
@@ -4966,6 +5243,9 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4966
5243
|
const now = /* @__PURE__ */ new Date();
|
|
4967
5244
|
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
4968
5245
|
const sessionTag = data.exeSession ? ` [${data.exeSession}]` : "";
|
|
5246
|
+
if (data.embedding && data.embedding.totalMemories < 5 && data.globalTasks.length === 0 && (data.projects?.length ?? 0) === 0) {
|
|
5247
|
+
return buildFirstBootBrief(employees, dateStr, sessionTag);
|
|
5248
|
+
}
|
|
4969
5249
|
const sections = [];
|
|
4970
5250
|
sections.push([` EXE STATUS BRIEF \u2014 ${dateStr}${sessionTag}`]);
|
|
4971
5251
|
const reminderLines = buildReminders(data);
|
|
@@ -4975,6 +5255,8 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4975
5255
|
sections.push(buildProjects(data));
|
|
4976
5256
|
sections.push(buildTeam(employees, data));
|
|
4977
5257
|
sections.push(buildHealth(data));
|
|
5258
|
+
const termCols = process.stdout.columns || 80;
|
|
5259
|
+
const maxInnerW = termCols - 2;
|
|
4978
5260
|
let maxW = 0;
|
|
4979
5261
|
for (const sec of sections) {
|
|
4980
5262
|
for (const line of sec) {
|
|
@@ -4982,7 +5264,7 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4982
5264
|
if (w > maxW) maxW = w;
|
|
4983
5265
|
}
|
|
4984
5266
|
}
|
|
4985
|
-
const innerW = maxW + 4;
|
|
5267
|
+
const innerW = Math.min(maxW + 4, maxInnerW);
|
|
4986
5268
|
const out = [];
|
|
4987
5269
|
out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
|
|
4988
5270
|
for (let i = 0; i < sections.length; i++) {
|
|
@@ -4994,6 +5276,39 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4994
5276
|
out.push(boxBot(innerW));
|
|
4995
5277
|
return out.join("\n");
|
|
4996
5278
|
}
|
|
5279
|
+
function buildFirstBootBrief(employees, dateStr, sessionTag) {
|
|
5280
|
+
const termCols = process.stdout.columns || 80;
|
|
5281
|
+
const maxInnerW = termCols - 2;
|
|
5282
|
+
const lines = [];
|
|
5283
|
+
lines.push(` WELCOME TO EXE OS \u2014 ${dateStr}${sessionTag}`);
|
|
5284
|
+
const bodyLines = [];
|
|
5285
|
+
bodyLines.push(" \u{1F44B} First time? Here's your team:");
|
|
5286
|
+
bodyLines.push("");
|
|
5287
|
+
for (const emp of employees) {
|
|
5288
|
+
const emoji = EMPLOYEE_EMOJIS[emp.name] ?? "\u{1F464}";
|
|
5289
|
+
const role = emp.role ? ` (${emp.role})` : "";
|
|
5290
|
+
bodyLines.push(` ${emoji} ${emp.name}${role}`);
|
|
5291
|
+
}
|
|
5292
|
+
bodyLines.push("");
|
|
5293
|
+
bodyLines.push(" \u{1F4A1} Quick start:");
|
|
5294
|
+
bodyLines.push(" \u2022 Run `exe-os backfill-conversations` to import Claude Code history");
|
|
5295
|
+
bodyLines.push(" \u2022 Say `/exe` to launch your COO with a full status brief");
|
|
5296
|
+
bodyLines.push(" \u2022 Say `/exe-team` to manage your team");
|
|
5297
|
+
let maxW = 0;
|
|
5298
|
+
for (const line of [...lines, ...bodyLines]) {
|
|
5299
|
+
const w = displayWidth(line);
|
|
5300
|
+
if (w > maxW) maxW = w;
|
|
5301
|
+
}
|
|
5302
|
+
const innerW = Math.min(maxW + 4, maxInnerW);
|
|
5303
|
+
const out = [];
|
|
5304
|
+
out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
|
|
5305
|
+
out.push(boxTop(innerW));
|
|
5306
|
+
for (const line of lines) out.push(boxRow(line, innerW));
|
|
5307
|
+
out.push(boxMid(innerW));
|
|
5308
|
+
for (const line of bodyLines) out.push(boxRow(line, innerW));
|
|
5309
|
+
out.push(boxBot(innerW));
|
|
5310
|
+
return out.join("\n");
|
|
5311
|
+
}
|
|
4997
5312
|
function buildReminders(data) {
|
|
4998
5313
|
if (!data.reminders || data.reminders.length === 0) return [];
|
|
4999
5314
|
const lines = [];
|
|
@@ -5061,7 +5376,10 @@ function buildProjects(data) {
|
|
|
5061
5376
|
idle.push(p);
|
|
5062
5377
|
}
|
|
5063
5378
|
}
|
|
5064
|
-
|
|
5379
|
+
const MAX_SHOWN_PROJECTS = 6;
|
|
5380
|
+
const shown = active.slice(0, MAX_SHOWN_PROJECTS);
|
|
5381
|
+
const hidden = active.length - shown.length;
|
|
5382
|
+
for (const p of shown) {
|
|
5065
5383
|
const blocked = p.blockedCount ?? 0;
|
|
5066
5384
|
const statusEmoji = blocked > 0 ? "\u{1F534}" : "\u{1F7E2}";
|
|
5067
5385
|
const sessionStr = p.sessionName ? ` (${p.sessionName})` : p.isCurrent ? " (current)" : "";
|
|
@@ -5083,7 +5401,9 @@ function buildProjects(data) {
|
|
|
5083
5401
|
if (blocked > 0) {
|
|
5084
5402
|
lines.push(` Blocked: ${blocked}`);
|
|
5085
5403
|
}
|
|
5086
|
-
|
|
5404
|
+
}
|
|
5405
|
+
if (hidden > 0) {
|
|
5406
|
+
lines.push(` ... and ${hidden} more project${hidden === 1 ? "" : "s"}`);
|
|
5087
5407
|
}
|
|
5088
5408
|
if (idle.length > 0) {
|
|
5089
5409
|
const idleNames = idle.map((p) => p.projectName);
|
|
@@ -5143,7 +5463,8 @@ function buildHealth(data) {
|
|
|
5143
5463
|
}
|
|
5144
5464
|
}
|
|
5145
5465
|
}
|
|
5146
|
-
|
|
5466
|
+
const cloudLabel = data.cloudConnected ? "\u{1F7E2} synced" : data.cloudConnected === false && data.plan && data.plan !== "free" ? "\u{1F534} offline" : "not configured";
|
|
5467
|
+
lines.push(` \u2601\uFE0F Cloud: ${cloudLabel}`);
|
|
5147
5468
|
if (data.sessionsKilledToday !== void 0) {
|
|
5148
5469
|
lines.push(` \u{1F480} Sessions killed today: ${data.sessionsKilledToday}`);
|
|
5149
5470
|
}
|
|
@@ -5182,7 +5503,7 @@ init_notifications();
|
|
|
5182
5503
|
|
|
5183
5504
|
// src/adapters/claude/active-agent.ts
|
|
5184
5505
|
init_config();
|
|
5185
|
-
import { readFileSync as
|
|
5506
|
+
import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
|
|
5186
5507
|
import { execSync as execSync3 } from "child_process";
|
|
5187
5508
|
import path6 from "path";
|
|
5188
5509
|
|
|
@@ -5292,18 +5613,18 @@ async function boot(options) {
|
|
|
5292
5613
|
} catch {
|
|
5293
5614
|
}
|
|
5294
5615
|
try {
|
|
5295
|
-
const { readdirSync:
|
|
5616
|
+
const { readdirSync: readdirSync7, readFileSync: readFs } = await import("fs");
|
|
5296
5617
|
const { STATUS_RE: STATUS_RE2, PRIORITY_RE: PRIORITY_RE2, TITLE_RE: TITLE_RE2 } = await Promise.resolve().then(() => (init_task_scanner(), task_scanner_exports));
|
|
5297
5618
|
const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
5298
5619
|
const exeDir = "exe";
|
|
5299
|
-
const entries =
|
|
5620
|
+
const entries = readdirSync7(exeDir, { withFileTypes: true });
|
|
5300
5621
|
const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
|
|
5301
5622
|
for (const dir of employeeDirs) {
|
|
5302
5623
|
const employee = dir.name;
|
|
5303
|
-
const taskDir =
|
|
5624
|
+
const taskDir = path19.join(exeDir, employee);
|
|
5304
5625
|
let files;
|
|
5305
5626
|
try {
|
|
5306
|
-
files =
|
|
5627
|
+
files = readdirSync7(taskDir).filter((f) => f.endsWith(".md"));
|
|
5307
5628
|
} catch {
|
|
5308
5629
|
continue;
|
|
5309
5630
|
}
|
|
@@ -5311,7 +5632,7 @@ async function boot(options) {
|
|
|
5311
5632
|
const taskFilePath = `exe/${employee}/${file}`;
|
|
5312
5633
|
let content;
|
|
5313
5634
|
try {
|
|
5314
|
-
content = readFs(
|
|
5635
|
+
content = readFs(path19.join(taskDir, file), "utf8");
|
|
5315
5636
|
} catch {
|
|
5316
5637
|
continue;
|
|
5317
5638
|
}
|
|
@@ -5395,12 +5716,12 @@ async function boot(options) {
|
|
|
5395
5716
|
} catch {
|
|
5396
5717
|
}
|
|
5397
5718
|
try {
|
|
5398
|
-
const exeExeDir =
|
|
5399
|
-
if (
|
|
5400
|
-
for (const f of
|
|
5719
|
+
const exeExeDir = path19.join(process.cwd(), "exe", "exe");
|
|
5720
|
+
if (existsSync15(exeExeDir)) {
|
|
5721
|
+
for (const f of readdirSync6(exeExeDir)) {
|
|
5401
5722
|
if (f.startsWith("review-") && f.endsWith(".md")) {
|
|
5402
5723
|
try {
|
|
5403
|
-
unlinkSync5(
|
|
5724
|
+
unlinkSync5(path19.join(exeExeDir, f));
|
|
5404
5725
|
} catch {
|
|
5405
5726
|
}
|
|
5406
5727
|
}
|
|
@@ -5444,12 +5765,12 @@ async function boot(options) {
|
|
|
5444
5765
|
});
|
|
5445
5766
|
const taskFile = String(r.task_file);
|
|
5446
5767
|
try {
|
|
5447
|
-
const filePath =
|
|
5448
|
-
if (
|
|
5449
|
-
let content =
|
|
5768
|
+
const filePath = path19.join(process.cwd(), taskFile);
|
|
5769
|
+
if (existsSync15(filePath)) {
|
|
5770
|
+
let content = readFileSync13(filePath, "utf8");
|
|
5450
5771
|
content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
|
|
5451
|
-
const { writeFileSync:
|
|
5452
|
-
|
|
5772
|
+
const { writeFileSync: writeFileSync8 } = await import("fs");
|
|
5773
|
+
writeFileSync8(filePath, content);
|
|
5453
5774
|
}
|
|
5454
5775
|
} catch {
|
|
5455
5776
|
}
|
|
@@ -5909,19 +6230,19 @@ async function boot(options) {
|
|
|
5909
6230
|
})()
|
|
5910
6231
|
]);
|
|
5911
6232
|
try {
|
|
5912
|
-
const configPath =
|
|
5913
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6233
|
+
const configPath = path19.join(
|
|
6234
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
|
|
5914
6235
|
"config.json"
|
|
5915
6236
|
);
|
|
5916
|
-
if (
|
|
5917
|
-
const raw = JSON.parse(
|
|
6237
|
+
if (existsSync15(configPath)) {
|
|
6238
|
+
const raw = JSON.parse(readFileSync13(configPath, "utf8"));
|
|
5918
6239
|
briefData.cloudConnected = !!(raw.cloud || raw.turso);
|
|
5919
6240
|
}
|
|
5920
6241
|
} catch {
|
|
5921
6242
|
}
|
|
5922
6243
|
try {
|
|
5923
|
-
const backfillFlagPath =
|
|
5924
|
-
const isBackfillNeeded = () =>
|
|
6244
|
+
const backfillFlagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
6245
|
+
const isBackfillNeeded = () => existsSync15(backfillFlagPath);
|
|
5925
6246
|
const coverageResult = await client.execute({
|
|
5926
6247
|
sql: `SELECT COUNT(*) as total,
|
|
5927
6248
|
SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
|
|
@@ -5943,8 +6264,8 @@ async function boot(options) {
|
|
|
5943
6264
|
let daemonRunning = false;
|
|
5944
6265
|
let daemonUptime;
|
|
5945
6266
|
let daemonRequestsServed;
|
|
5946
|
-
const socketPath =
|
|
5947
|
-
if (
|
|
6267
|
+
const socketPath = path19.join(EXE_AI_DIR, "exed.sock");
|
|
6268
|
+
if (existsSync15(socketPath)) {
|
|
5948
6269
|
try {
|
|
5949
6270
|
const net = await import("net");
|
|
5950
6271
|
const health = await new Promise((resolve) => {
|
|
@@ -5986,10 +6307,10 @@ async function boot(options) {
|
|
|
5986
6307
|
}
|
|
5987
6308
|
}
|
|
5988
6309
|
if (!daemonRunning) {
|
|
5989
|
-
const pidPath =
|
|
5990
|
-
if (
|
|
6310
|
+
const pidPath = path19.join(EXE_AI_DIR, "exed.pid");
|
|
6311
|
+
if (existsSync15(pidPath)) {
|
|
5991
6312
|
try {
|
|
5992
|
-
const pid = parseInt(
|
|
6313
|
+
const pid = parseInt(readFileSync13(pidPath, "utf8").trim(), 10);
|
|
5993
6314
|
if (pid > 0) {
|
|
5994
6315
|
process.kill(pid, 0);
|
|
5995
6316
|
daemonRunning = true;
|
|
@@ -6000,8 +6321,8 @@ async function boot(options) {
|
|
|
6000
6321
|
}
|
|
6001
6322
|
if (nullCount === 0) {
|
|
6002
6323
|
try {
|
|
6003
|
-
const flagPath =
|
|
6004
|
-
if (
|
|
6324
|
+
const flagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
6325
|
+
if (existsSync15(flagPath)) {
|
|
6005
6326
|
const { unlinkSync: unlinkSync6 } = await import("fs");
|
|
6006
6327
|
unlinkSync6(flagPath);
|
|
6007
6328
|
}
|
|
@@ -6023,10 +6344,10 @@ async function boot(options) {
|
|
|
6023
6344
|
const { spawn } = await import("child_process");
|
|
6024
6345
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
6025
6346
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
6026
|
-
const backfillPath =
|
|
6027
|
-
if (
|
|
6347
|
+
const backfillPath = path19.resolve(path19.dirname(thisFile), "backfill-vectors.js");
|
|
6348
|
+
if (existsSync15(backfillPath)) {
|
|
6028
6349
|
const { openSync, closeSync } = await import("fs");
|
|
6029
|
-
const workerLogPath =
|
|
6350
|
+
const workerLogPath = path19.join(EXE_AI_DIR, "workers.log");
|
|
6030
6351
|
let stderrFd = "ignore";
|
|
6031
6352
|
try {
|
|
6032
6353
|
stderrFd = openSync(workerLogPath, "a");
|
|
@@ -6054,8 +6375,8 @@ async function boot(options) {
|
|
|
6054
6375
|
const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
|
|
6055
6376
|
const missing = [];
|
|
6056
6377
|
for (const bin of criticalBinaries) {
|
|
6057
|
-
const binPath =
|
|
6058
|
-
if (!
|
|
6378
|
+
const binPath = path19.resolve(path19.dirname(thisFile), bin);
|
|
6379
|
+
if (!existsSync15(binPath)) {
|
|
6059
6380
|
missing.push(`dist/bin/${bin}`);
|
|
6060
6381
|
}
|
|
6061
6382
|
}
|
|
@@ -6085,7 +6406,7 @@ async function boot(options) {
|
|
|
6085
6406
|
return;
|
|
6086
6407
|
}
|
|
6087
6408
|
const exeEmployee = employees.find((e) => e.name === "exe") ?? DEFAULT_EXE;
|
|
6088
|
-
const sessionDir =
|
|
6409
|
+
const sessionDir = path19.join(EXE_AI_DIR, "sessions", "exe");
|
|
6089
6410
|
await mkdir5(sessionDir, { recursive: true });
|
|
6090
6411
|
const claudeMdContent = `${exeEmployee.systemPrompt}
|
|
6091
6412
|
|
|
@@ -6094,7 +6415,7 @@ async function boot(options) {
|
|
|
6094
6415
|
# Status Brief
|
|
6095
6416
|
|
|
6096
6417
|
${brief}`;
|
|
6097
|
-
await writeFile6(
|
|
6418
|
+
await writeFile6(path19.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
|
|
6098
6419
|
const unread = await readUnreadNotifications();
|
|
6099
6420
|
if (unread.length > 0) {
|
|
6100
6421
|
console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
|
|
@@ -6103,12 +6424,12 @@ ${brief}`;
|
|
|
6103
6424
|
await cleanupOldNotifications();
|
|
6104
6425
|
console.log(brief);
|
|
6105
6426
|
try {
|
|
6106
|
-
const configPath2 =
|
|
6107
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6427
|
+
const configPath2 = path19.join(
|
|
6428
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
|
|
6108
6429
|
"config.json"
|
|
6109
6430
|
);
|
|
6110
|
-
if (
|
|
6111
|
-
const rawCfg = JSON.parse(
|
|
6431
|
+
if (existsSync15(configPath2)) {
|
|
6432
|
+
const rawCfg = JSON.parse(readFileSync13(configPath2, "utf8"));
|
|
6112
6433
|
const cloudCfg = rawCfg.cloud;
|
|
6113
6434
|
if (cloudCfg?.apiKey) {
|
|
6114
6435
|
const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
@@ -6118,9 +6439,7 @@ ${brief}`;
|
|
|
6118
6439
|
if (mk) initSyncCrypto2(mk);
|
|
6119
6440
|
}
|
|
6120
6441
|
const { cloudSync: cloudSync2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
6121
|
-
cloudSync2({ apiKey: cloudCfg.apiKey, endpoint: cloudCfg.endpoint ?? "" }).catch((
|
|
6122
|
-
process.stderr.write(`[boot] cloud sync failed: ${err instanceof Error ? err.message : String(err)}
|
|
6123
|
-
`);
|
|
6442
|
+
cloudSync2({ apiKey: cloudCfg.apiKey, endpoint: cloudCfg.endpoint ?? "" }).catch(() => {
|
|
6124
6443
|
});
|
|
6125
6444
|
}
|
|
6126
6445
|
}
|