@askexenow/exe-os 0.8.83 → 0.8.85
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
// src/lib/exe-daemon-client.ts
|
|
2
|
+
import net from "net";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
import { existsSync as existsSync2, unlinkSync, readFileSync as readFileSync2, openSync, closeSync, statSync } from "fs";
|
|
6
|
+
import path2 from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
// src/lib/config.ts
|
|
10
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
11
|
+
import { readFileSync, existsSync, renameSync } from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
function resolveDataDir() {
|
|
15
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
16
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
17
|
+
const newDir = path.join(os.homedir(), ".exe-os");
|
|
18
|
+
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
19
|
+
if (!existsSync(newDir) && existsSync(legacyDir)) {
|
|
20
|
+
try {
|
|
21
|
+
renameSync(legacyDir, newDir);
|
|
22
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
23
|
+
`);
|
|
24
|
+
} catch {
|
|
25
|
+
return legacyDir;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return newDir;
|
|
29
|
+
}
|
|
30
|
+
var EXE_AI_DIR = resolveDataDir();
|
|
31
|
+
var DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
32
|
+
var MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
33
|
+
var CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
34
|
+
var LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
35
|
+
var CURRENT_CONFIG_VERSION = 1;
|
|
36
|
+
var DEFAULT_CONFIG = {
|
|
37
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
38
|
+
dbPath: DB_PATH,
|
|
39
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
40
|
+
embeddingDim: 1024,
|
|
41
|
+
batchSize: 20,
|
|
42
|
+
flushIntervalMs: 1e4,
|
|
43
|
+
autoIngestion: true,
|
|
44
|
+
autoRetrieval: true,
|
|
45
|
+
searchMode: "hybrid",
|
|
46
|
+
hookSearchMode: "hybrid",
|
|
47
|
+
fileGrepEnabled: true,
|
|
48
|
+
splashEffect: true,
|
|
49
|
+
consolidationEnabled: true,
|
|
50
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
51
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
52
|
+
consolidationMaxCallsPerRun: 20,
|
|
53
|
+
selfQueryRouter: true,
|
|
54
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
55
|
+
rerankerEnabled: true,
|
|
56
|
+
scalingRoadmap: {
|
|
57
|
+
rerankerAutoTrigger: {
|
|
58
|
+
enabled: true,
|
|
59
|
+
broadQueryMinCardinality: 5e4,
|
|
60
|
+
fetchTopK: 150,
|
|
61
|
+
returnTopK: 5
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
graphRagEnabled: true,
|
|
65
|
+
wikiEnabled: false,
|
|
66
|
+
wikiUrl: "",
|
|
67
|
+
wikiApiKey: "",
|
|
68
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
69
|
+
wikiWorkspaceMapping: {},
|
|
70
|
+
wikiAutoUpdate: true,
|
|
71
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
72
|
+
wikiAutoUpdateCreateNew: true,
|
|
73
|
+
skillLearning: true,
|
|
74
|
+
skillThreshold: 3,
|
|
75
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
76
|
+
exeHeartbeat: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
intervalSeconds: 60,
|
|
79
|
+
staleInProgressThresholdHours: 2
|
|
80
|
+
},
|
|
81
|
+
sessionLifecycle: {
|
|
82
|
+
idleKillEnabled: true,
|
|
83
|
+
idleKillTicksRequired: 3,
|
|
84
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
85
|
+
maxAutoInstances: 10
|
|
86
|
+
},
|
|
87
|
+
autoUpdate: {
|
|
88
|
+
checkOnBoot: true,
|
|
89
|
+
autoInstall: false,
|
|
90
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/lib/exe-daemon-client.ts
|
|
95
|
+
var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path2.join(EXE_AI_DIR, "exed.sock");
|
|
96
|
+
var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path2.join(EXE_AI_DIR, "exed.pid");
|
|
97
|
+
var SPAWN_LOCK_PATH = path2.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
98
|
+
var SPAWN_LOCK_STALE_MS = 3e4;
|
|
99
|
+
var CONNECT_TIMEOUT_MS = 15e3;
|
|
100
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
101
|
+
var _socket = null;
|
|
102
|
+
var _connected = false;
|
|
103
|
+
var _buffer = "";
|
|
104
|
+
var _pending = /* @__PURE__ */ new Map();
|
|
105
|
+
var MAX_BUFFER = 1e7;
|
|
106
|
+
function handleData(chunk) {
|
|
107
|
+
_buffer += chunk.toString();
|
|
108
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
109
|
+
_buffer = "";
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let newlineIdx;
|
|
113
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
114
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
115
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
116
|
+
if (!line) continue;
|
|
117
|
+
try {
|
|
118
|
+
const response = JSON.parse(line);
|
|
119
|
+
const id = response.id;
|
|
120
|
+
if (!id) continue;
|
|
121
|
+
const entry = _pending.get(id);
|
|
122
|
+
if (entry) {
|
|
123
|
+
clearTimeout(entry.timer);
|
|
124
|
+
_pending.delete(id);
|
|
125
|
+
entry.resolve(response);
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function cleanupStaleFiles() {
|
|
132
|
+
if (existsSync2(PID_PATH)) {
|
|
133
|
+
try {
|
|
134
|
+
const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
|
|
135
|
+
if (pid > 0) {
|
|
136
|
+
try {
|
|
137
|
+
process.kill(pid, 0);
|
|
138
|
+
return;
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
unlinkSync(PID_PATH);
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
unlinkSync(SOCKET_PATH);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function findPackageRoot() {
|
|
155
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
156
|
+
const { root } = path2.parse(dir);
|
|
157
|
+
while (dir !== root) {
|
|
158
|
+
if (existsSync2(path2.join(dir, "package.json"))) return dir;
|
|
159
|
+
dir = path2.dirname(dir);
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function spawnDaemon() {
|
|
164
|
+
const pkgRoot = findPackageRoot();
|
|
165
|
+
if (!pkgRoot) {
|
|
166
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const daemonPath = path2.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
170
|
+
if (!existsSync2(daemonPath)) {
|
|
171
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
172
|
+
`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const resolvedPath = daemonPath;
|
|
176
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
177
|
+
`);
|
|
178
|
+
const logPath = path2.join(path2.dirname(SOCKET_PATH), "exed.log");
|
|
179
|
+
let stderrFd = "ignore";
|
|
180
|
+
try {
|
|
181
|
+
stderrFd = openSync(logPath, "a");
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
185
|
+
detached: true,
|
|
186
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
187
|
+
env: {
|
|
188
|
+
...process.env,
|
|
189
|
+
TMUX: void 0,
|
|
190
|
+
// Daemon is global — must not inherit session scope
|
|
191
|
+
TMUX_PANE: void 0,
|
|
192
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
193
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
194
|
+
EXE_DAEMON_PID: PID_PATH
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
child.unref();
|
|
198
|
+
if (typeof stderrFd === "number") {
|
|
199
|
+
try {
|
|
200
|
+
closeSync(stderrFd);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function acquireSpawnLock() {
|
|
206
|
+
try {
|
|
207
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
208
|
+
closeSync(fd);
|
|
209
|
+
return true;
|
|
210
|
+
} catch {
|
|
211
|
+
try {
|
|
212
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
213
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
214
|
+
try {
|
|
215
|
+
unlinkSync(SPAWN_LOCK_PATH);
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
220
|
+
closeSync(fd);
|
|
221
|
+
return true;
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function releaseSpawnLock() {
|
|
231
|
+
try {
|
|
232
|
+
unlinkSync(SPAWN_LOCK_PATH);
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function connectToSocket() {
|
|
237
|
+
return new Promise((resolve) => {
|
|
238
|
+
if (_socket && _connected) {
|
|
239
|
+
resolve(true);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
243
|
+
const connectTimeout = setTimeout(() => {
|
|
244
|
+
socket.destroy();
|
|
245
|
+
resolve(false);
|
|
246
|
+
}, 2e3);
|
|
247
|
+
socket.on("connect", () => {
|
|
248
|
+
clearTimeout(connectTimeout);
|
|
249
|
+
_socket = socket;
|
|
250
|
+
_connected = true;
|
|
251
|
+
_buffer = "";
|
|
252
|
+
socket.on("data", handleData);
|
|
253
|
+
socket.on("close", () => {
|
|
254
|
+
_connected = false;
|
|
255
|
+
_socket = null;
|
|
256
|
+
for (const [id, entry] of _pending) {
|
|
257
|
+
clearTimeout(entry.timer);
|
|
258
|
+
_pending.delete(id);
|
|
259
|
+
entry.resolve({ error: "Connection closed" });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
socket.on("error", () => {
|
|
263
|
+
_connected = false;
|
|
264
|
+
_socket = null;
|
|
265
|
+
});
|
|
266
|
+
resolve(true);
|
|
267
|
+
});
|
|
268
|
+
socket.on("error", () => {
|
|
269
|
+
clearTimeout(connectTimeout);
|
|
270
|
+
resolve(false);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
async function connectEmbedDaemon() {
|
|
275
|
+
if (_socket && _connected) return true;
|
|
276
|
+
if (await connectToSocket()) return true;
|
|
277
|
+
if (acquireSpawnLock()) {
|
|
278
|
+
try {
|
|
279
|
+
cleanupStaleFiles();
|
|
280
|
+
spawnDaemon();
|
|
281
|
+
} finally {
|
|
282
|
+
releaseSpawnLock();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const start = Date.now();
|
|
286
|
+
let delay = 100;
|
|
287
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
288
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
289
|
+
if (await connectToSocket()) return true;
|
|
290
|
+
delay = Math.min(delay * 2, 3e3);
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
295
|
+
return new Promise((resolve) => {
|
|
296
|
+
if (!_socket || !_connected) {
|
|
297
|
+
resolve({ error: "Not connected" });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const id = randomUUID();
|
|
301
|
+
const timer = setTimeout(() => {
|
|
302
|
+
_pending.delete(id);
|
|
303
|
+
resolve({ error: "Request timeout" });
|
|
304
|
+
}, timeoutMs);
|
|
305
|
+
_pending.set(id, { resolve, timer });
|
|
306
|
+
try {
|
|
307
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
308
|
+
} catch {
|
|
309
|
+
clearTimeout(timer);
|
|
310
|
+
_pending.delete(id);
|
|
311
|
+
resolve({ error: "Write failed" });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function isClientConnected() {
|
|
316
|
+
return _connected;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/lib/daemon-protocol.ts
|
|
320
|
+
function serializeValue(v) {
|
|
321
|
+
if (v === null || v === void 0) return null;
|
|
322
|
+
if (typeof v === "bigint") return Number(v);
|
|
323
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
324
|
+
if (v instanceof Uint8Array) {
|
|
325
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
326
|
+
}
|
|
327
|
+
if (ArrayBuffer.isView(v)) {
|
|
328
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
329
|
+
}
|
|
330
|
+
if (v instanceof ArrayBuffer) {
|
|
331
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
332
|
+
}
|
|
333
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
334
|
+
return String(v);
|
|
335
|
+
}
|
|
336
|
+
function deserializeValue(v) {
|
|
337
|
+
if (v === null) return null;
|
|
338
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
339
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
340
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
341
|
+
}
|
|
342
|
+
return v;
|
|
343
|
+
}
|
|
344
|
+
function deserializeResultSet(srs) {
|
|
345
|
+
const rows = srs.rows.map((obj) => {
|
|
346
|
+
const values = srs.columns.map(
|
|
347
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
348
|
+
);
|
|
349
|
+
const row = values;
|
|
350
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
351
|
+
const col = srs.columns[i];
|
|
352
|
+
if (col !== void 0) {
|
|
353
|
+
row[col] = values[i] ?? null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
Object.defineProperty(row, "length", {
|
|
357
|
+
value: values.length,
|
|
358
|
+
enumerable: false
|
|
359
|
+
});
|
|
360
|
+
return row;
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
columns: srs.columns,
|
|
364
|
+
columnTypes: srs.columnTypes ?? [],
|
|
365
|
+
rows,
|
|
366
|
+
rowsAffected: srs.rowsAffected,
|
|
367
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
368
|
+
toJSON: () => ({
|
|
369
|
+
columns: srs.columns,
|
|
370
|
+
columnTypes: srs.columnTypes ?? [],
|
|
371
|
+
rows: srs.rows,
|
|
372
|
+
rowsAffected: srs.rowsAffected,
|
|
373
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
374
|
+
})
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/lib/db-daemon-client.ts
|
|
379
|
+
function normalizeStatement(stmt) {
|
|
380
|
+
if (typeof stmt === "string") {
|
|
381
|
+
return { sql: stmt, args: [] };
|
|
382
|
+
}
|
|
383
|
+
const sql = stmt.sql;
|
|
384
|
+
let args = [];
|
|
385
|
+
if (Array.isArray(stmt.args)) {
|
|
386
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
387
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
388
|
+
const named = {};
|
|
389
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
390
|
+
named[key] = serializeValue(val);
|
|
391
|
+
}
|
|
392
|
+
return { sql, args: named };
|
|
393
|
+
}
|
|
394
|
+
return { sql, args };
|
|
395
|
+
}
|
|
396
|
+
function createDaemonDbClient(fallbackClient) {
|
|
397
|
+
let _useDaemon = false;
|
|
398
|
+
const client = {
|
|
399
|
+
async execute(stmt) {
|
|
400
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
401
|
+
return fallbackClient.execute(stmt);
|
|
402
|
+
}
|
|
403
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
404
|
+
const response = await sendDaemonRequest({
|
|
405
|
+
type: "db-execute",
|
|
406
|
+
sql,
|
|
407
|
+
args
|
|
408
|
+
});
|
|
409
|
+
if (response.error) {
|
|
410
|
+
const errMsg = String(response.error);
|
|
411
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
412
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
413
|
+
`);
|
|
414
|
+
return fallbackClient.execute(stmt);
|
|
415
|
+
}
|
|
416
|
+
throw new Error(errMsg);
|
|
417
|
+
}
|
|
418
|
+
if (response.db) {
|
|
419
|
+
return deserializeResultSet(response.db);
|
|
420
|
+
}
|
|
421
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
422
|
+
return fallbackClient.execute(stmt);
|
|
423
|
+
},
|
|
424
|
+
async batch(stmts, mode) {
|
|
425
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
426
|
+
return fallbackClient.batch(stmts, mode);
|
|
427
|
+
}
|
|
428
|
+
const statements = stmts.map(normalizeStatement);
|
|
429
|
+
const response = await sendDaemonRequest({
|
|
430
|
+
type: "db-batch",
|
|
431
|
+
statements,
|
|
432
|
+
mode: mode ?? "deferred"
|
|
433
|
+
});
|
|
434
|
+
if (response.error) {
|
|
435
|
+
const errMsg = String(response.error);
|
|
436
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
437
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
438
|
+
`);
|
|
439
|
+
return fallbackClient.batch(stmts, mode);
|
|
440
|
+
}
|
|
441
|
+
throw new Error(errMsg);
|
|
442
|
+
}
|
|
443
|
+
const batchResults = response["db-batch"];
|
|
444
|
+
if (batchResults) {
|
|
445
|
+
return batchResults.map(deserializeResultSet);
|
|
446
|
+
}
|
|
447
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
448
|
+
return fallbackClient.batch(stmts, mode);
|
|
449
|
+
},
|
|
450
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
451
|
+
async transaction(mode) {
|
|
452
|
+
return fallbackClient.transaction(mode);
|
|
453
|
+
},
|
|
454
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
455
|
+
async executeMultiple(sql) {
|
|
456
|
+
return fallbackClient.executeMultiple(sql);
|
|
457
|
+
},
|
|
458
|
+
// migrate — delegate to fallback
|
|
459
|
+
async migrate(stmts) {
|
|
460
|
+
return fallbackClient.migrate(stmts);
|
|
461
|
+
},
|
|
462
|
+
// Sync mode — delegate to fallback
|
|
463
|
+
sync() {
|
|
464
|
+
return fallbackClient.sync();
|
|
465
|
+
},
|
|
466
|
+
close() {
|
|
467
|
+
_useDaemon = false;
|
|
468
|
+
},
|
|
469
|
+
get closed() {
|
|
470
|
+
return fallbackClient.closed;
|
|
471
|
+
},
|
|
472
|
+
get protocol() {
|
|
473
|
+
return fallbackClient.protocol;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
return {
|
|
477
|
+
...client,
|
|
478
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
479
|
+
_enableDaemon() {
|
|
480
|
+
_useDaemon = true;
|
|
481
|
+
},
|
|
482
|
+
/** Check if daemon routing is active */
|
|
483
|
+
_isDaemonActive() {
|
|
484
|
+
return _useDaemon && isClientConnected();
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
489
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
490
|
+
const connected = await connectEmbedDaemon();
|
|
491
|
+
if (!connected) {
|
|
492
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
496
|
+
client._enableDaemon();
|
|
497
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
498
|
+
return client;
|
|
499
|
+
}
|
|
500
|
+
export {
|
|
501
|
+
createDaemonDbClient,
|
|
502
|
+
initDaemonDbClient
|
|
503
|
+
};
|