@askexenow/exe-os 0.9.113 → 0.9.115
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/agentic-ontology-backfill.js +36 -12
- package/dist/bin/agentic-reflection-backfill.js +36 -12
- package/dist/bin/agentic-semantic-label.js +36 -12
- package/dist/bin/backfill-conversations.js +36 -12
- package/dist/bin/backfill-responses.js +36 -12
- package/dist/bin/backfill-vectors.js +36 -12
- package/dist/bin/bulk-sync-postgres.js +36 -12
- package/dist/bin/cleanup-stale-review-tasks.js +470 -113
- package/dist/bin/cli.js +413 -62
- package/dist/bin/exe-agent.js +27 -0
- package/dist/bin/exe-assign.js +36 -12
- package/dist/bin/exe-boot.js +246 -54
- package/dist/bin/exe-call.js +8 -0
- package/dist/bin/exe-cloud.js +47 -12
- package/dist/bin/exe-dispatch.js +348 -53
- package/dist/bin/exe-doctor.js +51 -13
- package/dist/bin/exe-export-behaviors.js +37 -12
- package/dist/bin/exe-forget.js +36 -12
- package/dist/bin/exe-gateway.js +348 -53
- package/dist/bin/exe-heartbeat.js +471 -113
- package/dist/bin/exe-kill.js +36 -12
- package/dist/bin/exe-launch-agent.js +117 -18
- package/dist/bin/exe-new-employee.js +9 -1
- package/dist/bin/exe-pending-messages.js +452 -95
- package/dist/bin/exe-pending-notifications.js +452 -95
- package/dist/bin/exe-pending-reviews.js +452 -95
- package/dist/bin/exe-rename.js +36 -12
- package/dist/bin/exe-review.js +36 -12
- package/dist/bin/exe-search.js +37 -12
- package/dist/bin/exe-session-cleanup.js +348 -53
- package/dist/bin/exe-settings.js +12 -0
- package/dist/bin/exe-start-codex.js +46 -13
- package/dist/bin/exe-start-opencode.js +46 -13
- package/dist/bin/exe-status.js +460 -114
- package/dist/bin/exe-support.js +12 -0
- package/dist/bin/exe-team.js +36 -12
- package/dist/bin/git-sweep.js +348 -53
- package/dist/bin/graph-backfill.js +36 -12
- package/dist/bin/graph-export.js +36 -12
- package/dist/bin/install.js +9 -1
- package/dist/bin/intercom-check.js +255 -53
- package/dist/bin/scan-tasks.js +348 -53
- package/dist/bin/setup.js +74 -12
- package/dist/bin/shard-migrate.js +36 -12
- package/dist/gateway/index.js +348 -53
- package/dist/hooks/bug-report-worker.js +348 -53
- package/dist/hooks/codex-stop-task-finalizer.js +308 -37
- package/dist/hooks/commit-complete.js +348 -53
- package/dist/hooks/error-recall.js +37 -12
- package/dist/hooks/ingest.js +363 -54
- package/dist/hooks/instructions-loaded.js +36 -12
- package/dist/hooks/notification.js +36 -12
- package/dist/hooks/post-compact.js +426 -72
- package/dist/hooks/post-tool-combined.js +501 -146
- package/dist/hooks/pre-compact.js +348 -53
- package/dist/hooks/pre-tool-use.js +92 -13
- package/dist/hooks/prompt-submit.js +348 -53
- package/dist/hooks/session-end.js +158 -53
- package/dist/hooks/session-start.js +66 -13
- package/dist/hooks/stop.js +420 -72
- package/dist/hooks/subagent-stop.js +419 -72
- package/dist/hooks/summary-worker.js +442 -121
- package/dist/index.js +375 -53
- package/dist/lib/agent-config.js +8 -0
- package/dist/lib/cloud-sync.js +35 -12
- package/dist/lib/config.js +13 -0
- package/dist/lib/consolidation.js +9 -1
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/employees.js +8 -0
- package/dist/lib/exe-daemon.js +524 -60
- package/dist/lib/hybrid-search.js +37 -12
- package/dist/lib/keychain.js +25 -13
- package/dist/lib/messaging.js +395 -74
- package/dist/lib/schedules.js +36 -12
- package/dist/lib/skill-learning.js +21 -0
- package/dist/lib/store.js +36 -12
- package/dist/lib/tasks.js +324 -41
- package/dist/lib/tmux-routing.js +324 -41
- package/dist/mcp/server.js +374 -54
- package/dist/mcp/tools/create-task.js +324 -41
- package/dist/mcp/tools/list-tasks.js +406 -57
- package/dist/mcp/tools/send-message.js +395 -74
- package/dist/mcp/tools/update-task.js +324 -41
- package/dist/runtime/index.js +375 -53
- package/dist/tui/App.js +377 -55
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -155,6 +155,17 @@ function normalizeOrchestration(raw) {
|
|
|
155
155
|
const userOrg = raw.orchestration ?? {};
|
|
156
156
|
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
157
157
|
}
|
|
158
|
+
function normalizeCloudEndpoint(raw) {
|
|
159
|
+
const cloud = raw.cloud;
|
|
160
|
+
if (!cloud?.endpoint) return;
|
|
161
|
+
const ep = String(cloud.endpoint);
|
|
162
|
+
if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
|
|
163
|
+
cloud.endpoint = "https://cloud.askexe.com";
|
|
164
|
+
process.stderr.write(
|
|
165
|
+
"[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
158
169
|
async function loadConfig() {
|
|
159
170
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
160
171
|
await ensurePrivateDir(dir);
|
|
@@ -180,6 +191,7 @@ async function loadConfig() {
|
|
|
180
191
|
normalizeSessionLifecycle(migratedCfg);
|
|
181
192
|
normalizeAutoUpdate(migratedCfg);
|
|
182
193
|
normalizeOrchestration(migratedCfg);
|
|
194
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
183
195
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
184
196
|
if (config.dbPath.startsWith("~")) {
|
|
185
197
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -208,6 +220,7 @@ function loadConfigSync() {
|
|
|
208
220
|
normalizeSessionLifecycle(migratedCfg);
|
|
209
221
|
normalizeAutoUpdate(migratedCfg);
|
|
210
222
|
normalizeOrchestration(migratedCfg);
|
|
223
|
+
normalizeCloudEndpoint(migratedCfg);
|
|
211
224
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
212
225
|
if (config.dbPath.startsWith("~")) {
|
|
213
226
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -368,6 +381,7 @@ __export(agent_config_exports, {
|
|
|
368
381
|
clearAgentRuntime: () => clearAgentRuntime,
|
|
369
382
|
getAgentRuntime: () => getAgentRuntime,
|
|
370
383
|
loadAgentConfig: () => loadAgentConfig,
|
|
384
|
+
normalizeCcModelName: () => normalizeCcModelName,
|
|
371
385
|
saveAgentConfig: () => saveAgentConfig,
|
|
372
386
|
setAgentMcps: () => setAgentMcps,
|
|
373
387
|
setAgentRuntime: () => setAgentRuntime
|
|
@@ -396,6 +410,13 @@ function getAgentRuntime(agentId) {
|
|
|
396
410
|
if (orgDefault) return orgDefault;
|
|
397
411
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
398
412
|
}
|
|
413
|
+
function normalizeCcModelName(model) {
|
|
414
|
+
let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
415
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
416
|
+
ccModel += "[1m]";
|
|
417
|
+
}
|
|
418
|
+
return ccModel;
|
|
419
|
+
}
|
|
399
420
|
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
400
421
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
401
422
|
if (!knownModels) {
|
|
@@ -896,7 +917,7 @@ function readOrCreateDaemonToken(homeDir = os5.homedir()) {
|
|
|
896
917
|
function buildMcpHttpHeaders(homeDir = os5.homedir(), opts = {}) {
|
|
897
918
|
const agentId = opts.useShellPlaceholders ? "${AGENT_ID:-exe}" : opts.agentId ?? DEFAULT_MCP_HTTP_AGENT_ID;
|
|
898
919
|
const agentRole = opts.useShellPlaceholders ? "${AGENT_ROLE:-COO}" : opts.agentRole ?? DEFAULT_MCP_HTTP_AGENT_ROLE;
|
|
899
|
-
const sessionName = opts.useShellPlaceholders ? "
|
|
920
|
+
const sessionName = opts.useShellPlaceholders ? "" : process.env.EXE_SESSION_NAME ?? "";
|
|
900
921
|
const headers = {
|
|
901
922
|
Authorization: `Bearer ${readOrCreateDaemonToken(homeDir)}`,
|
|
902
923
|
"X-Agent-Id": agentId,
|
|
@@ -2555,7 +2576,7 @@ __export(keychain_exports, {
|
|
|
2555
2576
|
importMnemonic: () => importMnemonic,
|
|
2556
2577
|
setMasterKey: () => setMasterKey
|
|
2557
2578
|
});
|
|
2558
|
-
import { readFile as readFile4, writeFile as writeFile4, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
2579
|
+
import { readFile as readFile4, writeFile as writeFile4, unlink, mkdir as mkdir4, chmod as chmod2, rename, copyFile } from "fs/promises";
|
|
2559
2580
|
import { existsSync as existsSync10, statSync } from "fs";
|
|
2560
2581
|
import { execSync as execSync3 } from "child_process";
|
|
2561
2582
|
import path9 from "path";
|
|
@@ -2590,12 +2611,14 @@ function linuxSecretAvailable() {
|
|
|
2590
2611
|
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
2591
2612
|
if (process.platform !== "linux") return false;
|
|
2592
2613
|
try {
|
|
2593
|
-
const uid = typeof os8.userInfo().uid === "number" ? os8.userInfo().uid : -1;
|
|
2594
2614
|
const st = statSync(keyPath);
|
|
2595
2615
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
2616
|
+
const uid = typeof os8.userInfo().uid === "number" ? os8.userInfo().uid : -1;
|
|
2596
2617
|
if (uid === 0) return true;
|
|
2597
2618
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
2598
|
-
|
|
2619
|
+
if (exeOsDir && path9.resolve(keyPath).startsWith(path9.resolve(exeOsDir) + path9.sep)) return true;
|
|
2620
|
+
if (!linuxSecretAvailable()) return true;
|
|
2621
|
+
return false;
|
|
2599
2622
|
} catch {
|
|
2600
2623
|
return false;
|
|
2601
2624
|
}
|
|
@@ -2745,15 +2768,25 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
2745
2768
|
await mkdir4(dir, { recursive: true });
|
|
2746
2769
|
const keyPath = getKeyPath();
|
|
2747
2770
|
const machineKey = deriveMachineKey();
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2771
|
+
const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
|
|
2772
|
+
const result = machineKey ? "encrypted" : "plaintext";
|
|
2773
|
+
const tmpPath = keyPath + ".tmp";
|
|
2774
|
+
try {
|
|
2775
|
+
if (existsSync10(keyPath)) {
|
|
2776
|
+
await copyFile(keyPath, keyPath + ".bak").catch(() => {
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
await writeFile4(tmpPath, content, "utf-8");
|
|
2780
|
+
await chmod2(tmpPath, 384);
|
|
2781
|
+
await rename(tmpPath, keyPath);
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
try {
|
|
2784
|
+
await unlink(tmpPath);
|
|
2785
|
+
} catch {
|
|
2786
|
+
}
|
|
2787
|
+
throw err;
|
|
2753
2788
|
}
|
|
2754
|
-
|
|
2755
|
-
await chmod2(keyPath, 384);
|
|
2756
|
-
return "plaintext";
|
|
2789
|
+
return result;
|
|
2757
2790
|
}
|
|
2758
2791
|
async function getMasterKey() {
|
|
2759
2792
|
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
@@ -2820,7 +2853,7 @@ async function getMasterKey() {
|
|
|
2820
2853
|
b64Value = content;
|
|
2821
2854
|
}
|
|
2822
2855
|
const key = Buffer.from(b64Value, "base64");
|
|
2823
|
-
if (
|
|
2856
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
2824
2857
|
return key;
|
|
2825
2858
|
}
|
|
2826
2859
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
@@ -6999,6 +7032,7 @@ __export(cloud_sync_exports, {
|
|
|
6999
7032
|
markCloudReuploadRequired: () => markCloudReuploadRequired,
|
|
7000
7033
|
mergeConfig: () => mergeConfig,
|
|
7001
7034
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
7035
|
+
migrateEndpoint: () => migrateEndpoint,
|
|
7002
7036
|
pushToPostgres: () => pushToPostgres,
|
|
7003
7037
|
recordRosterDeletion: () => recordRosterDeletion
|
|
7004
7038
|
});
|
|
@@ -7169,6 +7203,15 @@ async function fetchWithRetry(url, init) {
|
|
|
7169
7203
|
}
|
|
7170
7204
|
throw lastError;
|
|
7171
7205
|
}
|
|
7206
|
+
function migrateEndpoint(endpoint) {
|
|
7207
|
+
if (endpoint === "https://askexe.com/cloud" || endpoint === "https://askexe.com/cloud/") {
|
|
7208
|
+
process.stderr.write(
|
|
7209
|
+
"[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
|
|
7210
|
+
);
|
|
7211
|
+
return "https://cloud.askexe.com";
|
|
7212
|
+
}
|
|
7213
|
+
return endpoint;
|
|
7214
|
+
}
|
|
7172
7215
|
function assertSecureEndpoint(endpoint) {
|
|
7173
7216
|
if (endpoint.startsWith("https://")) return;
|
|
7174
7217
|
if (endpoint.startsWith("http://")) {
|
|
@@ -7306,6 +7349,7 @@ async function markCloudReuploadRequired(client = getClient()) {
|
|
|
7306
7349
|
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
|
|
7307
7350
|
}
|
|
7308
7351
|
async function cloudSync(config) {
|
|
7352
|
+
config = { ...config, endpoint: migrateEndpoint(config.endpoint) };
|
|
7309
7353
|
if (!isSyncCryptoInitialized()) {
|
|
7310
7354
|
try {
|
|
7311
7355
|
const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
|
|
@@ -13929,6 +13973,7 @@ var init_provider_table = __esm({
|
|
|
13929
13973
|
// src/lib/intercom-queue.ts
|
|
13930
13974
|
var intercom_queue_exports = {};
|
|
13931
13975
|
__export(intercom_queue_exports, {
|
|
13976
|
+
_resetDrainGuard: () => _resetDrainGuard,
|
|
13932
13977
|
clearQueueForAgent: () => clearQueueForAgent,
|
|
13933
13978
|
drainForSession: () => drainForSession,
|
|
13934
13979
|
drainQueue: () => drainQueue,
|
|
@@ -13974,38 +14019,47 @@ function queueIntercom(targetSession, reason) {
|
|
|
13974
14019
|
writeQueue(queue);
|
|
13975
14020
|
}
|
|
13976
14021
|
function drainQueue(isSessionBusy2, sendKeys) {
|
|
14022
|
+
if (_draining) {
|
|
14023
|
+
logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
|
|
14024
|
+
return { drained: 0, failed: 0 };
|
|
14025
|
+
}
|
|
13977
14026
|
const queue = readQueue();
|
|
13978
14027
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
14028
|
+
_draining = true;
|
|
13979
14029
|
const remaining = [];
|
|
13980
14030
|
let drained = 0;
|
|
13981
14031
|
let failed = 0;
|
|
13982
|
-
|
|
13983
|
-
const
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
14032
|
+
try {
|
|
14033
|
+
for (const item of queue) {
|
|
14034
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
14035
|
+
if (age > TTL_MS) {
|
|
14036
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
14037
|
+
failed++;
|
|
14038
|
+
continue;
|
|
14039
|
+
}
|
|
14040
|
+
try {
|
|
14041
|
+
if (!isSessionBusy2(item.targetSession)) {
|
|
14042
|
+
const success = sendKeys(item.targetSession);
|
|
14043
|
+
if (success) {
|
|
14044
|
+
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
14045
|
+
drained++;
|
|
14046
|
+
continue;
|
|
14047
|
+
}
|
|
13996
14048
|
}
|
|
14049
|
+
} catch {
|
|
13997
14050
|
}
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14051
|
+
item.attempts++;
|
|
14052
|
+
if (item.attempts >= MAX_RETRIES2) {
|
|
14053
|
+
logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
|
|
14054
|
+
failed++;
|
|
14055
|
+
continue;
|
|
14056
|
+
}
|
|
14057
|
+
remaining.push(item);
|
|
14005
14058
|
}
|
|
14006
|
-
remaining
|
|
14059
|
+
writeQueue(remaining);
|
|
14060
|
+
} finally {
|
|
14061
|
+
_draining = false;
|
|
14007
14062
|
}
|
|
14008
|
-
writeQueue(remaining);
|
|
14009
14063
|
return { drained, failed };
|
|
14010
14064
|
}
|
|
14011
14065
|
function drainForSession(targetSession, sendKeys) {
|
|
@@ -14030,6 +14084,9 @@ function clearQueueForAgent(agentName) {
|
|
|
14030
14084
|
logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
|
|
14031
14085
|
}
|
|
14032
14086
|
}
|
|
14087
|
+
function _resetDrainGuard() {
|
|
14088
|
+
_draining = false;
|
|
14089
|
+
}
|
|
14033
14090
|
function logQueue(msg) {
|
|
14034
14091
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
|
|
14035
14092
|
`;
|
|
@@ -14041,13 +14098,14 @@ function logQueue(msg) {
|
|
|
14041
14098
|
} catch {
|
|
14042
14099
|
}
|
|
14043
14100
|
}
|
|
14044
|
-
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
14101
|
+
var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
|
|
14045
14102
|
var init_intercom_queue = __esm({
|
|
14046
14103
|
"src/lib/intercom-queue.ts"() {
|
|
14047
14104
|
"use strict";
|
|
14048
14105
|
QUEUE_PATH = path26.join(os15.homedir(), ".exe-os", "intercom-queue.json");
|
|
14049
14106
|
MAX_RETRIES2 = 5;
|
|
14050
14107
|
TTL_MS = 60 * 60 * 1e3;
|
|
14108
|
+
_draining = false;
|
|
14051
14109
|
INTERCOM_LOG = path26.join(os15.homedir(), ".exe-os", "intercom.log");
|
|
14052
14110
|
}
|
|
14053
14111
|
});
|
|
@@ -14170,6 +14228,17 @@ var init_task_scope = __esm({
|
|
|
14170
14228
|
});
|
|
14171
14229
|
|
|
14172
14230
|
// src/lib/notifications.ts
|
|
14231
|
+
var notifications_exports = {};
|
|
14232
|
+
__export(notifications_exports, {
|
|
14233
|
+
cleanupOldNotifications: () => cleanupOldNotifications,
|
|
14234
|
+
formatNotifications: () => formatNotifications,
|
|
14235
|
+
markAsRead: () => markAsRead,
|
|
14236
|
+
markAsReadByTaskFile: () => markAsReadByTaskFile,
|
|
14237
|
+
markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
|
|
14238
|
+
migrateJsonNotifications: () => migrateJsonNotifications,
|
|
14239
|
+
readUnreadNotifications: () => readUnreadNotifications,
|
|
14240
|
+
writeNotification: () => writeNotification
|
|
14241
|
+
});
|
|
14173
14242
|
import crypto7 from "crypto";
|
|
14174
14243
|
import path28 from "path";
|
|
14175
14244
|
import os16 from "os";
|
|
@@ -14206,6 +14275,52 @@ async function writeNotification(notification) {
|
|
|
14206
14275
|
`);
|
|
14207
14276
|
}
|
|
14208
14277
|
}
|
|
14278
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
14279
|
+
try {
|
|
14280
|
+
const client = getClient();
|
|
14281
|
+
const conditions = ["read = 0"];
|
|
14282
|
+
const args2 = [];
|
|
14283
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14284
|
+
if (agentFilter) {
|
|
14285
|
+
conditions.push("agent_id = ?");
|
|
14286
|
+
args2.push(agentFilter);
|
|
14287
|
+
}
|
|
14288
|
+
const result = await client.execute({
|
|
14289
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
14290
|
+
FROM notifications
|
|
14291
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
14292
|
+
ORDER BY created_at ASC`,
|
|
14293
|
+
args: [...args2, ...scope.args]
|
|
14294
|
+
});
|
|
14295
|
+
return result.rows.map((r) => ({
|
|
14296
|
+
id: String(r.id),
|
|
14297
|
+
agentId: String(r.agent_id),
|
|
14298
|
+
agentRole: String(r.agent_role),
|
|
14299
|
+
event: String(r.event),
|
|
14300
|
+
project: String(r.project),
|
|
14301
|
+
summary: String(r.summary),
|
|
14302
|
+
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
14303
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
14304
|
+
timestamp: String(r.created_at),
|
|
14305
|
+
read: false
|
|
14306
|
+
}));
|
|
14307
|
+
} catch {
|
|
14308
|
+
return [];
|
|
14309
|
+
}
|
|
14310
|
+
}
|
|
14311
|
+
async function markAsRead(ids, sessionScope) {
|
|
14312
|
+
if (ids.length === 0) return;
|
|
14313
|
+
try {
|
|
14314
|
+
const client = getClient();
|
|
14315
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
14316
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14317
|
+
await client.execute({
|
|
14318
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
14319
|
+
args: [...ids, ...scope.args]
|
|
14320
|
+
});
|
|
14321
|
+
} catch {
|
|
14322
|
+
}
|
|
14323
|
+
}
|
|
14209
14324
|
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
14210
14325
|
try {
|
|
14211
14326
|
const client = getClient();
|
|
@@ -14218,11 +14333,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
|
14218
14333
|
} catch {
|
|
14219
14334
|
}
|
|
14220
14335
|
}
|
|
14336
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
14337
|
+
try {
|
|
14338
|
+
const client = getClient();
|
|
14339
|
+
const cutoff = new Date(
|
|
14340
|
+
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
14341
|
+
).toISOString();
|
|
14342
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14343
|
+
const result = await client.execute({
|
|
14344
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
14345
|
+
args: [cutoff, ...scope.args]
|
|
14346
|
+
});
|
|
14347
|
+
return result.rowsAffected;
|
|
14348
|
+
} catch {
|
|
14349
|
+
return 0;
|
|
14350
|
+
}
|
|
14351
|
+
}
|
|
14352
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
14353
|
+
try {
|
|
14354
|
+
const client = getClient();
|
|
14355
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
14356
|
+
const result = await client.execute({
|
|
14357
|
+
sql: `UPDATE notifications SET read = 1
|
|
14358
|
+
WHERE read = 0
|
|
14359
|
+
AND task_file IS NOT NULL
|
|
14360
|
+
${scope.sql}
|
|
14361
|
+
AND task_file IN (
|
|
14362
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
14363
|
+
)`,
|
|
14364
|
+
args: [...scope.args, ...scope.args]
|
|
14365
|
+
});
|
|
14366
|
+
return result.rowsAffected;
|
|
14367
|
+
} catch {
|
|
14368
|
+
return 0;
|
|
14369
|
+
}
|
|
14370
|
+
}
|
|
14371
|
+
function formatNotifications(notifications) {
|
|
14372
|
+
if (notifications.length === 0) return "";
|
|
14373
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
14374
|
+
for (const n of notifications) {
|
|
14375
|
+
const key = `${n.agentId}|${n.agentRole}`;
|
|
14376
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
14377
|
+
grouped.get(key).push(n);
|
|
14378
|
+
}
|
|
14379
|
+
const lines = [];
|
|
14380
|
+
lines.push(`## Notifications (${notifications.length} unread)
|
|
14381
|
+
`);
|
|
14382
|
+
for (const [key, items] of grouped) {
|
|
14383
|
+
const [agentId, agentRole] = key.split("|");
|
|
14384
|
+
lines.push(`**${agentId}** (${agentRole}):`);
|
|
14385
|
+
for (const item of items) {
|
|
14386
|
+
const ago = formatTimeAgo(item.timestamp);
|
|
14387
|
+
const icon = eventIcon(item.event);
|
|
14388
|
+
lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
|
|
14389
|
+
}
|
|
14390
|
+
lines.push("");
|
|
14391
|
+
}
|
|
14392
|
+
return lines.join("\n");
|
|
14393
|
+
}
|
|
14394
|
+
async function migrateJsonNotifications() {
|
|
14395
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path28.join(os16.homedir(), ".exe-os");
|
|
14396
|
+
const notifDir = path28.join(base, "notifications");
|
|
14397
|
+
if (!existsSync26(notifDir)) return 0;
|
|
14398
|
+
let migrated = 0;
|
|
14399
|
+
try {
|
|
14400
|
+
const files = readdirSync5(notifDir).filter((f) => f.endsWith(".json"));
|
|
14401
|
+
if (files.length === 0) return 0;
|
|
14402
|
+
const client = getClient();
|
|
14403
|
+
for (const file of files) {
|
|
14404
|
+
try {
|
|
14405
|
+
const filePath = path28.join(notifDir, file);
|
|
14406
|
+
const data = JSON.parse(readFileSync21(filePath, "utf8"));
|
|
14407
|
+
await client.execute({
|
|
14408
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
14409
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
14410
|
+
args: [
|
|
14411
|
+
crypto7.randomUUID(),
|
|
14412
|
+
data.agentId ?? "unknown",
|
|
14413
|
+
data.agentRole ?? "unknown",
|
|
14414
|
+
data.event ?? "session_summary",
|
|
14415
|
+
data.project ?? "unknown",
|
|
14416
|
+
data.summary ?? "",
|
|
14417
|
+
data.taskFile ?? null,
|
|
14418
|
+
null,
|
|
14419
|
+
data.read ? 1 : 0,
|
|
14420
|
+
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
14421
|
+
]
|
|
14422
|
+
});
|
|
14423
|
+
unlinkSync9(filePath);
|
|
14424
|
+
migrated++;
|
|
14425
|
+
} catch {
|
|
14426
|
+
}
|
|
14427
|
+
}
|
|
14428
|
+
try {
|
|
14429
|
+
const remaining = readdirSync5(notifDir);
|
|
14430
|
+
if (remaining.length === 0) {
|
|
14431
|
+
rmdirSync(notifDir);
|
|
14432
|
+
}
|
|
14433
|
+
} catch {
|
|
14434
|
+
}
|
|
14435
|
+
} catch {
|
|
14436
|
+
}
|
|
14437
|
+
return migrated;
|
|
14438
|
+
}
|
|
14439
|
+
function eventIcon(event) {
|
|
14440
|
+
switch (event) {
|
|
14441
|
+
case "task_complete":
|
|
14442
|
+
return "Completed:";
|
|
14443
|
+
case "task_needs_fix":
|
|
14444
|
+
return "Needs fix:";
|
|
14445
|
+
case "session_summary":
|
|
14446
|
+
return "Session:";
|
|
14447
|
+
case "error_spike":
|
|
14448
|
+
return "Errors:";
|
|
14449
|
+
case "orphan_task":
|
|
14450
|
+
return "Orphan:";
|
|
14451
|
+
case "subtasks_complete":
|
|
14452
|
+
return "Subtasks done:";
|
|
14453
|
+
case "capacity_relaunch":
|
|
14454
|
+
return "Relaunched:";
|
|
14455
|
+
}
|
|
14456
|
+
}
|
|
14457
|
+
function formatTimeAgo(timestamp) {
|
|
14458
|
+
const diffMs = Date.now() - new Date(timestamp).getTime();
|
|
14459
|
+
const mins = Math.floor(diffMs / 6e4);
|
|
14460
|
+
if (mins < 1) return "just now";
|
|
14461
|
+
if (mins < 60) return `${mins}m ago`;
|
|
14462
|
+
const hours = Math.floor(mins / 60);
|
|
14463
|
+
if (hours < 24) return `${hours}h ago`;
|
|
14464
|
+
const days = Math.floor(hours / 24);
|
|
14465
|
+
return `${days}d ago`;
|
|
14466
|
+
}
|
|
14467
|
+
var CLEANUP_DAYS;
|
|
14221
14468
|
var init_notifications = __esm({
|
|
14222
14469
|
"src/lib/notifications.ts"() {
|
|
14223
14470
|
"use strict";
|
|
14224
14471
|
init_database();
|
|
14225
14472
|
init_task_scope();
|
|
14473
|
+
CLEANUP_DAYS = 7;
|
|
14226
14474
|
}
|
|
14227
14475
|
});
|
|
14228
14476
|
|
|
@@ -15229,7 +15477,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now2) {
|
|
|
15229
15477
|
taskFile
|
|
15230
15478
|
});
|
|
15231
15479
|
const originalPriority = String(row.priority).toLowerCase();
|
|
15232
|
-
const
|
|
15480
|
+
const resultLower = result?.toLowerCase() ?? "";
|
|
15481
|
+
const hasTestEvidence = (
|
|
15482
|
+
// Vitest/Jest output patterns (hard to fake without actually running tests)
|
|
15483
|
+
/\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
|
|
15484
|
+
);
|
|
15485
|
+
const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
|
|
15486
|
+
const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
|
|
15233
15487
|
if (!autoApprove) {
|
|
15234
15488
|
try {
|
|
15235
15489
|
const key = getSessionKey();
|
|
@@ -15237,6 +15491,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now2) {
|
|
|
15237
15491
|
if (exeSession) {
|
|
15238
15492
|
sendIntercom(exeSession);
|
|
15239
15493
|
}
|
|
15494
|
+
if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
|
|
15495
|
+
const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
15496
|
+
if (exeSession) {
|
|
15497
|
+
const reviewerSession = employeeSessionName2(reviewer, exeSession);
|
|
15498
|
+
sendIntercom(reviewerSession);
|
|
15499
|
+
}
|
|
15500
|
+
}
|
|
15240
15501
|
} catch {
|
|
15241
15502
|
}
|
|
15242
15503
|
}
|
|
@@ -16012,6 +16273,20 @@ async function updateTask(input) {
|
|
|
16012
16273
|
notifyTaskDone();
|
|
16013
16274
|
}
|
|
16014
16275
|
await markTaskNotificationsRead(taskFile);
|
|
16276
|
+
if (input.status === "needs_review" && !isCoordinator) {
|
|
16277
|
+
try {
|
|
16278
|
+
const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
16279
|
+
await writeNotification2({
|
|
16280
|
+
agentId: String(row.assigned_to),
|
|
16281
|
+
agentRole: String(row.assigned_to),
|
|
16282
|
+
event: "task_complete",
|
|
16283
|
+
project: String(row.project_name),
|
|
16284
|
+
summary: `"${String(row.title)}" is ready for review`,
|
|
16285
|
+
taskFile
|
|
16286
|
+
});
|
|
16287
|
+
} catch {
|
|
16288
|
+
}
|
|
16289
|
+
}
|
|
16015
16290
|
if (input.status === "done" || input.status === "closed") {
|
|
16016
16291
|
try {
|
|
16017
16292
|
await cascadeUnblock(taskId, input.baseDir, now2);
|
|
@@ -16444,18 +16719,31 @@ function acquireSpawnLock2(sessionName) {
|
|
|
16444
16719
|
mkdirSync21(SPAWN_LOCK_DIR, { recursive: true });
|
|
16445
16720
|
}
|
|
16446
16721
|
const lockFile = spawnLockPath(sessionName);
|
|
16447
|
-
|
|
16448
|
-
|
|
16449
|
-
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
|
|
16722
|
+
const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
16723
|
+
const { openSync: openSync5, closeSync: closeSync5, writeSync } = __require("fs");
|
|
16724
|
+
const { constants: constants3 } = __require("fs");
|
|
16725
|
+
try {
|
|
16726
|
+
const fd = openSync5(lockFile, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL, 420);
|
|
16727
|
+
writeSync(fd, lockData);
|
|
16728
|
+
closeSync5(fd);
|
|
16729
|
+
return true;
|
|
16730
|
+
} catch (err) {
|
|
16731
|
+
if (err?.code !== "EEXIST") {
|
|
16732
|
+
return true;
|
|
16455
16733
|
}
|
|
16456
16734
|
}
|
|
16457
|
-
|
|
16458
|
-
|
|
16735
|
+
try {
|
|
16736
|
+
const lock = JSON.parse(readFileSync23(lockFile, "utf8"));
|
|
16737
|
+
const age = Date.now() - lock.timestamp;
|
|
16738
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
16739
|
+
return false;
|
|
16740
|
+
}
|
|
16741
|
+
writeFileSync20(lockFile, lockData);
|
|
16742
|
+
return true;
|
|
16743
|
+
} catch {
|
|
16744
|
+
writeFileSync20(lockFile, lockData);
|
|
16745
|
+
return true;
|
|
16746
|
+
}
|
|
16459
16747
|
}
|
|
16460
16748
|
function releaseSpawnLock2(sessionName) {
|
|
16461
16749
|
try {
|
|
@@ -16534,6 +16822,21 @@ function parseParentExe(sessionName, agentId) {
|
|
|
16534
16822
|
function extractRootExe(name) {
|
|
16535
16823
|
if (!name) return null;
|
|
16536
16824
|
if (!name.includes("-")) return name;
|
|
16825
|
+
try {
|
|
16826
|
+
const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
|
|
16827
|
+
if (roster.length > 0) {
|
|
16828
|
+
const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
|
|
16829
|
+
for (const agentName of sortedNames) {
|
|
16830
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16831
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
16832
|
+
const match = name.match(regex);
|
|
16833
|
+
if (match) {
|
|
16834
|
+
return extractRootExe(match[1]);
|
|
16835
|
+
}
|
|
16836
|
+
}
|
|
16837
|
+
}
|
|
16838
|
+
} catch {
|
|
16839
|
+
}
|
|
16537
16840
|
const parts = name.split("-").filter(Boolean);
|
|
16538
16841
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
16539
16842
|
}
|
|
@@ -16552,6 +16855,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
16552
16855
|
function getParentExe(sessionKey) {
|
|
16553
16856
|
try {
|
|
16554
16857
|
const data = JSON.parse(readFileSync23(path34.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
16858
|
+
if (data.registeredAt) {
|
|
16859
|
+
const age = Date.now() - new Date(data.registeredAt).getTime();
|
|
16860
|
+
if (age > PARENT_EXE_CACHE_TTL_MS) return null;
|
|
16861
|
+
}
|
|
16555
16862
|
return data.parentExe || null;
|
|
16556
16863
|
} catch {
|
|
16557
16864
|
return null;
|
|
@@ -17100,7 +17407,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17100
17407
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
17101
17408
|
} catch {
|
|
17102
17409
|
}
|
|
17103
|
-
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
17410
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
17104
17411
|
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
17105
17412
|
const cfg = PROVIDER_TABLE[ccProvider];
|
|
17106
17413
|
if (cfg?.apiKeyEnv) {
|
|
@@ -17135,10 +17442,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17135
17442
|
}
|
|
17136
17443
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
17137
17444
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
ccModel += "[1m]";
|
|
17141
|
-
}
|
|
17445
|
+
const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
|
|
17446
|
+
const ccModel = normalizeCcModelName2(agentRtConfig.model);
|
|
17142
17447
|
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
17143
17448
|
}
|
|
17144
17449
|
}
|
|
@@ -17239,7 +17544,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
17239
17544
|
releaseSpawnLock2(sessionName);
|
|
17240
17545
|
return { sessionName };
|
|
17241
17546
|
}
|
|
17242
|
-
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
17547
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, PARENT_EXE_CACHE_TTL_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
17243
17548
|
var init_tmux_routing = __esm({
|
|
17244
17549
|
"src/lib/tmux-routing.ts"() {
|
|
17245
17550
|
"use strict";
|
|
@@ -17259,6 +17564,7 @@ var init_tmux_routing = __esm({
|
|
|
17259
17564
|
SESSION_CACHE = path34.join(os18.homedir(), ".exe-os", "session-cache");
|
|
17260
17565
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
17261
17566
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
17567
|
+
PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
17262
17568
|
VERIFY_PANE_LINES = 200;
|
|
17263
17569
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
17264
17570
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
@@ -20483,6 +20789,24 @@ async function runSetupWizard(opts = {}) {
|
|
|
20483
20789
|
log("");
|
|
20484
20790
|
log("=== exe-os Setup ===");
|
|
20485
20791
|
log("");
|
|
20792
|
+
if (process.platform === "darwin") {
|
|
20793
|
+
const currentShell = process.env.SHELL ?? "";
|
|
20794
|
+
if (currentShell.includes("bash")) {
|
|
20795
|
+
log(" \u26A0 Your default shell is bash.");
|
|
20796
|
+
log(" macOS uses zsh by default since Catalina (2019).");
|
|
20797
|
+
log(" exe-os works with bash, but switching to zsh is recommended:");
|
|
20798
|
+
log(" chsh -s /bin/zsh");
|
|
20799
|
+
log(" Then restart your terminal.");
|
|
20800
|
+
log("");
|
|
20801
|
+
const cont = await ask3(rl, " Continue with bash? (y/n, default: y): ");
|
|
20802
|
+
if (cont.toLowerCase() === "n") {
|
|
20803
|
+
log(" Run `chsh -s /bin/zsh`, restart your terminal, then run setup again.");
|
|
20804
|
+
rl.close();
|
|
20805
|
+
return;
|
|
20806
|
+
}
|
|
20807
|
+
log("");
|
|
20808
|
+
}
|
|
20809
|
+
}
|
|
20486
20810
|
log("What are you setting up?");
|
|
20487
20811
|
log("");
|
|
20488
20812
|
log(" [1] First device / brand new Exe OS memory");
|
|
@@ -21155,7 +21479,7 @@ var init_setup_wizard = __esm({
|
|
|
21155
21479
|
});
|
|
21156
21480
|
|
|
21157
21481
|
// src/lib/update-backup.ts
|
|
21158
|
-
import { copyFile, readFile as readFile6, readdir as readdir3, writeFile as writeFile7, rm as rm2, mkdir as mkdir7, cp } from "fs/promises";
|
|
21482
|
+
import { copyFile as copyFile2, readFile as readFile6, readdir as readdir3, writeFile as writeFile7, rm as rm2, mkdir as mkdir7, cp } from "fs/promises";
|
|
21159
21483
|
import { existsSync as existsSync36 } from "fs";
|
|
21160
21484
|
import path42 from "path";
|
|
21161
21485
|
import os20 from "os";
|
|
@@ -21191,7 +21515,7 @@ async function createUpdateBackup(currentVersion, dataDir2, homeDir = os20.homed
|
|
|
21191
21515
|
if (!existsSync36(src)) continue;
|
|
21192
21516
|
const dest = path42.join(backupDir, target.name);
|
|
21193
21517
|
if (target.type === "file") {
|
|
21194
|
-
await
|
|
21518
|
+
await copyFile2(src, dest);
|
|
21195
21519
|
} else {
|
|
21196
21520
|
await cp(src, dest, { recursive: true });
|
|
21197
21521
|
}
|
|
@@ -21202,7 +21526,7 @@ async function createUpdateBackup(currentVersion, dataDir2, homeDir = os20.homed
|
|
|
21202
21526
|
if (entry.isFile() && entry.name.endsWith(".db") && entry.name !== BACKUP_DIR_NAME) {
|
|
21203
21527
|
const src = path42.join(dir, entry.name);
|
|
21204
21528
|
const dest = path42.join(backupDir, entry.name);
|
|
21205
|
-
await
|
|
21529
|
+
await copyFile2(src, dest);
|
|
21206
21530
|
backedUpFiles.push(entry.name);
|
|
21207
21531
|
}
|
|
21208
21532
|
}
|
|
@@ -21211,7 +21535,7 @@ async function createUpdateBackup(currentVersion, dataDir2, homeDir = os20.homed
|
|
|
21211
21535
|
for (const target of externalBackupTargets(homeDir)) {
|
|
21212
21536
|
if (!existsSync36(target.path)) continue;
|
|
21213
21537
|
await mkdir7(externalDir, { recursive: true });
|
|
21214
|
-
await
|
|
21538
|
+
await copyFile2(target.path, path42.join(externalDir, target.name));
|
|
21215
21539
|
externalFiles.push(target);
|
|
21216
21540
|
}
|
|
21217
21541
|
const manifest = {
|
|
@@ -21246,14 +21570,14 @@ async function restoreFromBackup(dataDir2) {
|
|
|
21246
21570
|
if (stat2.isDirectory()) {
|
|
21247
21571
|
await cp(src, dest, { recursive: true, force: true });
|
|
21248
21572
|
} else {
|
|
21249
|
-
await
|
|
21573
|
+
await copyFile2(src, dest);
|
|
21250
21574
|
}
|
|
21251
21575
|
}
|
|
21252
21576
|
for (const external of manifest.externalFiles ?? []) {
|
|
21253
21577
|
const src = path42.join(backupDir, "external", external.name);
|
|
21254
21578
|
if (!existsSync36(src)) continue;
|
|
21255
21579
|
await mkdir7(path42.dirname(external.path), { recursive: true });
|
|
21256
|
-
await
|
|
21580
|
+
await copyFile2(src, external.path);
|
|
21257
21581
|
}
|
|
21258
21582
|
return manifest;
|
|
21259
21583
|
}
|
|
@@ -32121,6 +32445,33 @@ var init_dangerous_patterns = __esm({
|
|
|
32121
32445
|
regex: /\bkill\s+-9\b/,
|
|
32122
32446
|
severity: "warning",
|
|
32123
32447
|
reason: "Force kill signal"
|
|
32448
|
+
},
|
|
32449
|
+
// MCP bypass — agents must use MCP tools, never access the DB directly.
|
|
32450
|
+
// These patterns catch attempts to work around a disconnected MCP server.
|
|
32451
|
+
{
|
|
32452
|
+
regex: /\bsqlite3\b.*\bmemories\.db\b/,
|
|
32453
|
+
severity: "critical",
|
|
32454
|
+
reason: "Direct SQLite access bypasses MCP contract boundary \u2014 use MCP tools"
|
|
32455
|
+
},
|
|
32456
|
+
{
|
|
32457
|
+
regex: /\bsqlite3\b.*\.exe-os\b/,
|
|
32458
|
+
severity: "critical",
|
|
32459
|
+
reason: "Direct SQLite access to exe-os database \u2014 use MCP tools"
|
|
32460
|
+
},
|
|
32461
|
+
{
|
|
32462
|
+
regex: /\bnode\s+-e\b.*\b(better-sqlite3|libsql|sqlite3)\b/,
|
|
32463
|
+
severity: "critical",
|
|
32464
|
+
reason: "Inline Node.js script accessing SQLite directly \u2014 use MCP tools"
|
|
32465
|
+
},
|
|
32466
|
+
{
|
|
32467
|
+
regex: /\brequire\s*\(\s*['"].*memories\.db['"]\s*\)/,
|
|
32468
|
+
severity: "critical",
|
|
32469
|
+
reason: "Direct require of memories database \u2014 use MCP tools"
|
|
32470
|
+
},
|
|
32471
|
+
{
|
|
32472
|
+
regex: /\bcat\b.*\bmemories\.db\b/,
|
|
32473
|
+
severity: "warning",
|
|
32474
|
+
reason: "Reading raw database file \u2014 encrypted data, use MCP tools instead"
|
|
32124
32475
|
}
|
|
32125
32476
|
];
|
|
32126
32477
|
}
|
|
@@ -35693,7 +36044,7 @@ function agentColor(agentId) {
|
|
|
35693
36044
|
return "#F0EDE8";
|
|
35694
36045
|
}
|
|
35695
36046
|
}
|
|
35696
|
-
function
|
|
36047
|
+
function formatTimeAgo2(iso) {
|
|
35697
36048
|
const diff2 = Date.now() - new Date(iso).getTime();
|
|
35698
36049
|
const hours = Math.floor(diff2 / 36e5);
|
|
35699
36050
|
if (hours < 1) return "just now";
|
|
@@ -36019,7 +36370,7 @@ function WikiView({ onBack }) {
|
|
|
36019
36370
|
result.rawText.length > 80 ? "..." : ""
|
|
36020
36371
|
] }),
|
|
36021
36372
|
" ",
|
|
36022
|
-
/* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children:
|
|
36373
|
+
/* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children: formatTimeAgo2(result.timestamp) })
|
|
36023
36374
|
]
|
|
36024
36375
|
}
|
|
36025
36376
|
),
|