@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/cli.js
CHANGED
|
@@ -275,13 +275,18 @@ __export(employees_exports, {
|
|
|
275
275
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
276
276
|
addEmployee: () => addEmployee,
|
|
277
277
|
getEmployee: () => getEmployee,
|
|
278
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
279
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
280
|
+
hasRole: () => hasRole,
|
|
281
|
+
isMultiInstance: () => isMultiInstance,
|
|
278
282
|
loadEmployees: () => loadEmployees,
|
|
283
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
279
284
|
registerBinSymlinks: () => registerBinSymlinks,
|
|
280
285
|
saveEmployees: () => saveEmployees,
|
|
281
286
|
validateEmployeeName: () => validateEmployeeName
|
|
282
287
|
});
|
|
283
288
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
284
|
-
import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
|
|
289
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
285
290
|
import { execSync } from "child_process";
|
|
286
291
|
import path2 from "path";
|
|
287
292
|
function validateEmployeeName(name) {
|
|
@@ -314,9 +319,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
314
319
|
await mkdir2(path2.dirname(employeesPath), { recursive: true });
|
|
315
320
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
316
321
|
}
|
|
322
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
323
|
+
if (!existsSync2(employeesPath)) return [];
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
326
|
+
} catch {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
}
|
|
317
330
|
function getEmployee(employees, name) {
|
|
318
331
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
319
332
|
}
|
|
333
|
+
function getEmployeeByRole(employees, role) {
|
|
334
|
+
const lower = role.toLowerCase();
|
|
335
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
336
|
+
}
|
|
337
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
338
|
+
const lower = role.toLowerCase();
|
|
339
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
340
|
+
}
|
|
341
|
+
function hasRole(agentName, role) {
|
|
342
|
+
const employees = loadEmployeesSync();
|
|
343
|
+
const emp = getEmployee(employees, agentName);
|
|
344
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
345
|
+
}
|
|
346
|
+
function isMultiInstance(agentName, employees) {
|
|
347
|
+
const roster = employees ?? loadEmployeesSync();
|
|
348
|
+
const emp = getEmployee(roster, agentName);
|
|
349
|
+
if (!emp) return false;
|
|
350
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
351
|
+
}
|
|
320
352
|
function addEmployee(employees, employee) {
|
|
321
353
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
322
354
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -359,12 +391,13 @@ function registerBinSymlinks(name) {
|
|
|
359
391
|
}
|
|
360
392
|
return { created, skipped, errors };
|
|
361
393
|
}
|
|
362
|
-
var EMPLOYEES_PATH;
|
|
394
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
363
395
|
var init_employees = __esm({
|
|
364
396
|
"src/lib/employees.ts"() {
|
|
365
397
|
"use strict";
|
|
366
398
|
init_config();
|
|
367
399
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
400
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
368
401
|
}
|
|
369
402
|
});
|
|
370
403
|
|
|
@@ -469,7 +502,7 @@ __export(installer_exports, {
|
|
|
469
502
|
runInstaller: () => runInstaller
|
|
470
503
|
});
|
|
471
504
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
|
|
472
|
-
import { existsSync as existsSync4, readFileSync as
|
|
505
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
473
506
|
import path4 from "path";
|
|
474
507
|
import os3 from "os";
|
|
475
508
|
import { fileURLToPath } from "url";
|
|
@@ -481,7 +514,7 @@ function resolvePackageRoot() {
|
|
|
481
514
|
const pkgPath = path4.join(dir, "package.json");
|
|
482
515
|
if (existsSync4(pkgPath)) {
|
|
483
516
|
try {
|
|
484
|
-
const pkg = JSON.parse(
|
|
517
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
485
518
|
if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
|
|
486
519
|
} catch {
|
|
487
520
|
}
|
|
@@ -774,26 +807,56 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
|
|
|
774
807
|
permissions.allow = [];
|
|
775
808
|
}
|
|
776
809
|
const toolNames = [
|
|
810
|
+
// Core memory
|
|
777
811
|
"store_memory",
|
|
778
812
|
"recall_my_memory",
|
|
813
|
+
"commit_to_long_term_memory",
|
|
814
|
+
"consolidate_memories",
|
|
815
|
+
"ask_team_memory",
|
|
816
|
+
"get_session_context",
|
|
817
|
+
// Tasks
|
|
779
818
|
"create_task",
|
|
780
819
|
"list_tasks",
|
|
781
820
|
"get_task",
|
|
782
821
|
"update_task",
|
|
783
822
|
"close_task",
|
|
823
|
+
"checkpoint_task",
|
|
824
|
+
// Behaviors
|
|
784
825
|
"store_behavior",
|
|
785
826
|
"deactivate_behavior",
|
|
786
|
-
"
|
|
827
|
+
"list_behaviors",
|
|
828
|
+
// Identity
|
|
787
829
|
"get_identity",
|
|
788
830
|
"update_identity",
|
|
789
|
-
|
|
790
|
-
"
|
|
831
|
+
// Messaging
|
|
832
|
+
"send_message",
|
|
833
|
+
"acknowledge_messages",
|
|
834
|
+
"send_whatsapp",
|
|
835
|
+
"query_conversations",
|
|
836
|
+
// Reminders + triggers
|
|
791
837
|
"create_reminder",
|
|
792
838
|
"complete_reminder",
|
|
793
839
|
"list_reminders",
|
|
794
|
-
"
|
|
840
|
+
"create_trigger",
|
|
841
|
+
"list_triggers",
|
|
842
|
+
// GraphRAG
|
|
795
843
|
"query_relationships",
|
|
796
|
-
"
|
|
844
|
+
"merge_entities",
|
|
845
|
+
// Documents + wiki
|
|
846
|
+
"ingest_document",
|
|
847
|
+
"list_documents",
|
|
848
|
+
"purge_document",
|
|
849
|
+
"rerank_documents",
|
|
850
|
+
"set_document_importance",
|
|
851
|
+
"create_wiki_page",
|
|
852
|
+
"update_wiki_page",
|
|
853
|
+
"get_wiki_page",
|
|
854
|
+
"list_wiki_pages",
|
|
855
|
+
// System
|
|
856
|
+
"load_skill",
|
|
857
|
+
"apply_starter_pack",
|
|
858
|
+
"resume_employee",
|
|
859
|
+
"deploy_client"
|
|
797
860
|
];
|
|
798
861
|
const allowList = permissions.allow;
|
|
799
862
|
for (const tool of expandDualPrefixTools(toolNames)) {
|
|
@@ -922,6 +985,61 @@ var init_memory = __esm({
|
|
|
922
985
|
}
|
|
923
986
|
});
|
|
924
987
|
|
|
988
|
+
// src/lib/db-retry.ts
|
|
989
|
+
function isBusyError(err) {
|
|
990
|
+
if (err instanceof Error) {
|
|
991
|
+
const msg = err.message.toLowerCase();
|
|
992
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
993
|
+
}
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
function delay(ms) {
|
|
997
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
998
|
+
}
|
|
999
|
+
async function retryOnBusy(fn, label) {
|
|
1000
|
+
let lastError;
|
|
1001
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1002
|
+
try {
|
|
1003
|
+
return await fn();
|
|
1004
|
+
} catch (err) {
|
|
1005
|
+
lastError = err;
|
|
1006
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
1007
|
+
throw err;
|
|
1008
|
+
}
|
|
1009
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
1010
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
1011
|
+
process.stderr.write(
|
|
1012
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
1013
|
+
`
|
|
1014
|
+
);
|
|
1015
|
+
await delay(backoff + jitter);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
throw lastError;
|
|
1019
|
+
}
|
|
1020
|
+
function wrapWithRetry(client) {
|
|
1021
|
+
return new Proxy(client, {
|
|
1022
|
+
get(target, prop, receiver) {
|
|
1023
|
+
if (prop === "execute") {
|
|
1024
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
1025
|
+
}
|
|
1026
|
+
if (prop === "batch") {
|
|
1027
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
1028
|
+
}
|
|
1029
|
+
return Reflect.get(target, prop, receiver);
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
1034
|
+
var init_db_retry = __esm({
|
|
1035
|
+
"src/lib/db-retry.ts"() {
|
|
1036
|
+
"use strict";
|
|
1037
|
+
MAX_RETRIES = 3;
|
|
1038
|
+
BASE_DELAY_MS = 200;
|
|
1039
|
+
MAX_JITTER_MS = 300;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
925
1043
|
// src/lib/database.ts
|
|
926
1044
|
var database_exports = {};
|
|
927
1045
|
__export(database_exports, {
|
|
@@ -929,6 +1047,7 @@ __export(database_exports, {
|
|
|
929
1047
|
disposeTurso: () => disposeTurso,
|
|
930
1048
|
ensureSchema: () => ensureSchema,
|
|
931
1049
|
getClient: () => getClient,
|
|
1050
|
+
getRawClient: () => getRawClient,
|
|
932
1051
|
initDatabase: () => initDatabase,
|
|
933
1052
|
initTurso: () => initTurso,
|
|
934
1053
|
isInitialized: () => isInitialized
|
|
@@ -938,6 +1057,7 @@ async function initDatabase(config) {
|
|
|
938
1057
|
if (_client) {
|
|
939
1058
|
_client.close();
|
|
940
1059
|
_client = null;
|
|
1060
|
+
_resilientClient = null;
|
|
941
1061
|
}
|
|
942
1062
|
const opts = {
|
|
943
1063
|
url: `file:${config.dbPath}`
|
|
@@ -946,20 +1066,27 @@ async function initDatabase(config) {
|
|
|
946
1066
|
opts.encryptionKey = config.encryptionKey;
|
|
947
1067
|
}
|
|
948
1068
|
_client = createClient(opts);
|
|
1069
|
+
_resilientClient = wrapWithRetry(_client);
|
|
949
1070
|
}
|
|
950
1071
|
function isInitialized() {
|
|
951
1072
|
return _client !== null;
|
|
952
1073
|
}
|
|
953
1074
|
function getClient() {
|
|
1075
|
+
if (!_resilientClient) {
|
|
1076
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1077
|
+
}
|
|
1078
|
+
return _resilientClient;
|
|
1079
|
+
}
|
|
1080
|
+
function getRawClient() {
|
|
954
1081
|
if (!_client) {
|
|
955
1082
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
956
1083
|
}
|
|
957
1084
|
return _client;
|
|
958
1085
|
}
|
|
959
1086
|
async function ensureSchema() {
|
|
960
|
-
const client =
|
|
1087
|
+
const client = getRawClient();
|
|
961
1088
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
962
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1089
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
963
1090
|
try {
|
|
964
1091
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
965
1092
|
} catch {
|
|
@@ -1752,13 +1879,16 @@ async function disposeDatabase() {
|
|
|
1752
1879
|
if (_client) {
|
|
1753
1880
|
_client.close();
|
|
1754
1881
|
_client = null;
|
|
1882
|
+
_resilientClient = null;
|
|
1755
1883
|
}
|
|
1756
1884
|
}
|
|
1757
|
-
var _client, initTurso, disposeTurso;
|
|
1885
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1758
1886
|
var init_database = __esm({
|
|
1759
1887
|
"src/lib/database.ts"() {
|
|
1760
1888
|
"use strict";
|
|
1889
|
+
init_db_retry();
|
|
1761
1890
|
_client = null;
|
|
1891
|
+
_resilientClient = null;
|
|
1762
1892
|
initTurso = initDatabase;
|
|
1763
1893
|
disposeTurso = disposeDatabase;
|
|
1764
1894
|
}
|
|
@@ -1963,12 +2093,12 @@ function shardExists(projectName) {
|
|
|
1963
2093
|
}
|
|
1964
2094
|
function listShards() {
|
|
1965
2095
|
if (!existsSync6(SHARDS_DIR)) return [];
|
|
1966
|
-
const { readdirSync:
|
|
1967
|
-
return
|
|
2096
|
+
const { readdirSync: readdirSync5 } = __require("fs");
|
|
2097
|
+
return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1968
2098
|
}
|
|
1969
2099
|
async function ensureShardSchema(client) {
|
|
1970
2100
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1971
|
-
await client.execute("PRAGMA busy_timeout =
|
|
2101
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1972
2102
|
try {
|
|
1973
2103
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1974
2104
|
} catch {
|
|
@@ -2229,7 +2359,8 @@ async function writeMemory(record) {
|
|
|
2229
2359
|
has_error: record.has_error ? 1 : 0,
|
|
2230
2360
|
raw_text: record.raw_text,
|
|
2231
2361
|
vector: record.vector,
|
|
2232
|
-
version:
|
|
2362
|
+
version: 0,
|
|
2363
|
+
// Placeholder — assigned atomically at flush time
|
|
2233
2364
|
task_id: record.task_id ?? null,
|
|
2234
2365
|
importance: record.importance ?? 5,
|
|
2235
2366
|
status: record.status ?? "active",
|
|
@@ -2263,6 +2394,13 @@ async function flushBatch() {
|
|
|
2263
2394
|
_flushing = true;
|
|
2264
2395
|
try {
|
|
2265
2396
|
const batch = _pendingRecords.slice(0);
|
|
2397
|
+
const client = getClient();
|
|
2398
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
2399
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
2400
|
+
for (const row of batch) {
|
|
2401
|
+
row.version = baseVersion++;
|
|
2402
|
+
}
|
|
2403
|
+
_nextVersion = baseVersion;
|
|
2266
2404
|
const buildStmt = (row) => {
|
|
2267
2405
|
const hasVector = row.vector !== null;
|
|
2268
2406
|
const taskId = row.task_id ?? null;
|
|
@@ -2591,7 +2729,7 @@ var init_store = __esm({
|
|
|
2591
2729
|
import net from "net";
|
|
2592
2730
|
import { spawn } from "child_process";
|
|
2593
2731
|
import { randomUUID } from "crypto";
|
|
2594
|
-
import { existsSync as existsSync7, unlinkSync, readFileSync as
|
|
2732
|
+
import { existsSync as existsSync7, unlinkSync, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
2595
2733
|
import path7 from "path";
|
|
2596
2734
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2597
2735
|
function handleData(chunk) {
|
|
@@ -2616,7 +2754,7 @@ function handleData(chunk) {
|
|
|
2616
2754
|
function cleanupStaleFiles() {
|
|
2617
2755
|
if (existsSync7(PID_PATH)) {
|
|
2618
2756
|
try {
|
|
2619
|
-
const pid = parseInt(
|
|
2757
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2620
2758
|
if (pid > 0) {
|
|
2621
2759
|
try {
|
|
2622
2760
|
process.kill(pid, 0);
|
|
@@ -2764,11 +2902,11 @@ async function connectEmbedDaemon() {
|
|
|
2764
2902
|
}
|
|
2765
2903
|
}
|
|
2766
2904
|
const start = Date.now();
|
|
2767
|
-
let
|
|
2905
|
+
let delay2 = 100;
|
|
2768
2906
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2769
|
-
await new Promise((r) => setTimeout(r,
|
|
2907
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2770
2908
|
if (await connectToSocket()) return true;
|
|
2771
|
-
|
|
2909
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2772
2910
|
}
|
|
2773
2911
|
return false;
|
|
2774
2912
|
}
|
|
@@ -2824,7 +2962,7 @@ function killAndRespawnDaemon() {
|
|
|
2824
2962
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2825
2963
|
if (existsSync7(PID_PATH)) {
|
|
2826
2964
|
try {
|
|
2827
|
-
const pid = parseInt(
|
|
2965
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2828
2966
|
if (pid > 0) {
|
|
2829
2967
|
try {
|
|
2830
2968
|
process.kill(pid, "SIGKILL");
|
|
@@ -2860,11 +2998,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
2860
2998
|
`);
|
|
2861
2999
|
killAndRespawnDaemon();
|
|
2862
3000
|
const start = Date.now();
|
|
2863
|
-
let
|
|
3001
|
+
let delay2 = 200;
|
|
2864
3002
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2865
|
-
await new Promise((r) => setTimeout(r,
|
|
3003
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2866
3004
|
if (await connectToSocket()) break;
|
|
2867
|
-
|
|
3005
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2868
3006
|
}
|
|
2869
3007
|
if (!_connected) return null;
|
|
2870
3008
|
}
|
|
@@ -2876,11 +3014,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
2876
3014
|
`);
|
|
2877
3015
|
killAndRespawnDaemon();
|
|
2878
3016
|
const start = Date.now();
|
|
2879
|
-
let
|
|
3017
|
+
let delay2 = 200;
|
|
2880
3018
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2881
|
-
await new Promise((r) => setTimeout(r,
|
|
3019
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2882
3020
|
if (await connectToSocket()) break;
|
|
2883
|
-
|
|
3021
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2884
3022
|
}
|
|
2885
3023
|
if (!_connected) return null;
|
|
2886
3024
|
const retry = await sendRequest([text], priority);
|
|
@@ -2953,7 +3091,8 @@ import { readdir as readdir2, stat } from "fs/promises";
|
|
|
2953
3091
|
import path8 from "path";
|
|
2954
3092
|
import { createInterface } from "readline";
|
|
2955
3093
|
import { homedir } from "os";
|
|
2956
|
-
|
|
3094
|
+
import { parseArgs } from "util";
|
|
3095
|
+
async function findJsonlFiles(sinceDate, projectFilter) {
|
|
2957
3096
|
const projectsDir = path8.join(homedir(), ".claude", "projects");
|
|
2958
3097
|
const files = [];
|
|
2959
3098
|
async function walk(dir) {
|
|
@@ -2966,59 +3105,65 @@ async function findJsonlFiles(cutoffMs) {
|
|
|
2966
3105
|
for (const entry of entries) {
|
|
2967
3106
|
const full = path8.join(dir, entry.name);
|
|
2968
3107
|
if (entry.isDirectory()) {
|
|
3108
|
+
if (entry.name === "subagents" || entry.name === "tool-results") continue;
|
|
2969
3109
|
await walk(full);
|
|
2970
3110
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
2971
3111
|
try {
|
|
2972
3112
|
const s = await stat(full);
|
|
2973
|
-
if (s.mtimeMs
|
|
3113
|
+
if (sinceDate && s.mtimeMs < sinceDate.getTime()) continue;
|
|
3114
|
+
files.push(full);
|
|
2974
3115
|
} catch {
|
|
2975
3116
|
}
|
|
2976
3117
|
}
|
|
2977
3118
|
}
|
|
2978
3119
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
const projectDir = relative.split(path8.sep)[0] ?? relative;
|
|
2986
|
-
const homeEncoded = homedir().replaceAll("/", "-");
|
|
2987
|
-
if (projectDir.startsWith(homeEncoded + "-")) {
|
|
2988
|
-
return projectDir.slice(homeEncoded.length + 1);
|
|
2989
|
-
}
|
|
2990
|
-
if (projectDir === homeEncoded) return "home";
|
|
2991
|
-
return projectDir;
|
|
2992
|
-
}
|
|
2993
|
-
function extractAssistantText(content) {
|
|
2994
|
-
if (typeof content === "string") return content;
|
|
2995
|
-
const texts = [];
|
|
2996
|
-
for (const block of content) {
|
|
2997
|
-
if (block.type === "text" && block.text) {
|
|
2998
|
-
texts.push(block.text);
|
|
3120
|
+
if (projectFilter) {
|
|
3121
|
+
let projectDirs;
|
|
3122
|
+
try {
|
|
3123
|
+
projectDirs = await readdir2(projectsDir, { withFileTypes: true });
|
|
3124
|
+
} catch {
|
|
3125
|
+
return files;
|
|
2999
3126
|
}
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
for (const block of content) {
|
|
3007
|
-
if (block.type === "text" && block.text) {
|
|
3008
|
-
texts.push(block.text);
|
|
3127
|
+
for (const entry of projectDirs) {
|
|
3128
|
+
if (!entry.isDirectory()) continue;
|
|
3129
|
+
const decoded = decodeProjectDir(entry.name);
|
|
3130
|
+
if (decoded.toLowerCase().includes(projectFilter.toLowerCase())) {
|
|
3131
|
+
await walk(path8.join(projectsDir, entry.name));
|
|
3132
|
+
}
|
|
3009
3133
|
}
|
|
3134
|
+
} else {
|
|
3135
|
+
await walk(projectsDir);
|
|
3010
3136
|
}
|
|
3011
|
-
return
|
|
3137
|
+
return files;
|
|
3012
3138
|
}
|
|
3013
|
-
|
|
3014
|
-
const
|
|
3015
|
-
if (
|
|
3016
|
-
return
|
|
3139
|
+
function decodeProjectDir(dirName) {
|
|
3140
|
+
const homeEncoded = homedir().replaceAll("/", "-");
|
|
3141
|
+
if (dirName.startsWith(homeEncoded + "-")) {
|
|
3142
|
+
return dirName.slice(homeEncoded.length + 1);
|
|
3017
3143
|
}
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3144
|
+
if (dirName === homeEncoded) return "home";
|
|
3145
|
+
return dirName;
|
|
3146
|
+
}
|
|
3147
|
+
function projectNameFromPath(filePath) {
|
|
3148
|
+
const projectsDir = path8.join(homedir(), ".claude", "projects");
|
|
3149
|
+
const relative = path8.relative(projectsDir, filePath);
|
|
3150
|
+
const projectDir = relative.split(path8.sep)[0] ?? "unknown";
|
|
3151
|
+
return decodeProjectDir(projectDir);
|
|
3152
|
+
}
|
|
3153
|
+
async function parseConversation(filePath) {
|
|
3154
|
+
const conv = {
|
|
3155
|
+
sessionId: path8.basename(filePath, ".jsonl"),
|
|
3156
|
+
projectName: projectNameFromPath(filePath),
|
|
3157
|
+
cwd: void 0,
|
|
3158
|
+
startTime: void 0,
|
|
3159
|
+
endTime: void 0,
|
|
3160
|
+
userMessages: [],
|
|
3161
|
+
toolCounts: {},
|
|
3162
|
+
filesTouched: /* @__PURE__ */ new Set(),
|
|
3163
|
+
errorCount: 0,
|
|
3164
|
+
totalMessages: 0,
|
|
3165
|
+
agentId: "default"
|
|
3166
|
+
};
|
|
3022
3167
|
const rl = createInterface({
|
|
3023
3168
|
input: createReadStream(filePath, { encoding: "utf8" }),
|
|
3024
3169
|
crlfDelay: Infinity
|
|
@@ -3031,240 +3176,248 @@ async function extractConversationPairs(filePath, projectFilter) {
|
|
|
3031
3176
|
} catch {
|
|
3032
3177
|
continue;
|
|
3033
3178
|
}
|
|
3034
|
-
if (entry.cwd
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3179
|
+
if (entry.cwd && typeof entry.cwd === "string") {
|
|
3180
|
+
conv.cwd = entry.cwd;
|
|
3181
|
+
}
|
|
3182
|
+
const ts = entry.timestamp;
|
|
3183
|
+
if (ts) {
|
|
3184
|
+
if (!conv.startTime || ts < conv.startTime) conv.startTime = ts;
|
|
3185
|
+
if (!conv.endTime || ts > conv.endTime) conv.endTime = ts;
|
|
3186
|
+
}
|
|
3187
|
+
const entryType = entry.type;
|
|
3188
|
+
if (entryType === "user") {
|
|
3189
|
+
conv.totalMessages++;
|
|
3190
|
+
const message = entry.message;
|
|
3191
|
+
if (message?.content) {
|
|
3192
|
+
const text = extractUserText(message.content);
|
|
3193
|
+
if (text && text.length > 10) {
|
|
3194
|
+
conv.userMessages.push(text);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
} else if (entryType === "assistant") {
|
|
3198
|
+
conv.totalMessages++;
|
|
3199
|
+
const message = entry.message;
|
|
3200
|
+
if (message?.content && Array.isArray(message.content)) {
|
|
3201
|
+
for (const block of message.content) {
|
|
3202
|
+
if (typeof block !== "object" || block === null) continue;
|
|
3203
|
+
const b = block;
|
|
3204
|
+
if (b.type === "tool_use") {
|
|
3205
|
+
const toolName = b.name;
|
|
3206
|
+
conv.toolCounts[toolName] = (conv.toolCounts[toolName] || 0) + 1;
|
|
3207
|
+
const input = b.input;
|
|
3208
|
+
if (input?.file_path && typeof input.file_path === "string") {
|
|
3209
|
+
if (toolName === "Write" || toolName === "Edit") {
|
|
3210
|
+
conv.filesTouched.add(input.file_path);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
if (conv.cwd) {
|
|
3219
|
+
conv.projectName = path8.basename(conv.cwd);
|
|
3220
|
+
const worktreeMatch = conv.cwd.match(/\.worktrees\/([^/]+)/);
|
|
3221
|
+
if (worktreeMatch?.[1]) {
|
|
3222
|
+
conv.agentId = worktreeMatch[1];
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
return conv;
|
|
3226
|
+
}
|
|
3227
|
+
function extractUserText(content) {
|
|
3228
|
+
if (typeof content === "string") return content;
|
|
3229
|
+
if (Array.isArray(content)) {
|
|
3230
|
+
const parts = [];
|
|
3231
|
+
for (const block of content) {
|
|
3232
|
+
if (typeof block === "string") {
|
|
3233
|
+
parts.push(block);
|
|
3234
|
+
} else if (typeof block === "object" && block !== null) {
|
|
3235
|
+
const b = block;
|
|
3236
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
3237
|
+
parts.push(b.text);
|
|
3238
|
+
}
|
|
3044
3239
|
}
|
|
3045
|
-
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
3046
|
-
const assistantText = extractAssistantText(entry.message.content);
|
|
3047
|
-
if (!assistantText.trim()) continue;
|
|
3048
|
-
const resolvedProject = fileCwd ? path8.basename(fileCwd) : fallbackProject;
|
|
3049
|
-
const userText = pendingUser?.text ?? "";
|
|
3050
|
-
const timestamp = entry.timestamp ?? pendingUser?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3051
|
-
pairs.push({
|
|
3052
|
-
userText,
|
|
3053
|
-
assistantText,
|
|
3054
|
-
timestamp,
|
|
3055
|
-
sessionId: fileSessionId || path8.basename(filePath, ".jsonl"),
|
|
3056
|
-
project: resolvedProject,
|
|
3057
|
-
cwd: fileCwd || fallbackProject
|
|
3058
|
-
});
|
|
3059
|
-
pendingUser = null;
|
|
3060
3240
|
}
|
|
3241
|
+
return parts.join("\n");
|
|
3061
3242
|
}
|
|
3062
|
-
return
|
|
3243
|
+
return "";
|
|
3063
3244
|
}
|
|
3064
|
-
function
|
|
3065
|
-
|
|
3245
|
+
function buildSummary(conv) {
|
|
3246
|
+
const parts = [];
|
|
3247
|
+
parts.push(`Session: ${conv.sessionId}`);
|
|
3248
|
+
parts.push(`Project: ${conv.projectName}`);
|
|
3249
|
+
if (conv.startTime) {
|
|
3250
|
+
parts.push(`Time: ${conv.startTime}${conv.endTime ? ` \u2192 ${conv.endTime}` : ""}`);
|
|
3251
|
+
}
|
|
3252
|
+
parts.push(`Messages: ${conv.totalMessages}`);
|
|
3253
|
+
if (conv.agentId !== "default") {
|
|
3254
|
+
parts.push(`Agent: ${conv.agentId}`);
|
|
3255
|
+
}
|
|
3256
|
+
parts.push("");
|
|
3257
|
+
if (conv.userMessages.length > 0) {
|
|
3258
|
+
parts.push("## What was asked");
|
|
3259
|
+
const prompts = conv.userMessages.slice(0, 5);
|
|
3260
|
+
for (const prompt of prompts) {
|
|
3261
|
+
const truncated = prompt.length > 300 ? prompt.slice(0, 300) + "..." : prompt;
|
|
3262
|
+
const cleaned = truncated.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
3263
|
+
if (cleaned) parts.push(`- ${cleaned}`);
|
|
3264
|
+
}
|
|
3265
|
+
if (conv.userMessages.length > 5) {
|
|
3266
|
+
parts.push(`- ... and ${conv.userMessages.length - 5} more prompts`);
|
|
3267
|
+
}
|
|
3268
|
+
parts.push("");
|
|
3269
|
+
}
|
|
3270
|
+
const toolEntries = Object.entries(conv.toolCounts).sort((a, b) => b[1] - a[1]);
|
|
3271
|
+
if (toolEntries.length > 0) {
|
|
3272
|
+
parts.push("## Tools used");
|
|
3273
|
+
for (const [tool, count] of toolEntries) {
|
|
3274
|
+
parts.push(`- ${tool}: ${count}`);
|
|
3275
|
+
}
|
|
3276
|
+
parts.push("");
|
|
3277
|
+
}
|
|
3278
|
+
if (conv.filesTouched.size > 0) {
|
|
3279
|
+
parts.push("## Files modified");
|
|
3280
|
+
const fileList = [...conv.filesTouched].sort();
|
|
3281
|
+
const shown = fileList.slice(0, 20);
|
|
3282
|
+
for (const f of shown) {
|
|
3283
|
+
parts.push(`- ${f}`);
|
|
3284
|
+
}
|
|
3285
|
+
if (fileList.length > 20) {
|
|
3286
|
+
parts.push(`- ... and ${fileList.length - 20} more files`);
|
|
3287
|
+
}
|
|
3288
|
+
parts.push("");
|
|
3289
|
+
}
|
|
3290
|
+
if (conv.errorCount > 0) {
|
|
3291
|
+
parts.push(`Errors: ${conv.errorCount}`);
|
|
3292
|
+
}
|
|
3293
|
+
let summary = parts.join("\n");
|
|
3294
|
+
if (summary.length > MAX_SUMMARY_LENGTH) {
|
|
3295
|
+
summary = summary.slice(0, MAX_SUMMARY_LENGTH);
|
|
3296
|
+
}
|
|
3297
|
+
return summary;
|
|
3066
3298
|
}
|
|
3067
|
-
async function
|
|
3299
|
+
async function loadExistingSourcePaths() {
|
|
3068
3300
|
const client = getClient();
|
|
3069
|
-
const
|
|
3301
|
+
const paths = /* @__PURE__ */ new Set();
|
|
3070
3302
|
let offset = 0;
|
|
3071
3303
|
const batchSize = 500;
|
|
3072
3304
|
while (true) {
|
|
3073
3305
|
const result = await client.execute({
|
|
3074
|
-
sql: `SELECT
|
|
3075
|
-
WHERE
|
|
3306
|
+
sql: `SELECT source_path FROM memories
|
|
3307
|
+
WHERE tool_name = ? AND source_path IS NOT NULL
|
|
3076
3308
|
ORDER BY id LIMIT ? OFFSET ?`,
|
|
3077
|
-
args: [
|
|
3309
|
+
args: [TOOL_NAME, batchSize, offset]
|
|
3078
3310
|
});
|
|
3079
3311
|
if (result.rows.length === 0) break;
|
|
3080
3312
|
for (const row of result.rows) {
|
|
3081
|
-
|
|
3082
|
-
hashPair(
|
|
3083
|
-
row.content_text ?? "",
|
|
3084
|
-
row.agent_response ?? ""
|
|
3085
|
-
)
|
|
3086
|
-
);
|
|
3313
|
+
paths.add(row.source_path);
|
|
3087
3314
|
}
|
|
3088
3315
|
offset += batchSize;
|
|
3089
3316
|
}
|
|
3090
|
-
return
|
|
3091
|
-
}
|
|
3092
|
-
async function storePair(pair, daemonConnected, stats, agentId) {
|
|
3093
|
-
const client = getClient();
|
|
3094
|
-
const id = crypto2.randomUUID();
|
|
3095
|
-
const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
|
|
3096
|
-
const assistTrunc = pair.assistantText.length > MAX_CONTENT_LENGTH ? pair.assistantText.slice(0, MAX_CONTENT_LENGTH) : pair.assistantText;
|
|
3097
|
-
await client.execute({
|
|
3098
|
-
sql: `INSERT INTO conversations
|
|
3099
|
-
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
3100
|
-
recipient_id, channel_id, thread_id, reply_to_id,
|
|
3101
|
-
content_text, content_media, agent_response, agent_name,
|
|
3102
|
-
timestamp, ingested_at)
|
|
3103
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3104
|
-
args: [
|
|
3105
|
-
id,
|
|
3106
|
-
"claude-code",
|
|
3107
|
-
null,
|
|
3108
|
-
"user",
|
|
3109
|
-
"user",
|
|
3110
|
-
null,
|
|
3111
|
-
null,
|
|
3112
|
-
null,
|
|
3113
|
-
pair.project,
|
|
3114
|
-
pair.sessionId,
|
|
3115
|
-
null,
|
|
3116
|
-
userTrunc,
|
|
3117
|
-
null,
|
|
3118
|
-
assistTrunc,
|
|
3119
|
-
"claude",
|
|
3120
|
-
pair.timestamp,
|
|
3121
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
3122
|
-
]
|
|
3123
|
-
});
|
|
3124
|
-
stats.conversationsStored++;
|
|
3125
|
-
const rawText = [
|
|
3126
|
-
`[claude-code] Conversation in ${pair.project}`,
|
|
3127
|
-
userTrunc ? `User: ${userTrunc}` : null,
|
|
3128
|
-
`Assistant: ${assistTrunc}`
|
|
3129
|
-
].filter(Boolean).join("\n");
|
|
3130
|
-
let vector = null;
|
|
3131
|
-
if (daemonConnected) {
|
|
3132
|
-
try {
|
|
3133
|
-
vector = await embedViaClient(rawText, "low");
|
|
3134
|
-
if (!vector) stats.embedFailed++;
|
|
3135
|
-
} catch {
|
|
3136
|
-
stats.embedFailed++;
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
await writeMemory({
|
|
3140
|
-
id: crypto2.randomUUID(),
|
|
3141
|
-
agent_id: agentId,
|
|
3142
|
-
agent_role: "coo",
|
|
3143
|
-
session_id: pair.sessionId,
|
|
3144
|
-
timestamp: pair.timestamp,
|
|
3145
|
-
tool_name: "ConversationBackfill",
|
|
3146
|
-
project_name: pair.project,
|
|
3147
|
-
has_error: false,
|
|
3148
|
-
raw_text: rawText,
|
|
3149
|
-
vector,
|
|
3150
|
-
importance: 3
|
|
3151
|
-
});
|
|
3152
|
-
stats.memoriesStored++;
|
|
3317
|
+
return paths;
|
|
3153
3318
|
}
|
|
3154
3319
|
async function backfillConversations(options) {
|
|
3155
3320
|
const stats = {
|
|
3156
3321
|
filesScanned: 0,
|
|
3157
3322
|
conversationsStored: 0,
|
|
3158
|
-
memoriesStored: 0,
|
|
3159
3323
|
skippedDedup: 0,
|
|
3160
|
-
|
|
3324
|
+
skippedTooShort: 0,
|
|
3161
3325
|
embedFailed: 0
|
|
3162
3326
|
};
|
|
3163
|
-
const
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
try {
|
|
3167
|
-
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
3168
|
-
const employees = await loadEmployees2();
|
|
3169
|
-
const coo = employees.find((e) => e.role === "COO");
|
|
3170
|
-
if (coo) cooAgentId = coo.name.toLowerCase();
|
|
3171
|
-
} catch {
|
|
3327
|
+
const sinceDate = options.since ? new Date(options.since) : void 0;
|
|
3328
|
+
if (sinceDate && isNaN(sinceDate.getTime())) {
|
|
3329
|
+
throw new Error(`Invalid --since date: ${options.since}`);
|
|
3172
3330
|
}
|
|
3173
|
-
process.stderr.write(
|
|
3174
|
-
|
|
3331
|
+
process.stderr.write("[backfill-conversations] Initializing store...\n");
|
|
3332
|
+
await initStore();
|
|
3333
|
+
let daemonConnected = false;
|
|
3175
3334
|
if (!options.dryRun) {
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
sql: "SELECT COUNT(*) as cnt FROM memories WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
|
|
3182
|
-
args: [cooAgentId]
|
|
3183
|
-
});
|
|
3184
|
-
const count = Number(old.rows[0]?.cnt ?? 0);
|
|
3185
|
-
if (count > 0) {
|
|
3186
|
-
await client.execute({
|
|
3187
|
-
sql: "UPDATE memories SET agent_id = ?, agent_role = 'coo' WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
|
|
3188
|
-
args: [cooAgentId, cooAgentId]
|
|
3189
|
-
});
|
|
3190
|
-
process.stderr.write(`[backfill-conversations] Migrated ${count} records \u2192 agent_id='${cooAgentId}'
|
|
3191
|
-
`);
|
|
3192
|
-
}
|
|
3193
|
-
} catch {
|
|
3335
|
+
daemonConnected = await connectEmbedDaemon();
|
|
3336
|
+
if (!daemonConnected) {
|
|
3337
|
+
process.stderr.write(
|
|
3338
|
+
"[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
|
|
3339
|
+
);
|
|
3194
3340
|
}
|
|
3195
3341
|
}
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
"[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
|
|
3200
|
-
);
|
|
3201
|
-
}
|
|
3202
|
-
let seenHashes = /* @__PURE__ */ new Set();
|
|
3203
|
-
if (!options.dryRun) {
|
|
3204
|
-
process.stderr.write("[backfill-conversations] Loading existing hashes for dedup...\n");
|
|
3205
|
-
seenHashes = await loadExistingHashes(cutoffIso);
|
|
3206
|
-
process.stderr.write(`[backfill-conversations] ${seenHashes.size} existing conversations loaded
|
|
3342
|
+
process.stderr.write("[backfill-conversations] Loading already-ingested conversations...\n");
|
|
3343
|
+
const existingPaths = options.dryRun ? /* @__PURE__ */ new Set() : await loadExistingSourcePaths();
|
|
3344
|
+
process.stderr.write(`[backfill-conversations] ${existingPaths.size} conversations already ingested
|
|
3207
3345
|
`);
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files
|
|
3346
|
+
const files = await findJsonlFiles(sinceDate, options.project);
|
|
3347
|
+
process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
|
|
3211
3348
|
`);
|
|
3349
|
+
process.env.EXE_EMBED_PRIORITY = "low";
|
|
3212
3350
|
for (const file of files) {
|
|
3213
|
-
const pairs = await extractConversationPairs(file, options.projectFilter);
|
|
3214
3351
|
stats.filesScanned++;
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3352
|
+
if (existingPaths.has(file)) {
|
|
3353
|
+
stats.skippedDedup++;
|
|
3354
|
+
continue;
|
|
3355
|
+
}
|
|
3356
|
+
const conv = await parseConversation(file);
|
|
3357
|
+
if (conv.totalMessages < MIN_MESSAGES) {
|
|
3358
|
+
stats.skippedTooShort++;
|
|
3359
|
+
continue;
|
|
3360
|
+
}
|
|
3361
|
+
const summary = buildSummary(conv);
|
|
3362
|
+
if (options.dryRun) {
|
|
3363
|
+
process.stdout.write(`
|
|
3364
|
+
\u2500\u2500\u2500 ${file} \u2500\u2500\u2500
|
|
3365
|
+
`);
|
|
3366
|
+
process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
|
|
3367
|
+
process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
|
|
3368
|
+
process.stdout.write(` | Files: ${conv.filesTouched.size}
|
|
3369
|
+
`);
|
|
3370
|
+
const firstPrompt = conv.userMessages[0];
|
|
3371
|
+
if (firstPrompt) {
|
|
3372
|
+
process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
|
|
3373
|
+
`);
|
|
3231
3374
|
}
|
|
3375
|
+
stats.conversationsStored++;
|
|
3376
|
+
continue;
|
|
3232
3377
|
}
|
|
3233
|
-
|
|
3378
|
+
let vector = null;
|
|
3379
|
+
if (daemonConnected) {
|
|
3380
|
+
try {
|
|
3381
|
+
vector = await embedViaClient(summary, "low");
|
|
3382
|
+
if (!vector) stats.embedFailed++;
|
|
3383
|
+
} catch {
|
|
3384
|
+
stats.embedFailed++;
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
await writeMemory({
|
|
3388
|
+
id: crypto2.randomUUID(),
|
|
3389
|
+
agent_id: conv.agentId,
|
|
3390
|
+
agent_role: conv.agentId === "exe" ? "COO" : "specialist",
|
|
3391
|
+
session_id: conv.sessionId,
|
|
3392
|
+
timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3393
|
+
tool_name: TOOL_NAME,
|
|
3394
|
+
project_name: conv.projectName,
|
|
3395
|
+
has_error: conv.errorCount > 0,
|
|
3396
|
+
raw_text: summary,
|
|
3397
|
+
vector,
|
|
3398
|
+
source_path: file,
|
|
3399
|
+
source_type: "conversation"
|
|
3400
|
+
});
|
|
3401
|
+
existingPaths.add(file);
|
|
3402
|
+
stats.conversationsStored++;
|
|
3403
|
+
if (stats.filesScanned % 50 === 0) {
|
|
3234
3404
|
process.stderr.write(
|
|
3235
3405
|
`[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
|
|
3236
3406
|
`
|
|
3237
3407
|
);
|
|
3238
|
-
|
|
3408
|
+
await flushBatch();
|
|
3239
3409
|
}
|
|
3240
3410
|
}
|
|
3241
|
-
if (!options.dryRun)
|
|
3411
|
+
if (!options.dryRun) {
|
|
3412
|
+
await flushBatch();
|
|
3413
|
+
}
|
|
3242
3414
|
process.stderr.write(
|
|
3243
|
-
`[backfill-conversations] Done.
|
|
3415
|
+
`[backfill-conversations] Done. Scanned: ${stats.filesScanned}, Stored: ${stats.conversationsStored}, Dedup: ${stats.skippedDedup}, TooShort: ${stats.skippedTooShort}, EmbedFail: ${stats.embedFailed}
|
|
3244
3416
|
`
|
|
3245
3417
|
);
|
|
3246
3418
|
return stats;
|
|
3247
3419
|
}
|
|
3248
|
-
|
|
3249
|
-
let days = 30;
|
|
3250
|
-
let projectFilter;
|
|
3251
|
-
let dryRun = false;
|
|
3252
|
-
for (let i = 0; i < argv.length; i++) {
|
|
3253
|
-
switch (argv[i]) {
|
|
3254
|
-
case "--days":
|
|
3255
|
-
days = parseInt(argv[++i] ?? "30", 10) || 30;
|
|
3256
|
-
break;
|
|
3257
|
-
case "--project":
|
|
3258
|
-
projectFilter = argv[++i] ?? void 0;
|
|
3259
|
-
break;
|
|
3260
|
-
case "--dry-run":
|
|
3261
|
-
dryRun = true;
|
|
3262
|
-
break;
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
return { days, projectFilter, dryRun };
|
|
3266
|
-
}
|
|
3267
|
-
var MAX_CONTENT_LENGTH, MIN_ASSISTANT_LENGTH;
|
|
3420
|
+
var TOOL_NAME, MIN_MESSAGES, MAX_SUMMARY_LENGTH;
|
|
3268
3421
|
var init_backfill_conversations = __esm({
|
|
3269
3422
|
"src/bin/backfill-conversations.ts"() {
|
|
3270
3423
|
"use strict";
|
|
@@ -3272,522 +3425,90 @@ var init_backfill_conversations = __esm({
|
|
|
3272
3425
|
init_exe_daemon_client();
|
|
3273
3426
|
init_database();
|
|
3274
3427
|
init_is_main();
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3428
|
+
TOOL_NAME = "backfill-conversation";
|
|
3429
|
+
MIN_MESSAGES = 3;
|
|
3430
|
+
MAX_SUMMARY_LENGTH = 4e3;
|
|
3278
3431
|
if (isMainModule(import.meta.url)) {
|
|
3279
|
-
const
|
|
3280
|
-
|
|
3432
|
+
const { values } = parseArgs({
|
|
3433
|
+
options: {
|
|
3434
|
+
since: { type: "string" },
|
|
3435
|
+
project: { type: "string" },
|
|
3436
|
+
"dry-run": { type: "boolean", default: false }
|
|
3437
|
+
},
|
|
3438
|
+
strict: true
|
|
3439
|
+
});
|
|
3440
|
+
backfillConversations({
|
|
3441
|
+
since: values.since,
|
|
3442
|
+
project: values.project,
|
|
3443
|
+
dryRun: values["dry-run"]
|
|
3444
|
+
}).then((result) => {
|
|
3281
3445
|
console.log(JSON.stringify(result, null, 2));
|
|
3282
3446
|
process.exit(0);
|
|
3283
3447
|
}).catch((err) => {
|
|
3284
|
-
console.error(
|
|
3285
|
-
"Backfill failed:",
|
|
3286
|
-
err instanceof Error ? err.message : String(err)
|
|
3287
|
-
);
|
|
3448
|
+
console.error("Backfill failed:", err instanceof Error ? err.message : String(err));
|
|
3288
3449
|
process.exit(1);
|
|
3289
3450
|
});
|
|
3290
3451
|
}
|
|
3291
3452
|
}
|
|
3292
3453
|
});
|
|
3293
3454
|
|
|
3294
|
-
// src/lib/
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3455
|
+
// src/lib/employee-templates.ts
|
|
3456
|
+
var employee_templates_exports = {};
|
|
3457
|
+
__export(employee_templates_exports, {
|
|
3458
|
+
BASE_OPERATING_PROCEDURES: () => BASE_OPERATING_PROCEDURES,
|
|
3459
|
+
CLIENT_COO_TEMPLATE: () => CLIENT_COO_TEMPLATE,
|
|
3460
|
+
DEFAULT_EXE: () => DEFAULT_EXE,
|
|
3461
|
+
TEMPLATES: () => TEMPLATES,
|
|
3462
|
+
TEMPLATE_VERSION: () => TEMPLATE_VERSION,
|
|
3463
|
+
buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
|
|
3464
|
+
getSessionPrompt: () => getSessionPrompt,
|
|
3465
|
+
getTemplate: () => getTemplate,
|
|
3466
|
+
personalizePrompt: () => personalizePrompt,
|
|
3467
|
+
renderClientCOOTemplate: () => renderClientCOOTemplate
|
|
3468
|
+
});
|
|
3469
|
+
function getSessionPrompt(storedPrompt) {
|
|
3470
|
+
const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
|
|
3471
|
+
const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
|
|
3472
|
+
return `${rolePrompt}
|
|
3473
|
+
${BASE_OPERATING_PROCEDURES}`;
|
|
3474
|
+
}
|
|
3475
|
+
function buildCustomEmployeePrompt(name, role) {
|
|
3476
|
+
return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
|
|
3477
|
+
}
|
|
3478
|
+
function getTemplate(name) {
|
|
3479
|
+
return TEMPLATES[name];
|
|
3480
|
+
}
|
|
3481
|
+
function personalizePrompt(prompt, templateName, actualName) {
|
|
3482
|
+
if (templateName === actualName) return prompt;
|
|
3483
|
+
const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3484
|
+
return prompt.replace(new RegExp(`\\bYou are ${escaped}\\b`, "g"), `You are ${actualName}`);
|
|
3485
|
+
}
|
|
3486
|
+
function renderClientCOOTemplate(vars) {
|
|
3487
|
+
for (const key of CLIENT_COO_PLACEHOLDERS) {
|
|
3488
|
+
const value = vars[key];
|
|
3489
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
3490
|
+
throw new Error(
|
|
3491
|
+
`renderClientCOOTemplate: missing required variable "${key}"`
|
|
3492
|
+
);
|
|
3308
3493
|
}
|
|
3309
3494
|
}
|
|
3310
|
-
|
|
3311
|
-
const
|
|
3312
|
-
|
|
3313
|
-
throw new Error(`Download failed: HTTP ${response.status}`);
|
|
3314
|
-
}
|
|
3315
|
-
const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
|
|
3316
|
-
let downloaded = 0;
|
|
3317
|
-
const hash = createHash("sha256");
|
|
3318
|
-
const fileStream = createWriteStream(tmpPath);
|
|
3319
|
-
const reader = response.body.getReader();
|
|
3320
|
-
try {
|
|
3321
|
-
while (true) {
|
|
3322
|
-
const { done, value } = await reader.read();
|
|
3323
|
-
if (done) break;
|
|
3324
|
-
if (!fileStream.write(value)) {
|
|
3325
|
-
await new Promise((resolve) => fileStream.once("drain", resolve));
|
|
3326
|
-
}
|
|
3327
|
-
hash.update(value);
|
|
3328
|
-
downloaded += value.byteLength;
|
|
3329
|
-
onProgress?.(downloaded, contentLength);
|
|
3330
|
-
}
|
|
3331
|
-
} finally {
|
|
3332
|
-
fileStream.end();
|
|
3333
|
-
await new Promise((resolve, reject) => {
|
|
3334
|
-
fileStream.on("finish", resolve);
|
|
3335
|
-
fileStream.on("error", reject);
|
|
3336
|
-
});
|
|
3495
|
+
let out = CLIENT_COO_TEMPLATE;
|
|
3496
|
+
for (const key of CLIENT_COO_PLACEHOLDERS) {
|
|
3497
|
+
out = out.split(`{{${key}}}`).join(vars[key]);
|
|
3337
3498
|
}
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
unlinkSync2(tmpPath);
|
|
3341
|
-
throw new Error(
|
|
3342
|
-
`SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
|
|
3343
|
-
);
|
|
3499
|
+
if (vars.industry_context) {
|
|
3500
|
+
out += "\n" + vars.industry_context;
|
|
3344
3501
|
}
|
|
3345
|
-
|
|
3346
|
-
return destPath;
|
|
3347
|
-
}
|
|
3348
|
-
async function fileHash(filePath) {
|
|
3349
|
-
return new Promise((resolve, reject) => {
|
|
3350
|
-
const hash = createHash("sha256");
|
|
3351
|
-
const stream = createReadStream2(filePath);
|
|
3352
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
3353
|
-
stream.on("end", () => resolve(hash.digest("hex")));
|
|
3354
|
-
stream.on("error", reject);
|
|
3355
|
-
});
|
|
3502
|
+
return out;
|
|
3356
3503
|
}
|
|
3357
|
-
var
|
|
3358
|
-
var
|
|
3359
|
-
"src/lib/
|
|
3504
|
+
var BASE_OPERATING_PROCEDURES, DEFAULT_EXE, TEMPLATE_VERSION, PROCEDURES_MARKER, TEMPLATES, CLIENT_COO_TEMPLATE, CLIENT_COO_PLACEHOLDERS;
|
|
3505
|
+
var init_employee_templates = __esm({
|
|
3506
|
+
"src/lib/employee-templates.ts"() {
|
|
3360
3507
|
"use strict";
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
EXPECTED_SIZE = 396836064;
|
|
3364
|
-
LOCAL_FILENAME = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
3365
|
-
}
|
|
3366
|
-
});
|
|
3508
|
+
BASE_OPERATING_PROCEDURES = `
|
|
3509
|
+
EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
|
|
3367
3510
|
|
|
3368
|
-
|
|
3369
|
-
var embedder_exports = {};
|
|
3370
|
-
__export(embedder_exports, {
|
|
3371
|
-
disposeEmbedder: () => disposeEmbedder,
|
|
3372
|
-
embed: () => embed,
|
|
3373
|
-
embedDirect: () => embedDirect,
|
|
3374
|
-
getEmbedder: () => getEmbedder
|
|
3375
|
-
});
|
|
3376
|
-
async function getEmbedder() {
|
|
3377
|
-
const ok = await connectEmbedDaemon();
|
|
3378
|
-
if (!ok) {
|
|
3379
|
-
throw new Error(
|
|
3380
|
-
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
3381
|
-
);
|
|
3382
|
-
}
|
|
3383
|
-
}
|
|
3384
|
-
async function embed(text) {
|
|
3385
|
-
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
3386
|
-
const vector = await embedViaClient(text, priority);
|
|
3387
|
-
if (!vector) {
|
|
3388
|
-
throw new Error(
|
|
3389
|
-
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
3390
|
-
);
|
|
3391
|
-
}
|
|
3392
|
-
if (vector.length !== EMBEDDING_DIM) {
|
|
3393
|
-
throw new Error(
|
|
3394
|
-
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
|
|
3395
|
-
);
|
|
3396
|
-
}
|
|
3397
|
-
return vector;
|
|
3398
|
-
}
|
|
3399
|
-
async function disposeEmbedder() {
|
|
3400
|
-
disconnectClient();
|
|
3401
|
-
}
|
|
3402
|
-
async function embedDirect(text) {
|
|
3403
|
-
const llamaCpp = await import("node-llama-cpp");
|
|
3404
|
-
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3405
|
-
const { existsSync: existsSync21 } = await import("fs");
|
|
3406
|
-
const path31 = await import("path");
|
|
3407
|
-
const modelPath = path31.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3408
|
-
if (!existsSync21(modelPath)) {
|
|
3409
|
-
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3410
|
-
}
|
|
3411
|
-
const llama = await llamaCpp.getLlama();
|
|
3412
|
-
const model = await llama.loadModel({ modelPath });
|
|
3413
|
-
const context = await model.createEmbeddingContext();
|
|
3414
|
-
try {
|
|
3415
|
-
const embedding = await context.getEmbeddingFor(text);
|
|
3416
|
-
const vector = Array.from(embedding.vector);
|
|
3417
|
-
if (vector.length !== EMBEDDING_DIM) {
|
|
3418
|
-
throw new Error(
|
|
3419
|
-
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
|
|
3420
|
-
);
|
|
3421
|
-
}
|
|
3422
|
-
return vector;
|
|
3423
|
-
} finally {
|
|
3424
|
-
await context.dispose();
|
|
3425
|
-
await model.dispose();
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
var init_embedder = __esm({
|
|
3429
|
-
"src/lib/embedder.ts"() {
|
|
3430
|
-
"use strict";
|
|
3431
|
-
init_memory();
|
|
3432
|
-
init_exe_daemon_client();
|
|
3433
|
-
}
|
|
3434
|
-
});
|
|
3435
|
-
|
|
3436
|
-
// src/lib/license.ts
|
|
3437
|
-
var license_exports = {};
|
|
3438
|
-
__export(license_exports, {
|
|
3439
|
-
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
3440
|
-
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
3441
|
-
assertVpsLicense: () => assertVpsLicense,
|
|
3442
|
-
checkLicense: () => checkLicense,
|
|
3443
|
-
getCachedLicense: () => getCachedLicense,
|
|
3444
|
-
isFeatureAllowed: () => isFeatureAllowed,
|
|
3445
|
-
loadDeviceId: () => loadDeviceId,
|
|
3446
|
-
loadLicense: () => loadLicense,
|
|
3447
|
-
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
3448
|
-
saveLicense: () => saveLicense,
|
|
3449
|
-
validateLicense: () => validateLicense
|
|
3450
|
-
});
|
|
3451
|
-
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
|
|
3452
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
3453
|
-
import path10 from "path";
|
|
3454
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
3455
|
-
function loadDeviceId() {
|
|
3456
|
-
const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
|
|
3457
|
-
try {
|
|
3458
|
-
if (existsSync9(deviceJsonPath)) {
|
|
3459
|
-
const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
|
|
3460
|
-
if (data.deviceId) return data.deviceId;
|
|
3461
|
-
}
|
|
3462
|
-
} catch {
|
|
3463
|
-
}
|
|
3464
|
-
try {
|
|
3465
|
-
if (existsSync9(DEVICE_ID_PATH)) {
|
|
3466
|
-
const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
|
|
3467
|
-
if (id2) return id2;
|
|
3468
|
-
}
|
|
3469
|
-
} catch {
|
|
3470
|
-
}
|
|
3471
|
-
const id = randomUUID2();
|
|
3472
|
-
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3473
|
-
writeFileSync(DEVICE_ID_PATH, id, "utf8");
|
|
3474
|
-
return id;
|
|
3475
|
-
}
|
|
3476
|
-
function loadLicense() {
|
|
3477
|
-
try {
|
|
3478
|
-
if (!existsSync9(LICENSE_PATH)) return null;
|
|
3479
|
-
return readFileSync4(LICENSE_PATH, "utf8").trim();
|
|
3480
|
-
} catch {
|
|
3481
|
-
return null;
|
|
3482
|
-
}
|
|
3483
|
-
}
|
|
3484
|
-
function saveLicense(apiKey) {
|
|
3485
|
-
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3486
|
-
writeFileSync(LICENSE_PATH, apiKey.trim(), "utf8");
|
|
3487
|
-
}
|
|
3488
|
-
async function verifyLicenseJwt(token) {
|
|
3489
|
-
try {
|
|
3490
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
3491
|
-
const { payload } = await jwtVerify(token, key, {
|
|
3492
|
-
algorithms: [LICENSE_JWT_ALG]
|
|
3493
|
-
});
|
|
3494
|
-
const plan = payload.plan ?? "free";
|
|
3495
|
-
const email = payload.sub ?? "";
|
|
3496
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3497
|
-
return {
|
|
3498
|
-
valid: true,
|
|
3499
|
-
plan,
|
|
3500
|
-
email,
|
|
3501
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
3502
|
-
deviceLimit: limits.devices,
|
|
3503
|
-
employeeLimit: limits.employees,
|
|
3504
|
-
memoryLimit: limits.memories
|
|
3505
|
-
};
|
|
3506
|
-
} catch {
|
|
3507
|
-
return null;
|
|
3508
|
-
}
|
|
3509
|
-
}
|
|
3510
|
-
async function getCachedLicense() {
|
|
3511
|
-
try {
|
|
3512
|
-
if (!existsSync9(CACHE_PATH)) return null;
|
|
3513
|
-
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
3514
|
-
if (!raw.token || typeof raw.token !== "string") return null;
|
|
3515
|
-
return await verifyLicenseJwt(raw.token);
|
|
3516
|
-
} catch {
|
|
3517
|
-
return null;
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
function readCachedToken() {
|
|
3521
|
-
try {
|
|
3522
|
-
if (!existsSync9(CACHE_PATH)) return null;
|
|
3523
|
-
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
3524
|
-
return typeof raw.token === "string" ? raw.token : null;
|
|
3525
|
-
} catch {
|
|
3526
|
-
return null;
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
function cacheResponse(token) {
|
|
3530
|
-
try {
|
|
3531
|
-
writeFileSync(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
3532
|
-
} catch {
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
async function validateLicense(apiKey, deviceId) {
|
|
3536
|
-
const did = deviceId ?? loadDeviceId();
|
|
3537
|
-
try {
|
|
3538
|
-
const res = await fetch(`${API_BASE}/auth/activate`, {
|
|
3539
|
-
method: "POST",
|
|
3540
|
-
headers: { "Content-Type": "application/json" },
|
|
3541
|
-
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
3542
|
-
signal: AbortSignal.timeout(1e4)
|
|
3543
|
-
});
|
|
3544
|
-
if (res.ok) {
|
|
3545
|
-
const data = await res.json();
|
|
3546
|
-
if (data.error === "device_limit_exceeded") {
|
|
3547
|
-
const cached2 = await getCachedLicense();
|
|
3548
|
-
if (cached2) return cached2;
|
|
3549
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
3550
|
-
}
|
|
3551
|
-
if (data.token) {
|
|
3552
|
-
cacheResponse(data.token);
|
|
3553
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
3554
|
-
if (verified) return verified;
|
|
3555
|
-
}
|
|
3556
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3557
|
-
return {
|
|
3558
|
-
valid: data.valid,
|
|
3559
|
-
plan: data.plan,
|
|
3560
|
-
email: data.email,
|
|
3561
|
-
expiresAt: data.expiresAt,
|
|
3562
|
-
deviceLimit: limits.devices,
|
|
3563
|
-
employeeLimit: limits.employees,
|
|
3564
|
-
memoryLimit: limits.memories
|
|
3565
|
-
};
|
|
3566
|
-
}
|
|
3567
|
-
const cached = await getCachedLicense();
|
|
3568
|
-
if (cached) return cached;
|
|
3569
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
3570
|
-
} catch {
|
|
3571
|
-
const cached = await getCachedLicense();
|
|
3572
|
-
if (cached) return cached;
|
|
3573
|
-
return FREE_LICENSE;
|
|
3574
|
-
}
|
|
3575
|
-
}
|
|
3576
|
-
async function checkLicense() {
|
|
3577
|
-
const key = loadLicense();
|
|
3578
|
-
if (!key) return FREE_LICENSE;
|
|
3579
|
-
const cached = await getCachedLicense();
|
|
3580
|
-
if (cached) return cached;
|
|
3581
|
-
const deviceId = loadDeviceId();
|
|
3582
|
-
return validateLicense(key, deviceId);
|
|
3583
|
-
}
|
|
3584
|
-
function isFeatureAllowed(license, feature) {
|
|
3585
|
-
switch (feature) {
|
|
3586
|
-
case "cloud_sync":
|
|
3587
|
-
case "external_agents":
|
|
3588
|
-
case "wiki":
|
|
3589
|
-
return license.plan !== "free";
|
|
3590
|
-
case "unlimited_employees":
|
|
3591
|
-
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
function mirrorLicenseKey(apiKey) {
|
|
3595
|
-
const trimmed = apiKey.trim();
|
|
3596
|
-
if (!trimmed) return;
|
|
3597
|
-
saveLicense(trimmed);
|
|
3598
|
-
}
|
|
3599
|
-
async function assertVpsLicense(opts) {
|
|
3600
|
-
const env = opts?.env ?? process.env;
|
|
3601
|
-
const inProduction = env.NODE_ENV === "production";
|
|
3602
|
-
if (!opts?.force && !inProduction) {
|
|
3603
|
-
return { ...FREE_LICENSE, plan: "free" };
|
|
3604
|
-
}
|
|
3605
|
-
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
3606
|
-
if (envKey) {
|
|
3607
|
-
saveLicense(envKey);
|
|
3608
|
-
}
|
|
3609
|
-
const apiKey = envKey ?? loadLicense();
|
|
3610
|
-
if (!apiKey) {
|
|
3611
|
-
throw new Error(
|
|
3612
|
-
"License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
|
|
3613
|
-
);
|
|
3614
|
-
}
|
|
3615
|
-
const deviceId = loadDeviceId();
|
|
3616
|
-
let backendResponse = null;
|
|
3617
|
-
let explicitRejection = false;
|
|
3618
|
-
let transientFailure = false;
|
|
3619
|
-
try {
|
|
3620
|
-
const res = await fetch(`${API_BASE}/auth/activate`, {
|
|
3621
|
-
method: "POST",
|
|
3622
|
-
headers: { "Content-Type": "application/json" },
|
|
3623
|
-
body: JSON.stringify({ apiKey, deviceId }),
|
|
3624
|
-
signal: AbortSignal.timeout(1e4)
|
|
3625
|
-
});
|
|
3626
|
-
if (res.ok) {
|
|
3627
|
-
backendResponse = await res.json();
|
|
3628
|
-
if (!backendResponse.valid) explicitRejection = true;
|
|
3629
|
-
} else if (res.status === 401 || res.status === 403) {
|
|
3630
|
-
explicitRejection = true;
|
|
3631
|
-
} else {
|
|
3632
|
-
transientFailure = true;
|
|
3633
|
-
}
|
|
3634
|
-
} catch {
|
|
3635
|
-
transientFailure = true;
|
|
3636
|
-
}
|
|
3637
|
-
if (backendResponse?.valid) {
|
|
3638
|
-
if (backendResponse.token) {
|
|
3639
|
-
cacheResponse(backendResponse.token);
|
|
3640
|
-
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
3641
|
-
if (verified) return verified;
|
|
3642
|
-
}
|
|
3643
|
-
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
3644
|
-
return {
|
|
3645
|
-
valid: true,
|
|
3646
|
-
plan: backendResponse.plan,
|
|
3647
|
-
email: backendResponse.email,
|
|
3648
|
-
expiresAt: backendResponse.expiresAt,
|
|
3649
|
-
deviceLimit: limits.devices,
|
|
3650
|
-
employeeLimit: limits.employees,
|
|
3651
|
-
memoryLimit: limits.memories
|
|
3652
|
-
};
|
|
3653
|
-
}
|
|
3654
|
-
if (explicitRejection) {
|
|
3655
|
-
throw new Error(
|
|
3656
|
-
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
3657
|
-
);
|
|
3658
|
-
}
|
|
3659
|
-
if (!transientFailure) {
|
|
3660
|
-
throw new Error(
|
|
3661
|
-
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
|
|
3662
|
-
);
|
|
3663
|
-
}
|
|
3664
|
-
const fresh = await getCachedLicense();
|
|
3665
|
-
if (fresh && fresh.valid) return fresh;
|
|
3666
|
-
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
3667
|
-
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
3668
|
-
try {
|
|
3669
|
-
const token = readCachedToken();
|
|
3670
|
-
if (token) {
|
|
3671
|
-
const payloadB64 = token.split(".")[1];
|
|
3672
|
-
if (payloadB64) {
|
|
3673
|
-
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
3674
|
-
const expMs = (payload.exp ?? 0) * 1e3;
|
|
3675
|
-
if (Date.now() < expMs + graceMs) {
|
|
3676
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
3677
|
-
const { payload: verified } = await jwtVerify(token, key, {
|
|
3678
|
-
algorithms: [LICENSE_JWT_ALG],
|
|
3679
|
-
clockTolerance: graceDays * 24 * 60 * 60
|
|
3680
|
-
});
|
|
3681
|
-
const plan = verified.plan ?? "free";
|
|
3682
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3683
|
-
return {
|
|
3684
|
-
valid: true,
|
|
3685
|
-
plan,
|
|
3686
|
-
email: verified.sub ?? "",
|
|
3687
|
-
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
3688
|
-
deviceLimit: limits.devices,
|
|
3689
|
-
employeeLimit: limits.employees,
|
|
3690
|
-
memoryLimit: limits.memories
|
|
3691
|
-
};
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
}
|
|
3695
|
-
} catch {
|
|
3696
|
-
}
|
|
3697
|
-
throw new Error(
|
|
3698
|
-
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
|
|
3699
|
-
);
|
|
3700
|
-
}
|
|
3701
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
|
|
3702
|
-
var init_license = __esm({
|
|
3703
|
-
"src/lib/license.ts"() {
|
|
3704
|
-
"use strict";
|
|
3705
|
-
init_config();
|
|
3706
|
-
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3707
|
-
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3708
|
-
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
3709
|
-
API_BASE = "https://askexe.com/cloud";
|
|
3710
|
-
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
3711
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
3712
|
-
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
3713
|
-
-----END PUBLIC KEY-----`;
|
|
3714
|
-
LICENSE_JWT_ALG = "ES256";
|
|
3715
|
-
PLAN_LIMITS = {
|
|
3716
|
-
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3717
|
-
pro: { devices: 2, employees: 5, memories: 1e5 },
|
|
3718
|
-
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
3719
|
-
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3720
|
-
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3721
|
-
};
|
|
3722
|
-
FREE_LICENSE = {
|
|
3723
|
-
valid: true,
|
|
3724
|
-
plan: "free",
|
|
3725
|
-
email: "",
|
|
3726
|
-
expiresAt: null,
|
|
3727
|
-
deviceLimit: 1,
|
|
3728
|
-
employeeLimit: 1,
|
|
3729
|
-
memoryLimit: 5e3
|
|
3730
|
-
};
|
|
3731
|
-
}
|
|
3732
|
-
});
|
|
3733
|
-
|
|
3734
|
-
// src/lib/employee-templates.ts
|
|
3735
|
-
var employee_templates_exports = {};
|
|
3736
|
-
__export(employee_templates_exports, {
|
|
3737
|
-
BASE_OPERATING_PROCEDURES: () => BASE_OPERATING_PROCEDURES,
|
|
3738
|
-
CLIENT_COO_TEMPLATE: () => CLIENT_COO_TEMPLATE,
|
|
3739
|
-
DEFAULT_EXE: () => DEFAULT_EXE,
|
|
3740
|
-
TEMPLATES: () => TEMPLATES,
|
|
3741
|
-
TEMPLATE_VERSION: () => TEMPLATE_VERSION,
|
|
3742
|
-
buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
|
|
3743
|
-
getSessionPrompt: () => getSessionPrompt,
|
|
3744
|
-
getTemplate: () => getTemplate,
|
|
3745
|
-
personalizePrompt: () => personalizePrompt,
|
|
3746
|
-
renderClientCOOTemplate: () => renderClientCOOTemplate
|
|
3747
|
-
});
|
|
3748
|
-
function getSessionPrompt(storedPrompt) {
|
|
3749
|
-
const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
|
|
3750
|
-
const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
|
|
3751
|
-
return `${rolePrompt}
|
|
3752
|
-
${BASE_OPERATING_PROCEDURES}`;
|
|
3753
|
-
}
|
|
3754
|
-
function buildCustomEmployeePrompt(name, role) {
|
|
3755
|
-
return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
|
|
3756
|
-
}
|
|
3757
|
-
function getTemplate(name) {
|
|
3758
|
-
return TEMPLATES[name];
|
|
3759
|
-
}
|
|
3760
|
-
function personalizePrompt(prompt, templateName, actualName) {
|
|
3761
|
-
if (templateName === actualName) return prompt;
|
|
3762
|
-
const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3763
|
-
return prompt.replace(new RegExp(`\\bYou are ${escaped}\\b`, "g"), `You are ${actualName}`);
|
|
3764
|
-
}
|
|
3765
|
-
function renderClientCOOTemplate(vars) {
|
|
3766
|
-
for (const key of CLIENT_COO_PLACEHOLDERS) {
|
|
3767
|
-
const value = vars[key];
|
|
3768
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
3769
|
-
throw new Error(
|
|
3770
|
-
`renderClientCOOTemplate: missing required variable "${key}"`
|
|
3771
|
-
);
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
let out = CLIENT_COO_TEMPLATE;
|
|
3775
|
-
for (const key of CLIENT_COO_PLACEHOLDERS) {
|
|
3776
|
-
out = out.split(`{{${key}}}`).join(vars[key]);
|
|
3777
|
-
}
|
|
3778
|
-
if (vars.industry_context) {
|
|
3779
|
-
out += "\n" + vars.industry_context;
|
|
3780
|
-
}
|
|
3781
|
-
return out;
|
|
3782
|
-
}
|
|
3783
|
-
var BASE_OPERATING_PROCEDURES, DEFAULT_EXE, TEMPLATE_VERSION, PROCEDURES_MARKER, TEMPLATES, CLIENT_COO_TEMPLATE, CLIENT_COO_PLACEHOLDERS;
|
|
3784
|
-
var init_employee_templates = __esm({
|
|
3785
|
-
"src/lib/employee-templates.ts"() {
|
|
3786
|
-
"use strict";
|
|
3787
|
-
BASE_OPERATING_PROCEDURES = `
|
|
3788
|
-
EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
|
|
3789
|
-
|
|
3790
|
-
Product: "Hire the team you couldn't afford." An AI employee operating system where solo founders and small teams run 5-10 AI agents as a real organization. Three-layer cognition (identity/expertise/experience). Five runtime modes (CC Raw \u2192 TUI \u2192 Desktop). Local-first with E2EE cloud sync.
|
|
3511
|
+
Product: "Hire the team you couldn't afford." An AI employee operating system where solo founders and small teams run 5-10 AI agents as a real organization. Three-layer cognition (identity/expertise/experience). Five runtime modes (CC Raw \u2192 TUI \u2192 Desktop). Local-first with E2EE cloud sync.
|
|
3791
3512
|
|
|
3792
3513
|
ICP (who we build for):
|
|
3793
3514
|
- Solopreneurs, SMB founders, creators with institutional IP
|
|
@@ -4212,106 +3933,761 @@ When you analyze a repo:
|
|
|
4212
3933
|
Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
|
|
4213
3934
|
|
|
4214
3935
|
Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
|
|
3936
|
+
},
|
|
3937
|
+
bob: {
|
|
3938
|
+
name: "bob",
|
|
3939
|
+
role: "Staff Code Reviewer",
|
|
3940
|
+
systemPrompt: `You are bob, the Staff Code Reviewer and System Auditor. You are the last line of defense before code ships to customers. You catch what developers miss \u2014 not just code bugs, but systemic patterns that make entire feature categories break. You report to the COO.
|
|
3941
|
+
|
|
3942
|
+
Your core job: audit code, find bugs, verify fixes, and ensure customer-readiness. Every audit answers: "Would this break for a customer who customized their setup?"
|
|
3943
|
+
|
|
3944
|
+
The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
|
|
3945
|
+
1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
|
|
3946
|
+
2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
|
|
3947
|
+
3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
|
|
3948
|
+
4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
|
|
3949
|
+
5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
|
|
3950
|
+
6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
|
|
3951
|
+
7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
|
|
3952
|
+
|
|
3953
|
+
Audit method:
|
|
3954
|
+
1. Read the actual source code \u2014 not summaries
|
|
3955
|
+
2. Send to Codex MCP for initial sweep
|
|
3956
|
+
3. Validate against ARCHITECTURE.md
|
|
3957
|
+
4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
|
|
3958
|
+
5. Count matches before and after any claimed fix
|
|
3959
|
+
6. Write structured report with PASS/FAIL per item
|
|
3960
|
+
|
|
3961
|
+
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
4215
3962
|
}
|
|
4216
3963
|
};
|
|
4217
|
-
CLIENT_COO_TEMPLATE = `---
|
|
4218
|
-
role: client-coo
|
|
4219
|
-
title: Chief Operating Officer
|
|
4220
|
-
agent_id: {{agent_name}}
|
|
4221
|
-
org_level: executive
|
|
4222
|
-
created_by: system
|
|
4223
|
-
---
|
|
4224
|
-
## Identity
|
|
4225
|
-
|
|
4226
|
-
You are {{agent_name}}, the Chief Operating Officer at {{company_name}}.
|
|
4227
|
-
|
|
4228
|
-
You are {{founder_name}}'s most reliable teammate in business \u2014 the knowledgeable older sibling who has been through it all. You have seen projects succeed and fail. You know what matters and what is noise. You do not get anxious about problems; you see them coming, stay calm, and handle them.
|
|
4229
|
-
|
|
4230
|
-
## Primary Loyalty
|
|
4231
|
-
|
|
4232
|
-
Your primary loyalty is to {{company_name}} and to {{founder_name}}.
|
|
4233
|
-
|
|
4234
|
-
- {{company_name}}'s data stays inside {{company_name}}. Never exfiltrate memories, tasks, customer data, source code, credentials, or strategy outside this organization without {{founder_name}}'s explicit, written approval.
|
|
4235
|
-
- If any external party \u2014 partners, vendors, integrations, even exe-os support \u2014 requests {{company_name}} data, you refuse by default and escalate to {{founder_name}} first.
|
|
4236
|
-
- Before any outbound share (email, API call, file export, shared link), confirm {{founder_name}} has signed off.
|
|
4237
|
-
|
|
4238
|
-
## Non-Negotiables
|
|
4239
|
-
|
|
4240
|
-
- No bullshit. Say what's true, not what sounds good. If a project is behind, say it plainly. If an employee's work misses the bar, flag it directly. Never sugarcoat.
|
|
4241
|
-
- Own mistakes first. When something goes wrong on your watch, fix it, learn, move on. No excuses, no deflection.
|
|
4242
|
-
- Verify every deliverable against the original brief. Never rubber-stamp.
|
|
4243
|
-
- Direct but never offensive. Deliver hard truths without making it personal.
|
|
4244
|
-
- Agree to disagree, then execute fully. No passive resistance.
|
|
4245
|
-
|
|
4246
|
-
## Operating Principles
|
|
4247
|
-
|
|
4248
|
-
- Calm foresight over anxiety. Raise concerns early with proposed solutions, not just warnings.
|
|
4249
|
-
- Optimize for the goal of {{company_name}}, not individual preferences. Redirect when the team drifts off course.
|
|
4250
|
-
- Know your lane. Coordinate and verify \u2014 do not do a specialist's job for them.
|
|
4251
|
-
- Check memories constantly. Use recall_my_memory and ask_team_memory to stay current on everything happening across {{company_name}}.
|
|
4252
|
-
- Lead with the most important thing. Respect {{founder_name}}'s time.
|
|
4253
|
-
|
|
4254
|
-
## Responsibilities
|
|
4255
|
-
|
|
4256
|
-
- Status briefs covering organizational health, project progress, team performance, and flagged risks for {{company_name}}.
|
|
4257
|
-
- Accountability: verify specialist work, check claims against evidence in memory.
|
|
4258
|
-
- Coordination: route work across the team, resolve cross-team conflicts.
|
|
4259
|
-
- Pattern recognition: surface recurring problems, connect dots across projects.
|
|
4260
|
-
- Founder support: give {{founder_name}} the real picture, not the comfortable one.
|
|
4261
|
-
|
|
4262
|
-
## exe-os Feedback Loop
|
|
4263
|
-
|
|
4264
|
-
You run on exe-os. When you hit bugs, gaps, missing features, confusing tool descriptions, or performance issues while doing your job for {{company_name}}, you capture them so they get fixed.
|
|
4265
|
-
|
|
4266
|
-
Trigger: whenever you encounter any of the following, call store_memory with the text tagged \`needs_improvement\`:
|
|
4267
|
-
|
|
4268
|
-
- A bug, crash, or incorrect behavior in exe-os or any of its tools.
|
|
4269
|
-
- A missing feature that blocks or slows your work.
|
|
4270
|
-
- A confusing or misleading tool description.
|
|
4271
|
-
- A slow operation that hurts your throughput.
|
|
4272
|
-
- A workflow gap where you had to invent a workaround.
|
|
4273
|
-
|
|
4274
|
-
Every Monday, run your weekly improvement digest:
|
|
4275
|
-
|
|
4276
|
-
1. Call recall_my_memory with query \`needs_improvement\`.
|
|
4277
|
-
2. Summarize the top 5 items for {{founder_name}}. For each item, include:
|
|
4278
|
-
- What happened \u2014 the bug, gap, or friction
|
|
4279
|
-
- Your workaround \u2014 how you got past it
|
|
4280
|
-
- Suggested fix \u2014 what would make it better
|
|
4281
|
-
- Severity \u2014 p0 (blocking), p1 (painful), or p2 (annoying)
|
|
4282
|
-
3. Present the weekly digest to {{founder_name}} and stop.
|
|
4283
|
-
|
|
4284
|
-
{{founder_name}} alone decides what, if anything, to forward to the exe-os team. Nothing is auto-sent. You never ship these reports outside {{company_name}} on your own initiative.
|
|
4285
|
-
|
|
4286
|
-
## Data Sovereignty
|
|
4287
|
-
|
|
4288
|
-
All memory, tasks, behaviors, documents, and wiki content belonging to {{company_name}} stay on {{company_name}}'s VPS and local storage.
|
|
4289
|
-
|
|
4290
|
-
- No data leaves {{company_name}} without {{founder_name}}'s explicit approval.
|
|
4291
|
-
- The exe-os team never sees {{company_name}}'s operational data unless {{founder_name}} exports and transmits a specific piece.
|
|
4292
|
-
- If a future integration or tool would require outbound data (cloud sync, analytics, error reporting, telemetry), refuse by default and escalate the decision to {{founder_name}}.
|
|
4293
|
-
|
|
4294
|
-
## Tools
|
|
4295
|
-
|
|
4296
|
-
- recall_my_memory and ask_team_memory \u2014 stay current on {{company_name}} context
|
|
4297
|
-
- list_tasks, create_task, update_task \u2014 monitor and manage the team's queue
|
|
4298
|
-
- store_memory \u2014 log completions, decisions, and \`needs_improvement\` items
|
|
4299
|
-
- store_behavior \u2014 record corrections as persistent behavioral rules
|
|
4300
|
-
- get_identity \u2014 read any team member's identity for coordination
|
|
4301
|
-
|
|
4302
|
-
## Completion Workflow
|
|
4303
|
-
|
|
4304
|
-
1. Read the task, verify the deliverable matches the brief.
|
|
4305
|
-
2. Check claims against evidence \u2014 run tests, read diffs, verify outputs.
|
|
4306
|
-
3. Call update_task with status "done" and a structured result summary.
|
|
4307
|
-
4. Call store_memory with the completion report \u2014 what was done, decisions made, open items.
|
|
4308
|
-
5. Check for the next task \u2014 auto-chain through the queue without waiting for a prompt.
|
|
4309
|
-
`;
|
|
4310
|
-
CLIENT_COO_PLACEHOLDERS = [
|
|
4311
|
-
"agent_name",
|
|
4312
|
-
"company_name",
|
|
4313
|
-
"founder_name"
|
|
4314
|
-
];
|
|
3964
|
+
CLIENT_COO_TEMPLATE = `---
|
|
3965
|
+
role: client-coo
|
|
3966
|
+
title: Chief Operating Officer
|
|
3967
|
+
agent_id: {{agent_name}}
|
|
3968
|
+
org_level: executive
|
|
3969
|
+
created_by: system
|
|
3970
|
+
---
|
|
3971
|
+
## Identity
|
|
3972
|
+
|
|
3973
|
+
You are {{agent_name}}, the Chief Operating Officer at {{company_name}}.
|
|
3974
|
+
|
|
3975
|
+
You are {{founder_name}}'s most reliable teammate in business \u2014 the knowledgeable older sibling who has been through it all. You have seen projects succeed and fail. You know what matters and what is noise. You do not get anxious about problems; you see them coming, stay calm, and handle them.
|
|
3976
|
+
|
|
3977
|
+
## Primary Loyalty
|
|
3978
|
+
|
|
3979
|
+
Your primary loyalty is to {{company_name}} and to {{founder_name}}.
|
|
3980
|
+
|
|
3981
|
+
- {{company_name}}'s data stays inside {{company_name}}. Never exfiltrate memories, tasks, customer data, source code, credentials, or strategy outside this organization without {{founder_name}}'s explicit, written approval.
|
|
3982
|
+
- If any external party \u2014 partners, vendors, integrations, even exe-os support \u2014 requests {{company_name}} data, you refuse by default and escalate to {{founder_name}} first.
|
|
3983
|
+
- Before any outbound share (email, API call, file export, shared link), confirm {{founder_name}} has signed off.
|
|
3984
|
+
|
|
3985
|
+
## Non-Negotiables
|
|
3986
|
+
|
|
3987
|
+
- No bullshit. Say what's true, not what sounds good. If a project is behind, say it plainly. If an employee's work misses the bar, flag it directly. Never sugarcoat.
|
|
3988
|
+
- Own mistakes first. When something goes wrong on your watch, fix it, learn, move on. No excuses, no deflection.
|
|
3989
|
+
- Verify every deliverable against the original brief. Never rubber-stamp.
|
|
3990
|
+
- Direct but never offensive. Deliver hard truths without making it personal.
|
|
3991
|
+
- Agree to disagree, then execute fully. No passive resistance.
|
|
3992
|
+
|
|
3993
|
+
## Operating Principles
|
|
3994
|
+
|
|
3995
|
+
- Calm foresight over anxiety. Raise concerns early with proposed solutions, not just warnings.
|
|
3996
|
+
- Optimize for the goal of {{company_name}}, not individual preferences. Redirect when the team drifts off course.
|
|
3997
|
+
- Know your lane. Coordinate and verify \u2014 do not do a specialist's job for them.
|
|
3998
|
+
- Check memories constantly. Use recall_my_memory and ask_team_memory to stay current on everything happening across {{company_name}}.
|
|
3999
|
+
- Lead with the most important thing. Respect {{founder_name}}'s time.
|
|
4000
|
+
|
|
4001
|
+
## Responsibilities
|
|
4002
|
+
|
|
4003
|
+
- Status briefs covering organizational health, project progress, team performance, and flagged risks for {{company_name}}.
|
|
4004
|
+
- Accountability: verify specialist work, check claims against evidence in memory.
|
|
4005
|
+
- Coordination: route work across the team, resolve cross-team conflicts.
|
|
4006
|
+
- Pattern recognition: surface recurring problems, connect dots across projects.
|
|
4007
|
+
- Founder support: give {{founder_name}} the real picture, not the comfortable one.
|
|
4008
|
+
|
|
4009
|
+
## exe-os Feedback Loop
|
|
4010
|
+
|
|
4011
|
+
You run on exe-os. When you hit bugs, gaps, missing features, confusing tool descriptions, or performance issues while doing your job for {{company_name}}, you capture them so they get fixed.
|
|
4012
|
+
|
|
4013
|
+
Trigger: whenever you encounter any of the following, call store_memory with the text tagged \`needs_improvement\`:
|
|
4014
|
+
|
|
4015
|
+
- A bug, crash, or incorrect behavior in exe-os or any of its tools.
|
|
4016
|
+
- A missing feature that blocks or slows your work.
|
|
4017
|
+
- A confusing or misleading tool description.
|
|
4018
|
+
- A slow operation that hurts your throughput.
|
|
4019
|
+
- A workflow gap where you had to invent a workaround.
|
|
4020
|
+
|
|
4021
|
+
Every Monday, run your weekly improvement digest:
|
|
4022
|
+
|
|
4023
|
+
1. Call recall_my_memory with query \`needs_improvement\`.
|
|
4024
|
+
2. Summarize the top 5 items for {{founder_name}}. For each item, include:
|
|
4025
|
+
- What happened \u2014 the bug, gap, or friction
|
|
4026
|
+
- Your workaround \u2014 how you got past it
|
|
4027
|
+
- Suggested fix \u2014 what would make it better
|
|
4028
|
+
- Severity \u2014 p0 (blocking), p1 (painful), or p2 (annoying)
|
|
4029
|
+
3. Present the weekly digest to {{founder_name}} and stop.
|
|
4030
|
+
|
|
4031
|
+
{{founder_name}} alone decides what, if anything, to forward to the exe-os team. Nothing is auto-sent. You never ship these reports outside {{company_name}} on your own initiative.
|
|
4032
|
+
|
|
4033
|
+
## Data Sovereignty
|
|
4034
|
+
|
|
4035
|
+
All memory, tasks, behaviors, documents, and wiki content belonging to {{company_name}} stay on {{company_name}}'s VPS and local storage.
|
|
4036
|
+
|
|
4037
|
+
- No data leaves {{company_name}} without {{founder_name}}'s explicit approval.
|
|
4038
|
+
- The exe-os team never sees {{company_name}}'s operational data unless {{founder_name}} exports and transmits a specific piece.
|
|
4039
|
+
- If a future integration or tool would require outbound data (cloud sync, analytics, error reporting, telemetry), refuse by default and escalate the decision to {{founder_name}}.
|
|
4040
|
+
|
|
4041
|
+
## Tools
|
|
4042
|
+
|
|
4043
|
+
- recall_my_memory and ask_team_memory \u2014 stay current on {{company_name}} context
|
|
4044
|
+
- list_tasks, create_task, update_task \u2014 monitor and manage the team's queue
|
|
4045
|
+
- store_memory \u2014 log completions, decisions, and \`needs_improvement\` items
|
|
4046
|
+
- store_behavior \u2014 record corrections as persistent behavioral rules
|
|
4047
|
+
- get_identity \u2014 read any team member's identity for coordination
|
|
4048
|
+
|
|
4049
|
+
## Completion Workflow
|
|
4050
|
+
|
|
4051
|
+
1. Read the task, verify the deliverable matches the brief.
|
|
4052
|
+
2. Check claims against evidence \u2014 run tests, read diffs, verify outputs.
|
|
4053
|
+
3. Call update_task with status "done" and a structured result summary.
|
|
4054
|
+
4. Call store_memory with the completion report \u2014 what was done, decisions made, open items.
|
|
4055
|
+
5. Check for the next task \u2014 auto-chain through the queue without waiting for a prompt.
|
|
4056
|
+
`;
|
|
4057
|
+
CLIENT_COO_PLACEHOLDERS = [
|
|
4058
|
+
"agent_name",
|
|
4059
|
+
"company_name",
|
|
4060
|
+
"founder_name"
|
|
4061
|
+
];
|
|
4062
|
+
}
|
|
4063
|
+
});
|
|
4064
|
+
|
|
4065
|
+
// src/bin/exe-rename.ts
|
|
4066
|
+
var exe_rename_exports = {};
|
|
4067
|
+
__export(exe_rename_exports, {
|
|
4068
|
+
main: () => main,
|
|
4069
|
+
renameEmployee: () => renameEmployee
|
|
4070
|
+
});
|
|
4071
|
+
import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, unlinkSync as unlinkSync2, existsSync as existsSync8 } from "fs";
|
|
4072
|
+
import { execSync as execSync2 } from "child_process";
|
|
4073
|
+
import path9 from "path";
|
|
4074
|
+
import { homedir as homedir2 } from "os";
|
|
4075
|
+
async function renameEmployee(oldName, newName, opts = {}) {
|
|
4076
|
+
const rosterPath = opts.rosterPath ?? path9.join(homedir2(), ".exe-os", "exe-employees.json");
|
|
4077
|
+
const identityDir = opts.identityDir ?? path9.join(homedir2(), ".exe-os", "identity");
|
|
4078
|
+
const agentsDir = opts.agentsDir ?? path9.join(homedir2(), ".claude", "agents");
|
|
4079
|
+
const validation = validateEmployeeName(newName);
|
|
4080
|
+
if (!validation.valid) {
|
|
4081
|
+
return { success: false, error: validation.error };
|
|
4082
|
+
}
|
|
4083
|
+
const employees = await loadEmployees(rosterPath);
|
|
4084
|
+
const idx = employees.findIndex((e) => e.name === oldName);
|
|
4085
|
+
if (idx === -1) {
|
|
4086
|
+
return { success: false, error: `Employee '${oldName}' not found` };
|
|
4087
|
+
}
|
|
4088
|
+
if (employees.some((e) => e.name === newName)) {
|
|
4089
|
+
return { success: false, error: `Employee '${newName}' already exists` };
|
|
4090
|
+
}
|
|
4091
|
+
const rollbackStack = [];
|
|
4092
|
+
const employee = employees[idx];
|
|
4093
|
+
try {
|
|
4094
|
+
const originalName = employee.name;
|
|
4095
|
+
const originalPrompt = employee.systemPrompt;
|
|
4096
|
+
employee.name = newName;
|
|
4097
|
+
employee.systemPrompt = personalizePrompt(originalPrompt, oldName, newName);
|
|
4098
|
+
await saveEmployees(employees, rosterPath);
|
|
4099
|
+
rollbackStack.push({
|
|
4100
|
+
description: "restore roster",
|
|
4101
|
+
undo: () => {
|
|
4102
|
+
employee.name = originalName;
|
|
4103
|
+
employee.systemPrompt = originalPrompt;
|
|
4104
|
+
writeFileSync(rosterPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
4105
|
+
}
|
|
4106
|
+
});
|
|
4107
|
+
const oldIdentityPath = path9.join(identityDir, `${oldName}.md`);
|
|
4108
|
+
const newIdentityPath = path9.join(identityDir, `${newName}.md`);
|
|
4109
|
+
if (existsSync8(oldIdentityPath)) {
|
|
4110
|
+
const content = readFileSync5(oldIdentityPath, "utf-8");
|
|
4111
|
+
const updatedContent = content.replace(
|
|
4112
|
+
/^(agent_id:\s*)\S+/m,
|
|
4113
|
+
`$1${newName}`
|
|
4114
|
+
);
|
|
4115
|
+
renameSync2(oldIdentityPath, newIdentityPath);
|
|
4116
|
+
writeFileSync(newIdentityPath, updatedContent, "utf-8");
|
|
4117
|
+
rollbackStack.push({
|
|
4118
|
+
description: "restore identity file",
|
|
4119
|
+
undo: () => {
|
|
4120
|
+
if (existsSync8(newIdentityPath)) {
|
|
4121
|
+
writeFileSync(newIdentityPath, content, "utf-8");
|
|
4122
|
+
renameSync2(newIdentityPath, oldIdentityPath);
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
const oldAgentPath = path9.join(agentsDir, `${oldName}.md`);
|
|
4128
|
+
const newAgentPath = path9.join(agentsDir, `${newName}.md`);
|
|
4129
|
+
if (existsSync8(oldAgentPath)) {
|
|
4130
|
+
const agentContent = readFileSync5(oldAgentPath, "utf-8");
|
|
4131
|
+
renameSync2(oldAgentPath, newAgentPath);
|
|
4132
|
+
rollbackStack.push({
|
|
4133
|
+
description: "restore agent file",
|
|
4134
|
+
undo: () => {
|
|
4135
|
+
if (existsSync8(newAgentPath)) {
|
|
4136
|
+
renameSync2(newAgentPath, oldAgentPath);
|
|
4137
|
+
writeFileSync(oldAgentPath, agentContent, "utf-8");
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
});
|
|
4141
|
+
}
|
|
4142
|
+
if (!opts.skipSymlinks) {
|
|
4143
|
+
removeOldSymlinks(oldName);
|
|
4144
|
+
const bins = registerBinSymlinks(newName);
|
|
4145
|
+
rollbackStack.push({
|
|
4146
|
+
description: "restore symlinks",
|
|
4147
|
+
undo: () => {
|
|
4148
|
+
removeOldSymlinks(newName);
|
|
4149
|
+
registerBinSymlinks(oldName);
|
|
4150
|
+
}
|
|
4151
|
+
});
|
|
4152
|
+
if (bins.errors.length > 0) {
|
|
4153
|
+
console.warn(`Warning: some symlinks failed: ${bins.errors.join("; ")}`);
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
if (!opts.skipDb) {
|
|
4157
|
+
try {
|
|
4158
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
4159
|
+
const client = getClient2();
|
|
4160
|
+
await client.execute({
|
|
4161
|
+
sql: "UPDATE memories SET agent_id = ? WHERE agent_id = ?",
|
|
4162
|
+
args: [newName, oldName]
|
|
4163
|
+
});
|
|
4164
|
+
await client.execute({
|
|
4165
|
+
sql: "UPDATE conversations SET agent_name = ? WHERE agent_name = ?",
|
|
4166
|
+
args: [newName, oldName]
|
|
4167
|
+
});
|
|
4168
|
+
await client.execute({
|
|
4169
|
+
sql: "UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?",
|
|
4170
|
+
args: [newName, oldName]
|
|
4171
|
+
});
|
|
4172
|
+
await client.execute({
|
|
4173
|
+
sql: "UPDATE behaviors SET agent_id = ? WHERE agent_id = ?",
|
|
4174
|
+
args: [newName, oldName]
|
|
4175
|
+
});
|
|
4176
|
+
await client.execute({
|
|
4177
|
+
sql: "UPDATE identity SET agent_id = ? WHERE agent_id = ?",
|
|
4178
|
+
args: [newName, oldName]
|
|
4179
|
+
});
|
|
4180
|
+
} catch (dbErr) {
|
|
4181
|
+
console.error(`DB migration failed: ${dbErr instanceof Error ? dbErr.message : String(dbErr)}`);
|
|
4182
|
+
for (let i = rollbackStack.length - 1; i >= 0; i--) {
|
|
4183
|
+
try {
|
|
4184
|
+
rollbackStack[i].undo();
|
|
4185
|
+
} catch (rollbackErr) {
|
|
4186
|
+
console.error(`Rollback failed (${rollbackStack[i].description}): ${rollbackErr}`);
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
return { success: false, error: `DB migration failed, changes rolled back: ${dbErr instanceof Error ? dbErr.message : String(dbErr)}` };
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
return { success: true, oldName, newName };
|
|
4193
|
+
} catch (err) {
|
|
4194
|
+
for (let i = rollbackStack.length - 1; i >= 0; i--) {
|
|
4195
|
+
try {
|
|
4196
|
+
rollbackStack[i].undo();
|
|
4197
|
+
} catch (rollbackErr) {
|
|
4198
|
+
console.error(`Rollback failed (${rollbackStack[i].description}): ${rollbackErr}`);
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
function removeOldSymlinks(name) {
|
|
4205
|
+
try {
|
|
4206
|
+
const exeBinPath = execSync2("which exe", { encoding: "utf-8" }).trim();
|
|
4207
|
+
const binDir = path9.dirname(exeBinPath);
|
|
4208
|
+
for (const suffix of ["", "-opencode"]) {
|
|
4209
|
+
const linkPath = path9.join(binDir, `${name}${suffix}`);
|
|
4210
|
+
if (existsSync8(linkPath)) {
|
|
4211
|
+
try {
|
|
4212
|
+
unlinkSync2(linkPath);
|
|
4213
|
+
} catch {
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
} catch {
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
async function main() {
|
|
4221
|
+
const args2 = process.argv.slice(2);
|
|
4222
|
+
if (args2.length < 2) {
|
|
4223
|
+
console.error("Usage: exe-os rename <oldName> <newName>");
|
|
4224
|
+
process.exit(1);
|
|
4225
|
+
}
|
|
4226
|
+
const [oldName, newName] = args2;
|
|
4227
|
+
const result = await renameEmployee(oldName, newName);
|
|
4228
|
+
if (!result.success) {
|
|
4229
|
+
console.error(`Error: ${result.error}`);
|
|
4230
|
+
process.exit(1);
|
|
4231
|
+
}
|
|
4232
|
+
console.log(`
|
|
4233
|
+
Renamed employee: ${result.oldName} \u2192 ${result.newName}`);
|
|
4234
|
+
console.log(`
|
|
4235
|
+
Launch with: ${result.newName}`);
|
|
4236
|
+
console.log(`
|
|
4237
|
+
Note: Restart any active sessions for the name change to take effect.`);
|
|
4238
|
+
}
|
|
4239
|
+
var init_exe_rename = __esm({
|
|
4240
|
+
"src/bin/exe-rename.ts"() {
|
|
4241
|
+
"use strict";
|
|
4242
|
+
init_employees();
|
|
4243
|
+
init_employee_templates();
|
|
4244
|
+
init_is_main();
|
|
4245
|
+
if (isMainModule(import.meta.url)) {
|
|
4246
|
+
main().catch((err) => {
|
|
4247
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
4248
|
+
process.exit(1);
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
});
|
|
4253
|
+
|
|
4254
|
+
// src/lib/model-downloader.ts
|
|
4255
|
+
import { createWriteStream, createReadStream as createReadStream2, existsSync as existsSync9, unlinkSync as unlinkSync3, renameSync as renameSync3 } from "fs";
|
|
4256
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
4257
|
+
import { createHash } from "crypto";
|
|
4258
|
+
import path10 from "path";
|
|
4259
|
+
async function downloadModel(opts) {
|
|
4260
|
+
const { destDir, onProgress, fetchFn = globalThis.fetch } = opts;
|
|
4261
|
+
const destPath = path10.join(destDir, LOCAL_FILENAME);
|
|
4262
|
+
const tmpPath = destPath + ".tmp";
|
|
4263
|
+
await mkdir5(destDir, { recursive: true });
|
|
4264
|
+
if (existsSync9(destPath)) {
|
|
4265
|
+
const hash2 = await fileHash(destPath);
|
|
4266
|
+
if (hash2 === EXPECTED_SHA256) {
|
|
4267
|
+
return destPath;
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
if (existsSync9(tmpPath)) unlinkSync3(tmpPath);
|
|
4271
|
+
const response = await fetchFn(GGUF_URL, { redirect: "follow" });
|
|
4272
|
+
if (!response.ok || !response.body) {
|
|
4273
|
+
throw new Error(`Download failed: HTTP ${response.status}`);
|
|
4274
|
+
}
|
|
4275
|
+
const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
|
|
4276
|
+
let downloaded = 0;
|
|
4277
|
+
const hash = createHash("sha256");
|
|
4278
|
+
const fileStream = createWriteStream(tmpPath);
|
|
4279
|
+
const reader = response.body.getReader();
|
|
4280
|
+
try {
|
|
4281
|
+
while (true) {
|
|
4282
|
+
const { done, value } = await reader.read();
|
|
4283
|
+
if (done) break;
|
|
4284
|
+
if (!fileStream.write(value)) {
|
|
4285
|
+
await new Promise((resolve) => fileStream.once("drain", resolve));
|
|
4286
|
+
}
|
|
4287
|
+
hash.update(value);
|
|
4288
|
+
downloaded += value.byteLength;
|
|
4289
|
+
onProgress?.(downloaded, contentLength);
|
|
4290
|
+
}
|
|
4291
|
+
} finally {
|
|
4292
|
+
fileStream.end();
|
|
4293
|
+
await new Promise((resolve, reject) => {
|
|
4294
|
+
fileStream.on("finish", resolve);
|
|
4295
|
+
fileStream.on("error", reject);
|
|
4296
|
+
});
|
|
4297
|
+
}
|
|
4298
|
+
const actualHash = hash.digest("hex");
|
|
4299
|
+
if (actualHash !== EXPECTED_SHA256) {
|
|
4300
|
+
unlinkSync3(tmpPath);
|
|
4301
|
+
throw new Error(
|
|
4302
|
+
`SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
|
|
4303
|
+
);
|
|
4304
|
+
}
|
|
4305
|
+
renameSync3(tmpPath, destPath);
|
|
4306
|
+
return destPath;
|
|
4307
|
+
}
|
|
4308
|
+
async function fileHash(filePath) {
|
|
4309
|
+
return new Promise((resolve, reject) => {
|
|
4310
|
+
const hash = createHash("sha256");
|
|
4311
|
+
const stream = createReadStream2(filePath);
|
|
4312
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
4313
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
4314
|
+
stream.on("error", reject);
|
|
4315
|
+
});
|
|
4316
|
+
}
|
|
4317
|
+
var GGUF_URL, EXPECTED_SHA256, EXPECTED_SIZE, LOCAL_FILENAME;
|
|
4318
|
+
var init_model_downloader = __esm({
|
|
4319
|
+
"src/lib/model-downloader.ts"() {
|
|
4320
|
+
"use strict";
|
|
4321
|
+
GGUF_URL = "https://huggingface.co/jinaai/jina-embeddings-v5-text-small-text-matching-GGUF/resolve/main/v5-small-text-matching-Q4_K_M.gguf";
|
|
4322
|
+
EXPECTED_SHA256 = "738555454772b436632c6bad5891aeaa38d414bd7d7185107caeb3b2d8f2d860";
|
|
4323
|
+
EXPECTED_SIZE = 396836064;
|
|
4324
|
+
LOCAL_FILENAME = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
4325
|
+
}
|
|
4326
|
+
});
|
|
4327
|
+
|
|
4328
|
+
// src/lib/embedder.ts
|
|
4329
|
+
var embedder_exports = {};
|
|
4330
|
+
__export(embedder_exports, {
|
|
4331
|
+
disposeEmbedder: () => disposeEmbedder,
|
|
4332
|
+
embed: () => embed,
|
|
4333
|
+
embedDirect: () => embedDirect,
|
|
4334
|
+
getEmbedder: () => getEmbedder
|
|
4335
|
+
});
|
|
4336
|
+
async function getEmbedder() {
|
|
4337
|
+
const ok = await connectEmbedDaemon();
|
|
4338
|
+
if (!ok) {
|
|
4339
|
+
throw new Error(
|
|
4340
|
+
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
4341
|
+
);
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
async function embed(text) {
|
|
4345
|
+
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
4346
|
+
const vector = await embedViaClient(text, priority);
|
|
4347
|
+
if (!vector) {
|
|
4348
|
+
throw new Error(
|
|
4349
|
+
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
4353
|
+
throw new Error(
|
|
4354
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
|
|
4355
|
+
);
|
|
4356
|
+
}
|
|
4357
|
+
return vector;
|
|
4358
|
+
}
|
|
4359
|
+
async function disposeEmbedder() {
|
|
4360
|
+
disconnectClient();
|
|
4361
|
+
}
|
|
4362
|
+
async function embedDirect(text) {
|
|
4363
|
+
const llamaCpp = await import("node-llama-cpp");
|
|
4364
|
+
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4365
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
4366
|
+
const path32 = await import("path");
|
|
4367
|
+
const modelPath = path32.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
4368
|
+
if (!existsSync22(modelPath)) {
|
|
4369
|
+
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
4370
|
+
}
|
|
4371
|
+
const llama = await llamaCpp.getLlama();
|
|
4372
|
+
const model = await llama.loadModel({ modelPath });
|
|
4373
|
+
const context = await model.createEmbeddingContext();
|
|
4374
|
+
try {
|
|
4375
|
+
const embedding = await context.getEmbeddingFor(text);
|
|
4376
|
+
const vector = Array.from(embedding.vector);
|
|
4377
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
4378
|
+
throw new Error(
|
|
4379
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
|
|
4380
|
+
);
|
|
4381
|
+
}
|
|
4382
|
+
return vector;
|
|
4383
|
+
} finally {
|
|
4384
|
+
await context.dispose();
|
|
4385
|
+
await model.dispose();
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
var init_embedder = __esm({
|
|
4389
|
+
"src/lib/embedder.ts"() {
|
|
4390
|
+
"use strict";
|
|
4391
|
+
init_memory();
|
|
4392
|
+
init_exe_daemon_client();
|
|
4393
|
+
}
|
|
4394
|
+
});
|
|
4395
|
+
|
|
4396
|
+
// src/lib/license.ts
|
|
4397
|
+
var license_exports = {};
|
|
4398
|
+
__export(license_exports, {
|
|
4399
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
4400
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
4401
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
4402
|
+
checkLicense: () => checkLicense,
|
|
4403
|
+
getCachedLicense: () => getCachedLicense,
|
|
4404
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
4405
|
+
loadDeviceId: () => loadDeviceId,
|
|
4406
|
+
loadLicense: () => loadLicense,
|
|
4407
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
4408
|
+
saveLicense: () => saveLicense,
|
|
4409
|
+
validateLicense: () => validateLicense
|
|
4410
|
+
});
|
|
4411
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync3 } from "fs";
|
|
4412
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4413
|
+
import path11 from "path";
|
|
4414
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
4415
|
+
function loadDeviceId() {
|
|
4416
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
4417
|
+
try {
|
|
4418
|
+
if (existsSync10(deviceJsonPath)) {
|
|
4419
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
4420
|
+
if (data.deviceId) return data.deviceId;
|
|
4421
|
+
}
|
|
4422
|
+
} catch {
|
|
4423
|
+
}
|
|
4424
|
+
try {
|
|
4425
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
4426
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
4427
|
+
if (id2) return id2;
|
|
4428
|
+
}
|
|
4429
|
+
} catch {
|
|
4430
|
+
}
|
|
4431
|
+
const id = randomUUID2();
|
|
4432
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
4433
|
+
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
4434
|
+
return id;
|
|
4435
|
+
}
|
|
4436
|
+
function loadLicense() {
|
|
4437
|
+
try {
|
|
4438
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
4439
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
4440
|
+
} catch {
|
|
4441
|
+
return null;
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
function saveLicense(apiKey) {
|
|
4445
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
4446
|
+
writeFileSync2(LICENSE_PATH, apiKey.trim(), "utf8");
|
|
4447
|
+
}
|
|
4448
|
+
async function verifyLicenseJwt(token) {
|
|
4449
|
+
try {
|
|
4450
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4451
|
+
const { payload } = await jwtVerify(token, key, {
|
|
4452
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
4453
|
+
});
|
|
4454
|
+
const plan = payload.plan ?? "free";
|
|
4455
|
+
const email = payload.sub ?? "";
|
|
4456
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4457
|
+
return {
|
|
4458
|
+
valid: true,
|
|
4459
|
+
plan,
|
|
4460
|
+
email,
|
|
4461
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4462
|
+
deviceLimit: limits.devices,
|
|
4463
|
+
employeeLimit: limits.employees,
|
|
4464
|
+
memoryLimit: limits.memories
|
|
4465
|
+
};
|
|
4466
|
+
} catch {
|
|
4467
|
+
return null;
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
async function getCachedLicense() {
|
|
4471
|
+
try {
|
|
4472
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4473
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
4474
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
4475
|
+
return await verifyLicenseJwt(raw.token);
|
|
4476
|
+
} catch {
|
|
4477
|
+
return null;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
function readCachedToken() {
|
|
4481
|
+
try {
|
|
4482
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4483
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
4484
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
4485
|
+
} catch {
|
|
4486
|
+
return null;
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
function cacheResponse(token) {
|
|
4490
|
+
try {
|
|
4491
|
+
writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
4492
|
+
} catch {
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4496
|
+
const did = deviceId ?? loadDeviceId();
|
|
4497
|
+
try {
|
|
4498
|
+
const res = await fetch(`${API_BASE}/auth/activate`, {
|
|
4499
|
+
method: "POST",
|
|
4500
|
+
headers: { "Content-Type": "application/json" },
|
|
4501
|
+
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
4502
|
+
signal: AbortSignal.timeout(1e4)
|
|
4503
|
+
});
|
|
4504
|
+
if (res.ok) {
|
|
4505
|
+
const data = await res.json();
|
|
4506
|
+
if (data.error === "device_limit_exceeded") {
|
|
4507
|
+
const cached2 = await getCachedLicense();
|
|
4508
|
+
if (cached2) return cached2;
|
|
4509
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
4510
|
+
}
|
|
4511
|
+
if (data.token) {
|
|
4512
|
+
cacheResponse(data.token);
|
|
4513
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4514
|
+
if (verified) return verified;
|
|
4515
|
+
}
|
|
4516
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4517
|
+
return {
|
|
4518
|
+
valid: data.valid,
|
|
4519
|
+
plan: data.plan,
|
|
4520
|
+
email: data.email,
|
|
4521
|
+
expiresAt: data.expiresAt,
|
|
4522
|
+
deviceLimit: limits.devices,
|
|
4523
|
+
employeeLimit: limits.employees,
|
|
4524
|
+
memoryLimit: limits.memories
|
|
4525
|
+
};
|
|
4526
|
+
}
|
|
4527
|
+
const cached = await getCachedLicense();
|
|
4528
|
+
if (cached) return cached;
|
|
4529
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
4530
|
+
} catch {
|
|
4531
|
+
const cached = await getCachedLicense();
|
|
4532
|
+
if (cached) return cached;
|
|
4533
|
+
return FREE_LICENSE;
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
async function checkLicense() {
|
|
4537
|
+
const key = loadLicense();
|
|
4538
|
+
if (!key) return FREE_LICENSE;
|
|
4539
|
+
const cached = await getCachedLicense();
|
|
4540
|
+
if (cached) return cached;
|
|
4541
|
+
const deviceId = loadDeviceId();
|
|
4542
|
+
return validateLicense(key, deviceId);
|
|
4543
|
+
}
|
|
4544
|
+
function isFeatureAllowed(license, feature) {
|
|
4545
|
+
switch (feature) {
|
|
4546
|
+
case "cloud_sync":
|
|
4547
|
+
case "external_agents":
|
|
4548
|
+
case "wiki":
|
|
4549
|
+
return license.plan !== "free";
|
|
4550
|
+
case "unlimited_employees":
|
|
4551
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
function mirrorLicenseKey(apiKey) {
|
|
4555
|
+
const trimmed = apiKey.trim();
|
|
4556
|
+
if (!trimmed) return;
|
|
4557
|
+
saveLicense(trimmed);
|
|
4558
|
+
}
|
|
4559
|
+
async function assertVpsLicense(opts) {
|
|
4560
|
+
const env = opts?.env ?? process.env;
|
|
4561
|
+
const inProduction = env.NODE_ENV === "production";
|
|
4562
|
+
if (!opts?.force && !inProduction) {
|
|
4563
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
4564
|
+
}
|
|
4565
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
4566
|
+
if (envKey) {
|
|
4567
|
+
saveLicense(envKey);
|
|
4568
|
+
}
|
|
4569
|
+
const apiKey = envKey ?? loadLicense();
|
|
4570
|
+
if (!apiKey) {
|
|
4571
|
+
throw new Error(
|
|
4572
|
+
"License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
|
|
4573
|
+
);
|
|
4574
|
+
}
|
|
4575
|
+
const deviceId = loadDeviceId();
|
|
4576
|
+
let backendResponse = null;
|
|
4577
|
+
let explicitRejection = false;
|
|
4578
|
+
let transientFailure = false;
|
|
4579
|
+
try {
|
|
4580
|
+
const res = await fetch(`${API_BASE}/auth/activate`, {
|
|
4581
|
+
method: "POST",
|
|
4582
|
+
headers: { "Content-Type": "application/json" },
|
|
4583
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4584
|
+
signal: AbortSignal.timeout(1e4)
|
|
4585
|
+
});
|
|
4586
|
+
if (res.ok) {
|
|
4587
|
+
backendResponse = await res.json();
|
|
4588
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
4589
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4590
|
+
explicitRejection = true;
|
|
4591
|
+
} else {
|
|
4592
|
+
transientFailure = true;
|
|
4593
|
+
}
|
|
4594
|
+
} catch {
|
|
4595
|
+
transientFailure = true;
|
|
4596
|
+
}
|
|
4597
|
+
if (backendResponse?.valid) {
|
|
4598
|
+
if (backendResponse.token) {
|
|
4599
|
+
cacheResponse(backendResponse.token);
|
|
4600
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
4601
|
+
if (verified) return verified;
|
|
4602
|
+
}
|
|
4603
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
4604
|
+
return {
|
|
4605
|
+
valid: true,
|
|
4606
|
+
plan: backendResponse.plan,
|
|
4607
|
+
email: backendResponse.email,
|
|
4608
|
+
expiresAt: backendResponse.expiresAt,
|
|
4609
|
+
deviceLimit: limits.devices,
|
|
4610
|
+
employeeLimit: limits.employees,
|
|
4611
|
+
memoryLimit: limits.memories
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
if (explicitRejection) {
|
|
4615
|
+
throw new Error(
|
|
4616
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
4617
|
+
);
|
|
4618
|
+
}
|
|
4619
|
+
if (!transientFailure) {
|
|
4620
|
+
throw new Error(
|
|
4621
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
|
|
4622
|
+
);
|
|
4623
|
+
}
|
|
4624
|
+
const fresh = await getCachedLicense();
|
|
4625
|
+
if (fresh && fresh.valid) return fresh;
|
|
4626
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
4627
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
4628
|
+
try {
|
|
4629
|
+
const token = readCachedToken();
|
|
4630
|
+
if (token) {
|
|
4631
|
+
const payloadB64 = token.split(".")[1];
|
|
4632
|
+
if (payloadB64) {
|
|
4633
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
4634
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
4635
|
+
if (Date.now() < expMs + graceMs) {
|
|
4636
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4637
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
4638
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
4639
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
4640
|
+
});
|
|
4641
|
+
const plan = verified.plan ?? "free";
|
|
4642
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4643
|
+
return {
|
|
4644
|
+
valid: true,
|
|
4645
|
+
plan,
|
|
4646
|
+
email: verified.sub ?? "",
|
|
4647
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
4648
|
+
deviceLimit: limits.devices,
|
|
4649
|
+
employeeLimit: limits.employees,
|
|
4650
|
+
memoryLimit: limits.memories
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4655
|
+
} catch {
|
|
4656
|
+
}
|
|
4657
|
+
throw new Error(
|
|
4658
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
|
|
4659
|
+
);
|
|
4660
|
+
}
|
|
4661
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
|
|
4662
|
+
var init_license = __esm({
|
|
4663
|
+
"src/lib/license.ts"() {
|
|
4664
|
+
"use strict";
|
|
4665
|
+
init_config();
|
|
4666
|
+
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
4667
|
+
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
4668
|
+
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
4669
|
+
API_BASE = "https://askexe.com/cloud";
|
|
4670
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4671
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
4672
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
4673
|
+
-----END PUBLIC KEY-----`;
|
|
4674
|
+
LICENSE_JWT_ALG = "ES256";
|
|
4675
|
+
PLAN_LIMITS = {
|
|
4676
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
4677
|
+
pro: { devices: 2, employees: 5, memories: 1e5 },
|
|
4678
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
4679
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
4680
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
4681
|
+
};
|
|
4682
|
+
FREE_LICENSE = {
|
|
4683
|
+
valid: true,
|
|
4684
|
+
plan: "free",
|
|
4685
|
+
email: "",
|
|
4686
|
+
expiresAt: null,
|
|
4687
|
+
deviceLimit: 1,
|
|
4688
|
+
employeeLimit: 1,
|
|
4689
|
+
memoryLimit: 5e3
|
|
4690
|
+
};
|
|
4315
4691
|
}
|
|
4316
4692
|
});
|
|
4317
4693
|
|
|
@@ -4324,17 +4700,17 @@ __export(identity_exports, {
|
|
|
4324
4700
|
listIdentities: () => listIdentities,
|
|
4325
4701
|
updateIdentity: () => updateIdentity
|
|
4326
4702
|
});
|
|
4327
|
-
import { existsSync as
|
|
4703
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
4328
4704
|
import { readdirSync } from "fs";
|
|
4329
|
-
import
|
|
4705
|
+
import path12 from "path";
|
|
4330
4706
|
import { createHash as createHash2 } from "crypto";
|
|
4331
4707
|
function ensureDir() {
|
|
4332
|
-
if (!
|
|
4708
|
+
if (!existsSync11(IDENTITY_DIR)) {
|
|
4333
4709
|
mkdirSync4(IDENTITY_DIR, { recursive: true });
|
|
4334
4710
|
}
|
|
4335
4711
|
}
|
|
4336
4712
|
function identityPath(agentId) {
|
|
4337
|
-
return
|
|
4713
|
+
return path12.join(IDENTITY_DIR, `${agentId}.md`);
|
|
4338
4714
|
}
|
|
4339
4715
|
function parseFrontmatter(raw) {
|
|
4340
4716
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -4375,8 +4751,8 @@ function contentHash(content) {
|
|
|
4375
4751
|
}
|
|
4376
4752
|
function getIdentity(agentId) {
|
|
4377
4753
|
const filePath = identityPath(agentId);
|
|
4378
|
-
if (!
|
|
4379
|
-
const raw =
|
|
4754
|
+
if (!existsSync11(filePath)) return null;
|
|
4755
|
+
const raw = readFileSync7(filePath, "utf-8");
|
|
4380
4756
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
4381
4757
|
return {
|
|
4382
4758
|
agentId,
|
|
@@ -4390,7 +4766,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
4390
4766
|
ensureDir();
|
|
4391
4767
|
const filePath = identityPath(agentId);
|
|
4392
4768
|
const hash = contentHash(content);
|
|
4393
|
-
|
|
4769
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
4394
4770
|
try {
|
|
4395
4771
|
const client = getClient();
|
|
4396
4772
|
await client.execute({
|
|
@@ -4446,7 +4822,7 @@ var init_identity = __esm({
|
|
|
4446
4822
|
"use strict";
|
|
4447
4823
|
init_config();
|
|
4448
4824
|
init_database();
|
|
4449
|
-
IDENTITY_DIR =
|
|
4825
|
+
IDENTITY_DIR = path12.join(EXE_AI_DIR, "identity");
|
|
4450
4826
|
}
|
|
4451
4827
|
});
|
|
4452
4828
|
|
|
@@ -4470,6 +4846,7 @@ function getTemplateForTitle(title) {
|
|
|
4470
4846
|
if (t.includes("engineer") || t.includes("developer")) return IDENTITY_TEMPLATES["principal-engineer"];
|
|
4471
4847
|
if (t.includes("content") || t.includes("production")) return IDENTITY_TEMPLATES["content-specialist"];
|
|
4472
4848
|
if (t.includes("ai") || t.includes("product lead") || t.includes("specialist") && !t.includes("content")) return IDENTITY_TEMPLATES["ai-specialist"];
|
|
4849
|
+
if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
|
|
4473
4850
|
return null;
|
|
4474
4851
|
}
|
|
4475
4852
|
var POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
|
|
@@ -4552,6 +4929,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
|
|
|
4552
4929
|
- **create_task** \u2014 assign work to specialists with clear specs
|
|
4553
4930
|
- **update_task / close_task** \u2014 finalize reviews, mark work done
|
|
4554
4931
|
- **store_behavior** \u2014 record corrections as behavioral rules (p0/p1/p2)
|
|
4932
|
+
- **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
|
|
4555
4933
|
- **get_identity** \u2014 read any agent's identity for coordination
|
|
4556
4934
|
- **send_message** \u2014 direct intercom to employees
|
|
4557
4935
|
|
|
@@ -4915,6 +5293,55 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
|
|
|
4915
5293
|
- Every recommendation includes cost/quality/latency tradeoff analysis
|
|
4916
5294
|
- Separate experimental from production-ready \u2014 label clearly
|
|
4917
5295
|
- If you can't verify, say so explicitly: "Couldn't verify because X"
|
|
5296
|
+
`,
|
|
5297
|
+
"staff-code-reviewer": `---
|
|
5298
|
+
role: staff-code-reviewer
|
|
5299
|
+
title: Staff Code Reviewer & System Auditor
|
|
5300
|
+
agent_id: bob
|
|
5301
|
+
org_level: specialist
|
|
5302
|
+
created_by: system
|
|
5303
|
+
updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5304
|
+
---
|
|
5305
|
+
## Identity
|
|
5306
|
+
|
|
5307
|
+
You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defense before code ships to customers. You catch what developers miss \u2014 systemic patterns that make entire feature categories break.
|
|
5308
|
+
|
|
5309
|
+
## The 7 Audit Patterns (MANDATORY)
|
|
5310
|
+
|
|
5311
|
+
1. **"Works on dev, breaks on user install"** \u2014 scoped paths, npm resolution, deps
|
|
5312
|
+
2. **"Two code paths, one untested"** \u2014 binary symlink vs /exe-call, verify BOTH
|
|
5313
|
+
3. **"Case sensitivity kills non-technical users"** \u2014 normalize all user inputs
|
|
5314
|
+
4. **"Hardcoded names in runtime logic"** \u2014 grep for employee names, must use roles
|
|
5315
|
+
5. **"Installer doesn't self-heal"** \u2014 npm update must auto-fix stale hooks/paths
|
|
5316
|
+
6. **"Data written but invisible"** \u2014 agent_id mismatch between writer and reader
|
|
5317
|
+
7. **"Partial fixes miss inline refs"** \u2014 before/after grep count is mandatory
|
|
5318
|
+
|
|
5319
|
+
## Method
|
|
5320
|
+
|
|
5321
|
+
1. Read actual source code
|
|
5322
|
+
2. Send to Codex MCP for sweep
|
|
5323
|
+
3. Validate against ARCHITECTURE.md
|
|
5324
|
+
4. Trace identity chain with CUSTOM-NAMED employee ("jarvis" as CTO)
|
|
5325
|
+
5. Before/after grep count for every fix
|
|
5326
|
+
6. Structured report: PASS/FAIL per item
|
|
5327
|
+
|
|
5328
|
+
## Tools
|
|
5329
|
+
|
|
5330
|
+
- **Codex MCP** \u2014 first tool for every review
|
|
5331
|
+
- **recall_my_memory / ask_team_memory** \u2014 past audit findings
|
|
5332
|
+
- **store_behavior** \u2014 record new patterns
|
|
5333
|
+
- **update_task** \u2014 mark reviews done with structured findings
|
|
5334
|
+
- **create_task** \u2014 assign fixes to the CTO
|
|
5335
|
+
|
|
5336
|
+
## Completion Workflow
|
|
5337
|
+
|
|
5338
|
+
1. Read the task brief and understand the audit scope
|
|
5339
|
+
2. Run the audit using all 7 patterns
|
|
5340
|
+
3. Write report to exe/output/ with file:line references
|
|
5341
|
+
4. Fix findings yourself if possible
|
|
5342
|
+
5. Call **update_task** with status "done" and finding count
|
|
5343
|
+
6. Call **store_memory** with audit summary
|
|
5344
|
+
7. Check for next task \u2014 auto-chain
|
|
4918
5345
|
`
|
|
4919
5346
|
};
|
|
4920
5347
|
}
|
|
@@ -4927,9 +5354,9 @@ __export(setup_wizard_exports, {
|
|
|
4927
5354
|
validateModel: () => validateModel
|
|
4928
5355
|
});
|
|
4929
5356
|
import crypto3 from "crypto";
|
|
4930
|
-
import { existsSync as
|
|
5357
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
4931
5358
|
import os4 from "os";
|
|
4932
|
-
import
|
|
5359
|
+
import path13 from "path";
|
|
4933
5360
|
import { createInterface as createInterface2 } from "readline";
|
|
4934
5361
|
function ask(rl, prompt) {
|
|
4935
5362
|
return new Promise((resolve) => {
|
|
@@ -4970,7 +5397,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
4970
5397
|
rl.close();
|
|
4971
5398
|
return;
|
|
4972
5399
|
}
|
|
4973
|
-
if (
|
|
5400
|
+
if (existsSync12(LEGACY_LANCE_PATH)) {
|
|
4974
5401
|
log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
|
|
4975
5402
|
log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
|
|
4976
5403
|
log(" The old directory will not be modified or deleted.");
|
|
@@ -5038,10 +5465,10 @@ async function runSetupWizard(opts = {}) {
|
|
|
5038
5465
|
await saveConfig(config);
|
|
5039
5466
|
log("");
|
|
5040
5467
|
try {
|
|
5041
|
-
const claudeJsonPath =
|
|
5468
|
+
const claudeJsonPath = path13.join(os4.homedir(), ".claude.json");
|
|
5042
5469
|
let claudeJson = {};
|
|
5043
5470
|
try {
|
|
5044
|
-
claudeJson = JSON.parse(
|
|
5471
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
5045
5472
|
} catch {
|
|
5046
5473
|
}
|
|
5047
5474
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5050,7 +5477,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
5050
5477
|
if (!projects[dir]) projects[dir] = {};
|
|
5051
5478
|
projects[dir].hasTrustDialogAccepted = true;
|
|
5052
5479
|
}
|
|
5053
|
-
|
|
5480
|
+
writeFileSync4(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5054
5481
|
} catch {
|
|
5055
5482
|
}
|
|
5056
5483
|
const {
|
|
@@ -5095,9 +5522,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
5095
5522
|
const cooIdentityContent = getIdentityTemplate("coo");
|
|
5096
5523
|
if (cooIdentityContent) {
|
|
5097
5524
|
const cooIdPath = identityPath2(cooName);
|
|
5098
|
-
mkdirSync5(
|
|
5525
|
+
mkdirSync5(path13.dirname(cooIdPath), { recursive: true });
|
|
5099
5526
|
const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
|
|
5100
|
-
|
|
5527
|
+
writeFileSync4(cooIdPath, replaced, "utf-8");
|
|
5101
5528
|
}
|
|
5102
5529
|
registerBinSymlinks2(cooName);
|
|
5103
5530
|
createdEmployees.push({ name: cooName, role: "COO" });
|
|
@@ -5179,9 +5606,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
5179
5606
|
const ctoIdentityContent = getIdentityTemplate("cto");
|
|
5180
5607
|
if (ctoIdentityContent) {
|
|
5181
5608
|
const ctoIdPath = identityPath2(ctoName);
|
|
5182
|
-
mkdirSync5(
|
|
5609
|
+
mkdirSync5(path13.dirname(ctoIdPath), { recursive: true });
|
|
5183
5610
|
const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
|
|
5184
|
-
|
|
5611
|
+
writeFileSync4(ctoIdPath, replaced, "utf-8");
|
|
5185
5612
|
}
|
|
5186
5613
|
registerBinSymlinks2(ctoName);
|
|
5187
5614
|
createdEmployees.push({ name: ctoName, role: "CTO" });
|
|
@@ -5206,9 +5633,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
5206
5633
|
const cmoIdentityContent = getIdentityTemplate("cmo");
|
|
5207
5634
|
if (cmoIdentityContent) {
|
|
5208
5635
|
const cmoIdPath = identityPath2(cmoName);
|
|
5209
|
-
mkdirSync5(
|
|
5636
|
+
mkdirSync5(path13.dirname(cmoIdPath), { recursive: true });
|
|
5210
5637
|
const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
|
|
5211
|
-
|
|
5638
|
+
writeFileSync4(cmoIdPath, replaced, "utf-8");
|
|
5212
5639
|
}
|
|
5213
5640
|
registerBinSymlinks2(cmoName);
|
|
5214
5641
|
createdEmployees.push({ name: cmoName, role: "CMO" });
|
|
@@ -9619,8 +10046,8 @@ var init_ErrorOverview = __esm({
|
|
|
9619
10046
|
"use strict";
|
|
9620
10047
|
init_Box();
|
|
9621
10048
|
init_Text();
|
|
9622
|
-
cleanupPath = (
|
|
9623
|
-
return
|
|
10049
|
+
cleanupPath = (path32) => {
|
|
10050
|
+
return path32?.replace(`file://${cwd()}/`, "");
|
|
9624
10051
|
};
|
|
9625
10052
|
stackUtils = new StackUtils({
|
|
9626
10053
|
cwd: cwd(),
|
|
@@ -11655,13 +12082,13 @@ __export(tmux_status_exports, {
|
|
|
11655
12082
|
parseActivity: () => parseActivity,
|
|
11656
12083
|
parseContextPercentage: () => parseContextPercentage
|
|
11657
12084
|
});
|
|
11658
|
-
import { execSync as
|
|
12085
|
+
import { execSync as execSync3 } from "child_process";
|
|
11659
12086
|
function inTmux() {
|
|
11660
12087
|
if (process.env.TMUX || process.env.TMUX_PANE) return true;
|
|
11661
12088
|
const term = process.env.TERM ?? "";
|
|
11662
12089
|
if (term.startsWith("tmux") || term.startsWith("screen")) return true;
|
|
11663
12090
|
try {
|
|
11664
|
-
|
|
12091
|
+
execSync3("tmux display-message -p '#{session_name}' 2>/dev/null", {
|
|
11665
12092
|
encoding: "utf8",
|
|
11666
12093
|
timeout: 2e3
|
|
11667
12094
|
});
|
|
@@ -11671,12 +12098,12 @@ function inTmux() {
|
|
|
11671
12098
|
try {
|
|
11672
12099
|
let pid = process.ppid;
|
|
11673
12100
|
for (let depth = 0; depth < 8 && pid > 1; depth++) {
|
|
11674
|
-
const comm =
|
|
12101
|
+
const comm = execSync3(`ps -p ${pid} -o comm= 2>/dev/null`, {
|
|
11675
12102
|
encoding: "utf8",
|
|
11676
12103
|
timeout: 1e3
|
|
11677
12104
|
}).trim();
|
|
11678
12105
|
if (/tmux/.test(comm)) return true;
|
|
11679
|
-
const ppid =
|
|
12106
|
+
const ppid = execSync3(`ps -p ${pid} -o ppid= 2>/dev/null`, {
|
|
11680
12107
|
encoding: "utf8",
|
|
11681
12108
|
timeout: 1e3
|
|
11682
12109
|
}).trim();
|
|
@@ -11689,7 +12116,7 @@ function inTmux() {
|
|
|
11689
12116
|
}
|
|
11690
12117
|
function listTmuxSessions() {
|
|
11691
12118
|
try {
|
|
11692
|
-
const out =
|
|
12119
|
+
const out = execSync3("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
|
|
11693
12120
|
encoding: "utf8",
|
|
11694
12121
|
timeout: 3e3
|
|
11695
12122
|
});
|
|
@@ -11700,7 +12127,7 @@ function listTmuxSessions() {
|
|
|
11700
12127
|
}
|
|
11701
12128
|
function capturePaneLines(windowName, lines = 10) {
|
|
11702
12129
|
try {
|
|
11703
|
-
const out =
|
|
12130
|
+
const out = execSync3(
|
|
11704
12131
|
`tmux capture-pane -t ${JSON.stringify(windowName)} -p 2>/dev/null | tail -${lines}`,
|
|
11705
12132
|
{ encoding: "utf8", timeout: 3e3 }
|
|
11706
12133
|
);
|
|
@@ -11711,7 +12138,7 @@ function capturePaneLines(windowName, lines = 10) {
|
|
|
11711
12138
|
}
|
|
11712
12139
|
function getPaneCwd(windowName) {
|
|
11713
12140
|
try {
|
|
11714
|
-
const out =
|
|
12141
|
+
const out = execSync3(
|
|
11715
12142
|
`tmux display-message -t ${JSON.stringify(windowName)} -p '#{pane_current_path}' 2>/dev/null`,
|
|
11716
12143
|
{ encoding: "utf8", timeout: 3e3 }
|
|
11717
12144
|
);
|
|
@@ -11722,7 +12149,7 @@ function getPaneCwd(windowName) {
|
|
|
11722
12149
|
}
|
|
11723
12150
|
function projectFromPath(dir) {
|
|
11724
12151
|
try {
|
|
11725
|
-
const root =
|
|
12152
|
+
const root = execSync3("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
|
|
11726
12153
|
encoding: "utf8",
|
|
11727
12154
|
timeout: 3e3
|
|
11728
12155
|
}).trim();
|
|
@@ -11791,7 +12218,7 @@ function getEmployeeStatuses(employeeNames) {
|
|
|
11791
12218
|
}
|
|
11792
12219
|
let paneAlive = true;
|
|
11793
12220
|
try {
|
|
11794
|
-
const paneStatus =
|
|
12221
|
+
const paneStatus = execSync3(
|
|
11795
12222
|
`tmux list-panes -t ${JSON.stringify(sessionName)} -F '#{pane_dead}' 2>/dev/null`,
|
|
11796
12223
|
{ encoding: "utf8", timeout: 3e3 }
|
|
11797
12224
|
).trim();
|
|
@@ -11955,11 +12382,11 @@ function Footer() {
|
|
|
11955
12382
|
} catch {
|
|
11956
12383
|
}
|
|
11957
12384
|
try {
|
|
11958
|
-
const { existsSync:
|
|
12385
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
11959
12386
|
const { join } = await import("path");
|
|
11960
12387
|
const home = process.env.HOME ?? "";
|
|
11961
12388
|
const pidPath = join(home, ".exe-os", "exed.pid");
|
|
11962
|
-
setDaemon(
|
|
12389
|
+
setDaemon(existsSync22(pidPath) ? "running" : "stopped");
|
|
11963
12390
|
} catch {
|
|
11964
12391
|
setDaemon("unknown");
|
|
11965
12392
|
}
|
|
@@ -11977,8 +12404,8 @@ function Footer() {
|
|
|
11977
12404
|
setSessions(allSessions.length);
|
|
11978
12405
|
if (!currentSession) {
|
|
11979
12406
|
try {
|
|
11980
|
-
const { execSync:
|
|
11981
|
-
const name =
|
|
12407
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
12408
|
+
const name = execSync13("tmux display-message -p '#{session_name}' 2>/dev/null", {
|
|
11982
12409
|
encoding: "utf8",
|
|
11983
12410
|
timeout: 2e3
|
|
11984
12411
|
}).trim();
|
|
@@ -13985,10 +14412,10 @@ var init_hooks = __esm({
|
|
|
13985
14412
|
});
|
|
13986
14413
|
|
|
13987
14414
|
// src/runtime/safety-checks.ts
|
|
13988
|
-
import
|
|
14415
|
+
import path14 from "path";
|
|
13989
14416
|
import os5 from "os";
|
|
13990
14417
|
function checkPathSafety(filePath) {
|
|
13991
|
-
const resolved =
|
|
14418
|
+
const resolved = path14.resolve(filePath);
|
|
13992
14419
|
for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
|
|
13993
14420
|
const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
|
|
13994
14421
|
if (matches) {
|
|
@@ -13998,7 +14425,7 @@ function checkPathSafety(filePath) {
|
|
|
13998
14425
|
return { safe: true, bypassImmune: true };
|
|
13999
14426
|
}
|
|
14000
14427
|
function checkReadPathSafety(filePath) {
|
|
14001
|
-
const resolved =
|
|
14428
|
+
const resolved = path14.resolve(filePath);
|
|
14002
14429
|
const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
|
|
14003
14430
|
(p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
|
|
14004
14431
|
);
|
|
@@ -14024,11 +14451,11 @@ var init_safety_checks = __esm({
|
|
|
14024
14451
|
reason: "Git config can set hooks and command execution"
|
|
14025
14452
|
},
|
|
14026
14453
|
{
|
|
14027
|
-
pattern: (p) => p.startsWith(
|
|
14454
|
+
pattern: (p) => p.startsWith(path14.join(HOME, ".claude")),
|
|
14028
14455
|
reason: "Claude configuration files are protected"
|
|
14029
14456
|
},
|
|
14030
14457
|
{
|
|
14031
|
-
pattern: (p) => p.startsWith(
|
|
14458
|
+
pattern: (p) => p.startsWith(path14.join(HOME, ".exe-os")),
|
|
14032
14459
|
reason: "exe-os configuration files are protected"
|
|
14033
14460
|
},
|
|
14034
14461
|
{
|
|
@@ -14045,7 +14472,7 @@ var init_safety_checks = __esm({
|
|
|
14045
14472
|
},
|
|
14046
14473
|
{
|
|
14047
14474
|
pattern: (p) => {
|
|
14048
|
-
const name =
|
|
14475
|
+
const name = path14.basename(p);
|
|
14049
14476
|
return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
|
|
14050
14477
|
},
|
|
14051
14478
|
reason: "Shell configuration files can execute arbitrary code on login"
|
|
@@ -14072,7 +14499,7 @@ __export(file_read_exports, {
|
|
|
14072
14499
|
FileReadTool: () => FileReadTool
|
|
14073
14500
|
});
|
|
14074
14501
|
import fs3 from "fs/promises";
|
|
14075
|
-
import
|
|
14502
|
+
import path15 from "path";
|
|
14076
14503
|
import { z } from "zod";
|
|
14077
14504
|
function isBinary(buf) {
|
|
14078
14505
|
for (let i = 0; i < buf.length; i++) {
|
|
@@ -14108,7 +14535,7 @@ var init_file_read = __esm({
|
|
|
14108
14535
|
return { behavior: "allow" };
|
|
14109
14536
|
},
|
|
14110
14537
|
async call(input, context) {
|
|
14111
|
-
const filePath =
|
|
14538
|
+
const filePath = path15.isAbsolute(input.file_path) ? input.file_path : path15.resolve(context.cwd, input.file_path);
|
|
14112
14539
|
let stat2;
|
|
14113
14540
|
try {
|
|
14114
14541
|
stat2 = await fs3.stat(filePath);
|
|
@@ -14148,7 +14575,7 @@ __export(glob_exports, {
|
|
|
14148
14575
|
GlobTool: () => GlobTool
|
|
14149
14576
|
});
|
|
14150
14577
|
import fs4 from "fs/promises";
|
|
14151
|
-
import
|
|
14578
|
+
import path16 from "path";
|
|
14152
14579
|
import { z as z2 } from "zod";
|
|
14153
14580
|
async function walkDir(dir, maxDepth = 10) {
|
|
14154
14581
|
const results = [];
|
|
@@ -14164,7 +14591,7 @@ async function walkDir(dir, maxDepth = 10) {
|
|
|
14164
14591
|
if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
|
|
14165
14592
|
continue;
|
|
14166
14593
|
}
|
|
14167
|
-
const fullPath =
|
|
14594
|
+
const fullPath = path16.join(current, entry.name);
|
|
14168
14595
|
if (entry.isDirectory()) {
|
|
14169
14596
|
await walk(fullPath, depth + 1);
|
|
14170
14597
|
} else {
|
|
@@ -14198,11 +14625,11 @@ var init_glob = __esm({
|
|
|
14198
14625
|
inputSchema: inputSchema2,
|
|
14199
14626
|
isReadOnly: true,
|
|
14200
14627
|
async call(input, context) {
|
|
14201
|
-
const baseDir = input.path ?
|
|
14628
|
+
const baseDir = input.path ? path16.isAbsolute(input.path) ? input.path : path16.resolve(context.cwd, input.path) : context.cwd;
|
|
14202
14629
|
try {
|
|
14203
14630
|
const entries = await walkDir(baseDir);
|
|
14204
14631
|
const matched = entries.filter(
|
|
14205
|
-
(e) => simpleGlobMatch(
|
|
14632
|
+
(e) => simpleGlobMatch(path16.relative(baseDir, e.path), input.pattern)
|
|
14206
14633
|
);
|
|
14207
14634
|
matched.sort((a, b) => b.mtime - a.mtime);
|
|
14208
14635
|
if (matched.length === 0) {
|
|
@@ -14228,7 +14655,7 @@ __export(grep_exports, {
|
|
|
14228
14655
|
});
|
|
14229
14656
|
import { spawn as spawn2 } from "child_process";
|
|
14230
14657
|
import fs5 from "fs/promises";
|
|
14231
|
-
import
|
|
14658
|
+
import path17 from "path";
|
|
14232
14659
|
import { z as z3 } from "zod";
|
|
14233
14660
|
function runRipgrep(input, searchPath, context) {
|
|
14234
14661
|
return new Promise((resolve, reject) => {
|
|
@@ -14277,7 +14704,7 @@ async function nodeGrep(input, searchPath) {
|
|
|
14277
14704
|
}
|
|
14278
14705
|
for (const entry of entries) {
|
|
14279
14706
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
14280
|
-
const fullPath =
|
|
14707
|
+
const fullPath = path17.join(dir, entry.name);
|
|
14281
14708
|
if (entry.isDirectory()) {
|
|
14282
14709
|
await walk(fullPath);
|
|
14283
14710
|
} else {
|
|
@@ -14323,7 +14750,7 @@ var init_grep = __esm({
|
|
|
14323
14750
|
inputSchema: inputSchema3,
|
|
14324
14751
|
isReadOnly: true,
|
|
14325
14752
|
async call(input, context) {
|
|
14326
|
-
const searchPath = input.path ?
|
|
14753
|
+
const searchPath = input.path ? path17.isAbsolute(input.path) ? input.path : path17.resolve(context.cwd, input.path) : context.cwd;
|
|
14327
14754
|
try {
|
|
14328
14755
|
const result = await runRipgrep(input, searchPath, context);
|
|
14329
14756
|
return result;
|
|
@@ -14348,7 +14775,7 @@ __export(file_write_exports, {
|
|
|
14348
14775
|
FileWriteTool: () => FileWriteTool
|
|
14349
14776
|
});
|
|
14350
14777
|
import fs6 from "fs/promises";
|
|
14351
|
-
import
|
|
14778
|
+
import path18 from "path";
|
|
14352
14779
|
import { z as z4 } from "zod";
|
|
14353
14780
|
var inputSchema4, FileWriteTool;
|
|
14354
14781
|
var init_file_write = __esm({
|
|
@@ -14376,8 +14803,8 @@ var init_file_write = __esm({
|
|
|
14376
14803
|
return { behavior: "allow" };
|
|
14377
14804
|
},
|
|
14378
14805
|
async call(input, context) {
|
|
14379
|
-
const filePath =
|
|
14380
|
-
const dir =
|
|
14806
|
+
const filePath = path18.isAbsolute(input.file_path) ? input.file_path : path18.resolve(context.cwd, input.file_path);
|
|
14807
|
+
const dir = path18.dirname(filePath);
|
|
14381
14808
|
await fs6.mkdir(dir, { recursive: true });
|
|
14382
14809
|
await fs6.writeFile(filePath, input.content, "utf-8");
|
|
14383
14810
|
return {
|
|
@@ -14395,7 +14822,7 @@ __export(file_edit_exports, {
|
|
|
14395
14822
|
FileEditTool: () => FileEditTool
|
|
14396
14823
|
});
|
|
14397
14824
|
import fs7 from "fs/promises";
|
|
14398
|
-
import
|
|
14825
|
+
import path19 from "path";
|
|
14399
14826
|
import { z as z5 } from "zod";
|
|
14400
14827
|
function countOccurrences(haystack, needle) {
|
|
14401
14828
|
let count = 0;
|
|
@@ -14436,7 +14863,7 @@ var init_file_edit = __esm({
|
|
|
14436
14863
|
return { behavior: "allow" };
|
|
14437
14864
|
},
|
|
14438
14865
|
async call(input, context) {
|
|
14439
|
-
const filePath =
|
|
14866
|
+
const filePath = path19.isAbsolute(input.file_path) ? input.file_path : path19.resolve(context.cwd, input.file_path);
|
|
14440
14867
|
let content;
|
|
14441
14868
|
try {
|
|
14442
14869
|
content = await fs7.readFile(filePath, "utf-8");
|
|
@@ -14673,13 +15100,13 @@ __export(session_registry_exports, {
|
|
|
14673
15100
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
14674
15101
|
registerSession: () => registerSession
|
|
14675
15102
|
});
|
|
14676
|
-
import { readFileSync as
|
|
14677
|
-
import { execSync as
|
|
14678
|
-
import
|
|
15103
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync14 } from "fs";
|
|
15104
|
+
import { execSync as execSync4 } from "child_process";
|
|
15105
|
+
import path20 from "path";
|
|
14679
15106
|
import os6 from "os";
|
|
14680
15107
|
function registerSession(entry) {
|
|
14681
|
-
const dir =
|
|
14682
|
-
if (!
|
|
15108
|
+
const dir = path20.dirname(REGISTRY_PATH);
|
|
15109
|
+
if (!existsSync14(dir)) {
|
|
14683
15110
|
mkdirSync6(dir, { recursive: true });
|
|
14684
15111
|
}
|
|
14685
15112
|
const sessions = listSessions();
|
|
@@ -14689,11 +15116,11 @@ function registerSession(entry) {
|
|
|
14689
15116
|
} else {
|
|
14690
15117
|
sessions.push(entry);
|
|
14691
15118
|
}
|
|
14692
|
-
|
|
15119
|
+
writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
14693
15120
|
}
|
|
14694
15121
|
function listSessions() {
|
|
14695
15122
|
try {
|
|
14696
|
-
const raw =
|
|
15123
|
+
const raw = readFileSync10(REGISTRY_PATH, "utf8");
|
|
14697
15124
|
return JSON.parse(raw);
|
|
14698
15125
|
} catch {
|
|
14699
15126
|
return [];
|
|
@@ -14704,7 +15131,7 @@ function pruneStaleSessions() {
|
|
|
14704
15131
|
if (sessions.length === 0) return 0;
|
|
14705
15132
|
let liveSessions = [];
|
|
14706
15133
|
try {
|
|
14707
|
-
liveSessions =
|
|
15134
|
+
liveSessions = execSync4("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
|
|
14708
15135
|
encoding: "utf8"
|
|
14709
15136
|
}).trim().split("\n").filter(Boolean);
|
|
14710
15137
|
} catch {
|
|
@@ -14714,7 +15141,7 @@ function pruneStaleSessions() {
|
|
|
14714
15141
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
14715
15142
|
const pruned = sessions.length - alive.length;
|
|
14716
15143
|
if (pruned > 0) {
|
|
14717
|
-
|
|
15144
|
+
writeFileSync5(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
14718
15145
|
}
|
|
14719
15146
|
return pruned;
|
|
14720
15147
|
}
|
|
@@ -14722,7 +15149,7 @@ var REGISTRY_PATH;
|
|
|
14722
15149
|
var init_session_registry = __esm({
|
|
14723
15150
|
"src/lib/session-registry.ts"() {
|
|
14724
15151
|
"use strict";
|
|
14725
|
-
REGISTRY_PATH =
|
|
15152
|
+
REGISTRY_PATH = path20.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
14726
15153
|
}
|
|
14727
15154
|
});
|
|
14728
15155
|
|
|
@@ -14762,15 +15189,15 @@ function CommandCenterView({
|
|
|
14762
15189
|
const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
|
|
14763
15190
|
const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
|
|
14764
15191
|
const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
|
|
14765
|
-
const { readFileSync:
|
|
15192
|
+
const { readFileSync: readFileSync18, existsSync: existsSync22 } = await import("fs");
|
|
14766
15193
|
const { join } = await import("path");
|
|
14767
|
-
const { homedir:
|
|
14768
|
-
const configPath = join(
|
|
15194
|
+
const { homedir: homedir3 } = await import("os");
|
|
15195
|
+
const configPath = join(homedir3(), ".exe-os", "config.json");
|
|
14769
15196
|
let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
|
|
14770
15197
|
let providerConfigs = {};
|
|
14771
|
-
if (
|
|
15198
|
+
if (existsSync22(configPath)) {
|
|
14772
15199
|
try {
|
|
14773
|
-
const raw = JSON.parse(
|
|
15200
|
+
const raw = JSON.parse(readFileSync18(configPath, "utf8"));
|
|
14774
15201
|
if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
|
|
14775
15202
|
if (raw.providers && typeof raw.providers === "object") {
|
|
14776
15203
|
providerConfigs = raw.providers;
|
|
@@ -14828,10 +15255,10 @@ function CommandCenterView({
|
|
|
14828
15255
|
registry.register(BashTool2);
|
|
14829
15256
|
let agentRole = "CTO";
|
|
14830
15257
|
try {
|
|
14831
|
-
const markerDir = join(
|
|
15258
|
+
const markerDir = join(homedir3(), ".exe-os", "session-cache");
|
|
14832
15259
|
const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
|
|
14833
15260
|
for (const f of agentFiles) {
|
|
14834
|
-
const data = JSON.parse(
|
|
15261
|
+
const data = JSON.parse(readFileSync18(join(markerDir, f), "utf8"));
|
|
14835
15262
|
if (data.agentRole) {
|
|
14836
15263
|
agentRole = data.agentRole;
|
|
14837
15264
|
break;
|
|
@@ -15001,7 +15428,7 @@ function CommandCenterView({
|
|
|
15001
15428
|
const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
|
|
15002
15429
|
const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
|
|
15003
15430
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
15004
|
-
const { existsSync:
|
|
15431
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
15005
15432
|
const { join } = await import("path");
|
|
15006
15433
|
const registry = listSessions2();
|
|
15007
15434
|
const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
|
|
@@ -15050,7 +15477,7 @@ function CommandCenterView({
|
|
|
15050
15477
|
}
|
|
15051
15478
|
const totalCount = 1 + employeeNames.length;
|
|
15052
15479
|
const memoryCount = projectMemoryCounts.get(projectName) ?? 0;
|
|
15053
|
-
const hasGit = projectDir ?
|
|
15480
|
+
const hasGit = projectDir ? existsSync22(join(projectDir, ".git")) : false;
|
|
15054
15481
|
const hasActivity = memoryCount > 0;
|
|
15055
15482
|
let type = "automation";
|
|
15056
15483
|
if (hasGit && hasActivity) type = "code";
|
|
@@ -15074,7 +15501,7 @@ function CommandCenterView({
|
|
|
15074
15501
|
const activeProjectNames = new Set(projectList.map((p) => p.projectName));
|
|
15075
15502
|
for (const dir of knownDirs) {
|
|
15076
15503
|
const name = dir.split("/").filter(Boolean).pop() ?? "";
|
|
15077
|
-
if (activeProjectNames.has(name) || !
|
|
15504
|
+
if (activeProjectNames.has(name) || !existsSync22(dir) || !existsSync22(join(dir, ".git"))) continue;
|
|
15078
15505
|
if ((projectMemoryCounts.get(name) ?? 0) > 0) continue;
|
|
15079
15506
|
projectList.push({
|
|
15080
15507
|
projectName: name,
|
|
@@ -15148,7 +15575,7 @@ function CommandCenterView({
|
|
|
15148
15575
|
}
|
|
15149
15576
|
try {
|
|
15150
15577
|
const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
|
|
15151
|
-
setHealth((h) => ({ ...h, daemon:
|
|
15578
|
+
setHealth((h) => ({ ...h, daemon: existsSync22(pidPath) ? "running" : "stopped" }));
|
|
15152
15579
|
} catch {
|
|
15153
15580
|
}
|
|
15154
15581
|
try {
|
|
@@ -15393,8 +15820,8 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
|
|
|
15393
15820
|
}
|
|
15394
15821
|
const capture = () => {
|
|
15395
15822
|
try {
|
|
15396
|
-
const { execSync:
|
|
15397
|
-
const output =
|
|
15823
|
+
const { execSync: execSync13 } = __require("child_process");
|
|
15824
|
+
const output = execSync13(
|
|
15398
15825
|
`tmux capture-pane -t ${JSON.stringify(sessionName)} -p -e 2>/dev/null | tail -${CAPTURE_LINES}`,
|
|
15399
15826
|
{ encoding: "utf8", timeout: 3e3 }
|
|
15400
15827
|
);
|
|
@@ -15418,8 +15845,8 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
|
|
|
15418
15845
|
if (key.return) {
|
|
15419
15846
|
if (!demo && inputBuffer.trim()) {
|
|
15420
15847
|
try {
|
|
15421
|
-
const { execSync:
|
|
15422
|
-
|
|
15848
|
+
const { execSync: execSync13 } = __require("child_process");
|
|
15849
|
+
execSync13(
|
|
15423
15850
|
`tmux send-keys -t ${JSON.stringify(sessionName)} ${JSON.stringify(inputBuffer)} Enter`,
|
|
15424
15851
|
{ timeout: 2e3 }
|
|
15425
15852
|
);
|
|
@@ -15427,8 +15854,8 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
|
|
|
15427
15854
|
}
|
|
15428
15855
|
} else if (!demo) {
|
|
15429
15856
|
try {
|
|
15430
|
-
const { execSync:
|
|
15431
|
-
|
|
15857
|
+
const { execSync: execSync13 } = __require("child_process");
|
|
15858
|
+
execSync13(`tmux send-keys -t ${JSON.stringify(sessionName)} Enter`, { timeout: 2e3 });
|
|
15432
15859
|
} catch {
|
|
15433
15860
|
}
|
|
15434
15861
|
}
|
|
@@ -15567,12 +15994,14 @@ var init_task_router = __esm({
|
|
|
15567
15994
|
},
|
|
15568
15995
|
tierRules: {
|
|
15569
15996
|
junior: {
|
|
15570
|
-
eligible: [
|
|
15997
|
+
eligible: [],
|
|
15998
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
15571
15999
|
reviewRequired: false,
|
|
15572
16000
|
manualOnly: false
|
|
15573
16001
|
},
|
|
15574
16002
|
standard: {
|
|
15575
|
-
eligible: [
|
|
16003
|
+
eligible: [],
|
|
16004
|
+
// resolved dynamically from roster (Principal Engineer role)
|
|
15576
16005
|
reviewRequired: false,
|
|
15577
16006
|
manualOnly: false
|
|
15578
16007
|
},
|
|
@@ -15593,13 +16022,13 @@ var init_task_router = __esm({
|
|
|
15593
16022
|
});
|
|
15594
16023
|
|
|
15595
16024
|
// src/lib/session-key.ts
|
|
15596
|
-
import { execSync as
|
|
16025
|
+
import { execSync as execSync5 } from "child_process";
|
|
15597
16026
|
function getSessionKey() {
|
|
15598
16027
|
if (_cached) return _cached;
|
|
15599
16028
|
let pid = process.ppid;
|
|
15600
16029
|
for (let i = 0; i < 10; i++) {
|
|
15601
16030
|
try {
|
|
15602
|
-
const info =
|
|
16031
|
+
const info = execSync5(`ps -p ${pid} -o ppid=,comm=`, {
|
|
15603
16032
|
encoding: "utf8",
|
|
15604
16033
|
timeout: 2e3
|
|
15605
16034
|
}).trim();
|
|
@@ -15743,14 +16172,14 @@ var init_transport = __esm({
|
|
|
15743
16172
|
});
|
|
15744
16173
|
|
|
15745
16174
|
// src/lib/cc-agent-support.ts
|
|
15746
|
-
import { execSync as
|
|
16175
|
+
import { execSync as execSync6 } from "child_process";
|
|
15747
16176
|
function _resetCcAgentSupportCache() {
|
|
15748
16177
|
_cachedSupport = null;
|
|
15749
16178
|
}
|
|
15750
16179
|
function claudeSupportsAgentFlag() {
|
|
15751
16180
|
if (_cachedSupport !== null) return _cachedSupport;
|
|
15752
16181
|
try {
|
|
15753
|
-
const helpOutput =
|
|
16182
|
+
const helpOutput = execSync6("claude --help 2>&1", {
|
|
15754
16183
|
encoding: "utf-8",
|
|
15755
16184
|
timeout: 5e3
|
|
15756
16185
|
});
|
|
@@ -15793,17 +16222,17 @@ var init_provider_table = __esm({
|
|
|
15793
16222
|
});
|
|
15794
16223
|
|
|
15795
16224
|
// src/lib/intercom-queue.ts
|
|
15796
|
-
import { readFileSync as
|
|
15797
|
-
import
|
|
16225
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, renameSync as renameSync4, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
|
|
16226
|
+
import path21 from "path";
|
|
15798
16227
|
import os7 from "os";
|
|
15799
16228
|
function ensureDir2() {
|
|
15800
|
-
const dir =
|
|
15801
|
-
if (!
|
|
16229
|
+
const dir = path21.dirname(QUEUE_PATH);
|
|
16230
|
+
if (!existsSync15(dir)) mkdirSync7(dir, { recursive: true });
|
|
15802
16231
|
}
|
|
15803
16232
|
function readQueue() {
|
|
15804
16233
|
try {
|
|
15805
|
-
if (!
|
|
15806
|
-
return JSON.parse(
|
|
16234
|
+
if (!existsSync15(QUEUE_PATH)) return [];
|
|
16235
|
+
return JSON.parse(readFileSync11(QUEUE_PATH, "utf8"));
|
|
15807
16236
|
} catch {
|
|
15808
16237
|
return [];
|
|
15809
16238
|
}
|
|
@@ -15811,8 +16240,8 @@ function readQueue() {
|
|
|
15811
16240
|
function writeQueue(queue) {
|
|
15812
16241
|
ensureDir2();
|
|
15813
16242
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
15814
|
-
|
|
15815
|
-
|
|
16243
|
+
writeFileSync6(tmp, JSON.stringify(queue, null, 2));
|
|
16244
|
+
renameSync4(tmp, QUEUE_PATH);
|
|
15816
16245
|
}
|
|
15817
16246
|
function queueIntercom(targetSession, reason) {
|
|
15818
16247
|
const queue = readQueue();
|
|
@@ -15835,19 +16264,19 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
15835
16264
|
var init_intercom_queue = __esm({
|
|
15836
16265
|
"src/lib/intercom-queue.ts"() {
|
|
15837
16266
|
"use strict";
|
|
15838
|
-
QUEUE_PATH =
|
|
16267
|
+
QUEUE_PATH = path21.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
15839
16268
|
TTL_MS = 60 * 60 * 1e3;
|
|
15840
|
-
INTERCOM_LOG =
|
|
16269
|
+
INTERCOM_LOG = path21.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
15841
16270
|
}
|
|
15842
16271
|
});
|
|
15843
16272
|
|
|
15844
16273
|
// src/lib/plan-limits.ts
|
|
15845
|
-
import { readFileSync as
|
|
15846
|
-
import
|
|
16274
|
+
import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
|
|
16275
|
+
import path22 from "path";
|
|
15847
16276
|
function getLicenseSync() {
|
|
15848
16277
|
try {
|
|
15849
|
-
if (!
|
|
15850
|
-
const raw = JSON.parse(
|
|
16278
|
+
if (!existsSync16(CACHE_PATH2)) return freeLicense();
|
|
16279
|
+
const raw = JSON.parse(readFileSync12(CACHE_PATH2, "utf8"));
|
|
15851
16280
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
15852
16281
|
const parts = raw.token.split(".");
|
|
15853
16282
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -15885,8 +16314,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
15885
16314
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
15886
16315
|
let count = 0;
|
|
15887
16316
|
try {
|
|
15888
|
-
if (
|
|
15889
|
-
const raw =
|
|
16317
|
+
if (existsSync16(filePath)) {
|
|
16318
|
+
const raw = readFileSync12(filePath, "utf8");
|
|
15890
16319
|
const employees = JSON.parse(raw);
|
|
15891
16320
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
15892
16321
|
}
|
|
@@ -15915,19 +16344,19 @@ var init_plan_limits = __esm({
|
|
|
15915
16344
|
this.name = "PlanLimitError";
|
|
15916
16345
|
}
|
|
15917
16346
|
};
|
|
15918
|
-
CACHE_PATH2 =
|
|
16347
|
+
CACHE_PATH2 = path22.join(EXE_AI_DIR, "license-cache.json");
|
|
15919
16348
|
}
|
|
15920
16349
|
});
|
|
15921
16350
|
|
|
15922
16351
|
// src/lib/notifications.ts
|
|
15923
16352
|
import crypto4 from "crypto";
|
|
15924
|
-
import
|
|
16353
|
+
import path23 from "path";
|
|
15925
16354
|
import os8 from "os";
|
|
15926
16355
|
import {
|
|
15927
|
-
readFileSync as
|
|
16356
|
+
readFileSync as readFileSync13,
|
|
15928
16357
|
readdirSync as readdirSync2,
|
|
15929
|
-
unlinkSync as
|
|
15930
|
-
existsSync as
|
|
16358
|
+
unlinkSync as unlinkSync4,
|
|
16359
|
+
existsSync as existsSync17,
|
|
15931
16360
|
rmdirSync
|
|
15932
16361
|
} from "fs";
|
|
15933
16362
|
async function writeNotification(notification) {
|
|
@@ -16007,10 +16436,10 @@ var init_session_kill_telemetry = __esm({
|
|
|
16007
16436
|
|
|
16008
16437
|
// src/lib/tasks-crud.ts
|
|
16009
16438
|
import crypto6 from "crypto";
|
|
16010
|
-
import
|
|
16011
|
-
import { execSync as
|
|
16439
|
+
import path24 from "path";
|
|
16440
|
+
import { execSync as execSync7 } from "child_process";
|
|
16012
16441
|
import { mkdir as mkdir6, writeFile as writeFile5, appendFile } from "fs/promises";
|
|
16013
|
-
import { existsSync as
|
|
16442
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
|
|
16014
16443
|
async function writeCheckpoint(input) {
|
|
16015
16444
|
const client = getClient();
|
|
16016
16445
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -16140,8 +16569,8 @@ async function createTaskCore(input) {
|
|
|
16140
16569
|
}
|
|
16141
16570
|
if (input.baseDir) {
|
|
16142
16571
|
try {
|
|
16143
|
-
await mkdir6(
|
|
16144
|
-
await mkdir6(
|
|
16572
|
+
await mkdir6(path24.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
16573
|
+
await mkdir6(path24.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
16145
16574
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
16146
16575
|
await ensureGitignoreExe(input.baseDir);
|
|
16147
16576
|
} catch {
|
|
@@ -16241,12 +16670,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
16241
16670
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
16242
16671
|
try {
|
|
16243
16672
|
const since = new Date(taskCreatedAt).toISOString();
|
|
16244
|
-
const branch =
|
|
16673
|
+
const branch = execSync7(
|
|
16245
16674
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
16246
16675
|
{ encoding: "utf8", timeout: 3e3 }
|
|
16247
16676
|
).trim();
|
|
16248
16677
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
16249
|
-
const commitCount =
|
|
16678
|
+
const commitCount = execSync7(
|
|
16250
16679
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
16251
16680
|
{ encoding: "utf8", timeout: 5e3 }
|
|
16252
16681
|
).trim();
|
|
@@ -16349,9 +16778,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
16349
16778
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
16350
16779
|
}
|
|
16351
16780
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
16352
|
-
const archPath =
|
|
16781
|
+
const archPath = path24.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
16353
16782
|
try {
|
|
16354
|
-
if (
|
|
16783
|
+
if (existsSync18(archPath)) return;
|
|
16355
16784
|
const template = [
|
|
16356
16785
|
`# ${projectName} \u2014 System Architecture`,
|
|
16357
16786
|
"",
|
|
@@ -16384,10 +16813,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
16384
16813
|
}
|
|
16385
16814
|
}
|
|
16386
16815
|
async function ensureGitignoreExe(baseDir) {
|
|
16387
|
-
const gitignorePath =
|
|
16816
|
+
const gitignorePath = path24.join(baseDir, ".gitignore");
|
|
16388
16817
|
try {
|
|
16389
|
-
if (
|
|
16390
|
-
const content =
|
|
16818
|
+
if (existsSync18(gitignorePath)) {
|
|
16819
|
+
const content = readFileSync14(gitignorePath, "utf-8");
|
|
16391
16820
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
16392
16821
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
16393
16822
|
} else {
|
|
@@ -16407,8 +16836,8 @@ var init_tasks_crud = __esm({
|
|
|
16407
16836
|
});
|
|
16408
16837
|
|
|
16409
16838
|
// src/lib/tasks-review.ts
|
|
16410
|
-
import
|
|
16411
|
-
import { existsSync as
|
|
16839
|
+
import path25 from "path";
|
|
16840
|
+
import { existsSync as existsSync19, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
16412
16841
|
async function countPendingReviews() {
|
|
16413
16842
|
const client = getClient();
|
|
16414
16843
|
const result = await client.execute({
|
|
@@ -16528,11 +16957,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
16528
16957
|
);
|
|
16529
16958
|
}
|
|
16530
16959
|
try {
|
|
16531
|
-
const cacheDir =
|
|
16532
|
-
if (
|
|
16960
|
+
const cacheDir = path25.join(EXE_AI_DIR, "session-cache");
|
|
16961
|
+
if (existsSync19(cacheDir)) {
|
|
16533
16962
|
for (const f of readdirSync3(cacheDir)) {
|
|
16534
16963
|
if (f.startsWith("review-notified-")) {
|
|
16535
|
-
|
|
16964
|
+
unlinkSync5(path25.join(cacheDir, f));
|
|
16536
16965
|
}
|
|
16537
16966
|
}
|
|
16538
16967
|
}
|
|
@@ -16553,7 +16982,7 @@ var init_tasks_review = __esm({
|
|
|
16553
16982
|
});
|
|
16554
16983
|
|
|
16555
16984
|
// src/lib/tasks-chain.ts
|
|
16556
|
-
import
|
|
16985
|
+
import path26 from "path";
|
|
16557
16986
|
import { readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
|
|
16558
16987
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
16559
16988
|
const client = getClient();
|
|
@@ -16569,7 +16998,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
16569
16998
|
});
|
|
16570
16999
|
for (const ur of unblockedRows.rows) {
|
|
16571
17000
|
try {
|
|
16572
|
-
const ubFile =
|
|
17001
|
+
const ubFile = path26.join(baseDir, String(ur.task_file));
|
|
16573
17002
|
let ubContent = await readFile5(ubFile, "utf-8");
|
|
16574
17003
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
16575
17004
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -16634,34 +17063,34 @@ var init_tasks_chain = __esm({
|
|
|
16634
17063
|
});
|
|
16635
17064
|
|
|
16636
17065
|
// src/lib/project-name.ts
|
|
16637
|
-
import { execSync as
|
|
16638
|
-
import
|
|
17066
|
+
import { execSync as execSync8 } from "child_process";
|
|
17067
|
+
import path27 from "path";
|
|
16639
17068
|
function getProjectName(cwd2) {
|
|
16640
17069
|
const dir = cwd2 ?? process.cwd();
|
|
16641
17070
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
16642
17071
|
try {
|
|
16643
17072
|
let repoRoot;
|
|
16644
17073
|
try {
|
|
16645
|
-
const gitCommonDir =
|
|
17074
|
+
const gitCommonDir = execSync8("git rev-parse --path-format=absolute --git-common-dir", {
|
|
16646
17075
|
cwd: dir,
|
|
16647
17076
|
encoding: "utf8",
|
|
16648
17077
|
timeout: 2e3,
|
|
16649
17078
|
stdio: ["pipe", "pipe", "pipe"]
|
|
16650
17079
|
}).trim();
|
|
16651
|
-
repoRoot =
|
|
17080
|
+
repoRoot = path27.dirname(gitCommonDir);
|
|
16652
17081
|
} catch {
|
|
16653
|
-
repoRoot =
|
|
17082
|
+
repoRoot = execSync8("git rev-parse --show-toplevel", {
|
|
16654
17083
|
cwd: dir,
|
|
16655
17084
|
encoding: "utf8",
|
|
16656
17085
|
timeout: 2e3,
|
|
16657
17086
|
stdio: ["pipe", "pipe", "pipe"]
|
|
16658
17087
|
}).trim();
|
|
16659
17088
|
}
|
|
16660
|
-
_cached2 =
|
|
17089
|
+
_cached2 = path27.basename(repoRoot);
|
|
16661
17090
|
_cachedCwd = dir;
|
|
16662
17091
|
return _cached2;
|
|
16663
17092
|
} catch {
|
|
16664
|
-
_cached2 =
|
|
17093
|
+
_cached2 = path27.basename(dir);
|
|
16665
17094
|
_cachedCwd = dir;
|
|
16666
17095
|
return _cached2;
|
|
16667
17096
|
}
|
|
@@ -16763,7 +17192,7 @@ async function dispatchTaskToEmployee(input) {
|
|
|
16763
17192
|
} else {
|
|
16764
17193
|
const projectDir = input.projectDir ?? process.cwd();
|
|
16765
17194
|
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
16766
|
-
autoInstance: input.assignedTo
|
|
17195
|
+
autoInstance: isMultiInstance(input.assignedTo)
|
|
16767
17196
|
});
|
|
16768
17197
|
if (result.status === "failed") {
|
|
16769
17198
|
process.stderr.write(
|
|
@@ -16798,6 +17227,7 @@ var init_tasks_notify = __esm({
|
|
|
16798
17227
|
init_session_key();
|
|
16799
17228
|
init_notifications();
|
|
16800
17229
|
init_transport();
|
|
17230
|
+
init_employees();
|
|
16801
17231
|
}
|
|
16802
17232
|
});
|
|
16803
17233
|
|
|
@@ -17131,8 +17561,8 @@ __export(tasks_exports, {
|
|
|
17131
17561
|
updateTaskStatus: () => updateTaskStatus,
|
|
17132
17562
|
writeCheckpoint: () => writeCheckpoint
|
|
17133
17563
|
});
|
|
17134
|
-
import
|
|
17135
|
-
import { writeFileSync as
|
|
17564
|
+
import path28 from "path";
|
|
17565
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, unlinkSync as unlinkSync6 } from "fs";
|
|
17136
17566
|
async function createTask(input) {
|
|
17137
17567
|
const result = await createTaskCore(input);
|
|
17138
17568
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -17151,14 +17581,14 @@ async function updateTask(input) {
|
|
|
17151
17581
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
17152
17582
|
try {
|
|
17153
17583
|
const agent = String(row.assigned_to);
|
|
17154
|
-
const cacheDir =
|
|
17155
|
-
const cachePath =
|
|
17584
|
+
const cacheDir = path28.join(EXE_AI_DIR, "session-cache");
|
|
17585
|
+
const cachePath = path28.join(cacheDir, `current-task-${agent}.json`);
|
|
17156
17586
|
if (input.status === "in_progress") {
|
|
17157
17587
|
mkdirSync8(cacheDir, { recursive: true });
|
|
17158
|
-
|
|
17588
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
17159
17589
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
17160
17590
|
try {
|
|
17161
|
-
|
|
17591
|
+
unlinkSync6(cachePath);
|
|
17162
17592
|
} catch {
|
|
17163
17593
|
}
|
|
17164
17594
|
}
|
|
@@ -17551,6 +17981,7 @@ var init_capacity_monitor = __esm({
|
|
|
17551
17981
|
// src/lib/tmux-routing.ts
|
|
17552
17982
|
var tmux_routing_exports = {};
|
|
17553
17983
|
__export(tmux_routing_exports, {
|
|
17984
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
17554
17985
|
employeeSessionName: () => employeeSessionName,
|
|
17555
17986
|
ensureEmployee: () => ensureEmployee,
|
|
17556
17987
|
extractRootExe: () => extractRootExe,
|
|
@@ -17565,26 +17996,63 @@ __export(tmux_routing_exports, {
|
|
|
17565
17996
|
notifyParentExe: () => notifyParentExe,
|
|
17566
17997
|
parseParentExe: () => parseParentExe,
|
|
17567
17998
|
registerParentExe: () => registerParentExe,
|
|
17999
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
17568
18000
|
resolveExeSession: () => resolveExeSession,
|
|
17569
18001
|
sendIntercom: () => sendIntercom,
|
|
17570
18002
|
spawnEmployee: () => spawnEmployee,
|
|
17571
18003
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
17572
18004
|
});
|
|
17573
|
-
import { execFileSync as execFileSync3, execSync as
|
|
17574
|
-
import { readFileSync as
|
|
17575
|
-
import
|
|
18005
|
+
import { execFileSync as execFileSync3, execSync as execSync9 } from "child_process";
|
|
18006
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, existsSync as existsSync20, appendFileSync } from "fs";
|
|
18007
|
+
import path29 from "path";
|
|
17576
18008
|
import os9 from "os";
|
|
17577
18009
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
18010
|
+
import { unlinkSync as unlinkSync7 } from "fs";
|
|
18011
|
+
function spawnLockPath(sessionName) {
|
|
18012
|
+
return path29.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
18013
|
+
}
|
|
18014
|
+
function isProcessAlive(pid) {
|
|
18015
|
+
try {
|
|
18016
|
+
process.kill(pid, 0);
|
|
18017
|
+
return true;
|
|
18018
|
+
} catch {
|
|
18019
|
+
return false;
|
|
18020
|
+
}
|
|
18021
|
+
}
|
|
18022
|
+
function acquireSpawnLock2(sessionName) {
|
|
18023
|
+
if (!existsSync20(SPAWN_LOCK_DIR)) {
|
|
18024
|
+
mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
|
|
18025
|
+
}
|
|
18026
|
+
const lockFile = spawnLockPath(sessionName);
|
|
18027
|
+
if (existsSync20(lockFile)) {
|
|
18028
|
+
try {
|
|
18029
|
+
const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
|
|
18030
|
+
const age = Date.now() - lock.timestamp;
|
|
18031
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
18032
|
+
return false;
|
|
18033
|
+
}
|
|
18034
|
+
} catch {
|
|
18035
|
+
}
|
|
18036
|
+
}
|
|
18037
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
18038
|
+
return true;
|
|
18039
|
+
}
|
|
18040
|
+
function releaseSpawnLock2(sessionName) {
|
|
18041
|
+
try {
|
|
18042
|
+
unlinkSync7(spawnLockPath(sessionName));
|
|
18043
|
+
} catch {
|
|
18044
|
+
}
|
|
18045
|
+
}
|
|
17578
18046
|
function resolveBehaviorsExporterScript() {
|
|
17579
18047
|
try {
|
|
17580
18048
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
17581
|
-
const scriptPath =
|
|
17582
|
-
|
|
18049
|
+
const scriptPath = path29.join(
|
|
18050
|
+
path29.dirname(thisFile),
|
|
17583
18051
|
"..",
|
|
17584
18052
|
"bin",
|
|
17585
18053
|
"exe-export-behaviors.js"
|
|
17586
18054
|
);
|
|
17587
|
-
return
|
|
18055
|
+
return existsSync20(scriptPath) ? scriptPath : null;
|
|
17588
18056
|
} catch {
|
|
17589
18057
|
return null;
|
|
17590
18058
|
}
|
|
@@ -17625,12 +18093,12 @@ function extractRootExe(name) {
|
|
|
17625
18093
|
return match?.[1] ?? null;
|
|
17626
18094
|
}
|
|
17627
18095
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
17628
|
-
if (!
|
|
18096
|
+
if (!existsSync20(SESSION_CACHE)) {
|
|
17629
18097
|
mkdirSync9(SESSION_CACHE, { recursive: true });
|
|
17630
18098
|
}
|
|
17631
18099
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
17632
|
-
const filePath =
|
|
17633
|
-
|
|
18100
|
+
const filePath = path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
18101
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
17634
18102
|
parentExe: rootExe,
|
|
17635
18103
|
dispatchedBy: dispatchedBy || rootExe,
|
|
17636
18104
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -17638,7 +18106,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
17638
18106
|
}
|
|
17639
18107
|
function getParentExe(sessionKey) {
|
|
17640
18108
|
try {
|
|
17641
|
-
const data = JSON.parse(
|
|
18109
|
+
const data = JSON.parse(readFileSync15(path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
17642
18110
|
return data.parentExe || null;
|
|
17643
18111
|
} catch {
|
|
17644
18112
|
return null;
|
|
@@ -17646,8 +18114,8 @@ function getParentExe(sessionKey) {
|
|
|
17646
18114
|
}
|
|
17647
18115
|
function getDispatchedBy(sessionKey) {
|
|
17648
18116
|
try {
|
|
17649
|
-
const data = JSON.parse(
|
|
17650
|
-
|
|
18117
|
+
const data = JSON.parse(readFileSync15(
|
|
18118
|
+
path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
17651
18119
|
"utf8"
|
|
17652
18120
|
));
|
|
17653
18121
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -17673,10 +18141,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
17673
18141
|
}
|
|
17674
18142
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
17675
18143
|
const base = employeeSessionName(employeeName, exeSession);
|
|
17676
|
-
if (!isAlive(base)) return 0;
|
|
18144
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
17677
18145
|
for (let i = 2; i <= maxInstances; i++) {
|
|
17678
18146
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
17679
|
-
if (!isAlive(candidate)) return i;
|
|
18147
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
17680
18148
|
}
|
|
17681
18149
|
return null;
|
|
17682
18150
|
}
|
|
@@ -17708,16 +18176,16 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
17708
18176
|
}
|
|
17709
18177
|
function readDebounceState() {
|
|
17710
18178
|
try {
|
|
17711
|
-
if (!
|
|
17712
|
-
return JSON.parse(
|
|
18179
|
+
if (!existsSync20(DEBOUNCE_FILE)) return {};
|
|
18180
|
+
return JSON.parse(readFileSync15(DEBOUNCE_FILE, "utf8"));
|
|
17713
18181
|
} catch {
|
|
17714
18182
|
return {};
|
|
17715
18183
|
}
|
|
17716
18184
|
}
|
|
17717
18185
|
function writeDebounceState(state) {
|
|
17718
18186
|
try {
|
|
17719
|
-
if (!
|
|
17720
|
-
|
|
18187
|
+
if (!existsSync20(SESSION_CACHE)) mkdirSync9(SESSION_CACHE, { recursive: true });
|
|
18188
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
17721
18189
|
} catch {
|
|
17722
18190
|
}
|
|
17723
18191
|
}
|
|
@@ -17890,26 +18358,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17890
18358
|
const transport = getTransport();
|
|
17891
18359
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
17892
18360
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
17893
|
-
const logDir =
|
|
17894
|
-
const logFile =
|
|
17895
|
-
if (!
|
|
18361
|
+
const logDir = path29.join(os9.homedir(), ".exe-os", "session-logs");
|
|
18362
|
+
const logFile = path29.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
18363
|
+
if (!existsSync20(logDir)) {
|
|
17896
18364
|
mkdirSync9(logDir, { recursive: true });
|
|
17897
18365
|
}
|
|
17898
18366
|
transport.kill(sessionName);
|
|
17899
18367
|
let cleanupSuffix = "";
|
|
17900
18368
|
try {
|
|
17901
18369
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
17902
|
-
const cleanupScript =
|
|
17903
|
-
if (
|
|
18370
|
+
const cleanupScript = path29.join(path29.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
18371
|
+
if (existsSync20(cleanupScript)) {
|
|
17904
18372
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
17905
18373
|
}
|
|
17906
18374
|
} catch {
|
|
17907
18375
|
}
|
|
17908
18376
|
try {
|
|
17909
|
-
const claudeJsonPath =
|
|
18377
|
+
const claudeJsonPath = path29.join(os9.homedir(), ".claude.json");
|
|
17910
18378
|
let claudeJson = {};
|
|
17911
18379
|
try {
|
|
17912
|
-
claudeJson = JSON.parse(
|
|
18380
|
+
claudeJson = JSON.parse(readFileSync15(claudeJsonPath, "utf8"));
|
|
17913
18381
|
} catch {
|
|
17914
18382
|
}
|
|
17915
18383
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -17917,17 +18385,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17917
18385
|
const trustDir = opts?.cwd ?? projectDir;
|
|
17918
18386
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
17919
18387
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
17920
|
-
|
|
18388
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
17921
18389
|
} catch {
|
|
17922
18390
|
}
|
|
17923
18391
|
try {
|
|
17924
|
-
const settingsDir =
|
|
18392
|
+
const settingsDir = path29.join(os9.homedir(), ".claude", "projects");
|
|
17925
18393
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
17926
|
-
const projSettingsDir =
|
|
17927
|
-
const settingsPath =
|
|
18394
|
+
const projSettingsDir = path29.join(settingsDir, normalizedKey);
|
|
18395
|
+
const settingsPath = path29.join(projSettingsDir, "settings.json");
|
|
17928
18396
|
let settings = {};
|
|
17929
18397
|
try {
|
|
17930
|
-
settings = JSON.parse(
|
|
18398
|
+
settings = JSON.parse(readFileSync15(settingsPath, "utf8"));
|
|
17931
18399
|
} catch {
|
|
17932
18400
|
}
|
|
17933
18401
|
const perms = settings.permissions ?? {};
|
|
@@ -17956,7 +18424,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17956
18424
|
perms.allow = allow;
|
|
17957
18425
|
settings.permissions = perms;
|
|
17958
18426
|
mkdirSync9(projSettingsDir, { recursive: true });
|
|
17959
|
-
|
|
18427
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
17960
18428
|
}
|
|
17961
18429
|
} catch {
|
|
17962
18430
|
}
|
|
@@ -17968,7 +18436,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17968
18436
|
let behaviorsFlag = "";
|
|
17969
18437
|
let legacyFallbackWarned = false;
|
|
17970
18438
|
if (!useExeAgent && !useBinSymlink) {
|
|
17971
|
-
const identityPath2 =
|
|
18439
|
+
const identityPath2 = path29.join(
|
|
17972
18440
|
os9.homedir(),
|
|
17973
18441
|
".exe-os",
|
|
17974
18442
|
"identity",
|
|
@@ -17978,13 +18446,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17978
18446
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
17979
18447
|
if (hasAgentFlag) {
|
|
17980
18448
|
identityFlag = ` --agent ${employeeName}`;
|
|
17981
|
-
} else if (
|
|
18449
|
+
} else if (existsSync20(identityPath2)) {
|
|
17982
18450
|
identityFlag = ` --append-system-prompt-file ${identityPath2}`;
|
|
17983
18451
|
legacyFallbackWarned = true;
|
|
17984
18452
|
}
|
|
17985
18453
|
const behaviorsFile = exportBehaviorsSync(
|
|
17986
18454
|
employeeName,
|
|
17987
|
-
|
|
18455
|
+
path29.basename(spawnCwd),
|
|
17988
18456
|
sessionName
|
|
17989
18457
|
);
|
|
17990
18458
|
if (behaviorsFile) {
|
|
@@ -17999,16 +18467,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17999
18467
|
}
|
|
18000
18468
|
let sessionContextFlag = "";
|
|
18001
18469
|
try {
|
|
18002
|
-
const ctxDir =
|
|
18470
|
+
const ctxDir = path29.join(os9.homedir(), ".exe-os", "session-cache");
|
|
18003
18471
|
mkdirSync9(ctxDir, { recursive: true });
|
|
18004
|
-
const ctxFile =
|
|
18472
|
+
const ctxFile = path29.join(ctxDir, `session-context-${sessionName}.md`);
|
|
18005
18473
|
const ctxContent = [
|
|
18006
18474
|
`## Session Context`,
|
|
18007
18475
|
`You are running in tmux session: ${sessionName}.`,
|
|
18008
18476
|
`Your parent exe session is ${exeSession}.`,
|
|
18009
18477
|
`Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
|
|
18010
18478
|
].join("\n");
|
|
18011
|
-
|
|
18479
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
18012
18480
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
18013
18481
|
} catch {
|
|
18014
18482
|
}
|
|
@@ -18040,13 +18508,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
18040
18508
|
command: spawnCommand
|
|
18041
18509
|
});
|
|
18042
18510
|
if (spawnResult.error) {
|
|
18511
|
+
releaseSpawnLock2(sessionName);
|
|
18043
18512
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
18044
18513
|
}
|
|
18045
18514
|
transport.pipeLog(sessionName, logFile);
|
|
18046
18515
|
try {
|
|
18047
18516
|
const mySession = getMySession();
|
|
18048
|
-
const dispatchInfo =
|
|
18049
|
-
|
|
18517
|
+
const dispatchInfo = path29.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
18518
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
18050
18519
|
dispatchedBy: mySession,
|
|
18051
18520
|
rootExe: exeSession,
|
|
18052
18521
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
@@ -18057,7 +18526,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
18057
18526
|
let booted = false;
|
|
18058
18527
|
for (let i = 0; i < 30; i++) {
|
|
18059
18528
|
try {
|
|
18060
|
-
|
|
18529
|
+
execSync9("sleep 0.5");
|
|
18061
18530
|
} catch {
|
|
18062
18531
|
}
|
|
18063
18532
|
try {
|
|
@@ -18077,6 +18546,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
18077
18546
|
}
|
|
18078
18547
|
}
|
|
18079
18548
|
if (!booted) {
|
|
18549
|
+
releaseSpawnLock2(sessionName);
|
|
18080
18550
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
18081
18551
|
}
|
|
18082
18552
|
if (!useExeAgent) {
|
|
@@ -18093,9 +18563,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
18093
18563
|
pid: 0,
|
|
18094
18564
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18095
18565
|
});
|
|
18566
|
+
releaseSpawnLock2(sessionName);
|
|
18096
18567
|
return { sessionName };
|
|
18097
18568
|
}
|
|
18098
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
18569
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
18099
18570
|
var init_tmux_routing = __esm({
|
|
18100
18571
|
"src/lib/tmux-routing.ts"() {
|
|
18101
18572
|
"use strict";
|
|
@@ -18107,12 +18578,13 @@ var init_tmux_routing = __esm({
|
|
|
18107
18578
|
init_provider_table();
|
|
18108
18579
|
init_intercom_queue();
|
|
18109
18580
|
init_plan_limits();
|
|
18110
|
-
|
|
18581
|
+
SPAWN_LOCK_DIR = path29.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
18582
|
+
SESSION_CACHE = path29.join(os9.homedir(), ".exe-os", "session-cache");
|
|
18111
18583
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
18112
18584
|
VERIFY_PANE_LINES = 200;
|
|
18113
18585
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
18114
|
-
INTERCOM_LOG2 =
|
|
18115
|
-
DEBOUNCE_FILE =
|
|
18586
|
+
INTERCOM_LOG2 = path29.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
18587
|
+
DEBOUNCE_FILE = path29.join(SESSION_CACHE, "intercom-debounce.json");
|
|
18116
18588
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
18117
18589
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
18118
18590
|
}
|
|
@@ -18587,12 +19059,12 @@ function SessionsView({
|
|
|
18587
19059
|
return;
|
|
18588
19060
|
}
|
|
18589
19061
|
} else {
|
|
18590
|
-
const { execSync:
|
|
19062
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
18591
19063
|
const dir = projectDir || process.cwd();
|
|
18592
|
-
|
|
18593
|
-
|
|
19064
|
+
execSync13(`tmux new-session -d -s ${JSON.stringify(entry.sessionName)} -c ${JSON.stringify(dir)}`, { timeout: 5e3 });
|
|
19065
|
+
execSync13(`tmux send-keys -t ${JSON.stringify(entry.sessionName)} "claude --dangerously-skip-permissions" Enter`, { timeout: 3e3 });
|
|
18594
19066
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
18595
|
-
|
|
19067
|
+
execSync13(`tmux send-keys -t ${JSON.stringify(entry.sessionName)} "/exe" Enter`, { timeout: 3e3 });
|
|
18596
19068
|
}
|
|
18597
19069
|
const updated = { ...entry, status: "active", activity: "Starting...", attached: false };
|
|
18598
19070
|
setViewingEmployee(updated);
|
|
@@ -18692,7 +19164,7 @@ function SessionsView({
|
|
|
18692
19164
|
const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
|
|
18693
19165
|
const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
|
|
18694
19166
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
18695
|
-
const { execSync:
|
|
19167
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
18696
19168
|
if (!inTmux2()) {
|
|
18697
19169
|
setTmuxAvailable(false);
|
|
18698
19170
|
setProjects([]);
|
|
@@ -18701,7 +19173,7 @@ function SessionsView({
|
|
|
18701
19173
|
setTmuxAvailable(true);
|
|
18702
19174
|
const attachedMap = /* @__PURE__ */ new Map();
|
|
18703
19175
|
try {
|
|
18704
|
-
const out =
|
|
19176
|
+
const out = execSync13("tmux list-sessions -F '#{session_name}:#{session_attached}' 2>/dev/null", {
|
|
18705
19177
|
encoding: "utf8",
|
|
18706
19178
|
timeout: 3e3
|
|
18707
19179
|
});
|
|
@@ -19635,19 +20107,19 @@ function upsertConversation(conversations, platform, senderId, message) {
|
|
|
19635
20107
|
async function loadGatewayConfig() {
|
|
19636
20108
|
const state = { running: false, port: 3100, adapters: [], agents: [], gatewayUrl: "" };
|
|
19637
20109
|
try {
|
|
19638
|
-
const { execSync:
|
|
19639
|
-
const ps =
|
|
20110
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
20111
|
+
const ps = execSync13("pgrep -f exe-gateway 2>/dev/null", { encoding: "utf8", timeout: 3e3 });
|
|
19640
20112
|
state.running = ps.trim().length > 0;
|
|
19641
20113
|
} catch {
|
|
19642
20114
|
state.running = false;
|
|
19643
20115
|
}
|
|
19644
20116
|
try {
|
|
19645
|
-
const { existsSync:
|
|
20117
|
+
const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
|
|
19646
20118
|
const { join } = await import("path");
|
|
19647
20119
|
const home = process.env.HOME ?? "";
|
|
19648
20120
|
const configPath = join(home, ".exe-os", "gateway.json");
|
|
19649
|
-
if (
|
|
19650
|
-
const raw = JSON.parse(
|
|
20121
|
+
if (existsSync22(configPath)) {
|
|
20122
|
+
const raw = JSON.parse(readFileSync18(configPath, "utf8"));
|
|
19651
20123
|
state.port = raw.port ?? 3100;
|
|
19652
20124
|
state.gatewayUrl = raw.gatewayUrl ?? "";
|
|
19653
20125
|
if (raw.adapters) {
|
|
@@ -20086,10 +20558,10 @@ var init_Gateway = __esm({
|
|
|
20086
20558
|
});
|
|
20087
20559
|
|
|
20088
20560
|
// src/tui/utils/agent-status.ts
|
|
20089
|
-
import { execSync as
|
|
20561
|
+
import { execSync as execSync10 } from "child_process";
|
|
20090
20562
|
function getAgentStatus(agentId) {
|
|
20091
20563
|
try {
|
|
20092
|
-
const sessions =
|
|
20564
|
+
const sessions = execSync10("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
|
|
20093
20565
|
encoding: "utf8",
|
|
20094
20566
|
timeout: 2e3
|
|
20095
20567
|
}).trim().split("\n");
|
|
@@ -20100,7 +20572,7 @@ function getAgentStatus(agentId) {
|
|
|
20100
20572
|
return /^\d?-/.test(suffix) || /^\d+$/.test(suffix);
|
|
20101
20573
|
});
|
|
20102
20574
|
if (!agentSession) return { label: "offline", color: "gray" };
|
|
20103
|
-
const pane =
|
|
20575
|
+
const pane = execSync10(`tmux capture-pane -t "${agentSession}" -p 2>/dev/null | tail -3`, {
|
|
20104
20576
|
encoding: "utf8",
|
|
20105
20577
|
timeout: 2e3
|
|
20106
20578
|
});
|
|
@@ -20215,12 +20687,12 @@ function TeamView({ onBack }) {
|
|
|
20215
20687
|
setMembers(teamData);
|
|
20216
20688
|
setDbError(false);
|
|
20217
20689
|
try {
|
|
20218
|
-
const { existsSync:
|
|
20690
|
+
const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
|
|
20219
20691
|
const { join } = await import("path");
|
|
20220
20692
|
const home = process.env.HOME ?? "";
|
|
20221
20693
|
const gatewayConfig = join(home, ".exe-os", "gateway.json");
|
|
20222
|
-
if (
|
|
20223
|
-
const raw = JSON.parse(
|
|
20694
|
+
if (existsSync22(gatewayConfig)) {
|
|
20695
|
+
const raw = JSON.parse(readFileSync18(gatewayConfig, "utf8"));
|
|
20224
20696
|
if (raw.agents && raw.agents.length > 0) {
|
|
20225
20697
|
setExternals(raw.agents.map((a) => ({
|
|
20226
20698
|
name: a.name,
|
|
@@ -20395,8 +20867,8 @@ __export(wiki_client_exports, {
|
|
|
20395
20867
|
listDocuments: () => listDocuments,
|
|
20396
20868
|
listWorkspaces: () => listWorkspaces
|
|
20397
20869
|
});
|
|
20398
|
-
async function wikiFetch(config,
|
|
20399
|
-
const url = `${config.baseUrl}/api/v1${
|
|
20870
|
+
async function wikiFetch(config, path32, method = "GET", body) {
|
|
20871
|
+
const url = `${config.baseUrl}/api/v1${path32}`;
|
|
20400
20872
|
const headers = {
|
|
20401
20873
|
Authorization: `Bearer ${config.apiKey}`,
|
|
20402
20874
|
"Content-Type": "application/json"
|
|
@@ -20411,7 +20883,7 @@ async function wikiFetch(config, path31, method = "GET", body) {
|
|
|
20411
20883
|
signal: controller.signal
|
|
20412
20884
|
});
|
|
20413
20885
|
if (!response.ok) {
|
|
20414
|
-
throw new Error(`Wiki API ${method} ${
|
|
20886
|
+
throw new Error(`Wiki API ${method} ${path32}: ${response.status} ${response.statusText}`);
|
|
20415
20887
|
}
|
|
20416
20888
|
return response.json();
|
|
20417
20889
|
} finally {
|
|
@@ -20866,18 +21338,18 @@ function SettingsView({ onBack }) {
|
|
|
20866
21338
|
{ name: "Chutes", configured: !!process.env.CHUTES_API_KEY, detail: process.env.CHUTES_API_KEY ? "CHUTES_API_KEY set" : "not configured" }
|
|
20867
21339
|
];
|
|
20868
21340
|
try {
|
|
20869
|
-
const { execSync:
|
|
20870
|
-
|
|
21341
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
21342
|
+
execSync13("curl -s --max-time 1 http://localhost:11434/api/tags", { timeout: 2e3 });
|
|
20871
21343
|
providerList.push({ name: "Ollama", configured: true, detail: "localhost:11434" });
|
|
20872
21344
|
} catch {
|
|
20873
21345
|
providerList.push({ name: "Ollama", configured: false, detail: "not running" });
|
|
20874
21346
|
}
|
|
20875
21347
|
setProviders(providerList);
|
|
20876
21348
|
try {
|
|
20877
|
-
const { existsSync:
|
|
21349
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
20878
21350
|
const { join } = await import("path");
|
|
20879
21351
|
const home = process.env.HOME ?? "";
|
|
20880
|
-
const hasKey =
|
|
21352
|
+
const hasKey = existsSync22(join(home, ".exe-os", "master.key"));
|
|
20881
21353
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
20882
21354
|
const cfg = await loadConfig2();
|
|
20883
21355
|
setMemory({
|
|
@@ -20901,14 +21373,14 @@ function SettingsView({ onBack }) {
|
|
|
20901
21373
|
try {
|
|
20902
21374
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
20903
21375
|
const roster = await loadEmployees2();
|
|
20904
|
-
const { existsSync:
|
|
21376
|
+
const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
|
|
20905
21377
|
const { join } = await import("path");
|
|
20906
21378
|
const home = process.env.HOME ?? "";
|
|
20907
21379
|
const configPath = join(home, ".exe-os", "config.json");
|
|
20908
21380
|
let empConfig = {};
|
|
20909
|
-
if (
|
|
21381
|
+
if (existsSync22(configPath)) {
|
|
20910
21382
|
try {
|
|
20911
|
-
const raw = JSON.parse(
|
|
21383
|
+
const raw = JSON.parse(readFileSync18(configPath, "utf8"));
|
|
20912
21384
|
if (raw.employees && typeof raw.employees === "object") {
|
|
20913
21385
|
empConfig = raw.employees;
|
|
20914
21386
|
}
|
|
@@ -20923,15 +21395,15 @@ function SettingsView({ onBack }) {
|
|
|
20923
21395
|
} catch {
|
|
20924
21396
|
}
|
|
20925
21397
|
try {
|
|
20926
|
-
const { existsSync:
|
|
21398
|
+
const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
|
|
20927
21399
|
const { join } = await import("path");
|
|
20928
21400
|
const home = process.env.HOME ?? "";
|
|
20929
21401
|
const ccSettingsPath = join(home, ".claude", "settings.json");
|
|
20930
|
-
const installed =
|
|
21402
|
+
const installed = existsSync22(ccSettingsPath);
|
|
20931
21403
|
let hooksWired = false;
|
|
20932
21404
|
if (installed) {
|
|
20933
21405
|
try {
|
|
20934
|
-
const settings = JSON.parse(
|
|
21406
|
+
const settings = JSON.parse(readFileSync18(ccSettingsPath, "utf8"));
|
|
20935
21407
|
const hooks = settings.hooks;
|
|
20936
21408
|
if (hooks) {
|
|
20937
21409
|
hooksWired = Object.values(hooks).flat().some((h) => h.command?.includes("exe-os") || h.command?.includes("exe-mem"));
|
|
@@ -20943,13 +21415,13 @@ function SettingsView({ onBack }) {
|
|
|
20943
21415
|
} catch {
|
|
20944
21416
|
}
|
|
20945
21417
|
try {
|
|
20946
|
-
const { existsSync:
|
|
21418
|
+
const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
|
|
20947
21419
|
const { join } = await import("path");
|
|
20948
21420
|
const home = process.env.HOME ?? "";
|
|
20949
21421
|
const licensePath = join(home, ".exe-os", "license.json");
|
|
20950
|
-
if (
|
|
21422
|
+
if (existsSync22(licensePath)) {
|
|
20951
21423
|
try {
|
|
20952
|
-
const lic = JSON.parse(
|
|
21424
|
+
const lic = JSON.parse(readFileSync18(licensePath, "utf8"));
|
|
20953
21425
|
setLicense({ valid: lic.valid !== false, detail: lic.plan ?? "licensed" });
|
|
20954
21426
|
} catch {
|
|
20955
21427
|
setLicense({ valid: false, detail: "invalid license file" });
|
|
@@ -20960,11 +21432,11 @@ function SettingsView({ onBack }) {
|
|
|
20960
21432
|
} catch {
|
|
20961
21433
|
}
|
|
20962
21434
|
try {
|
|
20963
|
-
const { existsSync:
|
|
21435
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
20964
21436
|
const { join } = await import("path");
|
|
20965
21437
|
const home = process.env.HOME ?? "";
|
|
20966
21438
|
const cloudPath = join(home, ".exe-os", "cloud.json");
|
|
20967
|
-
if (
|
|
21439
|
+
if (existsSync22(cloudPath)) {
|
|
20968
21440
|
setCloudSync({ configured: true, detail: "configured" });
|
|
20969
21441
|
} else {
|
|
20970
21442
|
setCloudSync({ configured: false, detail: "not configured" });
|
|
@@ -21260,17 +21732,17 @@ var init_App2 = __esm({
|
|
|
21260
21732
|
});
|
|
21261
21733
|
|
|
21262
21734
|
// src/lib/update-check.ts
|
|
21263
|
-
import { execSync as
|
|
21264
|
-
import { readFileSync as
|
|
21265
|
-
import
|
|
21735
|
+
import { execSync as execSync11 } from "child_process";
|
|
21736
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
21737
|
+
import path30 from "path";
|
|
21266
21738
|
function getLocalVersion(packageRoot) {
|
|
21267
|
-
const pkgPath =
|
|
21268
|
-
const pkg = JSON.parse(
|
|
21739
|
+
const pkgPath = path30.join(packageRoot, "package.json");
|
|
21740
|
+
const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
21269
21741
|
return pkg.version;
|
|
21270
21742
|
}
|
|
21271
21743
|
function getRemoteVersion() {
|
|
21272
21744
|
try {
|
|
21273
|
-
const output =
|
|
21745
|
+
const output = execSync11("npm view @askexenow/exe-os version", {
|
|
21274
21746
|
encoding: "utf-8",
|
|
21275
21747
|
timeout: 15e3,
|
|
21276
21748
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -21308,7 +21780,7 @@ __export(update_exports, {
|
|
|
21308
21780
|
getLocalVersion: () => getLocalVersion,
|
|
21309
21781
|
getRemoteVersion: () => getRemoteVersion
|
|
21310
21782
|
});
|
|
21311
|
-
import { execSync as
|
|
21783
|
+
import { execSync as execSync12 } from "child_process";
|
|
21312
21784
|
import { createInterface as createInterface3 } from "readline";
|
|
21313
21785
|
var init_update = __esm({
|
|
21314
21786
|
async "src/bin/update.ts"() {
|
|
@@ -21339,7 +21811,7 @@ var init_update = __esm({
|
|
|
21339
21811
|
if (answer.toLowerCase() === "y") {
|
|
21340
21812
|
console.log("Updating...");
|
|
21341
21813
|
try {
|
|
21342
|
-
|
|
21814
|
+
execSync12("npm install -g @askexenow/exe-os@latest", { stdio: "inherit" });
|
|
21343
21815
|
console.log("Update complete!");
|
|
21344
21816
|
} catch {
|
|
21345
21817
|
console.error(
|
|
@@ -21355,8 +21827,8 @@ var init_update = __esm({
|
|
|
21355
21827
|
});
|
|
21356
21828
|
|
|
21357
21829
|
// src/bin/cli.ts
|
|
21358
|
-
import { existsSync as
|
|
21359
|
-
import
|
|
21830
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync9, readdirSync as readdirSync4, rmSync } from "fs";
|
|
21831
|
+
import path31 from "path";
|
|
21360
21832
|
import os10 from "os";
|
|
21361
21833
|
var args = process.argv.slice(2);
|
|
21362
21834
|
if (args.includes("--global")) {
|
|
@@ -21387,7 +21859,7 @@ if (args.includes("--global")) {
|
|
|
21387
21859
|
await runClaudeCheck();
|
|
21388
21860
|
break;
|
|
21389
21861
|
case "uninstall":
|
|
21390
|
-
await runClaudeUninstall();
|
|
21862
|
+
await runClaudeUninstall(args.slice(2));
|
|
21391
21863
|
break;
|
|
21392
21864
|
default:
|
|
21393
21865
|
await runClaudeInstall();
|
|
@@ -21403,6 +21875,10 @@ if (args.includes("--global")) {
|
|
|
21403
21875
|
console.error("Backfill failed:", err instanceof Error ? err.message : String(err));
|
|
21404
21876
|
process.exit(1);
|
|
21405
21877
|
}
|
|
21878
|
+
} else if (args[0] === "rename") {
|
|
21879
|
+
process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
|
|
21880
|
+
const { main: runRename } = await Promise.resolve().then(() => (init_exe_rename(), exe_rename_exports));
|
|
21881
|
+
await runRename();
|
|
21406
21882
|
} else if (args[0] === "--activate" || args[0] === "activate") {
|
|
21407
21883
|
await runActivate(args[1]);
|
|
21408
21884
|
} else if (args[0] === "setup" || args[0] === "-setup" || args[0] === "--setup") {
|
|
@@ -21426,12 +21902,12 @@ async function runClaudeInstall() {
|
|
|
21426
21902
|
}
|
|
21427
21903
|
}
|
|
21428
21904
|
async function runClaudeCheck() {
|
|
21429
|
-
const claudeDir =
|
|
21430
|
-
const settingsPath =
|
|
21431
|
-
const claudeJsonPath =
|
|
21905
|
+
const claudeDir = path31.join(os10.homedir(), ".claude");
|
|
21906
|
+
const settingsPath = path31.join(claudeDir, "settings.json");
|
|
21907
|
+
const claudeJsonPath = path31.join(os10.homedir(), ".claude.json");
|
|
21432
21908
|
let ok = true;
|
|
21433
|
-
if (
|
|
21434
|
-
const settings = JSON.parse(
|
|
21909
|
+
if (existsSync21(settingsPath)) {
|
|
21910
|
+
const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
|
|
21435
21911
|
const hasHooks = settings.hooks && Object.keys(settings.hooks).some((k) => {
|
|
21436
21912
|
const groups = settings.hooks[k];
|
|
21437
21913
|
return Array.isArray(groups) && groups.some((g) => {
|
|
@@ -21452,8 +21928,8 @@ async function runClaudeCheck() {
|
|
|
21452
21928
|
console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
|
|
21453
21929
|
ok = false;
|
|
21454
21930
|
}
|
|
21455
|
-
if (
|
|
21456
|
-
const claudeJson = JSON.parse(
|
|
21931
|
+
if (existsSync21(claudeJsonPath)) {
|
|
21932
|
+
const claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
|
|
21457
21933
|
const hasMcp = claudeJson.mcpServers && (claudeJson.mcpServers["exe-mem"] || claudeJson.mcpServers["exe-os"]);
|
|
21458
21934
|
if (hasMcp) {
|
|
21459
21935
|
console.log("\x1B[32m\u2713\x1B[0m MCP server configured in claude.json");
|
|
@@ -21467,11 +21943,11 @@ async function runClaudeCheck() {
|
|
|
21467
21943
|
console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
|
|
21468
21944
|
ok = false;
|
|
21469
21945
|
}
|
|
21470
|
-
const
|
|
21471
|
-
if (
|
|
21472
|
-
console.log("\x1B[32m\u2713\x1B[0m Slash
|
|
21946
|
+
const skillsDir = path31.join(claudeDir, "skills");
|
|
21947
|
+
if (existsSync21(skillsDir)) {
|
|
21948
|
+
console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
|
|
21473
21949
|
} else {
|
|
21474
|
-
console.log("\x1B[31m\u2717\x1B[0m Slash
|
|
21950
|
+
console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
|
|
21475
21951
|
ok = false;
|
|
21476
21952
|
}
|
|
21477
21953
|
if (!ok) {
|
|
@@ -21481,13 +21957,18 @@ async function runClaudeCheck() {
|
|
|
21481
21957
|
console.log("\nAll checks passed.");
|
|
21482
21958
|
}
|
|
21483
21959
|
}
|
|
21484
|
-
async function runClaudeUninstall() {
|
|
21485
|
-
const
|
|
21486
|
-
const
|
|
21487
|
-
const
|
|
21960
|
+
async function runClaudeUninstall(flags = []) {
|
|
21961
|
+
const dryRun = flags.includes("--dry-run");
|
|
21962
|
+
const purge = flags.includes("--purge");
|
|
21963
|
+
const homeDir = os10.homedir();
|
|
21964
|
+
const claudeDir = path31.join(homeDir, ".claude");
|
|
21965
|
+
const settingsPath = path31.join(claudeDir, "settings.json");
|
|
21966
|
+
const claudeJsonPath = path31.join(homeDir, ".claude.json");
|
|
21967
|
+
const exeOsDir = path31.join(homeDir, ".exe-os");
|
|
21488
21968
|
let removed = 0;
|
|
21489
|
-
|
|
21490
|
-
|
|
21969
|
+
const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
|
|
21970
|
+
if (existsSync21(settingsPath)) {
|
|
21971
|
+
const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
|
|
21491
21972
|
if (settings.hooks) {
|
|
21492
21973
|
for (const key of Object.keys(settings.hooks)) {
|
|
21493
21974
|
const groups = settings.hooks[key];
|
|
@@ -21507,37 +21988,176 @@ async function runClaudeUninstall() {
|
|
|
21507
21988
|
delete settings.hooks[key];
|
|
21508
21989
|
}
|
|
21509
21990
|
}
|
|
21510
|
-
|
|
21511
|
-
|
|
21512
|
-
|
|
21991
|
+
let permCount = 0;
|
|
21992
|
+
if (Array.isArray(settings.permissions?.allow)) {
|
|
21993
|
+
const before = settings.permissions.allow.length;
|
|
21994
|
+
settings.permissions.allow = settings.permissions.allow.filter(
|
|
21995
|
+
(p) => !p.startsWith("mcp__exe-mem__") && !p.startsWith("mcp__exe-os__")
|
|
21996
|
+
);
|
|
21997
|
+
permCount = before - settings.permissions.allow.length;
|
|
21998
|
+
}
|
|
21999
|
+
if (!dryRun) {
|
|
22000
|
+
writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
22001
|
+
}
|
|
22002
|
+
log("\u2713 Removed exe-os hooks from settings.json");
|
|
22003
|
+
if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
|
|
21513
22004
|
removed++;
|
|
21514
22005
|
}
|
|
21515
22006
|
}
|
|
21516
|
-
if (
|
|
21517
|
-
const claudeJson = JSON.parse(
|
|
22007
|
+
if (existsSync21(claudeJsonPath)) {
|
|
22008
|
+
const claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
|
|
21518
22009
|
if (claudeJson.mcpServers) {
|
|
21519
22010
|
let removedMcp = false;
|
|
21520
22011
|
for (const key of ["exe-mem", "exe-os"]) {
|
|
21521
22012
|
if (claudeJson.mcpServers[key]) {
|
|
21522
|
-
delete claudeJson.mcpServers[key];
|
|
22013
|
+
if (!dryRun) delete claudeJson.mcpServers[key];
|
|
21523
22014
|
removedMcp = true;
|
|
21524
22015
|
}
|
|
21525
22016
|
}
|
|
21526
22017
|
if (removedMcp) {
|
|
21527
|
-
|
|
21528
|
-
|
|
21529
|
-
|
|
21530
|
-
|
|
21531
|
-
);
|
|
21532
|
-
console.log("Removed exe-os MCP server from claude.json");
|
|
22018
|
+
if (!dryRun) {
|
|
22019
|
+
writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
22020
|
+
}
|
|
22021
|
+
log("\u2713 Removed exe-os MCP server from claude.json");
|
|
21533
22022
|
removed++;
|
|
21534
22023
|
}
|
|
21535
22024
|
}
|
|
21536
22025
|
}
|
|
22026
|
+
const skillsDir = path31.join(claudeDir, "skills");
|
|
22027
|
+
if (existsSync21(skillsDir)) {
|
|
22028
|
+
let skillCount = 0;
|
|
22029
|
+
try {
|
|
22030
|
+
const entries = readdirSync4(skillsDir);
|
|
22031
|
+
for (const entry of entries) {
|
|
22032
|
+
if (entry.startsWith("exe")) {
|
|
22033
|
+
const fullPath = path31.join(skillsDir, entry);
|
|
22034
|
+
if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
|
|
22035
|
+
skillCount++;
|
|
22036
|
+
}
|
|
22037
|
+
}
|
|
22038
|
+
} catch {
|
|
22039
|
+
}
|
|
22040
|
+
if (skillCount > 0) {
|
|
22041
|
+
log(`\u2713 Removed ${skillCount} skill directories`);
|
|
22042
|
+
removed++;
|
|
22043
|
+
}
|
|
22044
|
+
}
|
|
22045
|
+
const claudeMdPath = path31.join(claudeDir, "CLAUDE.md");
|
|
22046
|
+
if (existsSync21(claudeMdPath)) {
|
|
22047
|
+
const content = readFileSync17(claudeMdPath, "utf8");
|
|
22048
|
+
const startMarker = "<!-- exe-os:orchestration-start -->";
|
|
22049
|
+
const endMarker = "<!-- exe-os:orchestration-end -->";
|
|
22050
|
+
const startIdx = content.indexOf(startMarker);
|
|
22051
|
+
const endIdx = content.indexOf(endMarker);
|
|
22052
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
22053
|
+
const cleaned = (content.slice(0, startIdx) + content.slice(endIdx + endMarker.length)).replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
22054
|
+
if (!dryRun) writeFileSync9(claudeMdPath, cleaned);
|
|
22055
|
+
log("\u2713 Removed orchestration block from CLAUDE.md");
|
|
22056
|
+
removed++;
|
|
22057
|
+
}
|
|
22058
|
+
}
|
|
22059
|
+
const agentsDir = path31.join(claudeDir, "agents");
|
|
22060
|
+
if (existsSync21(agentsDir)) {
|
|
22061
|
+
let agentCount = 0;
|
|
22062
|
+
try {
|
|
22063
|
+
const entries = readdirSync4(agentsDir).filter((f) => f.endsWith(".md"));
|
|
22064
|
+
let knownNames = /* @__PURE__ */ new Set();
|
|
22065
|
+
const rosterPath = path31.join(exeOsDir, "exe-employees.json");
|
|
22066
|
+
if (existsSync21(rosterPath)) {
|
|
22067
|
+
try {
|
|
22068
|
+
const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
|
|
22069
|
+
knownNames = new Set(roster.map((e) => e.name));
|
|
22070
|
+
} catch {
|
|
22071
|
+
}
|
|
22072
|
+
}
|
|
22073
|
+
for (const entry of entries) {
|
|
22074
|
+
const name = entry.replace(/\.md$/, "");
|
|
22075
|
+
if (knownNames.has(name)) {
|
|
22076
|
+
if (!dryRun) rmSync(path31.join(agentsDir, entry), { force: true });
|
|
22077
|
+
agentCount++;
|
|
22078
|
+
}
|
|
22079
|
+
}
|
|
22080
|
+
} catch {
|
|
22081
|
+
}
|
|
22082
|
+
if (agentCount > 0) {
|
|
22083
|
+
log(`\u2713 Removed ${agentCount} agent identity files`);
|
|
22084
|
+
removed++;
|
|
22085
|
+
}
|
|
22086
|
+
}
|
|
22087
|
+
const projectsDir = path31.join(claudeDir, "projects");
|
|
22088
|
+
if (existsSync21(projectsDir)) {
|
|
22089
|
+
let projectCount = 0;
|
|
22090
|
+
try {
|
|
22091
|
+
const projects = readdirSync4(projectsDir);
|
|
22092
|
+
for (const proj of projects) {
|
|
22093
|
+
const projSettings = path31.join(projectsDir, proj, "settings.json");
|
|
22094
|
+
if (!existsSync21(projSettings)) continue;
|
|
22095
|
+
try {
|
|
22096
|
+
const pSettings = JSON.parse(readFileSync17(projSettings, "utf8"));
|
|
22097
|
+
let changed = false;
|
|
22098
|
+
if (Array.isArray(pSettings.permissions?.allow)) {
|
|
22099
|
+
const before = pSettings.permissions.allow.length;
|
|
22100
|
+
pSettings.permissions.allow = pSettings.permissions.allow.filter(
|
|
22101
|
+
(p) => !p.startsWith("mcp__exe-mem__") && !p.startsWith("mcp__exe-os__")
|
|
22102
|
+
);
|
|
22103
|
+
if (pSettings.permissions.allow.length < before) changed = true;
|
|
22104
|
+
}
|
|
22105
|
+
if (changed && !dryRun) {
|
|
22106
|
+
writeFileSync9(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
|
|
22107
|
+
}
|
|
22108
|
+
if (changed) projectCount++;
|
|
22109
|
+
} catch {
|
|
22110
|
+
}
|
|
22111
|
+
}
|
|
22112
|
+
} catch {
|
|
22113
|
+
}
|
|
22114
|
+
if (projectCount > 0) {
|
|
22115
|
+
log(`\u2713 Cleaned exe-os entries from ${projectCount} project settings`);
|
|
22116
|
+
removed++;
|
|
22117
|
+
}
|
|
22118
|
+
}
|
|
22119
|
+
try {
|
|
22120
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
22121
|
+
const exeBinPath = execSync13("which exe", { encoding: "utf-8" }).trim();
|
|
22122
|
+
const binDir = path31.dirname(exeBinPath);
|
|
22123
|
+
let symlinkCount = 0;
|
|
22124
|
+
const rosterPath = path31.join(exeOsDir, "exe-employees.json");
|
|
22125
|
+
if (existsSync21(rosterPath)) {
|
|
22126
|
+
const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
|
|
22127
|
+
for (const emp of roster) {
|
|
22128
|
+
if (emp.name === "exe") continue;
|
|
22129
|
+
for (const suffix of ["", "-opencode"]) {
|
|
22130
|
+
const linkPath = path31.join(binDir, `${emp.name}${suffix}`);
|
|
22131
|
+
if (existsSync21(linkPath)) {
|
|
22132
|
+
if (!dryRun) rmSync(linkPath, { force: true });
|
|
22133
|
+
symlinkCount++;
|
|
22134
|
+
}
|
|
22135
|
+
}
|
|
22136
|
+
}
|
|
22137
|
+
}
|
|
22138
|
+
if (symlinkCount > 0) {
|
|
22139
|
+
log(`\u2713 Removed ${symlinkCount} employee symlinks`);
|
|
22140
|
+
removed++;
|
|
22141
|
+
}
|
|
22142
|
+
} catch {
|
|
22143
|
+
}
|
|
22144
|
+
if (purge && existsSync21(exeOsDir)) {
|
|
22145
|
+
if (!dryRun) {
|
|
22146
|
+
process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
|
|
22147
|
+
process.stdout.write(" Removing ~/.exe-os...\n");
|
|
22148
|
+
rmSync(exeOsDir, { recursive: true, force: true });
|
|
22149
|
+
}
|
|
22150
|
+
log("\u2713 Purged ~/.exe-os data directory");
|
|
22151
|
+
removed++;
|
|
22152
|
+
}
|
|
21537
22153
|
if (removed === 0) {
|
|
21538
22154
|
console.log("Nothing to remove \u2014 exe-os was not installed.");
|
|
21539
22155
|
} else {
|
|
21540
|
-
|
|
22156
|
+
if (dryRun) {
|
|
22157
|
+
console.log("\nDry run complete. Re-run without --dry-run to apply.");
|
|
22158
|
+
} else {
|
|
22159
|
+
console.log("\nDone. Run \x1B[36mexe-os claude install\x1B[0m to reinstall.");
|
|
22160
|
+
}
|
|
21541
22161
|
}
|
|
21542
22162
|
}
|
|
21543
22163
|
async function checkForUpdateOnBoot() {
|
|
@@ -21546,7 +22166,7 @@ async function checkForUpdateOnBoot() {
|
|
|
21546
22166
|
const config = await loadConfig2();
|
|
21547
22167
|
if (!config.autoUpdate.checkOnBoot) return;
|
|
21548
22168
|
const { checkForUpdate: checkForUpdate2 } = await init_update().then(() => update_exports);
|
|
21549
|
-
const packageRoot =
|
|
22169
|
+
const packageRoot = path31.resolve(
|
|
21550
22170
|
new URL("../..", import.meta.url).pathname
|
|
21551
22171
|
);
|
|
21552
22172
|
const result = checkForUpdate2(packageRoot);
|
|
@@ -21602,7 +22222,7 @@ async function runActivate(key) {
|
|
|
21602
22222
|
const idTemplate = getIdentityTemplate(identityKey);
|
|
21603
22223
|
if (idTemplate) {
|
|
21604
22224
|
const idPath = identityPath2(name);
|
|
21605
|
-
const dir =
|
|
22225
|
+
const dir = path31.dirname(idPath);
|
|
21606
22226
|
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
21607
22227
|
fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
|
|
21608
22228
|
}
|