@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
|
@@ -72,6 +72,44 @@ var init_db_retry = __esm({
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// src/lib/secure-files.ts
|
|
76
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
77
|
+
import { chmod, mkdir } from "fs/promises";
|
|
78
|
+
async function ensurePrivateDir(dirPath) {
|
|
79
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
80
|
+
try {
|
|
81
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function ensurePrivateDirSync(dirPath) {
|
|
86
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
87
|
+
try {
|
|
88
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function enforcePrivateFile(filePath) {
|
|
93
|
+
try {
|
|
94
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function enforcePrivateFileSync(filePath) {
|
|
99
|
+
try {
|
|
100
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
105
|
+
var init_secure_files = __esm({
|
|
106
|
+
"src/lib/secure-files.ts"() {
|
|
107
|
+
"use strict";
|
|
108
|
+
PRIVATE_DIR_MODE = 448;
|
|
109
|
+
PRIVATE_FILE_MODE = 384;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
75
113
|
// src/lib/config.ts
|
|
76
114
|
var config_exports = {};
|
|
77
115
|
__export(config_exports, {
|
|
@@ -88,8 +126,8 @@ __export(config_exports, {
|
|
|
88
126
|
migrateConfig: () => migrateConfig,
|
|
89
127
|
saveConfig: () => saveConfig
|
|
90
128
|
});
|
|
91
|
-
import { readFile, writeFile
|
|
92
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
129
|
+
import { readFile, writeFile } from "fs/promises";
|
|
130
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
93
131
|
import path from "path";
|
|
94
132
|
import os from "os";
|
|
95
133
|
function resolveDataDir() {
|
|
@@ -97,7 +135,7 @@ function resolveDataDir() {
|
|
|
97
135
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
98
136
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
99
137
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
100
|
-
if (!
|
|
138
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
101
139
|
try {
|
|
102
140
|
renameSync(legacyDir, newDir);
|
|
103
141
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -160,9 +198,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
160
198
|
}
|
|
161
199
|
async function loadConfig() {
|
|
162
200
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
163
|
-
await
|
|
201
|
+
await ensurePrivateDir(dir);
|
|
164
202
|
const configPath = path.join(dir, "config.json");
|
|
165
|
-
if (!
|
|
203
|
+
if (!existsSync2(configPath)) {
|
|
166
204
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
167
205
|
}
|
|
168
206
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -175,6 +213,7 @@ async function loadConfig() {
|
|
|
175
213
|
`);
|
|
176
214
|
try {
|
|
177
215
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
216
|
+
await enforcePrivateFile(configPath);
|
|
178
217
|
} catch {
|
|
179
218
|
}
|
|
180
219
|
}
|
|
@@ -193,7 +232,7 @@ async function loadConfig() {
|
|
|
193
232
|
function loadConfigSync() {
|
|
194
233
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
195
234
|
const configPath = path.join(dir, "config.json");
|
|
196
|
-
if (!
|
|
235
|
+
if (!existsSync2(configPath)) {
|
|
197
236
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
198
237
|
}
|
|
199
238
|
try {
|
|
@@ -211,12 +250,10 @@ function loadConfigSync() {
|
|
|
211
250
|
}
|
|
212
251
|
async function saveConfig(config) {
|
|
213
252
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
214
|
-
await
|
|
253
|
+
await ensurePrivateDir(dir);
|
|
215
254
|
const configPath = path.join(dir, "config.json");
|
|
216
255
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
217
|
-
|
|
218
|
-
await chmod(configPath, 384);
|
|
219
|
-
}
|
|
256
|
+
await enforcePrivateFile(configPath);
|
|
220
257
|
}
|
|
221
258
|
async function loadConfigFrom(configPath) {
|
|
222
259
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -236,6 +273,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
236
273
|
var init_config = __esm({
|
|
237
274
|
"src/lib/config.ts"() {
|
|
238
275
|
"use strict";
|
|
276
|
+
init_secure_files();
|
|
239
277
|
EXE_AI_DIR = resolveDataDir();
|
|
240
278
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
241
279
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -314,7 +352,7 @@ var init_config = __esm({
|
|
|
314
352
|
|
|
315
353
|
// src/lib/employees.ts
|
|
316
354
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
317
|
-
import { existsSync as
|
|
355
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
318
356
|
import { execSync } from "child_process";
|
|
319
357
|
import path2 from "path";
|
|
320
358
|
import os2 from "os";
|
|
@@ -331,7 +369,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
331
369
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
332
370
|
}
|
|
333
371
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
334
|
-
if (!
|
|
372
|
+
if (!existsSync3(employeesPath)) return [];
|
|
335
373
|
try {
|
|
336
374
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
337
375
|
} catch {
|
|
@@ -1276,6 +1314,7 @@ async function ensureSchema() {
|
|
|
1276
1314
|
project TEXT NOT NULL,
|
|
1277
1315
|
summary TEXT NOT NULL,
|
|
1278
1316
|
task_file TEXT,
|
|
1317
|
+
session_scope TEXT,
|
|
1279
1318
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1280
1319
|
created_at TEXT NOT NULL
|
|
1281
1320
|
);
|
|
@@ -1284,7 +1323,7 @@ async function ensureSchema() {
|
|
|
1284
1323
|
ON notifications(read);
|
|
1285
1324
|
|
|
1286
1325
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1287
|
-
ON notifications(agent_id);
|
|
1326
|
+
ON notifications(agent_id, session_scope);
|
|
1288
1327
|
|
|
1289
1328
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1290
1329
|
ON notifications(task_file);
|
|
@@ -1322,6 +1361,7 @@ async function ensureSchema() {
|
|
|
1322
1361
|
target_agent TEXT NOT NULL,
|
|
1323
1362
|
target_project TEXT,
|
|
1324
1363
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1364
|
+
session_scope TEXT,
|
|
1325
1365
|
content TEXT NOT NULL,
|
|
1326
1366
|
priority TEXT DEFAULT 'normal',
|
|
1327
1367
|
status TEXT DEFAULT 'pending',
|
|
@@ -1335,10 +1375,31 @@ async function ensureSchema() {
|
|
|
1335
1375
|
);
|
|
1336
1376
|
|
|
1337
1377
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1338
|
-
ON messages(target_agent, status);
|
|
1378
|
+
ON messages(target_agent, session_scope, status);
|
|
1339
1379
|
|
|
1340
1380
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1341
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1381
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1382
|
+
`);
|
|
1383
|
+
try {
|
|
1384
|
+
await client.execute({
|
|
1385
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1386
|
+
args: []
|
|
1387
|
+
});
|
|
1388
|
+
} catch {
|
|
1389
|
+
}
|
|
1390
|
+
try {
|
|
1391
|
+
await client.execute({
|
|
1392
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1393
|
+
args: []
|
|
1394
|
+
});
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
await client.executeMultiple(`
|
|
1398
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1399
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1400
|
+
|
|
1401
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1402
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1342
1403
|
`);
|
|
1343
1404
|
try {
|
|
1344
1405
|
await client.execute({
|
|
@@ -1922,6 +1983,13 @@ async function ensureSchema() {
|
|
|
1922
1983
|
} catch {
|
|
1923
1984
|
}
|
|
1924
1985
|
}
|
|
1986
|
+
try {
|
|
1987
|
+
await client.execute({
|
|
1988
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
1989
|
+
args: []
|
|
1990
|
+
});
|
|
1991
|
+
} catch {
|
|
1992
|
+
}
|
|
1925
1993
|
}
|
|
1926
1994
|
async function disposeDatabase() {
|
|
1927
1995
|
if (_walCheckpointTimer) {
|
|
@@ -1961,7 +2029,7 @@ var init_database = __esm({
|
|
|
1961
2029
|
|
|
1962
2030
|
// src/lib/keychain.ts
|
|
1963
2031
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1964
|
-
import { existsSync as
|
|
2032
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1965
2033
|
import path4 from "path";
|
|
1966
2034
|
import os4 from "os";
|
|
1967
2035
|
function getKeyDir() {
|
|
@@ -1989,7 +2057,7 @@ async function getMasterKey() {
|
|
|
1989
2057
|
}
|
|
1990
2058
|
}
|
|
1991
2059
|
const keyPath = getKeyPath();
|
|
1992
|
-
if (!
|
|
2060
|
+
if (!existsSync4(keyPath)) {
|
|
1993
2061
|
process.stderr.write(
|
|
1994
2062
|
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1995
2063
|
`
|
|
@@ -2076,6 +2144,7 @@ var shard_manager_exports = {};
|
|
|
2076
2144
|
__export(shard_manager_exports, {
|
|
2077
2145
|
disposeShards: () => disposeShards,
|
|
2078
2146
|
ensureShardSchema: () => ensureShardSchema,
|
|
2147
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2079
2148
|
getReadyShardClient: () => getReadyShardClient,
|
|
2080
2149
|
getShardClient: () => getShardClient,
|
|
2081
2150
|
getShardsDir: () => getShardsDir,
|
|
@@ -2085,14 +2154,17 @@ __export(shard_manager_exports, {
|
|
|
2085
2154
|
shardExists: () => shardExists
|
|
2086
2155
|
});
|
|
2087
2156
|
import path5 from "path";
|
|
2088
|
-
import { existsSync as
|
|
2157
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2089
2158
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2090
2159
|
function initShardManager(encryptionKey) {
|
|
2091
2160
|
_encryptionKey = encryptionKey;
|
|
2092
|
-
if (!
|
|
2093
|
-
|
|
2161
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2162
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2094
2163
|
}
|
|
2095
2164
|
_shardingEnabled = true;
|
|
2165
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2166
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2167
|
+
_evictionTimer.unref();
|
|
2096
2168
|
}
|
|
2097
2169
|
function isShardingEnabled() {
|
|
2098
2170
|
return _shardingEnabled;
|
|
@@ -2109,21 +2181,28 @@ function getShardClient(projectName) {
|
|
|
2109
2181
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2110
2182
|
}
|
|
2111
2183
|
const cached = _shards.get(safeName);
|
|
2112
|
-
if (cached)
|
|
2184
|
+
if (cached) {
|
|
2185
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2186
|
+
return cached;
|
|
2187
|
+
}
|
|
2188
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2189
|
+
evictLRU();
|
|
2190
|
+
}
|
|
2113
2191
|
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
2114
2192
|
const client = createClient2({
|
|
2115
2193
|
url: `file:${dbPath}`,
|
|
2116
2194
|
encryptionKey: _encryptionKey
|
|
2117
2195
|
});
|
|
2118
2196
|
_shards.set(safeName, client);
|
|
2197
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2119
2198
|
return client;
|
|
2120
2199
|
}
|
|
2121
2200
|
function shardExists(projectName) {
|
|
2122
2201
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2123
|
-
return
|
|
2202
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
2124
2203
|
}
|
|
2125
2204
|
function listShards() {
|
|
2126
|
-
if (!
|
|
2205
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
2127
2206
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2128
2207
|
}
|
|
2129
2208
|
async function ensureShardSchema(client) {
|
|
@@ -2175,6 +2254,8 @@ async function ensureShardSchema(client) {
|
|
|
2175
2254
|
for (const col of [
|
|
2176
2255
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2177
2256
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2257
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2258
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2178
2259
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2179
2260
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2180
2261
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2312,21 +2393,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2312
2393
|
await ensureShardSchema(client);
|
|
2313
2394
|
return client;
|
|
2314
2395
|
}
|
|
2396
|
+
function evictLRU() {
|
|
2397
|
+
let oldest = null;
|
|
2398
|
+
let oldestTime = Infinity;
|
|
2399
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2400
|
+
if (time < oldestTime) {
|
|
2401
|
+
oldestTime = time;
|
|
2402
|
+
oldest = name;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
if (oldest) {
|
|
2406
|
+
const client = _shards.get(oldest);
|
|
2407
|
+
if (client) {
|
|
2408
|
+
client.close();
|
|
2409
|
+
}
|
|
2410
|
+
_shards.delete(oldest);
|
|
2411
|
+
_shardLastAccess.delete(oldest);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
function evictIdleShards() {
|
|
2415
|
+
const now = Date.now();
|
|
2416
|
+
const toEvict = [];
|
|
2417
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2418
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2419
|
+
toEvict.push(name);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
for (const name of toEvict) {
|
|
2423
|
+
const client = _shards.get(name);
|
|
2424
|
+
if (client) {
|
|
2425
|
+
client.close();
|
|
2426
|
+
}
|
|
2427
|
+
_shards.delete(name);
|
|
2428
|
+
_shardLastAccess.delete(name);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
function getOpenShardCount() {
|
|
2432
|
+
return _shards.size;
|
|
2433
|
+
}
|
|
2315
2434
|
function disposeShards() {
|
|
2435
|
+
if (_evictionTimer) {
|
|
2436
|
+
clearInterval(_evictionTimer);
|
|
2437
|
+
_evictionTimer = null;
|
|
2438
|
+
}
|
|
2316
2439
|
for (const [, client] of _shards) {
|
|
2317
2440
|
client.close();
|
|
2318
2441
|
}
|
|
2319
2442
|
_shards.clear();
|
|
2443
|
+
_shardLastAccess.clear();
|
|
2320
2444
|
_shardingEnabled = false;
|
|
2321
2445
|
_encryptionKey = null;
|
|
2322
2446
|
}
|
|
2323
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2447
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2324
2448
|
var init_shard_manager = __esm({
|
|
2325
2449
|
"src/lib/shard-manager.ts"() {
|
|
2326
2450
|
"use strict";
|
|
2327
2451
|
init_config();
|
|
2328
2452
|
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
2453
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2454
|
+
MAX_OPEN_SHARDS = 10;
|
|
2455
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2329
2456
|
_shards = /* @__PURE__ */ new Map();
|
|
2457
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2458
|
+
_evictionTimer = null;
|
|
2330
2459
|
_encryptionKey = null;
|
|
2331
2460
|
_shardingEnabled = false;
|
|
2332
2461
|
}
|
|
@@ -3189,7 +3318,7 @@ __export(reranker_exports, {
|
|
|
3189
3318
|
rerankWithScores: () => rerankWithScores
|
|
3190
3319
|
});
|
|
3191
3320
|
import path6 from "path";
|
|
3192
|
-
import { existsSync as
|
|
3321
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3193
3322
|
function resetIdleTimer() {
|
|
3194
3323
|
if (_idleTimer) clearTimeout(_idleTimer);
|
|
3195
3324
|
_idleTimer = setTimeout(() => {
|
|
@@ -3200,7 +3329,7 @@ function resetIdleTimer() {
|
|
|
3200
3329
|
}
|
|
3201
3330
|
}
|
|
3202
3331
|
function isRerankerAvailable() {
|
|
3203
|
-
return
|
|
3332
|
+
return existsSync6(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
3204
3333
|
}
|
|
3205
3334
|
function getRerankerModelPath() {
|
|
3206
3335
|
return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
@@ -3211,7 +3340,7 @@ async function ensureLoaded() {
|
|
|
3211
3340
|
return;
|
|
3212
3341
|
}
|
|
3213
3342
|
const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
3214
|
-
if (!
|
|
3343
|
+
if (!existsSync6(modelPath)) {
|
|
3215
3344
|
throw new Error(
|
|
3216
3345
|
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
3217
3346
|
);
|
|
@@ -3307,13 +3436,50 @@ var init_reranker = __esm({
|
|
|
3307
3436
|
}
|
|
3308
3437
|
});
|
|
3309
3438
|
|
|
3439
|
+
// src/lib/daemon-auth.ts
|
|
3440
|
+
import crypto from "crypto";
|
|
3441
|
+
import path7 from "path";
|
|
3442
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3443
|
+
function normalizeToken(token) {
|
|
3444
|
+
if (!token) return null;
|
|
3445
|
+
const trimmed = token.trim();
|
|
3446
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3447
|
+
}
|
|
3448
|
+
function readDaemonToken() {
|
|
3449
|
+
try {
|
|
3450
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
3451
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
3452
|
+
} catch {
|
|
3453
|
+
return null;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
function ensureDaemonToken(seed) {
|
|
3457
|
+
const existing = readDaemonToken();
|
|
3458
|
+
if (existing) return existing;
|
|
3459
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
3460
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
3461
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
3462
|
+
`, "utf8");
|
|
3463
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
3464
|
+
return token;
|
|
3465
|
+
}
|
|
3466
|
+
var DAEMON_TOKEN_PATH;
|
|
3467
|
+
var init_daemon_auth = __esm({
|
|
3468
|
+
"src/lib/daemon-auth.ts"() {
|
|
3469
|
+
"use strict";
|
|
3470
|
+
init_config();
|
|
3471
|
+
init_secure_files();
|
|
3472
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
3473
|
+
}
|
|
3474
|
+
});
|
|
3475
|
+
|
|
3310
3476
|
// src/lib/exe-daemon-client.ts
|
|
3311
3477
|
import net from "net";
|
|
3312
3478
|
import os5 from "os";
|
|
3313
3479
|
import { spawn } from "child_process";
|
|
3314
3480
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3315
|
-
import { existsSync as
|
|
3316
|
-
import
|
|
3481
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
3482
|
+
import path8 from "path";
|
|
3317
3483
|
import { fileURLToPath } from "url";
|
|
3318
3484
|
function handleData(chunk) {
|
|
3319
3485
|
_buffer += chunk.toString();
|
|
@@ -3341,9 +3507,9 @@ function handleData(chunk) {
|
|
|
3341
3507
|
}
|
|
3342
3508
|
}
|
|
3343
3509
|
function cleanupStaleFiles() {
|
|
3344
|
-
if (
|
|
3510
|
+
if (existsSync8(PID_PATH)) {
|
|
3345
3511
|
try {
|
|
3346
|
-
const pid = parseInt(
|
|
3512
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
3347
3513
|
if (pid > 0) {
|
|
3348
3514
|
try {
|
|
3349
3515
|
process.kill(pid, 0);
|
|
@@ -3364,11 +3530,11 @@ function cleanupStaleFiles() {
|
|
|
3364
3530
|
}
|
|
3365
3531
|
}
|
|
3366
3532
|
function findPackageRoot() {
|
|
3367
|
-
let dir =
|
|
3368
|
-
const { root } =
|
|
3533
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
3534
|
+
const { root } = path8.parse(dir);
|
|
3369
3535
|
while (dir !== root) {
|
|
3370
|
-
if (
|
|
3371
|
-
dir =
|
|
3536
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
3537
|
+
dir = path8.dirname(dir);
|
|
3372
3538
|
}
|
|
3373
3539
|
return null;
|
|
3374
3540
|
}
|
|
@@ -3394,16 +3560,17 @@ function spawnDaemon() {
|
|
|
3394
3560
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
3395
3561
|
return;
|
|
3396
3562
|
}
|
|
3397
|
-
const daemonPath =
|
|
3398
|
-
if (!
|
|
3563
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
3564
|
+
if (!existsSync8(daemonPath)) {
|
|
3399
3565
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
3400
3566
|
`);
|
|
3401
3567
|
return;
|
|
3402
3568
|
}
|
|
3403
3569
|
const resolvedPath = daemonPath;
|
|
3570
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
3404
3571
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
3405
3572
|
`);
|
|
3406
|
-
const logPath =
|
|
3573
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
3407
3574
|
let stderrFd = "ignore";
|
|
3408
3575
|
try {
|
|
3409
3576
|
stderrFd = openSync(logPath, "a");
|
|
@@ -3421,7 +3588,8 @@ function spawnDaemon() {
|
|
|
3421
3588
|
TMUX_PANE: void 0,
|
|
3422
3589
|
// Prevents resolveExeSession() from scoping to one session
|
|
3423
3590
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
3424
|
-
EXE_DAEMON_PID: PID_PATH
|
|
3591
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
3592
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
3425
3593
|
}
|
|
3426
3594
|
});
|
|
3427
3595
|
child.unref();
|
|
@@ -3531,13 +3699,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
3531
3699
|
return;
|
|
3532
3700
|
}
|
|
3533
3701
|
const id = randomUUID2();
|
|
3702
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
3534
3703
|
const timer = setTimeout(() => {
|
|
3535
3704
|
_pending.delete(id);
|
|
3536
3705
|
resolve({ error: "Request timeout" });
|
|
3537
3706
|
}, timeoutMs);
|
|
3538
3707
|
_pending.set(id, { resolve, timer });
|
|
3539
3708
|
try {
|
|
3540
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
3709
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
3541
3710
|
} catch {
|
|
3542
3711
|
clearTimeout(timer);
|
|
3543
3712
|
_pending.delete(id);
|
|
@@ -3566,9 +3735,9 @@ function killAndRespawnDaemon() {
|
|
|
3566
3735
|
}
|
|
3567
3736
|
try {
|
|
3568
3737
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
3569
|
-
if (
|
|
3738
|
+
if (existsSync8(PID_PATH)) {
|
|
3570
3739
|
try {
|
|
3571
|
-
const pid = parseInt(
|
|
3740
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
3572
3741
|
if (pid > 0) {
|
|
3573
3742
|
try {
|
|
3574
3743
|
process.kill(pid, "SIGKILL");
|
|
@@ -3685,17 +3854,19 @@ function disconnectClient() {
|
|
|
3685
3854
|
entry.resolve({ error: "Client disconnected" });
|
|
3686
3855
|
}
|
|
3687
3856
|
}
|
|
3688
|
-
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;
|
|
3857
|
+
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;
|
|
3689
3858
|
var init_exe_daemon_client = __esm({
|
|
3690
3859
|
"src/lib/exe-daemon-client.ts"() {
|
|
3691
3860
|
"use strict";
|
|
3692
3861
|
init_config();
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3862
|
+
init_daemon_auth();
|
|
3863
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
3864
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
3865
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3696
3866
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3697
3867
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3698
3868
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
3869
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3699
3870
|
_socket = null;
|
|
3700
3871
|
_connected = false;
|
|
3701
3872
|
_buffer = "";
|
|
@@ -3747,10 +3918,10 @@ async function disposeEmbedder() {
|
|
|
3747
3918
|
async function embedDirect(text) {
|
|
3748
3919
|
const llamaCpp = await import("node-llama-cpp");
|
|
3749
3920
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3750
|
-
const { existsSync:
|
|
3751
|
-
const
|
|
3752
|
-
const modelPath =
|
|
3753
|
-
if (!
|
|
3921
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
3922
|
+
const path11 = await import("path");
|
|
3923
|
+
const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3924
|
+
if (!existsSync10(modelPath)) {
|
|
3754
3925
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3755
3926
|
}
|
|
3756
3927
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3785,7 +3956,7 @@ __export(project_name_exports, {
|
|
|
3785
3956
|
getProjectName: () => getProjectName
|
|
3786
3957
|
});
|
|
3787
3958
|
import { execSync as execSync2 } from "child_process";
|
|
3788
|
-
import
|
|
3959
|
+
import path9 from "path";
|
|
3789
3960
|
function getProjectName(cwd) {
|
|
3790
3961
|
const dir = cwd ?? process.cwd();
|
|
3791
3962
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
@@ -3798,7 +3969,7 @@ function getProjectName(cwd) {
|
|
|
3798
3969
|
timeout: 2e3,
|
|
3799
3970
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3800
3971
|
}).trim();
|
|
3801
|
-
repoRoot =
|
|
3972
|
+
repoRoot = path9.dirname(gitCommonDir);
|
|
3802
3973
|
} catch {
|
|
3803
3974
|
repoRoot = execSync2("git rev-parse --show-toplevel", {
|
|
3804
3975
|
cwd: dir,
|
|
@@ -3807,11 +3978,11 @@ function getProjectName(cwd) {
|
|
|
3807
3978
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3808
3979
|
}).trim();
|
|
3809
3980
|
}
|
|
3810
|
-
_cached =
|
|
3981
|
+
_cached = path9.basename(repoRoot);
|
|
3811
3982
|
_cachedCwd = dir;
|
|
3812
3983
|
return _cached;
|
|
3813
3984
|
} catch {
|
|
3814
|
-
_cached =
|
|
3985
|
+
_cached = path9.basename(dir);
|
|
3815
3986
|
_cachedCwd = dir;
|
|
3816
3987
|
return _cached;
|
|
3817
3988
|
}
|
|
@@ -3835,9 +4006,9 @@ __export(file_grep_exports, {
|
|
|
3835
4006
|
grepProjectFiles: () => grepProjectFiles
|
|
3836
4007
|
});
|
|
3837
4008
|
import { execSync as execSync3 } from "child_process";
|
|
3838
|
-
import { readFileSync as
|
|
3839
|
-
import
|
|
3840
|
-
import
|
|
4009
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
4010
|
+
import path10 from "path";
|
|
4011
|
+
import crypto2 from "crypto";
|
|
3841
4012
|
function hasRipgrep() {
|
|
3842
4013
|
if (_hasRg === null) {
|
|
3843
4014
|
try {
|
|
@@ -3870,13 +4041,13 @@ async function grepProjectFiles(query, projectRoot, options) {
|
|
|
3870
4041
|
const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
|
|
3871
4042
|
const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
|
|
3872
4043
|
return {
|
|
3873
|
-
id:
|
|
4044
|
+
id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
|
|
3874
4045
|
agent_id: "project",
|
|
3875
4046
|
agent_role: "file",
|
|
3876
4047
|
session_id: "file-grep",
|
|
3877
4048
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3878
4049
|
tool_name: "file_grep",
|
|
3879
|
-
project_name:
|
|
4050
|
+
project_name: path10.basename(projectRoot),
|
|
3880
4051
|
has_error: false,
|
|
3881
4052
|
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
3882
4053
|
vector: null,
|
|
@@ -3888,7 +4059,7 @@ function getChunkContext(filePath, lineNumber) {
|
|
|
3888
4059
|
try {
|
|
3889
4060
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
3890
4061
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
3891
|
-
const source =
|
|
4062
|
+
const source = readFileSync5(filePath, "utf8");
|
|
3892
4063
|
const lines = source.split("\n");
|
|
3893
4064
|
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
3894
4065
|
const line = lines[i];
|
|
@@ -3950,11 +4121,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
|
3950
4121
|
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
3951
4122
|
const hits = [];
|
|
3952
4123
|
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
3953
|
-
const absPath =
|
|
4124
|
+
const absPath = path10.join(projectRoot, filePath);
|
|
3954
4125
|
try {
|
|
3955
4126
|
const stat = statSync2(absPath);
|
|
3956
4127
|
if (stat.size > MAX_FILE_SIZE) continue;
|
|
3957
|
-
const content =
|
|
4128
|
+
const content = readFileSync5(absPath, "utf8");
|
|
3958
4129
|
const lines = content.split("\n");
|
|
3959
4130
|
const matches = content.match(regex);
|
|
3960
4131
|
if (!matches || matches.length === 0) continue;
|
|
@@ -3977,15 +4148,15 @@ function collectFiles(root, patterns) {
|
|
|
3977
4148
|
const files = [];
|
|
3978
4149
|
function walk(dir, relative) {
|
|
3979
4150
|
if (files.length >= MAX_FILES) return;
|
|
3980
|
-
const basename =
|
|
4151
|
+
const basename = path10.basename(dir);
|
|
3981
4152
|
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
3982
4153
|
try {
|
|
3983
4154
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
3984
4155
|
for (const entry of entries) {
|
|
3985
4156
|
if (files.length >= MAX_FILES) return;
|
|
3986
|
-
const rel =
|
|
4157
|
+
const rel = path10.join(relative, entry.name);
|
|
3987
4158
|
if (entry.isDirectory()) {
|
|
3988
|
-
walk(
|
|
4159
|
+
walk(path10.join(dir, entry.name), rel);
|
|
3989
4160
|
} else if (entry.isFile()) {
|
|
3990
4161
|
for (const pat of patterns) {
|
|
3991
4162
|
if (matchGlob(rel, pat)) {
|
|
@@ -4017,7 +4188,7 @@ function matchGlob(filePath, pattern) {
|
|
|
4017
4188
|
if (slashIdx !== -1) {
|
|
4018
4189
|
const dir = pattern.slice(0, slashIdx);
|
|
4019
4190
|
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
4020
|
-
const fileDir =
|
|
4191
|
+
const fileDir = path10.dirname(filePath);
|
|
4021
4192
|
return fileDir === dir && filePath.endsWith(ext2);
|
|
4022
4193
|
}
|
|
4023
4194
|
const ext = pattern.replace("*", "");
|
|
@@ -4025,9 +4196,9 @@ function matchGlob(filePath, pattern) {
|
|
|
4025
4196
|
}
|
|
4026
4197
|
function buildSnippet(hit, projectRoot) {
|
|
4027
4198
|
try {
|
|
4028
|
-
const absPath =
|
|
4029
|
-
if (!
|
|
4030
|
-
const lines =
|
|
4199
|
+
const absPath = path10.join(projectRoot, hit.filePath);
|
|
4200
|
+
if (!existsSync9(absPath)) return hit.matchLine;
|
|
4201
|
+
const lines = readFileSync5(absPath, "utf8").split("\n");
|
|
4031
4202
|
const start = Math.max(0, hit.lineNumber - 3);
|
|
4032
4203
|
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
4033
4204
|
return lines.slice(start, end).join("\n").slice(0, 500);
|