@askexenow/exe-os 0.9.68 → 0.9.69

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.
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin/exe-healthcheck.ts
4
- import { existsSync, readFileSync, readdirSync } from "fs";
5
- import path from "path";
4
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync } from "fs";
5
+ import path3 from "path";
6
6
  import { execSync } from "child_process";
7
- import { fileURLToPath as fileURLToPath2 } from "url";
7
+ import { fileURLToPath as fileURLToPath3 } from "url";
8
8
 
9
9
  // src/lib/is-main.ts
10
10
  import { realpathSync } from "fs";
@@ -21,31 +21,239 @@ function isMainModule(importMetaUrl) {
21
21
  }
22
22
  }
23
23
 
24
+ // src/lib/config.ts
25
+ import { readFile, writeFile } from "fs/promises";
26
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
27
+ import path from "path";
28
+ import os from "os";
29
+
30
+ // src/lib/secure-files.ts
31
+ import { chmodSync, existsSync, mkdirSync } from "fs";
32
+ import { chmod, mkdir } from "fs/promises";
33
+
34
+ // src/lib/config.ts
35
+ function resolveDataDir() {
36
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
37
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
38
+ const newDir = path.join(os.homedir(), ".exe-os");
39
+ const legacyDir = path.join(os.homedir(), ".exe-mem");
40
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
41
+ try {
42
+ renameSync(legacyDir, newDir);
43
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
44
+ `);
45
+ } catch {
46
+ return legacyDir;
47
+ }
48
+ }
49
+ return newDir;
50
+ }
51
+ var EXE_AI_DIR = resolveDataDir();
52
+ var DB_PATH = path.join(EXE_AI_DIR, "memories.db");
53
+ var MODELS_DIR = path.join(EXE_AI_DIR, "models");
54
+ var CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
55
+ var LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
56
+ var CURRENT_CONFIG_VERSION = 1;
57
+ var DEFAULT_CONFIG = {
58
+ config_version: CURRENT_CONFIG_VERSION,
59
+ dbPath: DB_PATH,
60
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
61
+ embeddingDim: 1024,
62
+ batchSize: 20,
63
+ flushIntervalMs: 1e4,
64
+ autoIngestion: true,
65
+ autoRetrieval: true,
66
+ searchMode: "hybrid",
67
+ hookSearchMode: "hybrid",
68
+ fileGrepEnabled: true,
69
+ splashEffect: true,
70
+ consolidationEnabled: true,
71
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
72
+ consolidationModel: "claude-haiku-4-5-20251001",
73
+ consolidationMaxCallsPerRun: 20,
74
+ selfQueryRouter: true,
75
+ selfQueryModel: "claude-haiku-4-5-20251001",
76
+ rerankerEnabled: true,
77
+ scalingRoadmap: {
78
+ rerankerAutoTrigger: {
79
+ enabled: true,
80
+ broadQueryMinCardinality: 5e4,
81
+ fetchTopK: 200,
82
+ returnTopK: 20
83
+ }
84
+ },
85
+ graphRagEnabled: true,
86
+ wikiEnabled: false,
87
+ wikiUrl: "",
88
+ wikiApiKey: "",
89
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
90
+ wikiWorkspaceMapping: {},
91
+ wikiAutoUpdate: true,
92
+ wikiAutoUpdateThreshold: 0.5,
93
+ wikiAutoUpdateCreateNew: true,
94
+ skillLearning: true,
95
+ skillThreshold: 3,
96
+ skillModel: "claude-haiku-4-5-20251001",
97
+ exeHeartbeat: {
98
+ enabled: true,
99
+ intervalSeconds: 60,
100
+ staleInProgressThresholdHours: 2
101
+ },
102
+ sessionLifecycle: {
103
+ idleKillEnabled: true,
104
+ idleKillTicksRequired: 3,
105
+ idleKillIntercomAckWindowMs: 1e4,
106
+ maxAutoInstances: 10
107
+ },
108
+ autoUpdate: {
109
+ checkOnBoot: true,
110
+ autoInstall: false,
111
+ checkIntervalMs: 24 * 60 * 60 * 1e3
112
+ },
113
+ orchestration: {
114
+ phase: "phase_1_coo",
115
+ phaseSetBy: "default"
116
+ }
117
+ };
118
+
119
+ // src/lib/mcp-transport-health.ts
120
+ import { appendFileSync, closeSync, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
121
+ import path2 from "path";
122
+ import { fileURLToPath as fileURLToPath2 } from "url";
123
+ var MCP_HTTP_EVENTS_PATH = path2.join(EXE_AI_DIR, "mcp-http-events.jsonl");
124
+ var MCP_MONITOR_DIR = path2.join(EXE_AI_DIR, "monitor");
125
+ var MCP_TRANSPORT_SUMMARY_PATH = path2.join(MCP_MONITOR_DIR, "mcp-transport-summary.json");
126
+ var DEFAULT_EVENT_READ_BYTES = 256 * 1024;
127
+ var DEFAULT_EVENT_MAX_BYTES = 1024 * 1024;
128
+ var EVENT_READ_BYTES = parsePositiveInt(process.env.EXE_MCP_HTTP_EVENT_READ_BYTES, DEFAULT_EVENT_READ_BYTES);
129
+ var EVENT_MAX_BYTES = parsePositiveInt(process.env.EXE_MCP_HTTP_EVENT_MAX_BYTES, DEFAULT_EVENT_MAX_BYTES);
130
+ function parsePositiveInt(value, fallback) {
131
+ if (!value) return fallback;
132
+ const parsed = Number.parseInt(value, 10);
133
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
134
+ }
135
+ function readTailText(filePath, maxBytes) {
136
+ const stats = statSync(filePath);
137
+ if (stats.size <= maxBytes) return readFileSync2(filePath, "utf8");
138
+ const fd = openSync(filePath, "r");
139
+ try {
140
+ const buffer = Buffer.allocUnsafe(maxBytes);
141
+ readSync(fd, buffer, 0, maxBytes, stats.size - maxBytes);
142
+ const text = buffer.toString("utf8");
143
+ const firstNewline = text.indexOf("\n");
144
+ return firstNewline >= 0 ? text.slice(firstNewline + 1) : text;
145
+ } finally {
146
+ closeSync(fd);
147
+ }
148
+ }
149
+ function readPackageVersion() {
150
+ let dir = path2.dirname(fileURLToPath2(import.meta.url));
151
+ const { root } = path2.parse(dir);
152
+ while (dir !== root) {
153
+ const pkgPath = path2.join(dir, "package.json");
154
+ if (existsSync3(pkgPath)) {
155
+ try {
156
+ const parsed = JSON.parse(readFileSync2(pkgPath, "utf8"));
157
+ return parsed.version ?? "unknown";
158
+ } catch {
159
+ return "unknown";
160
+ }
161
+ }
162
+ dir = path2.dirname(dir);
163
+ }
164
+ return "unknown";
165
+ }
166
+ function parseMcpHttpEventLines(raw, limit = 200) {
167
+ const lines = raw.trim().split("\n").filter(Boolean).slice(-limit);
168
+ const events = [];
169
+ for (const line of lines) {
170
+ try {
171
+ events.push(JSON.parse(line));
172
+ } catch {
173
+ }
174
+ }
175
+ return events;
176
+ }
177
+ function readMcpHttpEvents(limit = 200) {
178
+ if (!existsSync3(MCP_HTTP_EVENTS_PATH)) return [];
179
+ const raw = readTailText(MCP_HTTP_EVENTS_PATH, EVENT_READ_BYTES);
180
+ return parseMcpHttpEventLines(raw, limit);
181
+ }
182
+ function summarizeMcpTransport(events = readMcpHttpEvents()) {
183
+ const lastServerStartIndex = events.map((event) => event.message).lastIndexOf("server_started");
184
+ const scopedEvents = lastServerStartIndex >= 0 ? events.slice(lastServerStartIndex) : events;
185
+ const activeSessionEvent = [...scopedEvents].reverse().find((event) => typeof event.activeSessions === "number");
186
+ const successfulToolCalls = scopedEvents.filter((event) => event.message === "tool_call_ok");
187
+ const lastSuccessfulToolCall = successfulToolCalls.at(-1) ?? null;
188
+ const summary = {
189
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
190
+ version: readPackageVersion(),
191
+ scope: "local",
192
+ privacy: "metadata_only",
193
+ eventLogPath: MCP_HTTP_EVENTS_PATH,
194
+ activeSessions: activeSessionEvent?.activeSessions ?? null,
195
+ sessionsCreated: scopedEvents.filter((event) => event.message === "session_initialized").length,
196
+ sessionsClosed: scopedEvents.filter((event) => event.message === "session_closed").length,
197
+ missingSessionErrors: scopedEvents.filter((event) => event.message?.includes("missing MCP session")).length,
198
+ staleSessionErrors: scopedEvents.filter((event) => event.message?.includes("stale or unknown")).length,
199
+ transportErrors: scopedEvents.filter((event) => event.level === "warn" || event.level === "error").length,
200
+ lastSuccessfulHandshake: scopedEvents.filter((event) => event.message === "session_initialized").at(-1)?.timestamp ?? null,
201
+ lastSuccessfulToolCall: lastSuccessfulToolCall?.timestamp ?? null,
202
+ lastSuccessfulToolCallByRuntime: {},
203
+ lastSuccessfulToolCallByAgent: {},
204
+ lastSuccessfulToolCallByFamily: {},
205
+ activeSessionsByRuntime: {},
206
+ recentWarnings: scopedEvents.filter((event) => event.level === "warn" || event.level === "error").slice(-10),
207
+ remediation: [
208
+ "If MCP tools fail with missing/stale session, reconnect the MCP client (/mcp or restart the runtime).",
209
+ "Do not access the database directly; MCP is the only data interface.",
210
+ "Check get_daemon_health or exe-healthcheck for daemon/MCP endpoint status before retrying work."
211
+ ]
212
+ };
213
+ const createdBySession = /* @__PURE__ */ new Map();
214
+ for (const event of scopedEvents) {
215
+ if (event.message === "session_initialized" && event.sessionId) createdBySession.set(event.sessionId, event);
216
+ if ((event.message === "session_closed" || event.message === "session_expired") && event.sessionId) {
217
+ createdBySession.delete(event.sessionId);
218
+ }
219
+ }
220
+ for (const event of createdBySession.values()) {
221
+ const runtime = event.runtime ?? "Unknown";
222
+ summary.activeSessionsByRuntime[runtime] = (summary.activeSessionsByRuntime[runtime] ?? 0) + 1;
223
+ }
224
+ for (const event of successfulToolCalls) {
225
+ if (event.runtime) summary.lastSuccessfulToolCallByRuntime[event.runtime] = event.timestamp ?? "";
226
+ if (event.agentId) summary.lastSuccessfulToolCallByAgent[event.agentId] = event.timestamp ?? "";
227
+ if (event.toolFamily) summary.lastSuccessfulToolCallByFamily[event.toolFamily] = event.timestamp ?? "";
228
+ }
229
+ return summary;
230
+ }
231
+
24
232
  // src/bin/exe-healthcheck.ts
25
233
  function findPackageRoot() {
26
- let dir = path.dirname(fileURLToPath2(import.meta.url));
27
- const { root } = path.parse(dir);
234
+ let dir = path3.dirname(fileURLToPath3(import.meta.url));
235
+ const { root } = path3.parse(dir);
28
236
  while (dir !== root) {
29
- if (existsSync(path.join(dir, "package.json"))) return dir;
30
- dir = path.dirname(dir);
237
+ if (existsSync4(path3.join(dir, "package.json"))) return dir;
238
+ dir = path3.dirname(dir);
31
239
  }
32
240
  throw new Error("Cannot find package root");
33
241
  }
34
242
  function checkBuildIntegrity(pkgRoot) {
35
243
  const results = [];
36
- const tsupConfig = path.join(pkgRoot, "tsup.config.ts");
37
- if (!existsSync(tsupConfig)) {
244
+ const tsupConfig = path3.join(pkgRoot, "tsup.config.ts");
245
+ if (!existsSync4(tsupConfig)) {
38
246
  return [{ name: "build/tsup-config", pass: false, detail: "tsup.config.ts not found" }];
39
247
  }
40
- const configContent = readFileSync(tsupConfig, "utf-8");
248
+ const configContent = readFileSync3(tsupConfig, "utf-8");
41
249
  const entryMatches = configContent.matchAll(/"([^"]+)":\s*"src\//g);
42
250
  const missing = [];
43
251
  let total = 0;
44
252
  for (const match of entryMatches) {
45
253
  const outputKey = match[1];
46
- const expectedPath = path.join(pkgRoot, "dist", `${outputKey}.js`);
254
+ const expectedPath = path3.join(pkgRoot, "dist", `${outputKey}.js`);
47
255
  total++;
48
- if (!existsSync(expectedPath)) {
256
+ if (!existsSync4(expectedPath)) {
49
257
  missing.push(`dist/${outputKey}.js`);
50
258
  }
51
259
  }
@@ -67,8 +275,8 @@ function checkBuildIntegrity(pkgRoot) {
67
275
  }
68
276
  function checkEmbedPipeline(pkgRoot) {
69
277
  const results = [];
70
- const daemonPath = path.join(pkgRoot, "dist", "lib", "exe-daemon.js");
71
- if (!existsSync(daemonPath)) {
278
+ const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
279
+ if (!existsSync4(daemonPath)) {
72
280
  results.push({
73
281
  name: "exed/daemon-exists",
74
282
  pass: false,
@@ -79,17 +287,17 @@ function checkEmbedPipeline(pkgRoot) {
79
287
  results.push({ name: "exed/daemon-exists", pass: true, detail: "dist/lib/exe-daemon.js exists" });
80
288
  const entryDirs = ["dist/hooks", "dist/bin", "dist/mcp"];
81
289
  for (const dir of entryDirs) {
82
- const fullDir = path.join(pkgRoot, dir);
83
- if (!existsSync(fullDir)) continue;
290
+ const fullDir = path3.join(pkgRoot, dir);
291
+ if (!existsSync4(fullDir)) continue;
84
292
  let walkDir = fullDir;
85
- const { root } = path.parse(walkDir);
293
+ const { root } = path3.parse(walkDir);
86
294
  let foundRoot = null;
87
295
  while (walkDir !== root) {
88
- if (existsSync(path.join(walkDir, "package.json"))) {
296
+ if (existsSync4(path3.join(walkDir, "package.json"))) {
89
297
  foundRoot = walkDir;
90
298
  break;
91
299
  }
92
- walkDir = path.dirname(walkDir);
300
+ walkDir = path3.dirname(walkDir);
93
301
  }
94
302
  if (!foundRoot) {
95
303
  results.push({
@@ -99,8 +307,8 @@ function checkEmbedPipeline(pkgRoot) {
99
307
  });
100
308
  continue;
101
309
  }
102
- const resolvedDaemon = path.join(foundRoot, "dist", "lib", "exe-daemon.js");
103
- const reachable = existsSync(resolvedDaemon);
310
+ const resolvedDaemon = path3.join(foundRoot, "dist", "lib", "exe-daemon.js");
311
+ const reachable = existsSync4(resolvedDaemon);
104
312
  results.push({
105
313
  name: `exed/reachable-from-${dir}`,
106
314
  pass: reachable,
@@ -111,8 +319,8 @@ function checkEmbedPipeline(pkgRoot) {
111
319
  }
112
320
  function checkTaskSystem(pkgRoot) {
113
321
  const results = [];
114
- const scannerPath = path.join(pkgRoot, "dist", "bin", "scan-tasks.js");
115
- if (!existsSync(scannerPath)) {
322
+ const scannerPath = path3.join(pkgRoot, "dist", "bin", "scan-tasks.js");
323
+ if (!existsSync4(scannerPath)) {
116
324
  results.push({ name: "tasks/scanner", pass: false, detail: "scan-tasks.js not found" });
117
325
  return results;
118
326
  }
@@ -134,8 +342,8 @@ function checkTaskSystem(pkgRoot) {
134
342
  }
135
343
  function checkWorkerSpawning(pkgRoot) {
136
344
  const results = [];
137
- const workerPath = path.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
138
- if (!existsSync(workerPath)) {
345
+ const workerPath = path3.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
346
+ if (!existsSync4(workerPath)) {
139
347
  results.push({ name: "workers/ingest-worker", pass: false, detail: "ingest-worker.js not found" });
140
348
  return results;
141
349
  }
@@ -149,14 +357,14 @@ function checkWorkerSpawning(pkgRoot) {
149
357
  detail: `Parse error: ${err instanceof Error ? err.message.slice(0, 200) : String(err)}`
150
358
  });
151
359
  }
152
- const hooksDir = path.join(pkgRoot, "dist", "hooks");
153
- if (existsSync(hooksDir)) {
360
+ const hooksDir = path3.join(pkgRoot, "dist", "hooks");
361
+ if (existsSync4(hooksDir)) {
154
362
  const hookFiles = readdirSync(hooksDir).filter((f) => f.endsWith(".js") && !f.endsWith(".js.map"));
155
363
  let hooksPassed = 0;
156
364
  const hooksFailed = [];
157
365
  for (const hook of hookFiles) {
158
366
  try {
159
- execSync(`node --check "${path.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
367
+ execSync(`node --check "${path3.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
160
368
  hooksPassed++;
161
369
  } catch {
162
370
  hooksFailed.push(hook);
@@ -178,6 +386,60 @@ function checkWorkerSpawning(pkgRoot) {
178
386
  }
179
387
  return results;
180
388
  }
389
+ function checkMcpTransport() {
390
+ const results = [];
391
+ const pidPath = path3.join(EXE_AI_DIR, "exed.pid");
392
+ const tokenPath = path3.join(EXE_AI_DIR, "exed.token");
393
+ let daemonAlive = false;
394
+ if (existsSync4(pidPath)) {
395
+ try {
396
+ const pid = parseInt(readFileSync3(pidPath, "utf8").trim(), 10);
397
+ process.kill(pid, 0);
398
+ daemonAlive = true;
399
+ results.push({ name: "mcp/daemon-process", pass: true, detail: `exed process alive (pid ${pid})` });
400
+ } catch {
401
+ results.push({ name: "mcp/daemon-process", pass: false, detail: "exed.pid exists but process is not alive; restart exe-os/exed" });
402
+ }
403
+ } else {
404
+ results.push({ name: "mcp/daemon-process", pass: false, detail: "No exed.pid found; daemon is not running" });
405
+ }
406
+ if (daemonAlive && existsSync4(tokenPath)) {
407
+ try {
408
+ const token = readFileSync3(tokenPath, "utf8").trim();
409
+ const response = execSync(
410
+ `curl -sS -i -m 2 -H "Authorization: Bearer ${token}" -H 'Accept: application/json, text/event-stream' http://127.0.0.1:48739/mcp`,
411
+ { encoding: "utf8", timeout: 3e3 }
412
+ );
413
+ const jsonRpcError = response.includes("Content-Type: application/json") && response.includes("missing MCP session");
414
+ results.push({
415
+ name: "mcp/http-endpoint",
416
+ pass: jsonRpcError,
417
+ detail: jsonRpcError ? "MCP HTTP endpoint reachable; missing-session probe returns JSON-RPC (expected)" : "MCP HTTP endpoint reachable but did not return expected JSON-RPC missing-session response"
418
+ });
419
+ } catch (err) {
420
+ results.push({
421
+ name: "mcp/http-endpoint",
422
+ pass: false,
423
+ detail: `MCP HTTP endpoint not reachable: ${err instanceof Error ? err.message.slice(0, 180) : String(err)}`
424
+ });
425
+ }
426
+ } else if (daemonAlive) {
427
+ results.push({ name: "mcp/http-endpoint", pass: false, detail: "Daemon is alive but exed.token is missing; cannot probe MCP HTTP" });
428
+ }
429
+ const events = readMcpHttpEvents(200);
430
+ const summary = summarizeMcpTransport(events);
431
+ results.push({
432
+ name: "mcp/local-event-log",
433
+ pass: true,
434
+ detail: `${events.length} recent event(s); activeSessions=${summary.activeSessions ?? "unknown"}; lastHandshake=${summary.lastSuccessfulHandshake ?? "none"}; lastTool=${summary.lastSuccessfulToolCall ?? "none"}`
435
+ });
436
+ results.push({
437
+ name: "mcp/monitor-summary",
438
+ pass: true,
439
+ detail: `Privacy-safe local monitor summary: ${path3.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
440
+ });
441
+ return results;
442
+ }
181
443
  function checkClaudeCodeInstall() {
182
444
  const results = [];
183
445
  const execPath = process.env.CLAUDE_CODE_EXECPATH ?? "";
@@ -232,14 +494,14 @@ function checkClaudeCodeInstall() {
232
494
  detail: "Failed to check claude binary path"
233
495
  });
234
496
  }
235
- const versionsDir = path.join(
497
+ const versionsDir = path3.join(
236
498
  process.env.HOME ?? process.env.USERPROFILE ?? "",
237
499
  ".local",
238
500
  "share",
239
501
  "claude",
240
502
  "versions"
241
503
  );
242
- if (existsSync(versionsDir)) {
504
+ if (existsSync4(versionsDir)) {
243
505
  try {
244
506
  const entries = readdirSync(versionsDir);
245
507
  if (entries.length > 0) {
@@ -315,6 +577,7 @@ function runHealthCheck() {
315
577
  ...checkEmbedPipeline(pkgRoot),
316
578
  ...checkTaskSystem(pkgRoot),
317
579
  ...checkWorkerSpawning(pkgRoot),
580
+ ...checkMcpTransport(),
318
581
  ...checkClaudeCodeInstall()
319
582
  ];
320
583
  const passed = results.filter((r) => r.pass).length;