@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/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,10 +2575,46 @@ 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";
|
|
2582
|
+
import { unlinkSync as unlinkSync3 } from "fs";
|
|
2583
|
+
function spawnLockPath(sessionName) {
|
|
2584
|
+
return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2585
|
+
}
|
|
2586
|
+
function isProcessAlive(pid) {
|
|
2587
|
+
try {
|
|
2588
|
+
process.kill(pid, 0);
|
|
2589
|
+
return true;
|
|
2590
|
+
} catch {
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
function acquireSpawnLock(sessionName) {
|
|
2595
|
+
if (!existsSync10(SPAWN_LOCK_DIR)) {
|
|
2596
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
2597
|
+
}
|
|
2598
|
+
const lockFile = spawnLockPath(sessionName);
|
|
2599
|
+
if (existsSync10(lockFile)) {
|
|
2600
|
+
try {
|
|
2601
|
+
const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
|
|
2602
|
+
const age = Date.now() - lock.timestamp;
|
|
2603
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2606
|
+
} catch {
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
2610
|
+
return true;
|
|
2611
|
+
}
|
|
2612
|
+
function releaseSpawnLock(sessionName) {
|
|
2613
|
+
try {
|
|
2614
|
+
unlinkSync3(spawnLockPath(sessionName));
|
|
2615
|
+
} catch {
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2464
2618
|
function resolveBehaviorsExporterScript() {
|
|
2465
2619
|
try {
|
|
2466
2620
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
@@ -2506,7 +2660,7 @@ function extractRootExe(name) {
|
|
|
2506
2660
|
}
|
|
2507
2661
|
function getParentExe(sessionKey) {
|
|
2508
2662
|
try {
|
|
2509
|
-
const data = JSON.parse(
|
|
2663
|
+
const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2510
2664
|
return data.parentExe || null;
|
|
2511
2665
|
} catch {
|
|
2512
2666
|
return null;
|
|
@@ -2514,7 +2668,7 @@ function getParentExe(sessionKey) {
|
|
|
2514
2668
|
}
|
|
2515
2669
|
function getDispatchedBy(sessionKey) {
|
|
2516
2670
|
try {
|
|
2517
|
-
const data = JSON.parse(
|
|
2671
|
+
const data = JSON.parse(readFileSync9(
|
|
2518
2672
|
path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
2519
2673
|
"utf8"
|
|
2520
2674
|
));
|
|
@@ -2541,17 +2695,17 @@ function isEmployeeAlive(sessionName) {
|
|
|
2541
2695
|
}
|
|
2542
2696
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
2543
2697
|
const base = employeeSessionName(employeeName, exeSession);
|
|
2544
|
-
if (!isAlive(base)) return 0;
|
|
2698
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
2545
2699
|
for (let i = 2; i <= maxInstances; i++) {
|
|
2546
2700
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
2547
|
-
if (!isAlive(candidate)) return i;
|
|
2701
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
2548
2702
|
}
|
|
2549
2703
|
return null;
|
|
2550
2704
|
}
|
|
2551
2705
|
function readDebounceState() {
|
|
2552
2706
|
try {
|
|
2553
2707
|
if (!existsSync10(DEBOUNCE_FILE)) return {};
|
|
2554
|
-
return JSON.parse(
|
|
2708
|
+
return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
|
|
2555
2709
|
} catch {
|
|
2556
2710
|
return {};
|
|
2557
2711
|
}
|
|
@@ -2747,7 +2901,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2747
2901
|
const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
|
|
2748
2902
|
let claudeJson = {};
|
|
2749
2903
|
try {
|
|
2750
|
-
claudeJson = JSON.parse(
|
|
2904
|
+
claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
|
|
2751
2905
|
} catch {
|
|
2752
2906
|
}
|
|
2753
2907
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -2765,7 +2919,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2765
2919
|
const settingsPath = path11.join(projSettingsDir, "settings.json");
|
|
2766
2920
|
let settings = {};
|
|
2767
2921
|
try {
|
|
2768
|
-
settings = JSON.parse(
|
|
2922
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
2769
2923
|
} catch {
|
|
2770
2924
|
}
|
|
2771
2925
|
const perms = settings.permissions ?? {};
|
|
@@ -2878,6 +3032,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2878
3032
|
command: spawnCommand
|
|
2879
3033
|
});
|
|
2880
3034
|
if (spawnResult.error) {
|
|
3035
|
+
releaseSpawnLock(sessionName);
|
|
2881
3036
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
2882
3037
|
}
|
|
2883
3038
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -2915,6 +3070,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2915
3070
|
}
|
|
2916
3071
|
}
|
|
2917
3072
|
if (!booted) {
|
|
3073
|
+
releaseSpawnLock(sessionName);
|
|
2918
3074
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
2919
3075
|
}
|
|
2920
3076
|
if (!useExeAgent) {
|
|
@@ -2931,9 +3087,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2931
3087
|
pid: 0,
|
|
2932
3088
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2933
3089
|
});
|
|
3090
|
+
releaseSpawnLock(sessionName);
|
|
2934
3091
|
return { sessionName };
|
|
2935
3092
|
}
|
|
2936
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3093
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
2937
3094
|
var init_tmux_routing = __esm({
|
|
2938
3095
|
"src/lib/tmux-routing.ts"() {
|
|
2939
3096
|
"use strict";
|
|
@@ -2945,6 +3102,7 @@ var init_tmux_routing = __esm({
|
|
|
2945
3102
|
init_provider_table();
|
|
2946
3103
|
init_intercom_queue();
|
|
2947
3104
|
init_plan_limits();
|
|
3105
|
+
SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
2948
3106
|
SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
|
|
2949
3107
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
2950
3108
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -2966,7 +3124,7 @@ __export(task_scanner_exports, {
|
|
|
2966
3124
|
formatText: () => formatText,
|
|
2967
3125
|
scanAgentTasks: () => scanAgentTasks
|
|
2968
3126
|
});
|
|
2969
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
3127
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync10, existsSync as existsSync11, statSync } from "fs";
|
|
2970
3128
|
import { execSync as execSync7 } from "child_process";
|
|
2971
3129
|
import path12 from "path";
|
|
2972
3130
|
function getProjectRoot() {
|
|
@@ -2992,7 +3150,7 @@ function scanAgentTasks(agentId) {
|
|
|
2992
3150
|
total = files.length;
|
|
2993
3151
|
for (const f of files) {
|
|
2994
3152
|
try {
|
|
2995
|
-
const content =
|
|
3153
|
+
const content = readFileSync10(path12.join(taskDir, f), "utf8");
|
|
2996
3154
|
const statusMatch = content.match(STATUS_RE);
|
|
2997
3155
|
const status = statusMatch ? statusMatch[1].toLowerCase() : null;
|
|
2998
3156
|
if (status === "done") {
|
|
@@ -3146,7 +3304,7 @@ import crypto3 from "crypto";
|
|
|
3146
3304
|
import path14 from "path";
|
|
3147
3305
|
import { execSync as execSync9 } from "child_process";
|
|
3148
3306
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3149
|
-
import { existsSync as existsSync12, readFileSync as
|
|
3307
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3150
3308
|
async function writeCheckpoint(input) {
|
|
3151
3309
|
const client = getClient();
|
|
3152
3310
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3523,7 +3681,7 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
3523
3681
|
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
3524
3682
|
try {
|
|
3525
3683
|
if (existsSync12(gitignorePath)) {
|
|
3526
|
-
const content =
|
|
3684
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3527
3685
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3528
3686
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3529
3687
|
} else {
|
|
@@ -3544,7 +3702,7 @@ var init_tasks_crud = __esm({
|
|
|
3544
3702
|
|
|
3545
3703
|
// src/lib/tasks-review.ts
|
|
3546
3704
|
import path15 from "path";
|
|
3547
|
-
import { existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as
|
|
3705
|
+
import { existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
3548
3706
|
async function countPendingReviews() {
|
|
3549
3707
|
const client = getClient();
|
|
3550
3708
|
const result = await client.execute({
|
|
@@ -3668,7 +3826,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3668
3826
|
if (existsSync13(cacheDir)) {
|
|
3669
3827
|
for (const f of readdirSync4(cacheDir)) {
|
|
3670
3828
|
if (f.startsWith("review-notified-")) {
|
|
3671
|
-
|
|
3829
|
+
unlinkSync4(path15.join(cacheDir, f));
|
|
3672
3830
|
}
|
|
3673
3831
|
}
|
|
3674
3832
|
}
|
|
@@ -3857,7 +4015,7 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3857
4015
|
} else {
|
|
3858
4016
|
const projectDir = input.projectDir ?? process.cwd();
|
|
3859
4017
|
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
3860
|
-
autoInstance: input.assignedTo
|
|
4018
|
+
autoInstance: isMultiInstance(input.assignedTo)
|
|
3861
4019
|
});
|
|
3862
4020
|
if (result.status === "failed") {
|
|
3863
4021
|
process.stderr.write(
|
|
@@ -3892,6 +4050,7 @@ var init_tasks_notify = __esm({
|
|
|
3892
4050
|
init_session_key();
|
|
3893
4051
|
init_notifications();
|
|
3894
4052
|
init_transport();
|
|
4053
|
+
init_employees();
|
|
3895
4054
|
}
|
|
3896
4055
|
});
|
|
3897
4056
|
|
|
@@ -4226,7 +4385,7 @@ __export(tasks_exports, {
|
|
|
4226
4385
|
writeCheckpoint: () => writeCheckpoint
|
|
4227
4386
|
});
|
|
4228
4387
|
import path17 from "path";
|
|
4229
|
-
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as
|
|
4388
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
4230
4389
|
async function createTask(input) {
|
|
4231
4390
|
const result = await createTaskCore(input);
|
|
4232
4391
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4252,7 +4411,7 @@ async function updateTask(input) {
|
|
|
4252
4411
|
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4253
4412
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4254
4413
|
try {
|
|
4255
|
-
|
|
4414
|
+
unlinkSync5(cachePath);
|
|
4256
4415
|
} catch {
|
|
4257
4416
|
}
|
|
4258
4417
|
}
|
|
@@ -4548,10 +4707,49 @@ var init_compress = __esm({
|
|
|
4548
4707
|
// src/lib/cloud-sync.ts
|
|
4549
4708
|
var cloud_sync_exports = {};
|
|
4550
4709
|
__export(cloud_sync_exports, {
|
|
4710
|
+
buildRosterBlob: () => buildRosterBlob,
|
|
4551
4711
|
cloudPull: () => cloudPull,
|
|
4712
|
+
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
4713
|
+
cloudPullBlob: () => cloudPullBlob,
|
|
4714
|
+
cloudPullConversations: () => cloudPullConversations,
|
|
4715
|
+
cloudPullDocuments: () => cloudPullDocuments,
|
|
4716
|
+
cloudPullGraphRAG: () => cloudPullGraphRAG,
|
|
4717
|
+
cloudPullRoster: () => cloudPullRoster,
|
|
4718
|
+
cloudPullTasks: () => cloudPullTasks,
|
|
4552
4719
|
cloudPush: () => cloudPush,
|
|
4553
|
-
|
|
4720
|
+
cloudPushBehaviors: () => cloudPushBehaviors,
|
|
4721
|
+
cloudPushBlob: () => cloudPushBlob,
|
|
4722
|
+
cloudPushConversations: () => cloudPushConversations,
|
|
4723
|
+
cloudPushDocuments: () => cloudPushDocuments,
|
|
4724
|
+
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
4725
|
+
cloudPushRoster: () => cloudPushRoster,
|
|
4726
|
+
cloudPushTasks: () => cloudPushTasks,
|
|
4727
|
+
cloudSync: () => cloudSync,
|
|
4728
|
+
mergeConfig: () => mergeConfig,
|
|
4729
|
+
mergeRosterFromRemote: () => mergeRosterFromRemote
|
|
4554
4730
|
});
|
|
4731
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
|
|
4732
|
+
import path18 from "path";
|
|
4733
|
+
import { homedir } from "os";
|
|
4734
|
+
function logError(msg) {
|
|
4735
|
+
try {
|
|
4736
|
+
const logPath = path18.join(homedir(), ".exe-os", "workers.log");
|
|
4737
|
+
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
4738
|
+
`);
|
|
4739
|
+
} catch {
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
async function fetchWithRetry(url, init) {
|
|
4743
|
+
const attempt = async () => {
|
|
4744
|
+
const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
4745
|
+
return fetch(url, { ...init, signal });
|
|
4746
|
+
};
|
|
4747
|
+
const resp = await attempt();
|
|
4748
|
+
if (resp.status >= 500) {
|
|
4749
|
+
return attempt();
|
|
4750
|
+
}
|
|
4751
|
+
return resp;
|
|
4752
|
+
}
|
|
4555
4753
|
function assertSecureEndpoint(endpoint) {
|
|
4556
4754
|
if (endpoint.startsWith("https://")) return;
|
|
4557
4755
|
if (endpoint.startsWith("http://")) {
|
|
@@ -4573,7 +4771,7 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
4573
4771
|
const json = JSON.stringify(records);
|
|
4574
4772
|
const compressed = compress(Buffer.from(json, "utf8"));
|
|
4575
4773
|
const blob = encryptSyncBlob(compressed);
|
|
4576
|
-
const resp = await
|
|
4774
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
|
|
4577
4775
|
method: "POST",
|
|
4578
4776
|
headers: {
|
|
4579
4777
|
Authorization: `Bearer ${config.apiKey}`,
|
|
@@ -4584,15 +4782,14 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
4584
4782
|
});
|
|
4585
4783
|
return resp.ok;
|
|
4586
4784
|
} catch (err) {
|
|
4587
|
-
|
|
4588
|
-
`);
|
|
4785
|
+
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
4589
4786
|
return false;
|
|
4590
4787
|
}
|
|
4591
4788
|
}
|
|
4592
4789
|
async function cloudPull(sinceVersion, config) {
|
|
4593
4790
|
assertSecureEndpoint(config.endpoint);
|
|
4594
4791
|
try {
|
|
4595
|
-
const response = await
|
|
4792
|
+
const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
|
|
4596
4793
|
method: "POST",
|
|
4597
4794
|
headers: {
|
|
4598
4795
|
Authorization: `Bearer ${config.apiKey}`,
|
|
@@ -4616,8 +4813,7 @@ async function cloudPull(sinceVersion, config) {
|
|
|
4616
4813
|
}
|
|
4617
4814
|
return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
|
|
4618
4815
|
} catch (err) {
|
|
4619
|
-
|
|
4620
|
-
`);
|
|
4816
|
+
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
4621
4817
|
return { records: [], maxVersion: sinceVersion };
|
|
4622
4818
|
}
|
|
4623
4819
|
}
|
|
@@ -4706,9 +4902,604 @@ async function cloudSync(config) {
|
|
|
4706
4902
|
pushed = records.length;
|
|
4707
4903
|
}
|
|
4708
4904
|
}
|
|
4709
|
-
|
|
4905
|
+
try {
|
|
4906
|
+
await cloudPushRoster(config);
|
|
4907
|
+
} catch (err) {
|
|
4908
|
+
logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4909
|
+
}
|
|
4910
|
+
try {
|
|
4911
|
+
await cloudPullRoster(config);
|
|
4912
|
+
} catch (err) {
|
|
4913
|
+
logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4914
|
+
}
|
|
4915
|
+
let behaviorsResult = { pushed: false, pulled: 0 };
|
|
4916
|
+
try {
|
|
4917
|
+
behaviorsResult.pushed = await cloudPushBehaviors(config);
|
|
4918
|
+
} catch (err) {
|
|
4919
|
+
logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4920
|
+
}
|
|
4921
|
+
try {
|
|
4922
|
+
const pullResult2 = await cloudPullBehaviors(config);
|
|
4923
|
+
behaviorsResult.pulled = pullResult2.pulled;
|
|
4924
|
+
} catch (err) {
|
|
4925
|
+
logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4926
|
+
}
|
|
4927
|
+
let graphragResult = { pushed: false, pulled: 0 };
|
|
4928
|
+
try {
|
|
4929
|
+
graphragResult.pushed = await cloudPushGraphRAG(config);
|
|
4930
|
+
} catch (err) {
|
|
4931
|
+
logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4932
|
+
}
|
|
4933
|
+
try {
|
|
4934
|
+
const pullResult2 = await cloudPullGraphRAG(config);
|
|
4935
|
+
graphragResult.pulled = pullResult2.pulled;
|
|
4936
|
+
} catch (err) {
|
|
4937
|
+
logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4938
|
+
}
|
|
4939
|
+
let tasksResult = { pushed: false, pulled: 0 };
|
|
4940
|
+
try {
|
|
4941
|
+
tasksResult.pushed = await cloudPushTasks(config);
|
|
4942
|
+
} catch (err) {
|
|
4943
|
+
logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4944
|
+
}
|
|
4945
|
+
try {
|
|
4946
|
+
const pullResult2 = await cloudPullTasks(config);
|
|
4947
|
+
tasksResult.pulled = pullResult2.pulled;
|
|
4948
|
+
} catch (err) {
|
|
4949
|
+
logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4950
|
+
}
|
|
4951
|
+
let conversationsResult = { pushed: false, pulled: 0 };
|
|
4952
|
+
try {
|
|
4953
|
+
conversationsResult.pushed = await cloudPushConversations(config);
|
|
4954
|
+
} catch (err) {
|
|
4955
|
+
logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4956
|
+
}
|
|
4957
|
+
try {
|
|
4958
|
+
const pullResult2 = await cloudPullConversations(config);
|
|
4959
|
+
conversationsResult.pulled = pullResult2.pulled;
|
|
4960
|
+
} catch (err) {
|
|
4961
|
+
logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4962
|
+
}
|
|
4963
|
+
let documentsResult = { pushed: false, pulled: 0 };
|
|
4964
|
+
try {
|
|
4965
|
+
documentsResult.pushed = await cloudPushDocuments(config);
|
|
4966
|
+
} catch (err) {
|
|
4967
|
+
logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
|
|
4968
|
+
}
|
|
4969
|
+
try {
|
|
4970
|
+
const pullResult2 = await cloudPullDocuments(config);
|
|
4971
|
+
documentsResult.pulled = pullResult2.pulled;
|
|
4972
|
+
} catch (err) {
|
|
4973
|
+
logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
4974
|
+
}
|
|
4975
|
+
return {
|
|
4976
|
+
pushed,
|
|
4977
|
+
pulled,
|
|
4978
|
+
behaviors: behaviorsResult,
|
|
4979
|
+
graphrag: graphragResult,
|
|
4980
|
+
tasks: tasksResult,
|
|
4981
|
+
conversations: conversationsResult,
|
|
4982
|
+
documents: documentsResult
|
|
4983
|
+
};
|
|
4984
|
+
}
|
|
4985
|
+
function buildRosterBlob(paths) {
|
|
4986
|
+
const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
|
|
4987
|
+
const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
|
|
4988
|
+
const configPath = paths?.configPath ?? path18.join(EXE_AI_DIR, "config.json");
|
|
4989
|
+
let roster = [];
|
|
4990
|
+
if (existsSync14(rosterPath)) {
|
|
4991
|
+
try {
|
|
4992
|
+
roster = JSON.parse(readFileSync12(rosterPath, "utf-8"));
|
|
4993
|
+
} catch {
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
const identities = {};
|
|
4997
|
+
if (existsSync14(identityDir)) {
|
|
4998
|
+
for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
4999
|
+
try {
|
|
5000
|
+
identities[file] = readFileSync12(path18.join(identityDir, file), "utf-8");
|
|
5001
|
+
} catch {
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
let config;
|
|
5006
|
+
if (existsSync14(configPath)) {
|
|
5007
|
+
try {
|
|
5008
|
+
config = JSON.parse(readFileSync12(configPath, "utf-8"));
|
|
5009
|
+
} catch {
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
const content = JSON.stringify({ roster, identities, config });
|
|
5013
|
+
const hash = Buffer.from(content).length;
|
|
5014
|
+
return { roster, identities, config, version: hash };
|
|
5015
|
+
}
|
|
5016
|
+
async function cloudPushRoster(config) {
|
|
5017
|
+
assertSecureEndpoint(config.endpoint);
|
|
5018
|
+
const blob = buildRosterBlob();
|
|
5019
|
+
if (blob.roster.length === 0) return true;
|
|
5020
|
+
try {
|
|
5021
|
+
const client = getClient();
|
|
5022
|
+
const meta = await client.execute(
|
|
5023
|
+
"SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
|
|
5024
|
+
);
|
|
5025
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
5026
|
+
if (blob.version === lastVersion) return true;
|
|
5027
|
+
} catch {
|
|
5028
|
+
}
|
|
5029
|
+
try {
|
|
5030
|
+
const json = JSON.stringify(blob);
|
|
5031
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
5032
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
5033
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
|
|
5034
|
+
method: "POST",
|
|
5035
|
+
headers: {
|
|
5036
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
5037
|
+
"Content-Type": "application/json",
|
|
5038
|
+
"X-Device-Id": loadDeviceId()
|
|
5039
|
+
},
|
|
5040
|
+
body: JSON.stringify({ blob: encrypted })
|
|
5041
|
+
});
|
|
5042
|
+
if (resp.ok) {
|
|
5043
|
+
try {
|
|
5044
|
+
const client = getClient();
|
|
5045
|
+
await client.execute({
|
|
5046
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
|
|
5047
|
+
args: [String(blob.version)]
|
|
5048
|
+
});
|
|
5049
|
+
} catch {
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
return resp.ok;
|
|
5053
|
+
} catch (err) {
|
|
5054
|
+
process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
5055
|
+
`);
|
|
5056
|
+
return false;
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
async function cloudPullRoster(config) {
|
|
5060
|
+
assertSecureEndpoint(config.endpoint);
|
|
5061
|
+
try {
|
|
5062
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
|
|
5063
|
+
method: "GET",
|
|
5064
|
+
headers: {
|
|
5065
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
5066
|
+
"X-Device-Id": loadDeviceId()
|
|
5067
|
+
}
|
|
5068
|
+
});
|
|
5069
|
+
if (!resp.ok) return { added: 0 };
|
|
5070
|
+
const data = await resp.json();
|
|
5071
|
+
if (!data.blob) return { added: 0 };
|
|
5072
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
5073
|
+
const json = decompress(compressed).toString("utf8");
|
|
5074
|
+
const remote = JSON.parse(json);
|
|
5075
|
+
return mergeRosterFromRemote(remote);
|
|
5076
|
+
} catch (err) {
|
|
5077
|
+
process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
5078
|
+
`);
|
|
5079
|
+
return { added: 0 };
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
function mergeConfig(remoteConfig, configPath) {
|
|
5083
|
+
const cfgPath = configPath ?? path18.join(EXE_AI_DIR, "config.json");
|
|
5084
|
+
let local = {};
|
|
5085
|
+
if (existsSync14(cfgPath)) {
|
|
5086
|
+
try {
|
|
5087
|
+
local = JSON.parse(readFileSync12(cfgPath, "utf-8"));
|
|
5088
|
+
} catch {
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5091
|
+
const merged = { ...remoteConfig, ...local };
|
|
5092
|
+
const dir = path18.dirname(cfgPath);
|
|
5093
|
+
if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
|
|
5094
|
+
writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
5095
|
+
}
|
|
5096
|
+
async function mergeRosterFromRemote(remote, paths) {
|
|
5097
|
+
const rosterPath = paths?.rosterPath ?? void 0;
|
|
5098
|
+
const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
|
|
5099
|
+
const localEmployees = await loadEmployees(rosterPath);
|
|
5100
|
+
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
5101
|
+
let added = 0;
|
|
5102
|
+
for (const remoteEmp of remote.roster) {
|
|
5103
|
+
if (localNames.has(remoteEmp.name)) continue;
|
|
5104
|
+
localEmployees.push(remoteEmp);
|
|
5105
|
+
localNames.add(remoteEmp.name);
|
|
5106
|
+
added++;
|
|
5107
|
+
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
5108
|
+
if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
|
|
5109
|
+
const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
|
|
5110
|
+
if (!existsSync14(idPath)) {
|
|
5111
|
+
writeFileSync7(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
try {
|
|
5115
|
+
registerBinSymlinks(remoteEmp.name);
|
|
5116
|
+
} catch {
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
if (added > 0) {
|
|
5120
|
+
await saveEmployees(localEmployees, rosterPath);
|
|
5121
|
+
}
|
|
5122
|
+
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
5123
|
+
try {
|
|
5124
|
+
mergeConfig(remote.config, paths?.configPath);
|
|
5125
|
+
} catch {
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
return { added };
|
|
5129
|
+
}
|
|
5130
|
+
async function cloudPushBlob(route, data, metaKey, config) {
|
|
5131
|
+
if (data.length === 0) return { ok: true };
|
|
5132
|
+
assertSecureEndpoint(config.endpoint);
|
|
5133
|
+
const json = JSON.stringify(data);
|
|
5134
|
+
const version = Buffer.from(json).length;
|
|
5135
|
+
try {
|
|
5136
|
+
const client = getClient();
|
|
5137
|
+
const meta = await client.execute({
|
|
5138
|
+
sql: "SELECT value FROM sync_meta WHERE key = ?",
|
|
5139
|
+
args: [metaKey]
|
|
5140
|
+
});
|
|
5141
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
5142
|
+
if (version === lastVersion) return { ok: true };
|
|
5143
|
+
} catch {
|
|
5144
|
+
}
|
|
5145
|
+
try {
|
|
5146
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
5147
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
5148
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
5149
|
+
method: "POST",
|
|
5150
|
+
headers: {
|
|
5151
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
5152
|
+
"Content-Type": "application/json",
|
|
5153
|
+
"X-Device-Id": loadDeviceId()
|
|
5154
|
+
},
|
|
5155
|
+
body: JSON.stringify({ blob: encrypted })
|
|
5156
|
+
});
|
|
5157
|
+
if (resp.ok) {
|
|
5158
|
+
try {
|
|
5159
|
+
const client = getClient();
|
|
5160
|
+
await client.execute({
|
|
5161
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
|
|
5162
|
+
args: [metaKey, String(version)]
|
|
5163
|
+
});
|
|
5164
|
+
} catch {
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
return { ok: resp.ok };
|
|
5168
|
+
} catch (err) {
|
|
5169
|
+
logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5170
|
+
return { ok: false };
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
async function cloudPullBlob(route, config) {
|
|
5174
|
+
assertSecureEndpoint(config.endpoint);
|
|
5175
|
+
try {
|
|
5176
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
5177
|
+
method: "GET",
|
|
5178
|
+
headers: {
|
|
5179
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
5180
|
+
"X-Device-Id": loadDeviceId()
|
|
5181
|
+
}
|
|
5182
|
+
});
|
|
5183
|
+
if (!resp.ok) return null;
|
|
5184
|
+
const data = await resp.json();
|
|
5185
|
+
if (!data.blob) return null;
|
|
5186
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
5187
|
+
const json = decompress(compressed).toString("utf8");
|
|
5188
|
+
return JSON.parse(json);
|
|
5189
|
+
} catch (err) {
|
|
5190
|
+
logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5191
|
+
return null;
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
async function cloudPushBehaviors(config) {
|
|
5195
|
+
const client = getClient();
|
|
5196
|
+
const result = await client.execute("SELECT * FROM behaviors");
|
|
5197
|
+
const rows = result.rows;
|
|
5198
|
+
const { ok } = await cloudPushBlob(
|
|
5199
|
+
"/sync/push-behaviors",
|
|
5200
|
+
rows,
|
|
5201
|
+
"last_behaviors_push_version",
|
|
5202
|
+
config
|
|
5203
|
+
);
|
|
5204
|
+
return ok;
|
|
5205
|
+
}
|
|
5206
|
+
async function cloudPullBehaviors(config) {
|
|
5207
|
+
const remoteBehaviors = await cloudPullBlob(
|
|
5208
|
+
"/sync/pull-behaviors",
|
|
5209
|
+
config
|
|
5210
|
+
);
|
|
5211
|
+
if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
|
|
5212
|
+
const client = getClient();
|
|
5213
|
+
let pulled = 0;
|
|
5214
|
+
for (const behavior of remoteBehaviors) {
|
|
5215
|
+
const existing = await client.execute({
|
|
5216
|
+
sql: `SELECT COUNT(*) as cnt FROM behaviors
|
|
5217
|
+
WHERE agent_id = ? AND content = ?`,
|
|
5218
|
+
args: [behavior.agent_id, behavior.content]
|
|
5219
|
+
});
|
|
5220
|
+
if (Number(existing.rows[0]?.cnt) > 0) continue;
|
|
5221
|
+
await client.execute({
|
|
5222
|
+
sql: `INSERT OR IGNORE INTO behaviors
|
|
5223
|
+
(id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
|
|
5224
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5225
|
+
args: [
|
|
5226
|
+
behavior.id,
|
|
5227
|
+
behavior.agent_id,
|
|
5228
|
+
behavior.project_name ?? null,
|
|
5229
|
+
behavior.domain ?? null,
|
|
5230
|
+
behavior.content,
|
|
5231
|
+
behavior.active,
|
|
5232
|
+
behavior.priority ?? "p1",
|
|
5233
|
+
behavior.created_at,
|
|
5234
|
+
behavior.updated_at
|
|
5235
|
+
]
|
|
5236
|
+
});
|
|
5237
|
+
pulled++;
|
|
5238
|
+
}
|
|
5239
|
+
return { pulled };
|
|
4710
5240
|
}
|
|
4711
|
-
|
|
5241
|
+
async function cloudPushGraphRAG(config) {
|
|
5242
|
+
const client = getClient();
|
|
5243
|
+
const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
|
|
5244
|
+
client.execute("SELECT * FROM entities"),
|
|
5245
|
+
client.execute("SELECT * FROM relationships"),
|
|
5246
|
+
client.execute("SELECT * FROM entity_aliases"),
|
|
5247
|
+
client.execute("SELECT * FROM entity_memories"),
|
|
5248
|
+
client.execute("SELECT * FROM relationship_memories"),
|
|
5249
|
+
client.execute("SELECT * FROM hyperedges"),
|
|
5250
|
+
client.execute("SELECT * FROM hyperedge_nodes")
|
|
5251
|
+
]);
|
|
5252
|
+
const blob = {
|
|
5253
|
+
entities: entities.rows,
|
|
5254
|
+
relationships: relationships.rows,
|
|
5255
|
+
entity_aliases: aliases.rows,
|
|
5256
|
+
entity_memories: entityMems.rows,
|
|
5257
|
+
relationship_memories: relMems.rows,
|
|
5258
|
+
hyperedges: hyperedges.rows,
|
|
5259
|
+
hyperedge_nodes: hyperedgeNodes.rows
|
|
5260
|
+
};
|
|
5261
|
+
const { ok } = await cloudPushBlob(
|
|
5262
|
+
"/sync/push-graphrag",
|
|
5263
|
+
[blob],
|
|
5264
|
+
"last_graphrag_push_version",
|
|
5265
|
+
config
|
|
5266
|
+
);
|
|
5267
|
+
return ok;
|
|
5268
|
+
}
|
|
5269
|
+
async function cloudPullGraphRAG(config) {
|
|
5270
|
+
const data = await cloudPullBlob(
|
|
5271
|
+
"/sync/pull-graphrag",
|
|
5272
|
+
config
|
|
5273
|
+
);
|
|
5274
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
5275
|
+
const blob = data[0];
|
|
5276
|
+
const client = getClient();
|
|
5277
|
+
let pulled = 0;
|
|
5278
|
+
if (blob.entities.length > 0) {
|
|
5279
|
+
const stmts = blob.entities.map((e) => ({
|
|
5280
|
+
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
|
|
5281
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
5282
|
+
args: [e.id, e.name, e.type, e.first_seen, e.last_seen, e.properties ?? "{}"]
|
|
5283
|
+
}));
|
|
5284
|
+
await client.batch(stmts, "write");
|
|
5285
|
+
pulled += stmts.length;
|
|
5286
|
+
}
|
|
5287
|
+
if (blob.relationships.length > 0) {
|
|
5288
|
+
const stmts = blob.relationships.map((r) => ({
|
|
5289
|
+
sql: `INSERT OR IGNORE INTO relationships
|
|
5290
|
+
(id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
|
|
5291
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5292
|
+
args: [
|
|
5293
|
+
r.id,
|
|
5294
|
+
r.source_entity_id,
|
|
5295
|
+
r.target_entity_id,
|
|
5296
|
+
r.type,
|
|
5297
|
+
r.weight ?? 1,
|
|
5298
|
+
r.timestamp,
|
|
5299
|
+
r.properties ?? "{}",
|
|
5300
|
+
r.confidence ?? 1,
|
|
5301
|
+
r.confidence_label ?? "extracted"
|
|
5302
|
+
]
|
|
5303
|
+
}));
|
|
5304
|
+
await client.batch(stmts, "write");
|
|
5305
|
+
pulled += stmts.length;
|
|
5306
|
+
}
|
|
5307
|
+
if (blob.entity_aliases.length > 0) {
|
|
5308
|
+
const stmts = blob.entity_aliases.map((a) => ({
|
|
5309
|
+
sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
|
|
5310
|
+
args: [a.alias, a.canonical_entity_id]
|
|
5311
|
+
}));
|
|
5312
|
+
await client.batch(stmts, "write");
|
|
5313
|
+
pulled += stmts.length;
|
|
5314
|
+
}
|
|
5315
|
+
if (blob.entity_memories.length > 0) {
|
|
5316
|
+
const stmts = blob.entity_memories.map((em) => ({
|
|
5317
|
+
sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
|
|
5318
|
+
args: [em.entity_id, em.memory_id]
|
|
5319
|
+
}));
|
|
5320
|
+
await client.batch(stmts, "write");
|
|
5321
|
+
pulled += stmts.length;
|
|
5322
|
+
}
|
|
5323
|
+
if (blob.relationship_memories.length > 0) {
|
|
5324
|
+
const stmts = blob.relationship_memories.map((rm) => ({
|
|
5325
|
+
sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
|
|
5326
|
+
args: [rm.relationship_id, rm.memory_id]
|
|
5327
|
+
}));
|
|
5328
|
+
await client.batch(stmts, "write");
|
|
5329
|
+
pulled += stmts.length;
|
|
5330
|
+
}
|
|
5331
|
+
if (blob.hyperedges.length > 0) {
|
|
5332
|
+
const stmts = blob.hyperedges.map((h) => ({
|
|
5333
|
+
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
5334
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
5335
|
+
args: [h.id, h.label, h.relation, h.confidence ?? 1, h.timestamp]
|
|
5336
|
+
}));
|
|
5337
|
+
await client.batch(stmts, "write");
|
|
5338
|
+
pulled += stmts.length;
|
|
5339
|
+
}
|
|
5340
|
+
if (blob.hyperedge_nodes.length > 0) {
|
|
5341
|
+
const stmts = blob.hyperedge_nodes.map((hn) => ({
|
|
5342
|
+
sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
|
|
5343
|
+
args: [hn.hyperedge_id, hn.entity_id]
|
|
5344
|
+
}));
|
|
5345
|
+
await client.batch(stmts, "write");
|
|
5346
|
+
pulled += stmts.length;
|
|
5347
|
+
}
|
|
5348
|
+
return { pulled };
|
|
5349
|
+
}
|
|
5350
|
+
async function cloudPushTasks(config) {
|
|
5351
|
+
const client = getClient();
|
|
5352
|
+
const result = await client.execute("SELECT * FROM tasks");
|
|
5353
|
+
const rows = result.rows;
|
|
5354
|
+
const { ok } = await cloudPushBlob(
|
|
5355
|
+
"/sync/push-tasks",
|
|
5356
|
+
rows,
|
|
5357
|
+
"last_tasks_push_version",
|
|
5358
|
+
config
|
|
5359
|
+
);
|
|
5360
|
+
return ok;
|
|
5361
|
+
}
|
|
5362
|
+
async function cloudPullTasks(config) {
|
|
5363
|
+
const remoteTasks = await cloudPullBlob(
|
|
5364
|
+
"/sync/pull-tasks",
|
|
5365
|
+
config
|
|
5366
|
+
);
|
|
5367
|
+
if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
|
|
5368
|
+
const client = getClient();
|
|
5369
|
+
const stmts = remoteTasks.map((t) => ({
|
|
5370
|
+
sql: `INSERT OR IGNORE INTO tasks
|
|
5371
|
+
(id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
|
|
5372
|
+
blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
|
|
5373
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5374
|
+
args: [
|
|
5375
|
+
t.id,
|
|
5376
|
+
t.title,
|
|
5377
|
+
t.assigned_to,
|
|
5378
|
+
t.assigned_by,
|
|
5379
|
+
t.project_name,
|
|
5380
|
+
t.priority ?? "p1",
|
|
5381
|
+
t.status ?? "open",
|
|
5382
|
+
t.task_file ?? null,
|
|
5383
|
+
t.created_at,
|
|
5384
|
+
t.updated_at,
|
|
5385
|
+
t.blocked_by ?? null,
|
|
5386
|
+
t.parent_task_id ?? null,
|
|
5387
|
+
t.budget_tokens ?? null,
|
|
5388
|
+
t.budget_fallback_model ?? null,
|
|
5389
|
+
t.tokens_used ?? 0,
|
|
5390
|
+
t.tokens_warned_at ?? null
|
|
5391
|
+
]
|
|
5392
|
+
}));
|
|
5393
|
+
await client.batch(stmts, "write");
|
|
5394
|
+
return { pulled: remoteTasks.length };
|
|
5395
|
+
}
|
|
5396
|
+
async function cloudPushConversations(config) {
|
|
5397
|
+
const client = getClient();
|
|
5398
|
+
const result = await client.execute("SELECT * FROM conversations");
|
|
5399
|
+
const rows = result.rows;
|
|
5400
|
+
const { ok } = await cloudPushBlob(
|
|
5401
|
+
"/sync/push-conversations",
|
|
5402
|
+
rows,
|
|
5403
|
+
"last_conversations_push_version",
|
|
5404
|
+
config
|
|
5405
|
+
);
|
|
5406
|
+
return ok;
|
|
5407
|
+
}
|
|
5408
|
+
async function cloudPullConversations(config) {
|
|
5409
|
+
const remoteConvos = await cloudPullBlob(
|
|
5410
|
+
"/sync/pull-conversations",
|
|
5411
|
+
config
|
|
5412
|
+
);
|
|
5413
|
+
if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
|
|
5414
|
+
const client = getClient();
|
|
5415
|
+
const stmts = remoteConvos.map((c) => ({
|
|
5416
|
+
sql: `INSERT OR IGNORE INTO conversations
|
|
5417
|
+
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
5418
|
+
recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
|
|
5419
|
+
content_metadata, agent_response, agent_name, timestamp, ingested_at)
|
|
5420
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5421
|
+
args: [
|
|
5422
|
+
c.id,
|
|
5423
|
+
c.platform,
|
|
5424
|
+
c.external_id ?? null,
|
|
5425
|
+
c.sender_id,
|
|
5426
|
+
c.sender_name ?? null,
|
|
5427
|
+
c.sender_phone ?? null,
|
|
5428
|
+
c.sender_email ?? null,
|
|
5429
|
+
c.recipient_id ?? null,
|
|
5430
|
+
c.channel_id,
|
|
5431
|
+
c.thread_id ?? null,
|
|
5432
|
+
c.reply_to_id ?? null,
|
|
5433
|
+
c.content_text ?? null,
|
|
5434
|
+
c.content_media ?? null,
|
|
5435
|
+
c.content_metadata ?? null,
|
|
5436
|
+
c.agent_response ?? null,
|
|
5437
|
+
c.agent_name ?? null,
|
|
5438
|
+
c.timestamp,
|
|
5439
|
+
c.ingested_at
|
|
5440
|
+
]
|
|
5441
|
+
}));
|
|
5442
|
+
await client.batch(stmts, "write");
|
|
5443
|
+
return { pulled: remoteConvos.length };
|
|
5444
|
+
}
|
|
5445
|
+
async function cloudPushDocuments(config) {
|
|
5446
|
+
const client = getClient();
|
|
5447
|
+
const [workspaces, documents] = await Promise.all([
|
|
5448
|
+
client.execute("SELECT * FROM workspaces"),
|
|
5449
|
+
client.execute("SELECT * FROM documents")
|
|
5450
|
+
]);
|
|
5451
|
+
const blob = {
|
|
5452
|
+
workspaces: workspaces.rows,
|
|
5453
|
+
documents: documents.rows
|
|
5454
|
+
};
|
|
5455
|
+
const { ok } = await cloudPushBlob(
|
|
5456
|
+
"/sync/push-documents",
|
|
5457
|
+
[blob],
|
|
5458
|
+
"last_documents_push_version",
|
|
5459
|
+
config
|
|
5460
|
+
);
|
|
5461
|
+
return ok;
|
|
5462
|
+
}
|
|
5463
|
+
async function cloudPullDocuments(config) {
|
|
5464
|
+
const data = await cloudPullBlob(
|
|
5465
|
+
"/sync/pull-documents",
|
|
5466
|
+
config
|
|
5467
|
+
);
|
|
5468
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
5469
|
+
const blob = data[0];
|
|
5470
|
+
const client = getClient();
|
|
5471
|
+
let pulled = 0;
|
|
5472
|
+
if (blob.workspaces.length > 0) {
|
|
5473
|
+
const stmts = blob.workspaces.map((w) => ({
|
|
5474
|
+
sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
|
|
5475
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
5476
|
+
args: [w.id, w.slug, w.name, w.owner_agent_id ?? null, w.created_at, w.metadata ?? null]
|
|
5477
|
+
}));
|
|
5478
|
+
await client.batch(stmts, "write");
|
|
5479
|
+
pulled += stmts.length;
|
|
5480
|
+
}
|
|
5481
|
+
if (blob.documents.length > 0) {
|
|
5482
|
+
const stmts = blob.documents.map((d) => ({
|
|
5483
|
+
sql: `INSERT OR IGNORE INTO documents
|
|
5484
|
+
(id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
|
|
5485
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5486
|
+
args: [
|
|
5487
|
+
d.id,
|
|
5488
|
+
d.workspace_id,
|
|
5489
|
+
d.filename,
|
|
5490
|
+
d.mime ?? null,
|
|
5491
|
+
d.source_type ?? null,
|
|
5492
|
+
d.user_id ?? null,
|
|
5493
|
+
d.uploaded_at,
|
|
5494
|
+
d.metadata ?? null
|
|
5495
|
+
]
|
|
5496
|
+
}));
|
|
5497
|
+
await client.batch(stmts, "write");
|
|
5498
|
+
pulled += stmts.length;
|
|
5499
|
+
}
|
|
5500
|
+
return { pulled };
|
|
5501
|
+
}
|
|
5502
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
|
|
4712
5503
|
var init_cloud_sync = __esm({
|
|
4713
5504
|
"src/lib/cloud-sync.ts"() {
|
|
4714
5505
|
"use strict";
|
|
@@ -4717,7 +5508,10 @@ var init_cloud_sync = __esm({
|
|
|
4717
5508
|
init_compress();
|
|
4718
5509
|
init_plan_limits();
|
|
4719
5510
|
init_license();
|
|
5511
|
+
init_config();
|
|
5512
|
+
init_employees();
|
|
4720
5513
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
5514
|
+
FETCH_TIMEOUT_MS = 3e4;
|
|
4721
5515
|
}
|
|
4722
5516
|
});
|
|
4723
5517
|
|
|
@@ -4899,9 +5693,9 @@ var init_schedules = __esm({
|
|
|
4899
5693
|
|
|
4900
5694
|
// src/bin/exe-boot.ts
|
|
4901
5695
|
init_employees();
|
|
4902
|
-
import
|
|
5696
|
+
import path19 from "path";
|
|
4903
5697
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
4904
|
-
import { existsSync as
|
|
5698
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
4905
5699
|
import os6 from "os";
|
|
4906
5700
|
|
|
4907
5701
|
// src/lib/employee-templates.ts
|
|
@@ -4955,8 +5749,22 @@ function boxMid(w) {
|
|
|
4955
5749
|
function boxBot(w) {
|
|
4956
5750
|
return `\u2514${"\u2500".repeat(w)}\u2518`;
|
|
4957
5751
|
}
|
|
5752
|
+
function truncateToWidth(str, maxW) {
|
|
5753
|
+
if (displayWidth(str) <= maxW) return str;
|
|
5754
|
+
let w = 0;
|
|
5755
|
+
let i = 0;
|
|
5756
|
+
for (const ch of str) {
|
|
5757
|
+
const cw = ch.codePointAt(0) > 255 ? 2 : 1;
|
|
5758
|
+
if (w + cw + 1 > maxW) break;
|
|
5759
|
+
w += cw;
|
|
5760
|
+
i += ch.length;
|
|
5761
|
+
}
|
|
5762
|
+
return str.slice(0, i) + "\u2026";
|
|
5763
|
+
}
|
|
4958
5764
|
function boxRow(content, w) {
|
|
4959
|
-
|
|
5765
|
+
const maxContent = w - 2;
|
|
5766
|
+
const truncated = truncateToWidth(content, maxContent);
|
|
5767
|
+
return `\u2502 ${padRight(truncated, maxContent)} \u2502`;
|
|
4960
5768
|
}
|
|
4961
5769
|
function formatUptime(seconds) {
|
|
4962
5770
|
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
@@ -4966,6 +5774,9 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4966
5774
|
const now = /* @__PURE__ */ new Date();
|
|
4967
5775
|
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
5776
|
const sessionTag = data.exeSession ? ` [${data.exeSession}]` : "";
|
|
5777
|
+
if (data.embedding && data.embedding.totalMemories < 5 && data.globalTasks.length === 0 && (data.projects?.length ?? 0) === 0) {
|
|
5778
|
+
return buildFirstBootBrief(employees, dateStr, sessionTag);
|
|
5779
|
+
}
|
|
4969
5780
|
const sections = [];
|
|
4970
5781
|
sections.push([` EXE STATUS BRIEF \u2014 ${dateStr}${sessionTag}`]);
|
|
4971
5782
|
const reminderLines = buildReminders(data);
|
|
@@ -4975,6 +5786,8 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4975
5786
|
sections.push(buildProjects(data));
|
|
4976
5787
|
sections.push(buildTeam(employees, data));
|
|
4977
5788
|
sections.push(buildHealth(data));
|
|
5789
|
+
const termCols = process.stdout.columns || 80;
|
|
5790
|
+
const maxInnerW = termCols - 2;
|
|
4978
5791
|
let maxW = 0;
|
|
4979
5792
|
for (const sec of sections) {
|
|
4980
5793
|
for (const line of sec) {
|
|
@@ -4982,7 +5795,7 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4982
5795
|
if (w > maxW) maxW = w;
|
|
4983
5796
|
}
|
|
4984
5797
|
}
|
|
4985
|
-
const innerW = maxW + 4;
|
|
5798
|
+
const innerW = Math.min(maxW + 4, maxInnerW);
|
|
4986
5799
|
const out = [];
|
|
4987
5800
|
out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
|
|
4988
5801
|
for (let i = 0; i < sections.length; i++) {
|
|
@@ -4994,6 +5807,39 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
4994
5807
|
out.push(boxBot(innerW));
|
|
4995
5808
|
return out.join("\n");
|
|
4996
5809
|
}
|
|
5810
|
+
function buildFirstBootBrief(employees, dateStr, sessionTag) {
|
|
5811
|
+
const termCols = process.stdout.columns || 80;
|
|
5812
|
+
const maxInnerW = termCols - 2;
|
|
5813
|
+
const lines = [];
|
|
5814
|
+
lines.push(` WELCOME TO EXE OS \u2014 ${dateStr}${sessionTag}`);
|
|
5815
|
+
const bodyLines = [];
|
|
5816
|
+
bodyLines.push(" \u{1F44B} First time? Here's your team:");
|
|
5817
|
+
bodyLines.push("");
|
|
5818
|
+
for (const emp of employees) {
|
|
5819
|
+
const emoji = EMPLOYEE_EMOJIS[emp.name] ?? "\u{1F464}";
|
|
5820
|
+
const role = emp.role ? ` (${emp.role})` : "";
|
|
5821
|
+
bodyLines.push(` ${emoji} ${emp.name}${role}`);
|
|
5822
|
+
}
|
|
5823
|
+
bodyLines.push("");
|
|
5824
|
+
bodyLines.push(" \u{1F4A1} Quick start:");
|
|
5825
|
+
bodyLines.push(" \u2022 Run `exe-os backfill-conversations` to import Claude Code history");
|
|
5826
|
+
bodyLines.push(" \u2022 Say `/exe` to launch your COO with a full status brief");
|
|
5827
|
+
bodyLines.push(" \u2022 Say `/exe-team` to manage your team");
|
|
5828
|
+
let maxW = 0;
|
|
5829
|
+
for (const line of [...lines, ...bodyLines]) {
|
|
5830
|
+
const w = displayWidth(line);
|
|
5831
|
+
if (w > maxW) maxW = w;
|
|
5832
|
+
}
|
|
5833
|
+
const innerW = Math.min(maxW + 4, maxInnerW);
|
|
5834
|
+
const out = [];
|
|
5835
|
+
out.push("[VERBATIM OUTPUT \u2014 do not reformat]");
|
|
5836
|
+
out.push(boxTop(innerW));
|
|
5837
|
+
for (const line of lines) out.push(boxRow(line, innerW));
|
|
5838
|
+
out.push(boxMid(innerW));
|
|
5839
|
+
for (const line of bodyLines) out.push(boxRow(line, innerW));
|
|
5840
|
+
out.push(boxBot(innerW));
|
|
5841
|
+
return out.join("\n");
|
|
5842
|
+
}
|
|
4997
5843
|
function buildReminders(data) {
|
|
4998
5844
|
if (!data.reminders || data.reminders.length === 0) return [];
|
|
4999
5845
|
const lines = [];
|
|
@@ -5061,7 +5907,10 @@ function buildProjects(data) {
|
|
|
5061
5907
|
idle.push(p);
|
|
5062
5908
|
}
|
|
5063
5909
|
}
|
|
5064
|
-
|
|
5910
|
+
const MAX_SHOWN_PROJECTS = 6;
|
|
5911
|
+
const shown = active.slice(0, MAX_SHOWN_PROJECTS);
|
|
5912
|
+
const hidden = active.length - shown.length;
|
|
5913
|
+
for (const p of shown) {
|
|
5065
5914
|
const blocked = p.blockedCount ?? 0;
|
|
5066
5915
|
const statusEmoji = blocked > 0 ? "\u{1F534}" : "\u{1F7E2}";
|
|
5067
5916
|
const sessionStr = p.sessionName ? ` (${p.sessionName})` : p.isCurrent ? " (current)" : "";
|
|
@@ -5083,7 +5932,9 @@ function buildProjects(data) {
|
|
|
5083
5932
|
if (blocked > 0) {
|
|
5084
5933
|
lines.push(` Blocked: ${blocked}`);
|
|
5085
5934
|
}
|
|
5086
|
-
|
|
5935
|
+
}
|
|
5936
|
+
if (hidden > 0) {
|
|
5937
|
+
lines.push(` ... and ${hidden} more project${hidden === 1 ? "" : "s"}`);
|
|
5087
5938
|
}
|
|
5088
5939
|
if (idle.length > 0) {
|
|
5089
5940
|
const idleNames = idle.map((p) => p.projectName);
|
|
@@ -5143,7 +5994,8 @@ function buildHealth(data) {
|
|
|
5143
5994
|
}
|
|
5144
5995
|
}
|
|
5145
5996
|
}
|
|
5146
|
-
|
|
5997
|
+
const cloudLabel = data.cloudConnected ? "\u{1F7E2} synced" : data.cloudConnected === false && data.plan && data.plan !== "free" ? "\u{1F534} offline" : "not configured";
|
|
5998
|
+
lines.push(` \u2601\uFE0F Cloud: ${cloudLabel}`);
|
|
5147
5999
|
if (data.sessionsKilledToday !== void 0) {
|
|
5148
6000
|
lines.push(` \u{1F480} Sessions killed today: ${data.sessionsKilledToday}`);
|
|
5149
6001
|
}
|
|
@@ -5182,7 +6034,7 @@ init_notifications();
|
|
|
5182
6034
|
|
|
5183
6035
|
// src/adapters/claude/active-agent.ts
|
|
5184
6036
|
init_config();
|
|
5185
|
-
import { readFileSync as
|
|
6037
|
+
import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
|
|
5186
6038
|
import { execSync as execSync3 } from "child_process";
|
|
5187
6039
|
import path6 from "path";
|
|
5188
6040
|
|
|
@@ -5292,18 +6144,18 @@ async function boot(options) {
|
|
|
5292
6144
|
} catch {
|
|
5293
6145
|
}
|
|
5294
6146
|
try {
|
|
5295
|
-
const { readdirSync:
|
|
6147
|
+
const { readdirSync: readdirSync7, readFileSync: readFs } = await import("fs");
|
|
5296
6148
|
const { STATUS_RE: STATUS_RE2, PRIORITY_RE: PRIORITY_RE2, TITLE_RE: TITLE_RE2 } = await Promise.resolve().then(() => (init_task_scanner(), task_scanner_exports));
|
|
5297
6149
|
const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
5298
6150
|
const exeDir = "exe";
|
|
5299
|
-
const entries =
|
|
6151
|
+
const entries = readdirSync7(exeDir, { withFileTypes: true });
|
|
5300
6152
|
const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
|
|
5301
6153
|
for (const dir of employeeDirs) {
|
|
5302
6154
|
const employee = dir.name;
|
|
5303
|
-
const taskDir =
|
|
6155
|
+
const taskDir = path19.join(exeDir, employee);
|
|
5304
6156
|
let files;
|
|
5305
6157
|
try {
|
|
5306
|
-
files =
|
|
6158
|
+
files = readdirSync7(taskDir).filter((f) => f.endsWith(".md"));
|
|
5307
6159
|
} catch {
|
|
5308
6160
|
continue;
|
|
5309
6161
|
}
|
|
@@ -5311,7 +6163,7 @@ async function boot(options) {
|
|
|
5311
6163
|
const taskFilePath = `exe/${employee}/${file}`;
|
|
5312
6164
|
let content;
|
|
5313
6165
|
try {
|
|
5314
|
-
content = readFs(
|
|
6166
|
+
content = readFs(path19.join(taskDir, file), "utf8");
|
|
5315
6167
|
} catch {
|
|
5316
6168
|
continue;
|
|
5317
6169
|
}
|
|
@@ -5395,12 +6247,12 @@ async function boot(options) {
|
|
|
5395
6247
|
} catch {
|
|
5396
6248
|
}
|
|
5397
6249
|
try {
|
|
5398
|
-
const exeExeDir =
|
|
5399
|
-
if (
|
|
5400
|
-
for (const f of
|
|
6250
|
+
const exeExeDir = path19.join(process.cwd(), "exe", "exe");
|
|
6251
|
+
if (existsSync15(exeExeDir)) {
|
|
6252
|
+
for (const f of readdirSync6(exeExeDir)) {
|
|
5401
6253
|
if (f.startsWith("review-") && f.endsWith(".md")) {
|
|
5402
6254
|
try {
|
|
5403
|
-
|
|
6255
|
+
unlinkSync6(path19.join(exeExeDir, f));
|
|
5404
6256
|
} catch {
|
|
5405
6257
|
}
|
|
5406
6258
|
}
|
|
@@ -5444,12 +6296,12 @@ async function boot(options) {
|
|
|
5444
6296
|
});
|
|
5445
6297
|
const taskFile = String(r.task_file);
|
|
5446
6298
|
try {
|
|
5447
|
-
const filePath =
|
|
5448
|
-
if (
|
|
5449
|
-
let content =
|
|
6299
|
+
const filePath = path19.join(process.cwd(), taskFile);
|
|
6300
|
+
if (existsSync15(filePath)) {
|
|
6301
|
+
let content = readFileSync13(filePath, "utf8");
|
|
5450
6302
|
content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
|
|
5451
|
-
const { writeFileSync:
|
|
5452
|
-
|
|
6303
|
+
const { writeFileSync: writeFileSync8 } = await import("fs");
|
|
6304
|
+
writeFileSync8(filePath, content);
|
|
5453
6305
|
}
|
|
5454
6306
|
} catch {
|
|
5455
6307
|
}
|
|
@@ -5909,19 +6761,19 @@ async function boot(options) {
|
|
|
5909
6761
|
})()
|
|
5910
6762
|
]);
|
|
5911
6763
|
try {
|
|
5912
|
-
const configPath =
|
|
5913
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6764
|
+
const configPath = path19.join(
|
|
6765
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
|
|
5914
6766
|
"config.json"
|
|
5915
6767
|
);
|
|
5916
|
-
if (
|
|
5917
|
-
const raw = JSON.parse(
|
|
6768
|
+
if (existsSync15(configPath)) {
|
|
6769
|
+
const raw = JSON.parse(readFileSync13(configPath, "utf8"));
|
|
5918
6770
|
briefData.cloudConnected = !!(raw.cloud || raw.turso);
|
|
5919
6771
|
}
|
|
5920
6772
|
} catch {
|
|
5921
6773
|
}
|
|
5922
6774
|
try {
|
|
5923
|
-
const backfillFlagPath =
|
|
5924
|
-
const isBackfillNeeded = () =>
|
|
6775
|
+
const backfillFlagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
6776
|
+
const isBackfillNeeded = () => existsSync15(backfillFlagPath);
|
|
5925
6777
|
const coverageResult = await client.execute({
|
|
5926
6778
|
sql: `SELECT COUNT(*) as total,
|
|
5927
6779
|
SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
|
|
@@ -5943,8 +6795,8 @@ async function boot(options) {
|
|
|
5943
6795
|
let daemonRunning = false;
|
|
5944
6796
|
let daemonUptime;
|
|
5945
6797
|
let daemonRequestsServed;
|
|
5946
|
-
const socketPath =
|
|
5947
|
-
if (
|
|
6798
|
+
const socketPath = path19.join(EXE_AI_DIR, "exed.sock");
|
|
6799
|
+
if (existsSync15(socketPath)) {
|
|
5948
6800
|
try {
|
|
5949
6801
|
const net = await import("net");
|
|
5950
6802
|
const health = await new Promise((resolve) => {
|
|
@@ -5986,10 +6838,10 @@ async function boot(options) {
|
|
|
5986
6838
|
}
|
|
5987
6839
|
}
|
|
5988
6840
|
if (!daemonRunning) {
|
|
5989
|
-
const pidPath =
|
|
5990
|
-
if (
|
|
6841
|
+
const pidPath = path19.join(EXE_AI_DIR, "exed.pid");
|
|
6842
|
+
if (existsSync15(pidPath)) {
|
|
5991
6843
|
try {
|
|
5992
|
-
const pid = parseInt(
|
|
6844
|
+
const pid = parseInt(readFileSync13(pidPath, "utf8").trim(), 10);
|
|
5993
6845
|
if (pid > 0) {
|
|
5994
6846
|
process.kill(pid, 0);
|
|
5995
6847
|
daemonRunning = true;
|
|
@@ -6000,10 +6852,10 @@ async function boot(options) {
|
|
|
6000
6852
|
}
|
|
6001
6853
|
if (nullCount === 0) {
|
|
6002
6854
|
try {
|
|
6003
|
-
const flagPath =
|
|
6004
|
-
if (
|
|
6005
|
-
const { unlinkSync:
|
|
6006
|
-
|
|
6855
|
+
const flagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
6856
|
+
if (existsSync15(flagPath)) {
|
|
6857
|
+
const { unlinkSync: unlinkSync7 } = await import("fs");
|
|
6858
|
+
unlinkSync7(flagPath);
|
|
6007
6859
|
}
|
|
6008
6860
|
} catch {
|
|
6009
6861
|
}
|
|
@@ -6023,10 +6875,10 @@ async function boot(options) {
|
|
|
6023
6875
|
const { spawn } = await import("child_process");
|
|
6024
6876
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
6025
6877
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
6026
|
-
const backfillPath =
|
|
6027
|
-
if (
|
|
6878
|
+
const backfillPath = path19.resolve(path19.dirname(thisFile), "backfill-vectors.js");
|
|
6879
|
+
if (existsSync15(backfillPath)) {
|
|
6028
6880
|
const { openSync, closeSync } = await import("fs");
|
|
6029
|
-
const workerLogPath =
|
|
6881
|
+
const workerLogPath = path19.join(EXE_AI_DIR, "workers.log");
|
|
6030
6882
|
let stderrFd = "ignore";
|
|
6031
6883
|
try {
|
|
6032
6884
|
stderrFd = openSync(workerLogPath, "a");
|
|
@@ -6054,8 +6906,8 @@ async function boot(options) {
|
|
|
6054
6906
|
const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
|
|
6055
6907
|
const missing = [];
|
|
6056
6908
|
for (const bin of criticalBinaries) {
|
|
6057
|
-
const binPath =
|
|
6058
|
-
if (!
|
|
6909
|
+
const binPath = path19.resolve(path19.dirname(thisFile), bin);
|
|
6910
|
+
if (!existsSync15(binPath)) {
|
|
6059
6911
|
missing.push(`dist/bin/${bin}`);
|
|
6060
6912
|
}
|
|
6061
6913
|
}
|
|
@@ -6085,7 +6937,7 @@ async function boot(options) {
|
|
|
6085
6937
|
return;
|
|
6086
6938
|
}
|
|
6087
6939
|
const exeEmployee = employees.find((e) => e.name === "exe") ?? DEFAULT_EXE;
|
|
6088
|
-
const sessionDir =
|
|
6940
|
+
const sessionDir = path19.join(EXE_AI_DIR, "sessions", "exe");
|
|
6089
6941
|
await mkdir5(sessionDir, { recursive: true });
|
|
6090
6942
|
const claudeMdContent = `${exeEmployee.systemPrompt}
|
|
6091
6943
|
|
|
@@ -6094,7 +6946,7 @@ async function boot(options) {
|
|
|
6094
6946
|
# Status Brief
|
|
6095
6947
|
|
|
6096
6948
|
${brief}`;
|
|
6097
|
-
await writeFile6(
|
|
6949
|
+
await writeFile6(path19.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
|
|
6098
6950
|
const unread = await readUnreadNotifications();
|
|
6099
6951
|
if (unread.length > 0) {
|
|
6100
6952
|
console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
|
|
@@ -6103,12 +6955,12 @@ ${brief}`;
|
|
|
6103
6955
|
await cleanupOldNotifications();
|
|
6104
6956
|
console.log(brief);
|
|
6105
6957
|
try {
|
|
6106
|
-
const configPath2 =
|
|
6107
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6958
|
+
const configPath2 = path19.join(
|
|
6959
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
|
|
6108
6960
|
"config.json"
|
|
6109
6961
|
);
|
|
6110
|
-
if (
|
|
6111
|
-
const rawCfg = JSON.parse(
|
|
6962
|
+
if (existsSync15(configPath2)) {
|
|
6963
|
+
const rawCfg = JSON.parse(readFileSync13(configPath2, "utf8"));
|
|
6112
6964
|
const cloudCfg = rawCfg.cloud;
|
|
6113
6965
|
if (cloudCfg?.apiKey) {
|
|
6114
6966
|
const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
@@ -6118,9 +6970,7 @@ ${brief}`;
|
|
|
6118
6970
|
if (mk) initSyncCrypto2(mk);
|
|
6119
6971
|
}
|
|
6120
6972
|
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
|
-
`);
|
|
6973
|
+
cloudSync2({ apiKey: cloudCfg.apiKey, endpoint: cloudCfg.endpoint ?? "" }).catch(() => {
|
|
6124
6974
|
});
|
|
6125
6975
|
}
|
|
6126
6976
|
}
|