@askexenow/exe-os 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1295 -856
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +778 -427
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +276 -139
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +677 -388
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +440 -250
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +404 -212
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +412 -220
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +533 -320
- package/dist/hooks/bug-report-worker.js +344 -193
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +402 -210
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3423 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +408 -216
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +541 -328
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +443 -240
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +538 -324
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +935 -587
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +280 -234
- package/dist/lib/tmux-routing.js +172 -125
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1326 -609
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +306 -248
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1848 -199
- package/dist/runtime/index.js +441 -248
- package/dist/tui/App.js +761 -424
- package/package.json +1 -1
package/dist/bin/exe-search.js
CHANGED
|
@@ -73,6 +73,44 @@ var init_db_retry = __esm({
|
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
// src/lib/secure-files.ts
|
|
77
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
78
|
+
import { chmod, mkdir } from "fs/promises";
|
|
79
|
+
async function ensurePrivateDir(dirPath) {
|
|
80
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
81
|
+
try {
|
|
82
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function ensurePrivateDirSync(dirPath) {
|
|
87
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
88
|
+
try {
|
|
89
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function enforcePrivateFile(filePath) {
|
|
94
|
+
try {
|
|
95
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function enforcePrivateFileSync(filePath) {
|
|
100
|
+
try {
|
|
101
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
106
|
+
var init_secure_files = __esm({
|
|
107
|
+
"src/lib/secure-files.ts"() {
|
|
108
|
+
"use strict";
|
|
109
|
+
PRIVATE_DIR_MODE = 448;
|
|
110
|
+
PRIVATE_FILE_MODE = 384;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
76
114
|
// src/lib/config.ts
|
|
77
115
|
var config_exports = {};
|
|
78
116
|
__export(config_exports, {
|
|
@@ -89,8 +127,8 @@ __export(config_exports, {
|
|
|
89
127
|
migrateConfig: () => migrateConfig,
|
|
90
128
|
saveConfig: () => saveConfig
|
|
91
129
|
});
|
|
92
|
-
import { readFile, writeFile
|
|
93
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
130
|
+
import { readFile, writeFile } from "fs/promises";
|
|
131
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
94
132
|
import path from "path";
|
|
95
133
|
import os from "os";
|
|
96
134
|
function resolveDataDir() {
|
|
@@ -98,7 +136,7 @@ function resolveDataDir() {
|
|
|
98
136
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
99
137
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
100
138
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
101
|
-
if (!
|
|
139
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
102
140
|
try {
|
|
103
141
|
renameSync(legacyDir, newDir);
|
|
104
142
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -161,9 +199,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
161
199
|
}
|
|
162
200
|
async function loadConfig() {
|
|
163
201
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
164
|
-
await
|
|
202
|
+
await ensurePrivateDir(dir);
|
|
165
203
|
const configPath = path.join(dir, "config.json");
|
|
166
|
-
if (!
|
|
204
|
+
if (!existsSync2(configPath)) {
|
|
167
205
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
168
206
|
}
|
|
169
207
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -176,6 +214,7 @@ async function loadConfig() {
|
|
|
176
214
|
`);
|
|
177
215
|
try {
|
|
178
216
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
217
|
+
await enforcePrivateFile(configPath);
|
|
179
218
|
} catch {
|
|
180
219
|
}
|
|
181
220
|
}
|
|
@@ -194,7 +233,7 @@ async function loadConfig() {
|
|
|
194
233
|
function loadConfigSync() {
|
|
195
234
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
196
235
|
const configPath = path.join(dir, "config.json");
|
|
197
|
-
if (!
|
|
236
|
+
if (!existsSync2(configPath)) {
|
|
198
237
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
199
238
|
}
|
|
200
239
|
try {
|
|
@@ -212,12 +251,10 @@ function loadConfigSync() {
|
|
|
212
251
|
}
|
|
213
252
|
async function saveConfig(config) {
|
|
214
253
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
215
|
-
await
|
|
254
|
+
await ensurePrivateDir(dir);
|
|
216
255
|
const configPath = path.join(dir, "config.json");
|
|
217
256
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
218
|
-
|
|
219
|
-
await chmod(configPath, 384);
|
|
220
|
-
}
|
|
257
|
+
await enforcePrivateFile(configPath);
|
|
221
258
|
}
|
|
222
259
|
async function loadConfigFrom(configPath) {
|
|
223
260
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -237,6 +274,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
237
274
|
var init_config = __esm({
|
|
238
275
|
"src/lib/config.ts"() {
|
|
239
276
|
"use strict";
|
|
277
|
+
init_secure_files();
|
|
240
278
|
EXE_AI_DIR = resolveDataDir();
|
|
241
279
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
242
280
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -315,7 +353,7 @@ var init_config = __esm({
|
|
|
315
353
|
|
|
316
354
|
// src/lib/employees.ts
|
|
317
355
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
318
|
-
import { existsSync as
|
|
356
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
319
357
|
import { execSync } from "child_process";
|
|
320
358
|
import path2 from "path";
|
|
321
359
|
import os2 from "os";
|
|
@@ -332,7 +370,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
332
370
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
333
371
|
}
|
|
334
372
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
335
|
-
if (!
|
|
373
|
+
if (!existsSync3(employeesPath)) return [];
|
|
336
374
|
try {
|
|
337
375
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
338
376
|
} catch {
|
|
@@ -1277,6 +1315,7 @@ async function ensureSchema() {
|
|
|
1277
1315
|
project TEXT NOT NULL,
|
|
1278
1316
|
summary TEXT NOT NULL,
|
|
1279
1317
|
task_file TEXT,
|
|
1318
|
+
session_scope TEXT,
|
|
1280
1319
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1281
1320
|
created_at TEXT NOT NULL
|
|
1282
1321
|
);
|
|
@@ -1285,7 +1324,7 @@ async function ensureSchema() {
|
|
|
1285
1324
|
ON notifications(read);
|
|
1286
1325
|
|
|
1287
1326
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1288
|
-
ON notifications(agent_id);
|
|
1327
|
+
ON notifications(agent_id, session_scope);
|
|
1289
1328
|
|
|
1290
1329
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1291
1330
|
ON notifications(task_file);
|
|
@@ -1323,6 +1362,7 @@ async function ensureSchema() {
|
|
|
1323
1362
|
target_agent TEXT NOT NULL,
|
|
1324
1363
|
target_project TEXT,
|
|
1325
1364
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1365
|
+
session_scope TEXT,
|
|
1326
1366
|
content TEXT NOT NULL,
|
|
1327
1367
|
priority TEXT DEFAULT 'normal',
|
|
1328
1368
|
status TEXT DEFAULT 'pending',
|
|
@@ -1336,10 +1376,31 @@ async function ensureSchema() {
|
|
|
1336
1376
|
);
|
|
1337
1377
|
|
|
1338
1378
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1339
|
-
ON messages(target_agent, status);
|
|
1379
|
+
ON messages(target_agent, session_scope, status);
|
|
1340
1380
|
|
|
1341
1381
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1342
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1382
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1383
|
+
`);
|
|
1384
|
+
try {
|
|
1385
|
+
await client.execute({
|
|
1386
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1387
|
+
args: []
|
|
1388
|
+
});
|
|
1389
|
+
} catch {
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
await client.execute({
|
|
1393
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1394
|
+
args: []
|
|
1395
|
+
});
|
|
1396
|
+
} catch {
|
|
1397
|
+
}
|
|
1398
|
+
await client.executeMultiple(`
|
|
1399
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1400
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1401
|
+
|
|
1402
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1403
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1343
1404
|
`);
|
|
1344
1405
|
try {
|
|
1345
1406
|
await client.execute({
|
|
@@ -1923,6 +1984,13 @@ async function ensureSchema() {
|
|
|
1923
1984
|
} catch {
|
|
1924
1985
|
}
|
|
1925
1986
|
}
|
|
1987
|
+
try {
|
|
1988
|
+
await client.execute({
|
|
1989
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
1990
|
+
args: []
|
|
1991
|
+
});
|
|
1992
|
+
} catch {
|
|
1993
|
+
}
|
|
1926
1994
|
}
|
|
1927
1995
|
async function disposeDatabase() {
|
|
1928
1996
|
if (_walCheckpointTimer) {
|
|
@@ -1962,7 +2030,7 @@ var init_database = __esm({
|
|
|
1962
2030
|
|
|
1963
2031
|
// src/lib/keychain.ts
|
|
1964
2032
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1965
|
-
import { existsSync as
|
|
2033
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1966
2034
|
import path4 from "path";
|
|
1967
2035
|
import os4 from "os";
|
|
1968
2036
|
function getKeyDir() {
|
|
@@ -1990,7 +2058,7 @@ async function getMasterKey() {
|
|
|
1990
2058
|
}
|
|
1991
2059
|
}
|
|
1992
2060
|
const keyPath = getKeyPath();
|
|
1993
|
-
if (!
|
|
2061
|
+
if (!existsSync4(keyPath)) {
|
|
1994
2062
|
process.stderr.write(
|
|
1995
2063
|
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1996
2064
|
`
|
|
@@ -2077,6 +2145,7 @@ var shard_manager_exports = {};
|
|
|
2077
2145
|
__export(shard_manager_exports, {
|
|
2078
2146
|
disposeShards: () => disposeShards,
|
|
2079
2147
|
ensureShardSchema: () => ensureShardSchema,
|
|
2148
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2080
2149
|
getReadyShardClient: () => getReadyShardClient,
|
|
2081
2150
|
getShardClient: () => getShardClient,
|
|
2082
2151
|
getShardsDir: () => getShardsDir,
|
|
@@ -2086,14 +2155,17 @@ __export(shard_manager_exports, {
|
|
|
2086
2155
|
shardExists: () => shardExists
|
|
2087
2156
|
});
|
|
2088
2157
|
import path5 from "path";
|
|
2089
|
-
import { existsSync as
|
|
2158
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2090
2159
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2091
2160
|
function initShardManager(encryptionKey) {
|
|
2092
2161
|
_encryptionKey = encryptionKey;
|
|
2093
|
-
if (!
|
|
2094
|
-
|
|
2162
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2163
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2095
2164
|
}
|
|
2096
2165
|
_shardingEnabled = true;
|
|
2166
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2167
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2168
|
+
_evictionTimer.unref();
|
|
2097
2169
|
}
|
|
2098
2170
|
function isShardingEnabled() {
|
|
2099
2171
|
return _shardingEnabled;
|
|
@@ -2110,21 +2182,28 @@ function getShardClient(projectName) {
|
|
|
2110
2182
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2111
2183
|
}
|
|
2112
2184
|
const cached = _shards.get(safeName);
|
|
2113
|
-
if (cached)
|
|
2185
|
+
if (cached) {
|
|
2186
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2187
|
+
return cached;
|
|
2188
|
+
}
|
|
2189
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2190
|
+
evictLRU();
|
|
2191
|
+
}
|
|
2114
2192
|
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
2115
2193
|
const client = createClient2({
|
|
2116
2194
|
url: `file:${dbPath}`,
|
|
2117
2195
|
encryptionKey: _encryptionKey
|
|
2118
2196
|
});
|
|
2119
2197
|
_shards.set(safeName, client);
|
|
2198
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2120
2199
|
return client;
|
|
2121
2200
|
}
|
|
2122
2201
|
function shardExists(projectName) {
|
|
2123
2202
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2124
|
-
return
|
|
2203
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
2125
2204
|
}
|
|
2126
2205
|
function listShards() {
|
|
2127
|
-
if (!
|
|
2206
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
2128
2207
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2129
2208
|
}
|
|
2130
2209
|
async function ensureShardSchema(client) {
|
|
@@ -2176,6 +2255,8 @@ async function ensureShardSchema(client) {
|
|
|
2176
2255
|
for (const col of [
|
|
2177
2256
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2178
2257
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2258
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2259
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2179
2260
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2180
2261
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2181
2262
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2313,21 +2394,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2313
2394
|
await ensureShardSchema(client);
|
|
2314
2395
|
return client;
|
|
2315
2396
|
}
|
|
2397
|
+
function evictLRU() {
|
|
2398
|
+
let oldest = null;
|
|
2399
|
+
let oldestTime = Infinity;
|
|
2400
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2401
|
+
if (time < oldestTime) {
|
|
2402
|
+
oldestTime = time;
|
|
2403
|
+
oldest = name;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
if (oldest) {
|
|
2407
|
+
const client = _shards.get(oldest);
|
|
2408
|
+
if (client) {
|
|
2409
|
+
client.close();
|
|
2410
|
+
}
|
|
2411
|
+
_shards.delete(oldest);
|
|
2412
|
+
_shardLastAccess.delete(oldest);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
function evictIdleShards() {
|
|
2416
|
+
const now = Date.now();
|
|
2417
|
+
const toEvict = [];
|
|
2418
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2419
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2420
|
+
toEvict.push(name);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
for (const name of toEvict) {
|
|
2424
|
+
const client = _shards.get(name);
|
|
2425
|
+
if (client) {
|
|
2426
|
+
client.close();
|
|
2427
|
+
}
|
|
2428
|
+
_shards.delete(name);
|
|
2429
|
+
_shardLastAccess.delete(name);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
function getOpenShardCount() {
|
|
2433
|
+
return _shards.size;
|
|
2434
|
+
}
|
|
2316
2435
|
function disposeShards() {
|
|
2436
|
+
if (_evictionTimer) {
|
|
2437
|
+
clearInterval(_evictionTimer);
|
|
2438
|
+
_evictionTimer = null;
|
|
2439
|
+
}
|
|
2317
2440
|
for (const [, client] of _shards) {
|
|
2318
2441
|
client.close();
|
|
2319
2442
|
}
|
|
2320
2443
|
_shards.clear();
|
|
2444
|
+
_shardLastAccess.clear();
|
|
2321
2445
|
_shardingEnabled = false;
|
|
2322
2446
|
_encryptionKey = null;
|
|
2323
2447
|
}
|
|
2324
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2448
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2325
2449
|
var init_shard_manager = __esm({
|
|
2326
2450
|
"src/lib/shard-manager.ts"() {
|
|
2327
2451
|
"use strict";
|
|
2328
2452
|
init_config();
|
|
2329
2453
|
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
2454
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2455
|
+
MAX_OPEN_SHARDS = 10;
|
|
2456
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2330
2457
|
_shards = /* @__PURE__ */ new Map();
|
|
2458
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2459
|
+
_evictionTimer = null;
|
|
2331
2460
|
_encryptionKey = null;
|
|
2332
2461
|
_shardingEnabled = false;
|
|
2333
2462
|
}
|
|
@@ -3190,7 +3319,7 @@ __export(reranker_exports, {
|
|
|
3190
3319
|
rerankWithScores: () => rerankWithScores
|
|
3191
3320
|
});
|
|
3192
3321
|
import path6 from "path";
|
|
3193
|
-
import { existsSync as
|
|
3322
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3194
3323
|
function resetIdleTimer() {
|
|
3195
3324
|
if (_idleTimer) clearTimeout(_idleTimer);
|
|
3196
3325
|
_idleTimer = setTimeout(() => {
|
|
@@ -3201,7 +3330,7 @@ function resetIdleTimer() {
|
|
|
3201
3330
|
}
|
|
3202
3331
|
}
|
|
3203
3332
|
function isRerankerAvailable() {
|
|
3204
|
-
return
|
|
3333
|
+
return existsSync6(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
3205
3334
|
}
|
|
3206
3335
|
function getRerankerModelPath() {
|
|
3207
3336
|
return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
@@ -3212,7 +3341,7 @@ async function ensureLoaded() {
|
|
|
3212
3341
|
return;
|
|
3213
3342
|
}
|
|
3214
3343
|
const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
3215
|
-
if (!
|
|
3344
|
+
if (!existsSync6(modelPath)) {
|
|
3216
3345
|
throw new Error(
|
|
3217
3346
|
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
3218
3347
|
);
|
|
@@ -3308,13 +3437,50 @@ var init_reranker = __esm({
|
|
|
3308
3437
|
}
|
|
3309
3438
|
});
|
|
3310
3439
|
|
|
3440
|
+
// src/lib/daemon-auth.ts
|
|
3441
|
+
import crypto from "crypto";
|
|
3442
|
+
import path7 from "path";
|
|
3443
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3444
|
+
function normalizeToken(token) {
|
|
3445
|
+
if (!token) return null;
|
|
3446
|
+
const trimmed = token.trim();
|
|
3447
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3448
|
+
}
|
|
3449
|
+
function readDaemonToken() {
|
|
3450
|
+
try {
|
|
3451
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
3452
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
3453
|
+
} catch {
|
|
3454
|
+
return null;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
function ensureDaemonToken(seed) {
|
|
3458
|
+
const existing = readDaemonToken();
|
|
3459
|
+
if (existing) return existing;
|
|
3460
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
3461
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
3462
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
3463
|
+
`, "utf8");
|
|
3464
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
3465
|
+
return token;
|
|
3466
|
+
}
|
|
3467
|
+
var DAEMON_TOKEN_PATH;
|
|
3468
|
+
var init_daemon_auth = __esm({
|
|
3469
|
+
"src/lib/daemon-auth.ts"() {
|
|
3470
|
+
"use strict";
|
|
3471
|
+
init_config();
|
|
3472
|
+
init_secure_files();
|
|
3473
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
3474
|
+
}
|
|
3475
|
+
});
|
|
3476
|
+
|
|
3311
3477
|
// src/lib/exe-daemon-client.ts
|
|
3312
3478
|
import net from "net";
|
|
3313
3479
|
import os5 from "os";
|
|
3314
3480
|
import { spawn } from "child_process";
|
|
3315
3481
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3316
|
-
import { existsSync as
|
|
3317
|
-
import
|
|
3482
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
3483
|
+
import path8 from "path";
|
|
3318
3484
|
import { fileURLToPath } from "url";
|
|
3319
3485
|
function handleData(chunk) {
|
|
3320
3486
|
_buffer += chunk.toString();
|
|
@@ -3342,9 +3508,9 @@ function handleData(chunk) {
|
|
|
3342
3508
|
}
|
|
3343
3509
|
}
|
|
3344
3510
|
function cleanupStaleFiles() {
|
|
3345
|
-
if (
|
|
3511
|
+
if (existsSync8(PID_PATH)) {
|
|
3346
3512
|
try {
|
|
3347
|
-
const pid = parseInt(
|
|
3513
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
3348
3514
|
if (pid > 0) {
|
|
3349
3515
|
try {
|
|
3350
3516
|
process.kill(pid, 0);
|
|
@@ -3365,11 +3531,11 @@ function cleanupStaleFiles() {
|
|
|
3365
3531
|
}
|
|
3366
3532
|
}
|
|
3367
3533
|
function findPackageRoot() {
|
|
3368
|
-
let dir =
|
|
3369
|
-
const { root } =
|
|
3534
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
3535
|
+
const { root } = path8.parse(dir);
|
|
3370
3536
|
while (dir !== root) {
|
|
3371
|
-
if (
|
|
3372
|
-
dir =
|
|
3537
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
3538
|
+
dir = path8.dirname(dir);
|
|
3373
3539
|
}
|
|
3374
3540
|
return null;
|
|
3375
3541
|
}
|
|
@@ -3395,16 +3561,17 @@ function spawnDaemon() {
|
|
|
3395
3561
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
3396
3562
|
return;
|
|
3397
3563
|
}
|
|
3398
|
-
const daemonPath =
|
|
3399
|
-
if (!
|
|
3564
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
3565
|
+
if (!existsSync8(daemonPath)) {
|
|
3400
3566
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
3401
3567
|
`);
|
|
3402
3568
|
return;
|
|
3403
3569
|
}
|
|
3404
3570
|
const resolvedPath = daemonPath;
|
|
3571
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
3405
3572
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
3406
3573
|
`);
|
|
3407
|
-
const logPath =
|
|
3574
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
3408
3575
|
let stderrFd = "ignore";
|
|
3409
3576
|
try {
|
|
3410
3577
|
stderrFd = openSync(logPath, "a");
|
|
@@ -3422,7 +3589,8 @@ function spawnDaemon() {
|
|
|
3422
3589
|
TMUX_PANE: void 0,
|
|
3423
3590
|
// Prevents resolveExeSession() from scoping to one session
|
|
3424
3591
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
3425
|
-
EXE_DAEMON_PID: PID_PATH
|
|
3592
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
3593
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
3426
3594
|
}
|
|
3427
3595
|
});
|
|
3428
3596
|
child.unref();
|
|
@@ -3532,13 +3700,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
3532
3700
|
return;
|
|
3533
3701
|
}
|
|
3534
3702
|
const id = randomUUID2();
|
|
3703
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
3535
3704
|
const timer = setTimeout(() => {
|
|
3536
3705
|
_pending.delete(id);
|
|
3537
3706
|
resolve({ error: "Request timeout" });
|
|
3538
3707
|
}, timeoutMs);
|
|
3539
3708
|
_pending.set(id, { resolve, timer });
|
|
3540
3709
|
try {
|
|
3541
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
3710
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
3542
3711
|
} catch {
|
|
3543
3712
|
clearTimeout(timer);
|
|
3544
3713
|
_pending.delete(id);
|
|
@@ -3567,9 +3736,9 @@ function killAndRespawnDaemon() {
|
|
|
3567
3736
|
}
|
|
3568
3737
|
try {
|
|
3569
3738
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
3570
|
-
if (
|
|
3739
|
+
if (existsSync8(PID_PATH)) {
|
|
3571
3740
|
try {
|
|
3572
|
-
const pid = parseInt(
|
|
3741
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
3573
3742
|
if (pid > 0) {
|
|
3574
3743
|
try {
|
|
3575
3744
|
process.kill(pid, "SIGKILL");
|
|
@@ -3686,17 +3855,19 @@ function disconnectClient() {
|
|
|
3686
3855
|
entry.resolve({ error: "Client disconnected" });
|
|
3687
3856
|
}
|
|
3688
3857
|
}
|
|
3689
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
3858
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
3690
3859
|
var init_exe_daemon_client = __esm({
|
|
3691
3860
|
"src/lib/exe-daemon-client.ts"() {
|
|
3692
3861
|
"use strict";
|
|
3693
3862
|
init_config();
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3863
|
+
init_daemon_auth();
|
|
3864
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
3865
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
3866
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3697
3867
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3698
3868
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3699
3869
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
3870
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3700
3871
|
_socket = null;
|
|
3701
3872
|
_connected = false;
|
|
3702
3873
|
_buffer = "";
|
|
@@ -3748,10 +3919,10 @@ async function disposeEmbedder() {
|
|
|
3748
3919
|
async function embedDirect(text) {
|
|
3749
3920
|
const llamaCpp = await import("node-llama-cpp");
|
|
3750
3921
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3751
|
-
const { existsSync:
|
|
3752
|
-
const
|
|
3753
|
-
const modelPath =
|
|
3754
|
-
if (!
|
|
3922
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
3923
|
+
const path11 = await import("path");
|
|
3924
|
+
const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3925
|
+
if (!existsSync10(modelPath)) {
|
|
3755
3926
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3756
3927
|
}
|
|
3757
3928
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3786,7 +3957,7 @@ __export(project_name_exports, {
|
|
|
3786
3957
|
getProjectName: () => getProjectName
|
|
3787
3958
|
});
|
|
3788
3959
|
import { execSync as execSync2 } from "child_process";
|
|
3789
|
-
import
|
|
3960
|
+
import path9 from "path";
|
|
3790
3961
|
function getProjectName(cwd) {
|
|
3791
3962
|
const dir = cwd ?? process.cwd();
|
|
3792
3963
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
@@ -3799,7 +3970,7 @@ function getProjectName(cwd) {
|
|
|
3799
3970
|
timeout: 2e3,
|
|
3800
3971
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3801
3972
|
}).trim();
|
|
3802
|
-
repoRoot =
|
|
3973
|
+
repoRoot = path9.dirname(gitCommonDir);
|
|
3803
3974
|
} catch {
|
|
3804
3975
|
repoRoot = execSync2("git rev-parse --show-toplevel", {
|
|
3805
3976
|
cwd: dir,
|
|
@@ -3808,11 +3979,11 @@ function getProjectName(cwd) {
|
|
|
3808
3979
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3809
3980
|
}).trim();
|
|
3810
3981
|
}
|
|
3811
|
-
_cached =
|
|
3982
|
+
_cached = path9.basename(repoRoot);
|
|
3812
3983
|
_cachedCwd = dir;
|
|
3813
3984
|
return _cached;
|
|
3814
3985
|
} catch {
|
|
3815
|
-
_cached =
|
|
3986
|
+
_cached = path9.basename(dir);
|
|
3816
3987
|
_cachedCwd = dir;
|
|
3817
3988
|
return _cached;
|
|
3818
3989
|
}
|
|
@@ -3836,9 +4007,9 @@ __export(file_grep_exports, {
|
|
|
3836
4007
|
grepProjectFiles: () => grepProjectFiles
|
|
3837
4008
|
});
|
|
3838
4009
|
import { execSync as execSync3 } from "child_process";
|
|
3839
|
-
import { readFileSync as
|
|
3840
|
-
import
|
|
3841
|
-
import
|
|
4010
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
4011
|
+
import path10 from "path";
|
|
4012
|
+
import crypto2 from "crypto";
|
|
3842
4013
|
function hasRipgrep() {
|
|
3843
4014
|
if (_hasRg === null) {
|
|
3844
4015
|
try {
|
|
@@ -3871,13 +4042,13 @@ async function grepProjectFiles(query, projectRoot, options) {
|
|
|
3871
4042
|
const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
|
|
3872
4043
|
const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
|
|
3873
4044
|
return {
|
|
3874
|
-
id:
|
|
4045
|
+
id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
|
|
3875
4046
|
agent_id: "project",
|
|
3876
4047
|
agent_role: "file",
|
|
3877
4048
|
session_id: "file-grep",
|
|
3878
4049
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3879
4050
|
tool_name: "file_grep",
|
|
3880
|
-
project_name:
|
|
4051
|
+
project_name: path10.basename(projectRoot),
|
|
3881
4052
|
has_error: false,
|
|
3882
4053
|
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
3883
4054
|
vector: null,
|
|
@@ -3889,7 +4060,7 @@ function getChunkContext(filePath, lineNumber) {
|
|
|
3889
4060
|
try {
|
|
3890
4061
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
3891
4062
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
3892
|
-
const source =
|
|
4063
|
+
const source = readFileSync5(filePath, "utf8");
|
|
3893
4064
|
const lines = source.split("\n");
|
|
3894
4065
|
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
3895
4066
|
const line = lines[i];
|
|
@@ -3951,11 +4122,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
|
3951
4122
|
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
3952
4123
|
const hits = [];
|
|
3953
4124
|
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
3954
|
-
const absPath =
|
|
4125
|
+
const absPath = path10.join(projectRoot, filePath);
|
|
3955
4126
|
try {
|
|
3956
4127
|
const stat = statSync2(absPath);
|
|
3957
4128
|
if (stat.size > MAX_FILE_SIZE) continue;
|
|
3958
|
-
const content =
|
|
4129
|
+
const content = readFileSync5(absPath, "utf8");
|
|
3959
4130
|
const lines = content.split("\n");
|
|
3960
4131
|
const matches = content.match(regex);
|
|
3961
4132
|
if (!matches || matches.length === 0) continue;
|
|
@@ -3978,15 +4149,15 @@ function collectFiles(root, patterns) {
|
|
|
3978
4149
|
const files = [];
|
|
3979
4150
|
function walk(dir, relative) {
|
|
3980
4151
|
if (files.length >= MAX_FILES) return;
|
|
3981
|
-
const basename =
|
|
4152
|
+
const basename = path10.basename(dir);
|
|
3982
4153
|
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
3983
4154
|
try {
|
|
3984
4155
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
3985
4156
|
for (const entry of entries) {
|
|
3986
4157
|
if (files.length >= MAX_FILES) return;
|
|
3987
|
-
const rel =
|
|
4158
|
+
const rel = path10.join(relative, entry.name);
|
|
3988
4159
|
if (entry.isDirectory()) {
|
|
3989
|
-
walk(
|
|
4160
|
+
walk(path10.join(dir, entry.name), rel);
|
|
3990
4161
|
} else if (entry.isFile()) {
|
|
3991
4162
|
for (const pat of patterns) {
|
|
3992
4163
|
if (matchGlob(rel, pat)) {
|
|
@@ -4018,7 +4189,7 @@ function matchGlob(filePath, pattern) {
|
|
|
4018
4189
|
if (slashIdx !== -1) {
|
|
4019
4190
|
const dir = pattern.slice(0, slashIdx);
|
|
4020
4191
|
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
4021
|
-
const fileDir =
|
|
4192
|
+
const fileDir = path10.dirname(filePath);
|
|
4022
4193
|
return fileDir === dir && filePath.endsWith(ext2);
|
|
4023
4194
|
}
|
|
4024
4195
|
const ext = pattern.replace("*", "");
|
|
@@ -4026,9 +4197,9 @@ function matchGlob(filePath, pattern) {
|
|
|
4026
4197
|
}
|
|
4027
4198
|
function buildSnippet(hit, projectRoot) {
|
|
4028
4199
|
try {
|
|
4029
|
-
const absPath =
|
|
4030
|
-
if (!
|
|
4031
|
-
const lines =
|
|
4200
|
+
const absPath = path10.join(projectRoot, hit.filePath);
|
|
4201
|
+
if (!existsSync9(absPath)) return hit.matchLine;
|
|
4202
|
+
const lines = readFileSync5(absPath, "utf8").split("\n");
|
|
4032
4203
|
const start = Math.max(0, hit.lineNumber - 3);
|
|
4033
4204
|
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
4034
4205
|
return lines.slice(start, end).join("\n").slice(0, 500);
|