@adhdev/daemon-core 0.9.82-rc.331 → 0.9.82-rc.333
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/index.js +189 -88
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +187 -86
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +15 -0
- package/dist/system/load-better-sqlite3.d.ts +21 -0
- package/package.json +2 -2
- package/src/cli-adapters/provider-cli-adapter.ts +12 -0
- package/src/mesh/mesh-events-coordinator.ts +15 -0
- package/src/mesh/mesh-reconcile-loop.ts +38 -2
- package/src/mesh/mesh-runtime-store.ts +2 -5
- package/src/providers/cli-provider-instance.ts +45 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +2 -2
- package/src/providers/spec/native-history-executor.ts +2 -2
- package/src/system/load-better-sqlite3.ts +68 -0
|
@@ -134,6 +134,21 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
134
134
|
* coordinator as if they were task completions.
|
|
135
135
|
*/
|
|
136
136
|
detachMeshAssignment(): void;
|
|
137
|
+
/**
|
|
138
|
+
* The resolved modal-park status of this session, or null when it is not
|
|
139
|
+
* parked on a modal awaiting a human answer. Mirrors the overlay logic in
|
|
140
|
+
* getState(): an active AskUserQuestion interactive prompt resolves to
|
|
141
|
+
* waiting_choice; otherwise the adapter's waiting_approval (tool consent)
|
|
142
|
+
* counts — UNLESS auto-approve will dismiss it, in which case the session is
|
|
143
|
+
* effectively generating and is NOT modal-parked. This is the single signal
|
|
144
|
+
* the mesh force-inject guard consults, and the same status string the
|
|
145
|
+
* reconcile loop reads off get_status_metadata. Lowercase literals only —
|
|
146
|
+
* the SessionStatus enum is forked across modules and waiting_choice is
|
|
147
|
+
* absent from some of them.
|
|
148
|
+
*/
|
|
149
|
+
resolveModalParkStatus(): 'waiting_choice' | 'waiting_approval' | null;
|
|
150
|
+
/** True when this session is parked on a modal awaiting a human answer. */
|
|
151
|
+
isModalParked(): boolean;
|
|
137
152
|
onEvent(event: string, data?: any): void;
|
|
138
153
|
recordAcknowledgedUserInput(input: InputEnvelope | string): void;
|
|
139
154
|
dispose(): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* Load the `better-sqlite3` constructor in a way that survives every runtime the
|
|
4
|
+
* daemon ships in.
|
|
5
|
+
*
|
|
6
|
+
* The naive `typeof require === 'function' ? require : createRequire(import.meta.url)`
|
|
7
|
+
* is unsafe inside the daemon-cloud bundle: esbuild emits CJS output but, for any
|
|
8
|
+
* chunk that touches `import.meta.url`, it shims the local `require` with a stub
|
|
9
|
+
* that THROWS `Dynamic require of "..." is not supported`. That stub is still
|
|
10
|
+
* `typeof === 'function'`, so the ternary picks it and the call throws — it never
|
|
11
|
+
* reaches the `createRequire` fallback. The failure surfaced as `mesh_send_task`
|
|
12
|
+
* crashing the whole tool call instead of gracefully degrading.
|
|
13
|
+
*
|
|
14
|
+
* The robust approach is to ATTEMPT the real `require` and CATCH the esbuild stub's
|
|
15
|
+
* throw, then fall back to `createRequire`. We try multiple resolution bases so the
|
|
16
|
+
* load works whether the code runs as:
|
|
17
|
+
* - a genuine CJS module (bare `require` works),
|
|
18
|
+
* - an esbuild CJS bundle with the throwing `require` shim (createRequire(import.meta.url)),
|
|
19
|
+
* - a pure ESM module where `require` is undefined (createRequire(import.meta.url)).
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadBetterSqlite3(): typeof BetterSqlite3;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.333",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.333",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -1285,6 +1285,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1285
1285
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
1286
1286
|
const content = String(text || '');
|
|
1287
1287
|
if (!content.trim()) return;
|
|
1288
|
+
// Modal-park guard (defense-in-depth — the primary guard is at the
|
|
1289
|
+
// cli-provider-instance force-forward chokepoint). A force-write writes raw
|
|
1290
|
+
// keystrokes into the PTY, bypassing the busy send-guard. If the session is
|
|
1291
|
+
// parked on a tool-consent modal, the modal's key handler eats those bytes
|
|
1292
|
+
// and silently resolves an approval the user never made. Hold the write and
|
|
1293
|
+
// let the mesh reconcile loop redeliver once the modal is resolved. We only
|
|
1294
|
+
// hold for an actionable approval modal; plain generating is still force-written
|
|
1295
|
+
// (that is the deadlock the force path exists to break).
|
|
1296
|
+
if (this.engine.currentStatus === 'waiting_approval' || this.engine.hasActionableApproval()) {
|
|
1297
|
+
LOG.info('CLI', `[${this.cliType}] force-send held — session parked on approval modal (status=${this.engine.currentStatus})`);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1288
1300
|
LOG.info('CLI', `[${this.cliType}] force-sending prompt while status=${this.engine.currentStatus}`);
|
|
1289
1301
|
await this.writeToPty(content + this.sendKey);
|
|
1290
1302
|
this.onStatusChange?.();
|
|
@@ -1870,6 +1870,21 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
|
|
|
1870
1870
|
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
|
|
1871
1871
|
providerType: readNonEmptyString(payload.providerType),
|
|
1872
1872
|
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
1873
|
+
// Carry the session identity fields the worker provider event emits so the
|
|
1874
|
+
// coordinator's mirror (updateMeshOwnedSession) gets a real workspace/title/
|
|
1875
|
+
// settings. Without these the remote-relay hop reconstructs metadataEvent with
|
|
1876
|
+
// an empty workspace, and the dashboard flaps to the generic
|
|
1877
|
+
// "Terminal (Mesh Node)" title (and degrades the provider label) between live
|
|
1878
|
+
// events and the periodic get_status_metadata snapshot. The local in-process
|
|
1879
|
+
// forward path (onMeshCoordinatorEventForwarded) already preserves these; this
|
|
1880
|
+
// mirrors them for the remote-only relay path.
|
|
1881
|
+
workspace: readNonEmptyString(payload.workspace) || readNonEmptyString(payload.workspaceName),
|
|
1882
|
+
workspaceName: readNonEmptyString(payload.workspaceName) || readNonEmptyString(payload.workspace),
|
|
1883
|
+
sessionTitle: readNonEmptyString(payload.sessionTitle),
|
|
1884
|
+
sessionStatus: readNonEmptyString(payload.sessionStatus),
|
|
1885
|
+
sessionChatStatus: readNonEmptyString(payload.sessionChatStatus),
|
|
1886
|
+
providerName: readNonEmptyString(payload.providerName),
|
|
1887
|
+
...(payload.sessionSettings && typeof payload.sessionSettings === 'object' && !Array.isArray(payload.sessionSettings) ? { sessionSettings: payload.sessionSettings } : {}),
|
|
1873
1888
|
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
1874
1889
|
jobId: readNonEmptyString(payload.jobId),
|
|
1875
1890
|
interactionId: readNonEmptyString(payload.interactionId),
|
|
@@ -98,6 +98,13 @@ interface LiveCoordinator {
|
|
|
98
98
|
meshId: string;
|
|
99
99
|
instance: ReturnType<DaemonComponents['instanceManager']['getInstance']>;
|
|
100
100
|
idle: boolean;
|
|
101
|
+
// True when the coordinator session is parked on a harness modal awaiting a
|
|
102
|
+
// human answer — claude-cli AskUserQuestion (waiting_choice) or a tool-consent
|
|
103
|
+
// prompt (waiting_approval). A force-inject into such a session would write raw
|
|
104
|
+
// keystrokes the modal key handler consumes, silently selecting a choice the
|
|
105
|
+
// user never made (data corruption). PHASE 2 excludes these from force-inject
|
|
106
|
+
// and leaves the event queued for a later (modal-resolved) tick.
|
|
107
|
+
modalParked: boolean;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
// The set of coordinator-daemon ids THIS daemon answers to when draining the
|
|
@@ -193,7 +200,12 @@ function findLiveCoordinators(components: DaemonComponents): LiveCoordinator[] {
|
|
|
193
200
|
const meshId = readNonEmptyString(settings.meshCoordinatorFor);
|
|
194
201
|
if (!meshId) continue;
|
|
195
202
|
const status = readNonEmptyString(state.status).toLowerCase();
|
|
196
|
-
|
|
203
|
+
// getState() overlays the modal-park statuses: an active AskUserQuestion
|
|
204
|
+
// prompt surfaces as waiting_choice, a tool-consent prompt as waiting_approval.
|
|
205
|
+
// Lowercase literal compare — the SessionStatus enum is forked across modules
|
|
206
|
+
// and waiting_choice is absent from some of them (see cli-provider-instance).
|
|
207
|
+
const modalParked = status === 'waiting_choice' || status === 'waiting_approval';
|
|
208
|
+
out.push({ meshId, instance: inst, idle: status === 'idle', modalParked });
|
|
197
209
|
}
|
|
198
210
|
return out;
|
|
199
211
|
}
|
|
@@ -390,10 +402,34 @@ export async function runMeshReconcileTick(components: DaemonComponents): Promis
|
|
|
390
402
|
// tick — injecting them would be noise mid-generation. Both drains mark the
|
|
391
403
|
// consumed rows drained=1 atomically, so the pull path can't re-deliver.
|
|
392
404
|
const idleCoordinators = meshCoordinators.filter(c => c.idle);
|
|
393
|
-
|
|
405
|
+
// A coordinator parked on a harness modal (waiting_choice / waiting_approval)
|
|
406
|
+
// is non-idle, so it would otherwise be treated as a force-inject target. It
|
|
407
|
+
// must NOT be: a force-inject writes raw keystrokes into the PTY, which the
|
|
408
|
+
// modal's key handler consumes and silently resolves to a choice the user
|
|
409
|
+
// never made. Force-inject is only safe into a coordinator parked in plain
|
|
410
|
+
// `generating` (the deadlock the force path exists to break). So generating
|
|
411
|
+
// targets are the non-idle, non-modal-parked coordinators.
|
|
412
|
+
const generatingCoordinators = meshCoordinators.filter(c => !c.idle && !c.modalParked);
|
|
413
|
+
const modalParkedCoordinators = meshCoordinators.filter(c => !c.idle && c.modalParked);
|
|
394
414
|
const targetCoordinators = idleCoordinators.length > 0 ? idleCoordinators : generatingCoordinators;
|
|
395
415
|
const forceOnly = idleCoordinators.length === 0;
|
|
396
416
|
|
|
417
|
+
// ── modal-blocked short-circuit (MUST precede the drain) ──────────────────
|
|
418
|
+
// When the ONLY coordinators for this mesh are modal-parked (no idle, no plain
|
|
419
|
+
// generating target), there is nowhere safe to deliver. We skip-and-requeue:
|
|
420
|
+
// by NOT draining we leave the events at drained=0 in the queue, so a later tick
|
|
421
|
+
// (once the modal is resolved and the coordinator returns to idle/generating)
|
|
422
|
+
// delivers them. This short-circuit MUST run BEFORE drainPendingMeshCoordinatorEvents
|
|
423
|
+
// — the drain marks rows drained=1 atomically, which would lose the events for a
|
|
424
|
+
// coordinator that is only transiently blocked. (Note: generating is still
|
|
425
|
+
// force-injected via generatingCoordinators — we never block the deadlock-break.)
|
|
426
|
+
if (targetCoordinators.length === 0) {
|
|
427
|
+
if (modalParkedCoordinators.length > 0) {
|
|
428
|
+
LOG.info('MeshReconcile', `Reconcile skip → modal-parked: holding pending event(s) for mesh ${meshId} (${modalParkedCoordinators.length} coordinator(s) awaiting a modal answer; events left queued)`);
|
|
429
|
+
}
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
397
433
|
// O(1) guard: skip the drain entirely when the queue is empty.
|
|
398
434
|
if (store) {
|
|
399
435
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync } from 'fs';
|
|
2
2
|
import { dirname, join } from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { loadBetterSqlite3 } from '../system/load-better-sqlite3.js';
|
|
4
4
|
import { getLedgerDir } from './mesh-ledger.js';
|
|
5
5
|
import { nodeSatisfiesRequiredTags } from './mesh-work-queue.js';
|
|
6
6
|
import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
@@ -11,10 +11,7 @@ let DatabaseCtor: typeof BetterSqlite3 | undefined;
|
|
|
11
11
|
|
|
12
12
|
function loadDatabaseCtor(): typeof BetterSqlite3 {
|
|
13
13
|
if (DatabaseCtor) return DatabaseCtor;
|
|
14
|
-
|
|
15
|
-
? require
|
|
16
|
-
: createRequire(import.meta.url);
|
|
17
|
-
DatabaseCtor = runtimeRequire('better-sqlite3') as typeof BetterSqlite3;
|
|
14
|
+
DatabaseCtor = loadBetterSqlite3() as typeof BetterSqlite3;
|
|
18
15
|
return DatabaseCtor;
|
|
19
16
|
}
|
|
20
17
|
|
|
@@ -905,6 +905,37 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
905
905
|
this.adapter.updateRuntimeSettings?.(this.settings);
|
|
906
906
|
}
|
|
907
907
|
|
|
908
|
+
/**
|
|
909
|
+
* The resolved modal-park status of this session, or null when it is not
|
|
910
|
+
* parked on a modal awaiting a human answer. Mirrors the overlay logic in
|
|
911
|
+
* getState(): an active AskUserQuestion interactive prompt resolves to
|
|
912
|
+
* waiting_choice; otherwise the adapter's waiting_approval (tool consent)
|
|
913
|
+
* counts — UNLESS auto-approve will dismiss it, in which case the session is
|
|
914
|
+
* effectively generating and is NOT modal-parked. This is the single signal
|
|
915
|
+
* the mesh force-inject guard consults, and the same status string the
|
|
916
|
+
* reconcile loop reads off get_status_metadata. Lowercase literals only —
|
|
917
|
+
* the SessionStatus enum is forked across modules and waiting_choice is
|
|
918
|
+
* absent from some of them.
|
|
919
|
+
*/
|
|
920
|
+
resolveModalParkStatus(): 'waiting_choice' | 'waiting_approval' | null {
|
|
921
|
+
if (this.activeInteractivePrompt) return 'waiting_choice';
|
|
922
|
+
let adapterStatus: { status?: string };
|
|
923
|
+
try {
|
|
924
|
+
adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
925
|
+
} catch {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
if (adapterStatus.status === 'waiting_approval' && !this.shouldAutoApprove()) {
|
|
929
|
+
return 'waiting_approval';
|
|
930
|
+
}
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/** True when this session is parked on a modal awaiting a human answer. */
|
|
935
|
+
isModalParked(): boolean {
|
|
936
|
+
return this.resolveModalParkStatus() !== null;
|
|
937
|
+
}
|
|
938
|
+
|
|
908
939
|
onEvent(event: string, data?: any): void {
|
|
909
940
|
if (event === 'send_message') {
|
|
910
941
|
const input = normalizeInputEnvelope(data);
|
|
@@ -917,6 +948,20 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
917
948
|
// Without it the message is queued and only flushed on the coordinator's
|
|
918
949
|
// own idle transition — which never happens until it receives the message.
|
|
919
950
|
const force = data?.force === true;
|
|
951
|
+
// Modal guard: a force-inject still writes raw keystrokes into the PTY,
|
|
952
|
+
// bypassing the busy send-guard. If the coordinator is parked on a
|
|
953
|
+
// harness modal (claude-cli AskUserQuestion → waiting_choice, or a
|
|
954
|
+
// tool-consent waiting_approval), those keystrokes are consumed by the
|
|
955
|
+
// modal's key handler and silently select a choice the user never made
|
|
956
|
+
// (data corruption). Hold the force-inject in that narrow window —
|
|
957
|
+
// the event stays queued and the reconcile loop redelivers it on the
|
|
958
|
+
// next tick once the modal is resolved. We ONLY hold for the two modal
|
|
959
|
+
// states; generating is still force-injected (that is the deadlock the
|
|
960
|
+
// force path exists to break — see mesh-events-coordinator).
|
|
961
|
+
if (force && this.isModalParked()) {
|
|
962
|
+
LOG.info('CLI', `[${this.type}] force send_message held — coordinator parked on modal (${this.resolveModalParkStatus()})`);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
920
965
|
void this.adapter.sendMessage(promptText, force ? { force: true } : {}).catch((e: any) => {
|
|
921
966
|
LOG.warn('CLI', `[${this.type}] send_message failed: ${e?.message || e}`);
|
|
922
967
|
});
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import * as fs from 'node:fs';
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import * as os from 'node:os';
|
|
22
|
+
import { loadBetterSqlite3 } from '../../system/load-better-sqlite3.js';
|
|
22
23
|
|
|
23
24
|
export interface NativeHistoryMessage {
|
|
24
25
|
id: string;
|
|
@@ -61,8 +62,7 @@ function statMtimeMs(p: string): number {
|
|
|
61
62
|
function openDb(): any | null {
|
|
62
63
|
if (!fs.existsSync(HERMES_STATE_DB)) return null;
|
|
63
64
|
try {
|
|
64
|
-
|
|
65
|
-
const Database = require('better-sqlite3');
|
|
65
|
+
const Database = loadBetterSqlite3();
|
|
66
66
|
return new Database(HERMES_STATE_DB, { readonly: true, fileMustExist: true });
|
|
67
67
|
} catch {
|
|
68
68
|
return null;
|
|
@@ -22,6 +22,7 @@ import * as fs from 'node:fs';
|
|
|
22
22
|
import * as os from 'node:os';
|
|
23
23
|
import * as path from 'node:path';
|
|
24
24
|
import { LOG } from '../../logging/logger.js';
|
|
25
|
+
import { loadBetterSqlite3 } from '../../system/load-better-sqlite3.js';
|
|
25
26
|
import type {
|
|
26
27
|
NativeHistoryConfig,
|
|
27
28
|
NativeHistoryJsonlSource,
|
|
@@ -253,8 +254,7 @@ function executeSqlite(src: NativeHistorySqliteSource, input: NativeHistoryInput
|
|
|
253
254
|
|
|
254
255
|
let Database: any;
|
|
255
256
|
try {
|
|
256
|
-
|
|
257
|
-
Database = require('better-sqlite3');
|
|
257
|
+
Database = loadBetterSqlite3();
|
|
258
258
|
} catch { return null; }
|
|
259
259
|
|
|
260
260
|
let db: any;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
3
|
+
|
|
4
|
+
let cached: typeof BetterSqlite3 | undefined;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Load the `better-sqlite3` constructor in a way that survives every runtime the
|
|
8
|
+
* daemon ships in.
|
|
9
|
+
*
|
|
10
|
+
* The naive `typeof require === 'function' ? require : createRequire(import.meta.url)`
|
|
11
|
+
* is unsafe inside the daemon-cloud bundle: esbuild emits CJS output but, for any
|
|
12
|
+
* chunk that touches `import.meta.url`, it shims the local `require` with a stub
|
|
13
|
+
* that THROWS `Dynamic require of "..." is not supported`. That stub is still
|
|
14
|
+
* `typeof === 'function'`, so the ternary picks it and the call throws — it never
|
|
15
|
+
* reaches the `createRequire` fallback. The failure surfaced as `mesh_send_task`
|
|
16
|
+
* crashing the whole tool call instead of gracefully degrading.
|
|
17
|
+
*
|
|
18
|
+
* The robust approach is to ATTEMPT the real `require` and CATCH the esbuild stub's
|
|
19
|
+
* throw, then fall back to `createRequire`. We try multiple resolution bases so the
|
|
20
|
+
* load works whether the code runs as:
|
|
21
|
+
* - a genuine CJS module (bare `require` works),
|
|
22
|
+
* - an esbuild CJS bundle with the throwing `require` shim (createRequire(import.meta.url)),
|
|
23
|
+
* - a pure ESM module where `require` is undefined (createRequire(import.meta.url)).
|
|
24
|
+
*/
|
|
25
|
+
export function loadBetterSqlite3(): typeof BetterSqlite3 {
|
|
26
|
+
if (cached) return cached;
|
|
27
|
+
|
|
28
|
+
const errors: unknown[] = [];
|
|
29
|
+
|
|
30
|
+
// 1) Real CJS require, when present and not the esbuild throwing shim.
|
|
31
|
+
if (typeof require === 'function') {
|
|
32
|
+
try {
|
|
33
|
+
cached = require('better-sqlite3') as typeof BetterSqlite3;
|
|
34
|
+
return cached;
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Either the esbuild "Dynamic require is not supported" shim, or a
|
|
37
|
+
// genuine resolution failure. Fall through to createRequire.
|
|
38
|
+
errors.push(e);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2) createRequire anchored at this module's URL (works in ESM and in esbuild
|
|
43
|
+
// CJS bundles, where import.meta.url is rewritten to a usable value).
|
|
44
|
+
try {
|
|
45
|
+
const metaUrl = typeof import.meta?.url === 'string' ? import.meta.url : undefined;
|
|
46
|
+
if (metaUrl) {
|
|
47
|
+
cached = createRequire(metaUrl)('better-sqlite3') as typeof BetterSqlite3;
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
errors.push(e);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3) Last resort: createRequire anchored at the current working directory.
|
|
55
|
+
try {
|
|
56
|
+
cached = createRequire(`${process.cwd()}/__adhdev_better_sqlite3_loader__.js`)(
|
|
57
|
+
'better-sqlite3',
|
|
58
|
+
) as typeof BetterSqlite3;
|
|
59
|
+
return cached;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
errors.push(e);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const detail = errors
|
|
65
|
+
.map((e) => (e instanceof Error ? e.message : String(e)))
|
|
66
|
+
.join('; ');
|
|
67
|
+
throw new Error(`Failed to load better-sqlite3: ${detail}`);
|
|
68
|
+
}
|