@askexenow/exe-os 0.9.57 → 0.9.59
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/cli.js +15 -8
- package/dist/bin/exe-boot.js +3 -8
- package/dist/bin/exe-dispatch.js +3 -8
- package/dist/bin/exe-gateway.js +3 -8
- package/dist/bin/exe-new-employee.js +12 -0
- package/dist/bin/exe-session-cleanup.js +3 -8
- package/dist/bin/git-sweep.js +3 -8
- package/dist/bin/install.js +12 -0
- package/dist/bin/intercom-check.js +3 -8
- package/dist/bin/scan-tasks.js +3 -8
- package/dist/gateway/index.js +3 -8
- package/dist/hooks/bug-report-worker.js +3 -8
- package/dist/hooks/commit-complete.js +3 -8
- package/dist/hooks/ingest-worker.js +377 -373
- package/dist/hooks/post-tool-combined.js +123 -108
- package/dist/hooks/pre-compact.js +3 -8
- package/dist/hooks/prompt-submit.js +3 -8
- package/dist/hooks/session-end.js +3 -8
- package/dist/index.js +3 -8
- package/dist/lib/exe-daemon.js +3 -8
- package/dist/lib/post-tool-memory.js +373 -0
- package/dist/lib/tasks.js +3 -8
- package/dist/lib/tmux-routing.js +3 -8
- package/dist/mcp/server.js +3 -8
- package/dist/mcp/tools/create-task.js +3 -8
- package/dist/mcp/tools/update-task.js +3 -8
- package/dist/runtime/index.js +3 -8
- package/dist/tui/App.js +3 -8
- package/package.json +10 -2
|
@@ -5,8 +5,193 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
-
// src/lib/
|
|
8
|
+
// src/lib/exe-daemon-client.ts
|
|
9
|
+
import net from "net";
|
|
10
|
+
import os2 from "os";
|
|
11
|
+
import { spawn } from "child_process";
|
|
12
|
+
import { randomUUID } from "crypto";
|
|
13
|
+
import { existsSync as existsSync4, unlinkSync, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
14
|
+
import path3 from "path";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
|
|
17
|
+
// src/lib/config.ts
|
|
18
|
+
import { readFile, writeFile } from "fs/promises";
|
|
19
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import os from "os";
|
|
22
|
+
|
|
23
|
+
// src/lib/secure-files.ts
|
|
24
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
25
|
+
import { chmod, mkdir } from "fs/promises";
|
|
26
|
+
|
|
27
|
+
// src/lib/config.ts
|
|
28
|
+
function resolveDataDir() {
|
|
29
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
30
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
31
|
+
const newDir = path.join(os.homedir(), ".exe-os");
|
|
32
|
+
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
33
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
34
|
+
try {
|
|
35
|
+
renameSync(legacyDir, newDir);
|
|
36
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
37
|
+
`);
|
|
38
|
+
} catch {
|
|
39
|
+
return legacyDir;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return newDir;
|
|
43
|
+
}
|
|
44
|
+
var EXE_AI_DIR = resolveDataDir();
|
|
45
|
+
var DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
46
|
+
var MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
47
|
+
var CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
48
|
+
var LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
49
|
+
var CURRENT_CONFIG_VERSION = 1;
|
|
50
|
+
var DEFAULT_CONFIG = {
|
|
51
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
52
|
+
dbPath: DB_PATH,
|
|
53
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
54
|
+
embeddingDim: 1024,
|
|
55
|
+
batchSize: 20,
|
|
56
|
+
flushIntervalMs: 1e4,
|
|
57
|
+
autoIngestion: true,
|
|
58
|
+
autoRetrieval: true,
|
|
59
|
+
searchMode: "hybrid",
|
|
60
|
+
hookSearchMode: "hybrid",
|
|
61
|
+
fileGrepEnabled: true,
|
|
62
|
+
splashEffect: true,
|
|
63
|
+
consolidationEnabled: true,
|
|
64
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
65
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
66
|
+
consolidationMaxCallsPerRun: 20,
|
|
67
|
+
selfQueryRouter: true,
|
|
68
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
69
|
+
rerankerEnabled: true,
|
|
70
|
+
scalingRoadmap: {
|
|
71
|
+
rerankerAutoTrigger: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
broadQueryMinCardinality: 5e4,
|
|
74
|
+
fetchTopK: 150,
|
|
75
|
+
returnTopK: 5
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
graphRagEnabled: true,
|
|
79
|
+
wikiEnabled: false,
|
|
80
|
+
wikiUrl: "",
|
|
81
|
+
wikiApiKey: "",
|
|
82
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
83
|
+
wikiWorkspaceMapping: {},
|
|
84
|
+
wikiAutoUpdate: true,
|
|
85
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
86
|
+
wikiAutoUpdateCreateNew: true,
|
|
87
|
+
skillLearning: true,
|
|
88
|
+
skillThreshold: 3,
|
|
89
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
90
|
+
exeHeartbeat: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
intervalSeconds: 60,
|
|
93
|
+
staleInProgressThresholdHours: 2
|
|
94
|
+
},
|
|
95
|
+
sessionLifecycle: {
|
|
96
|
+
idleKillEnabled: true,
|
|
97
|
+
idleKillTicksRequired: 3,
|
|
98
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
99
|
+
maxAutoInstances: 10
|
|
100
|
+
},
|
|
101
|
+
autoUpdate: {
|
|
102
|
+
checkOnBoot: true,
|
|
103
|
+
autoInstall: false,
|
|
104
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/lib/daemon-auth.ts
|
|
9
109
|
import crypto from "crypto";
|
|
110
|
+
import path2 from "path";
|
|
111
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
112
|
+
var DAEMON_TOKEN_PATH = path2.join(EXE_AI_DIR, "exed.token");
|
|
113
|
+
function normalizeToken(token) {
|
|
114
|
+
if (!token) return null;
|
|
115
|
+
const trimmed = token.trim();
|
|
116
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
117
|
+
}
|
|
118
|
+
function readDaemonToken() {
|
|
119
|
+
try {
|
|
120
|
+
if (!existsSync3(DAEMON_TOKEN_PATH)) return null;
|
|
121
|
+
return normalizeToken(readFileSync2(DAEMON_TOKEN_PATH, "utf8"));
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/lib/exe-daemon-client.ts
|
|
128
|
+
var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
|
|
129
|
+
var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
|
|
130
|
+
var SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
131
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
132
|
+
var DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
133
|
+
var _socket = null;
|
|
134
|
+
var _connected = false;
|
|
135
|
+
var _pending = /* @__PURE__ */ new Map();
|
|
136
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
if (!_socket || !_connected) {
|
|
139
|
+
resolve({ error: "Not connected" });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const id = randomUUID();
|
|
143
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
_pending.delete(id);
|
|
146
|
+
resolve({ error: "Request timeout" });
|
|
147
|
+
}, timeoutMs);
|
|
148
|
+
_pending.set(id, { resolve, timer });
|
|
149
|
+
try {
|
|
150
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
151
|
+
} catch {
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
_pending.delete(id);
|
|
154
|
+
resolve({ error: "Write failed" });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function isClientConnected() {
|
|
159
|
+
return _connected;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/lib/memory-queue.ts
|
|
163
|
+
import { appendFileSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
164
|
+
import path4 from "path";
|
|
165
|
+
var QUEUE_PATH = path4.join(EXE_AI_DIR, "memory-queue.jsonl");
|
|
166
|
+
var PROCESSING_PATH = QUEUE_PATH + ".processing";
|
|
167
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
168
|
+
function enqueueMemory(entry) {
|
|
169
|
+
appendFileSync(QUEUE_PATH, JSON.stringify(entry) + "\n");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/lib/memory-queue-client.ts
|
|
173
|
+
async function writeMemoryViaDaemon(entry) {
|
|
174
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
175
|
+
enqueueMemory(entry);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (!isClientConnected()) {
|
|
179
|
+
enqueueMemory(entry);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const response = await sendDaemonRequest({
|
|
184
|
+
type: "write-memory",
|
|
185
|
+
entry
|
|
186
|
+
});
|
|
187
|
+
if (response.ok) return true;
|
|
188
|
+
enqueueMemory(entry);
|
|
189
|
+
return false;
|
|
190
|
+
} catch {
|
|
191
|
+
enqueueMemory(entry);
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
10
195
|
|
|
11
196
|
// src/lib/mcp-prefix.ts
|
|
12
197
|
var MCP_PRIMARY_KEY = "exe-os";
|
|
@@ -26,134 +211,6 @@ function stripExeMcpPrefix(toolName) {
|
|
|
26
211
|
return toolName;
|
|
27
212
|
}
|
|
28
213
|
|
|
29
|
-
// src/lib/error-detector.ts
|
|
30
|
-
var ERROR_PATTERNS = [
|
|
31
|
-
/\bError\b/i,
|
|
32
|
-
/\bERR!\b/,
|
|
33
|
-
/\bFAIL(ED|URE)?\b/i,
|
|
34
|
-
/\bException\b/i,
|
|
35
|
-
/\bTraceback\b/,
|
|
36
|
-
/\bpanic\b/,
|
|
37
|
-
/\bSIGSEGV\b/,
|
|
38
|
-
/\bSIGABRT\b/,
|
|
39
|
-
/exit code [1-9]/i,
|
|
40
|
-
/non-zero (exit|status)/i,
|
|
41
|
-
/command not found/i,
|
|
42
|
-
/permission denied/i,
|
|
43
|
-
/ENOENT/,
|
|
44
|
-
/EACCES/,
|
|
45
|
-
/ENOMEM/
|
|
46
|
-
];
|
|
47
|
-
var FILE_CONTENT_TOOLS = /* @__PURE__ */ new Set([
|
|
48
|
-
"Read",
|
|
49
|
-
"Write",
|
|
50
|
-
"Edit",
|
|
51
|
-
"Glob",
|
|
52
|
-
"Grep",
|
|
53
|
-
"NotebookEdit"
|
|
54
|
-
]);
|
|
55
|
-
var STDERR_IGNORE_PATTERNS = [
|
|
56
|
-
/^warning\b/i,
|
|
57
|
-
/\bDeprecationWarning\b/,
|
|
58
|
-
/^hint:/i,
|
|
59
|
-
/^npm warn\b/i,
|
|
60
|
-
/^npm notice\b/i,
|
|
61
|
-
/^\(node:\d+\) \w*Warning:/,
|
|
62
|
-
/^Cloning into/,
|
|
63
|
-
/^Already on/,
|
|
64
|
-
/^Switched to/,
|
|
65
|
-
/^Your branch is/,
|
|
66
|
-
/^Auto-merging/,
|
|
67
|
-
/^\s*$/
|
|
68
|
-
];
|
|
69
|
-
function isRealStderr(stderr) {
|
|
70
|
-
const lines = stderr.trim().split("\n");
|
|
71
|
-
const meaningful = lines.filter(
|
|
72
|
-
(line) => line.trim().length > 0 && !STDERR_IGNORE_PATTERNS.some((p) => p.test(line))
|
|
73
|
-
);
|
|
74
|
-
return meaningful.length > 0;
|
|
75
|
-
}
|
|
76
|
-
function logSuppression(exitCode, stderr, stdout) {
|
|
77
|
-
process.stderr.write(
|
|
78
|
-
`[error-detector] suppressed bash non-zero exit: code=${exitCode} stderr_len=${stderr.length} stdout_len=${stdout.length}
|
|
79
|
-
`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
function detectError(data) {
|
|
83
|
-
const response = data.tool_response;
|
|
84
|
-
if (!response) return false;
|
|
85
|
-
const toolName = data.tool_name ?? "";
|
|
86
|
-
if (FILE_CONTENT_TOOLS.has(toolName)) {
|
|
87
|
-
return response.type === "error" || response.error != null;
|
|
88
|
-
}
|
|
89
|
-
if (toolName === "Bash") {
|
|
90
|
-
const exitCode = typeof response.exitCode === "number" ? response.exitCode : 0;
|
|
91
|
-
const stderr = typeof response.stderr === "string" ? response.stderr : "";
|
|
92
|
-
const stdout = typeof response.stdout === "string" ? response.stdout : "";
|
|
93
|
-
if (exitCode === 141) {
|
|
94
|
-
logSuppression(exitCode, stderr, stdout);
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
if (stderr.trim().length > 0 && isRealStderr(stderr)) return true;
|
|
98
|
-
if (exitCode !== 0 && !isRealStderr(stderr) && stdout.trim().length > 0) {
|
|
99
|
-
logSuppression(exitCode, stderr, stdout);
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
if (exitCode !== 0) return true;
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
if (response.type === "error" || response.error != null || response.isError === true) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
if (typeof response.stderr === "string" && response.stderr.trim().length > 0) {
|
|
109
|
-
if (isRealStderr(response.stderr)) return true;
|
|
110
|
-
}
|
|
111
|
-
const textParts = [];
|
|
112
|
-
if (typeof response.stdout === "string") textParts.push(response.stdout);
|
|
113
|
-
if (typeof response.output === "string") textParts.push(response.output);
|
|
114
|
-
if (typeof response.text === "string") textParts.push(response.text);
|
|
115
|
-
if (typeof response.message === "string") textParts.push(response.message);
|
|
116
|
-
if (textParts.length === 0) return false;
|
|
117
|
-
const text = textParts.join("\n");
|
|
118
|
-
return ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/lib/project-name.ts
|
|
122
|
-
import { execSync } from "child_process";
|
|
123
|
-
import path from "path";
|
|
124
|
-
var _cached = null;
|
|
125
|
-
var _cachedCwd = null;
|
|
126
|
-
function getProjectName(cwd) {
|
|
127
|
-
const dir = cwd ?? process.cwd();
|
|
128
|
-
if (_cached && _cachedCwd === dir) return _cached;
|
|
129
|
-
try {
|
|
130
|
-
let repoRoot;
|
|
131
|
-
try {
|
|
132
|
-
const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
|
|
133
|
-
cwd: dir,
|
|
134
|
-
encoding: "utf8",
|
|
135
|
-
timeout: 2e3,
|
|
136
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
137
|
-
}).trim();
|
|
138
|
-
repoRoot = path.dirname(gitCommonDir);
|
|
139
|
-
} catch {
|
|
140
|
-
repoRoot = execSync("git rev-parse --show-toplevel", {
|
|
141
|
-
cwd: dir,
|
|
142
|
-
encoding: "utf8",
|
|
143
|
-
timeout: 2e3,
|
|
144
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
145
|
-
}).trim();
|
|
146
|
-
}
|
|
147
|
-
_cached = path.basename(repoRoot);
|
|
148
|
-
_cachedCwd = dir;
|
|
149
|
-
return _cached;
|
|
150
|
-
} catch {
|
|
151
|
-
_cached = path.basename(dir);
|
|
152
|
-
_cachedCwd = dir;
|
|
153
|
-
return _cached;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
214
|
// src/lib/content-extractor.ts
|
|
158
215
|
var MAX_CONTENT = 2e3;
|
|
159
216
|
var MAX_OUTPUT = 1e3;
|
|
@@ -298,233 +355,206 @@ Result: ${result.slice(0, MAX_CONTENT)}` : ""}`;
|
|
|
298
355
|
return `Listed tasks
|
|
299
356
|
${resultText.slice(0, MAX_OUTPUT)}`;
|
|
300
357
|
}
|
|
301
|
-
default: {
|
|
302
|
-
return extractGenericMcp(toolName, input2, response);
|
|
358
|
+
default: {
|
|
359
|
+
return extractGenericMcp(toolName, input2, response);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function extractGenericMcp(toolName, input2, response) {
|
|
364
|
+
const shortName = toolName.replace(/^mcp__[^_]+__/, "");
|
|
365
|
+
const inputParts = Object.entries(input2).filter(([, v]) => v != null && String(v).length > 0).map(([k, v]) => `${k}: ${String(v).slice(0, 200)}`).join(", ");
|
|
366
|
+
const resultText = extractResponseText(response);
|
|
367
|
+
return `${shortName}(${inputParts})
|
|
368
|
+
${resultText.slice(0, MAX_OUTPUT)}`;
|
|
369
|
+
}
|
|
370
|
+
function extractDefault(toolName, input2, response) {
|
|
371
|
+
const inputStr = JSON.stringify(input2);
|
|
372
|
+
const resultText = extractResponseText(response);
|
|
373
|
+
return `Tool: ${toolName}
|
|
374
|
+
${inputStr.slice(0, MAX_CONTENT / 2)}
|
|
375
|
+
${resultText.slice(0, MAX_OUTPUT)}`;
|
|
376
|
+
}
|
|
377
|
+
function extractResponseText(response) {
|
|
378
|
+
if (typeof response.text === "string") return response.text;
|
|
379
|
+
if (typeof response.content === "string") return response.content;
|
|
380
|
+
if (Array.isArray(response.content)) {
|
|
381
|
+
return response.content.map((block) => {
|
|
382
|
+
if (typeof block === "object" && block !== null && "text" in block) {
|
|
383
|
+
return String(block.text);
|
|
384
|
+
}
|
|
385
|
+
return "";
|
|
386
|
+
}).filter(Boolean).join("\n");
|
|
387
|
+
}
|
|
388
|
+
if (Array.isArray(response)) {
|
|
389
|
+
return response.map((item) => {
|
|
390
|
+
if (typeof item === "object" && item !== null && "text" in item) {
|
|
391
|
+
return String(item.text);
|
|
392
|
+
}
|
|
393
|
+
return "";
|
|
394
|
+
}).filter(Boolean).join("\n");
|
|
395
|
+
}
|
|
396
|
+
return JSON.stringify(response).slice(0, MAX_OUTPUT);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/lib/error-detector.ts
|
|
400
|
+
import crypto2 from "crypto";
|
|
401
|
+
var ERROR_PATTERNS = [
|
|
402
|
+
/\bError\b/i,
|
|
403
|
+
/\bERR!\b/,
|
|
404
|
+
/\bFAIL(ED|URE)?\b/i,
|
|
405
|
+
/\bException\b/i,
|
|
406
|
+
/\bTraceback\b/,
|
|
407
|
+
/\bpanic\b/,
|
|
408
|
+
/\bSIGSEGV\b/,
|
|
409
|
+
/\bSIGABRT\b/,
|
|
410
|
+
/exit code [1-9]/i,
|
|
411
|
+
/non-zero (exit|status)/i,
|
|
412
|
+
/command not found/i,
|
|
413
|
+
/permission denied/i,
|
|
414
|
+
/ENOENT/,
|
|
415
|
+
/EACCES/,
|
|
416
|
+
/ENOMEM/
|
|
417
|
+
];
|
|
418
|
+
var FILE_CONTENT_TOOLS = /* @__PURE__ */ new Set([
|
|
419
|
+
"Read",
|
|
420
|
+
"Write",
|
|
421
|
+
"Edit",
|
|
422
|
+
"Glob",
|
|
423
|
+
"Grep",
|
|
424
|
+
"NotebookEdit"
|
|
425
|
+
]);
|
|
426
|
+
var STDERR_IGNORE_PATTERNS = [
|
|
427
|
+
/^warning\b/i,
|
|
428
|
+
/\bDeprecationWarning\b/,
|
|
429
|
+
/^hint:/i,
|
|
430
|
+
/^npm warn\b/i,
|
|
431
|
+
/^npm notice\b/i,
|
|
432
|
+
/^\(node:\d+\) \w*Warning:/,
|
|
433
|
+
/^Cloning into/,
|
|
434
|
+
/^Already on/,
|
|
435
|
+
/^Switched to/,
|
|
436
|
+
/^Your branch is/,
|
|
437
|
+
/^Auto-merging/,
|
|
438
|
+
/^\s*$/
|
|
439
|
+
];
|
|
440
|
+
function isRealStderr(stderr) {
|
|
441
|
+
const lines = stderr.trim().split("\n");
|
|
442
|
+
const meaningful = lines.filter(
|
|
443
|
+
(line) => line.trim().length > 0 && !STDERR_IGNORE_PATTERNS.some((p) => p.test(line))
|
|
444
|
+
);
|
|
445
|
+
return meaningful.length > 0;
|
|
446
|
+
}
|
|
447
|
+
function logSuppression(exitCode, stderr, stdout) {
|
|
448
|
+
process.stderr.write(
|
|
449
|
+
`[error-detector] suppressed bash non-zero exit: code=${exitCode} stderr_len=${stderr.length} stdout_len=${stdout.length}
|
|
450
|
+
`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
function detectError(data) {
|
|
454
|
+
const response = data.tool_response;
|
|
455
|
+
if (!response) return false;
|
|
456
|
+
const toolName = data.tool_name ?? "";
|
|
457
|
+
if (FILE_CONTENT_TOOLS.has(toolName)) {
|
|
458
|
+
return response.type === "error" || response.error != null;
|
|
459
|
+
}
|
|
460
|
+
if (toolName === "Bash") {
|
|
461
|
+
const exitCode = typeof response.exitCode === "number" ? response.exitCode : 0;
|
|
462
|
+
const stderr = typeof response.stderr === "string" ? response.stderr : "";
|
|
463
|
+
const stdout = typeof response.stdout === "string" ? response.stdout : "";
|
|
464
|
+
if (exitCode === 141) {
|
|
465
|
+
logSuppression(exitCode, stderr, stdout);
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
if (stderr.trim().length > 0 && isRealStderr(stderr)) return true;
|
|
469
|
+
if (exitCode !== 0 && !isRealStderr(stderr) && stdout.trim().length > 0) {
|
|
470
|
+
logSuppression(exitCode, stderr, stdout);
|
|
471
|
+
return false;
|
|
303
472
|
}
|
|
473
|
+
if (exitCode !== 0) return true;
|
|
474
|
+
return false;
|
|
304
475
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const shortName = toolName.replace(/^mcp__[^_]+__/, "");
|
|
308
|
-
const inputParts = Object.entries(input2).filter(([, v]) => v != null && String(v).length > 0).map(([k, v]) => `${k}: ${String(v).slice(0, 200)}`).join(", ");
|
|
309
|
-
const resultText = extractResponseText(response);
|
|
310
|
-
return `${shortName}(${inputParts})
|
|
311
|
-
${resultText.slice(0, MAX_OUTPUT)}`;
|
|
312
|
-
}
|
|
313
|
-
function extractDefault(toolName, input2, response) {
|
|
314
|
-
const inputStr = JSON.stringify(input2);
|
|
315
|
-
const resultText = extractResponseText(response);
|
|
316
|
-
return `Tool: ${toolName}
|
|
317
|
-
${inputStr.slice(0, MAX_CONTENT / 2)}
|
|
318
|
-
${resultText.slice(0, MAX_OUTPUT)}`;
|
|
319
|
-
}
|
|
320
|
-
function extractResponseText(response) {
|
|
321
|
-
if (typeof response.text === "string") return response.text;
|
|
322
|
-
if (typeof response.content === "string") return response.content;
|
|
323
|
-
if (Array.isArray(response.content)) {
|
|
324
|
-
return response.content.map((block) => {
|
|
325
|
-
if (typeof block === "object" && block !== null && "text" in block) {
|
|
326
|
-
return String(block.text);
|
|
327
|
-
}
|
|
328
|
-
return "";
|
|
329
|
-
}).filter(Boolean).join("\n");
|
|
476
|
+
if (response.type === "error" || response.error != null || response.isError === true) {
|
|
477
|
+
return true;
|
|
330
478
|
}
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
if (typeof item === "object" && item !== null && "text" in item) {
|
|
334
|
-
return String(item.text);
|
|
335
|
-
}
|
|
336
|
-
return "";
|
|
337
|
-
}).filter(Boolean).join("\n");
|
|
479
|
+
if (typeof response.stderr === "string" && response.stderr.trim().length > 0) {
|
|
480
|
+
if (isRealStderr(response.stderr)) return true;
|
|
338
481
|
}
|
|
339
|
-
|
|
482
|
+
const textParts = [];
|
|
483
|
+
if (typeof response.stdout === "string") textParts.push(response.stdout);
|
|
484
|
+
if (typeof response.output === "string") textParts.push(response.output);
|
|
485
|
+
if (typeof response.text === "string") textParts.push(response.text);
|
|
486
|
+
if (typeof response.message === "string") textParts.push(response.message);
|
|
487
|
+
if (textParts.length === 0) return false;
|
|
488
|
+
const text = textParts.join("\n");
|
|
489
|
+
return ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
340
490
|
}
|
|
341
491
|
|
|
342
|
-
// src/lib/
|
|
343
|
-
import
|
|
344
|
-
import
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
import { readFile, writeFile } from "fs/promises";
|
|
353
|
-
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
354
|
-
import path2 from "path";
|
|
355
|
-
import os from "os";
|
|
356
|
-
|
|
357
|
-
// src/lib/secure-files.ts
|
|
358
|
-
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
359
|
-
import { chmod, mkdir } from "fs/promises";
|
|
360
|
-
|
|
361
|
-
// src/lib/config.ts
|
|
362
|
-
function resolveDataDir() {
|
|
363
|
-
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
364
|
-
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
365
|
-
const newDir = path2.join(os.homedir(), ".exe-os");
|
|
366
|
-
const legacyDir = path2.join(os.homedir(), ".exe-mem");
|
|
367
|
-
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
492
|
+
// src/lib/project-name.ts
|
|
493
|
+
import { execSync } from "child_process";
|
|
494
|
+
import path5 from "path";
|
|
495
|
+
var _cached = null;
|
|
496
|
+
var _cachedCwd = null;
|
|
497
|
+
function getProjectName(cwd) {
|
|
498
|
+
const dir = cwd ?? process.cwd();
|
|
499
|
+
if (_cached && _cachedCwd === dir) return _cached;
|
|
500
|
+
try {
|
|
501
|
+
let repoRoot;
|
|
368
502
|
try {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
503
|
+
const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
|
|
504
|
+
cwd: dir,
|
|
505
|
+
encoding: "utf8",
|
|
506
|
+
timeout: 2e3,
|
|
507
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
508
|
+
}).trim();
|
|
509
|
+
repoRoot = path5.dirname(gitCommonDir);
|
|
372
510
|
} catch {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
var DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
380
|
-
var MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
381
|
-
var CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
382
|
-
var LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
383
|
-
var CURRENT_CONFIG_VERSION = 1;
|
|
384
|
-
var DEFAULT_CONFIG = {
|
|
385
|
-
config_version: CURRENT_CONFIG_VERSION,
|
|
386
|
-
dbPath: DB_PATH,
|
|
387
|
-
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
388
|
-
embeddingDim: 1024,
|
|
389
|
-
batchSize: 20,
|
|
390
|
-
flushIntervalMs: 1e4,
|
|
391
|
-
autoIngestion: true,
|
|
392
|
-
autoRetrieval: true,
|
|
393
|
-
searchMode: "hybrid",
|
|
394
|
-
hookSearchMode: "hybrid",
|
|
395
|
-
fileGrepEnabled: true,
|
|
396
|
-
splashEffect: true,
|
|
397
|
-
consolidationEnabled: true,
|
|
398
|
-
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
399
|
-
consolidationModel: "claude-haiku-4-5-20251001",
|
|
400
|
-
consolidationMaxCallsPerRun: 20,
|
|
401
|
-
selfQueryRouter: true,
|
|
402
|
-
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
403
|
-
rerankerEnabled: true,
|
|
404
|
-
scalingRoadmap: {
|
|
405
|
-
rerankerAutoTrigger: {
|
|
406
|
-
enabled: true,
|
|
407
|
-
broadQueryMinCardinality: 5e4,
|
|
408
|
-
fetchTopK: 150,
|
|
409
|
-
returnTopK: 5
|
|
511
|
+
repoRoot = execSync("git rev-parse --show-toplevel", {
|
|
512
|
+
cwd: dir,
|
|
513
|
+
encoding: "utf8",
|
|
514
|
+
timeout: 2e3,
|
|
515
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
516
|
+
}).trim();
|
|
410
517
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
wikiUrl: "",
|
|
415
|
-
wikiApiKey: "",
|
|
416
|
-
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
417
|
-
wikiWorkspaceMapping: {},
|
|
418
|
-
wikiAutoUpdate: true,
|
|
419
|
-
wikiAutoUpdateThreshold: 0.5,
|
|
420
|
-
wikiAutoUpdateCreateNew: true,
|
|
421
|
-
skillLearning: true,
|
|
422
|
-
skillThreshold: 3,
|
|
423
|
-
skillModel: "claude-haiku-4-5-20251001",
|
|
424
|
-
exeHeartbeat: {
|
|
425
|
-
enabled: true,
|
|
426
|
-
intervalSeconds: 60,
|
|
427
|
-
staleInProgressThresholdHours: 2
|
|
428
|
-
},
|
|
429
|
-
sessionLifecycle: {
|
|
430
|
-
idleKillEnabled: true,
|
|
431
|
-
idleKillTicksRequired: 3,
|
|
432
|
-
idleKillIntercomAckWindowMs: 1e4,
|
|
433
|
-
maxAutoInstances: 10
|
|
434
|
-
},
|
|
435
|
-
autoUpdate: {
|
|
436
|
-
checkOnBoot: true,
|
|
437
|
-
autoInstall: false,
|
|
438
|
-
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
// src/lib/daemon-auth.ts
|
|
443
|
-
import crypto2 from "crypto";
|
|
444
|
-
import path3 from "path";
|
|
445
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
446
|
-
var DAEMON_TOKEN_PATH = path3.join(EXE_AI_DIR, "exed.token");
|
|
447
|
-
function normalizeToken(token) {
|
|
448
|
-
if (!token) return null;
|
|
449
|
-
const trimmed = token.trim();
|
|
450
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
451
|
-
}
|
|
452
|
-
function readDaemonToken() {
|
|
453
|
-
try {
|
|
454
|
-
if (!existsSync3(DAEMON_TOKEN_PATH)) return null;
|
|
455
|
-
return normalizeToken(readFileSync2(DAEMON_TOKEN_PATH, "utf8"));
|
|
518
|
+
_cached = path5.basename(repoRoot);
|
|
519
|
+
_cachedCwd = dir;
|
|
520
|
+
return _cached;
|
|
456
521
|
} catch {
|
|
457
|
-
|
|
522
|
+
_cached = path5.basename(dir);
|
|
523
|
+
_cachedCwd = dir;
|
|
524
|
+
return _cached;
|
|
458
525
|
}
|
|
459
526
|
}
|
|
460
527
|
|
|
461
|
-
// src/lib/
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
471
|
-
return new Promise((resolve) => {
|
|
472
|
-
if (!_socket || !_connected) {
|
|
473
|
-
resolve({ error: "Not connected" });
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
const id = randomUUID();
|
|
477
|
-
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
478
|
-
const timer = setTimeout(() => {
|
|
479
|
-
_pending.delete(id);
|
|
480
|
-
resolve({ error: "Request timeout" });
|
|
481
|
-
}, timeoutMs);
|
|
482
|
-
_pending.set(id, { resolve, timer });
|
|
483
|
-
try {
|
|
484
|
-
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
485
|
-
} catch {
|
|
486
|
-
clearTimeout(timer);
|
|
487
|
-
_pending.delete(id);
|
|
488
|
-
resolve({ error: "Write failed" });
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
function isClientConnected() {
|
|
493
|
-
return _connected;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// src/lib/memory-queue.ts
|
|
497
|
-
import { appendFileSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
498
|
-
import path5 from "path";
|
|
499
|
-
var QUEUE_PATH = path5.join(EXE_AI_DIR, "memory-queue.jsonl");
|
|
500
|
-
var PROCESSING_PATH = QUEUE_PATH + ".processing";
|
|
501
|
-
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
502
|
-
function enqueueMemory(entry) {
|
|
503
|
-
appendFileSync(QUEUE_PATH, JSON.stringify(entry) + "\n");
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// src/lib/memory-queue-client.ts
|
|
507
|
-
async function writeMemoryViaDaemon(entry) {
|
|
508
|
-
if (process.env.EXE_IS_DAEMON === "1") {
|
|
509
|
-
enqueueMemory(entry);
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
if (!isClientConnected()) {
|
|
513
|
-
enqueueMemory(entry);
|
|
514
|
-
return false;
|
|
515
|
-
}
|
|
516
|
-
try {
|
|
517
|
-
const response = await sendDaemonRequest({
|
|
518
|
-
type: "write-memory",
|
|
519
|
-
entry
|
|
520
|
-
});
|
|
521
|
-
if (response.ok) return true;
|
|
522
|
-
enqueueMemory(entry);
|
|
523
|
-
return false;
|
|
524
|
-
} catch {
|
|
525
|
-
enqueueMemory(entry);
|
|
526
|
-
return false;
|
|
528
|
+
// src/lib/post-tool-memory.ts
|
|
529
|
+
function buildPostToolMemoryEntry(data, agent) {
|
|
530
|
+
let rawText = extractSemanticText(data.tool_name, data.tool_input, data.tool_response);
|
|
531
|
+
if (rawText.trim().length < 10) {
|
|
532
|
+
const inputPreview = JSON.stringify(data.tool_input ?? "").slice(0, 500);
|
|
533
|
+
const outputPreview = JSON.stringify(data.tool_response ?? "").slice(0, 500);
|
|
534
|
+
rawText = `Tool call: ${data.tool_name}
|
|
535
|
+
Input: ${inputPreview}
|
|
536
|
+
Output: ${outputPreview}`;
|
|
527
537
|
}
|
|
538
|
+
if (rawText.trim().length < 10) return null;
|
|
539
|
+
const hasError = detectError(data);
|
|
540
|
+
const toolInputStr = JSON.stringify(data.tool_input ?? "").slice(0, 200);
|
|
541
|
+
const toolOutputStr = JSON.stringify(data.tool_response ?? "").slice(0, 200);
|
|
542
|
+
return {
|
|
543
|
+
raw_text: rawText,
|
|
544
|
+
agent_id: agent.agentId,
|
|
545
|
+
agent_role: agent.agentRole,
|
|
546
|
+
tool_name: data.tool_name,
|
|
547
|
+
project_name: getProjectName(data.cwd ?? process.cwd()),
|
|
548
|
+
session_id: data.session_id,
|
|
549
|
+
has_error: hasError,
|
|
550
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
551
|
+
trajectory: JSON.stringify({
|
|
552
|
+
input: toolInputStr,
|
|
553
|
+
tool: data.tool_name,
|
|
554
|
+
output: toolOutputStr,
|
|
555
|
+
result_type: hasError ? "error" : "success"
|
|
556
|
+
})
|
|
557
|
+
};
|
|
528
558
|
}
|
|
529
559
|
|
|
530
560
|
// src/adapters/claude/hooks/ingest-worker.ts
|
|
@@ -544,38 +574,12 @@ process.stdin.on("end", async () => {
|
|
|
544
574
|
`);
|
|
545
575
|
process.exit(0);
|
|
546
576
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const outputPreview = JSON.stringify(data.tool_response ?? "").slice(0, 500);
|
|
551
|
-
rawText = `Tool call: ${data.tool_name}
|
|
552
|
-
Input: ${inputPreview}
|
|
553
|
-
Output: ${outputPreview}`;
|
|
554
|
-
}
|
|
555
|
-
if (rawText.trim().length < 10) {
|
|
556
|
-
process.exit(0);
|
|
557
|
-
}
|
|
558
|
-
const agentId = process.env.AGENT_ID ?? "default";
|
|
559
|
-
const agentRole = process.env.AGENT_ROLE ?? "unknown";
|
|
560
|
-
const hasError = detectError(data);
|
|
561
|
-
const toolInputStr = JSON.stringify(data.tool_input ?? "").slice(0, 200);
|
|
562
|
-
const toolOutputStr = JSON.stringify(data.tool_response ?? "").slice(0, 200);
|
|
563
|
-
await writeMemoryViaDaemon({
|
|
564
|
-
raw_text: rawText,
|
|
565
|
-
agent_id: agentId,
|
|
566
|
-
agent_role: agentRole,
|
|
567
|
-
tool_name: data.tool_name,
|
|
568
|
-
project_name: getProjectName(data.cwd ?? process.cwd()),
|
|
569
|
-
session_id: data.session_id,
|
|
570
|
-
has_error: hasError,
|
|
571
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
572
|
-
trajectory: JSON.stringify({
|
|
573
|
-
input: toolInputStr,
|
|
574
|
-
tool: data.tool_name,
|
|
575
|
-
output: toolOutputStr,
|
|
576
|
-
result_type: hasError ? "error" : "success"
|
|
577
|
-
})
|
|
577
|
+
const entry = buildPostToolMemoryEntry(data, {
|
|
578
|
+
agentId: process.env.AGENT_ID ?? "default",
|
|
579
|
+
agentRole: process.env.AGENT_ROLE ?? "unknown"
|
|
578
580
|
});
|
|
581
|
+
if (!entry) process.exit(0);
|
|
582
|
+
await writeMemoryViaDaemon(entry);
|
|
579
583
|
} catch (err) {
|
|
580
584
|
process.stderr.write(`[ingest-worker] FATAL: ${err instanceof Error ? err.message : String(err)}
|
|
581
585
|
`);
|