@duetso/agent 0.1.98 → 0.1.101
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/package.json +1 -1
- package/dist/src/memory/context-pack.d.ts +2 -2
- package/dist/src/memory/context-pack.d.ts.map +1 -1
- package/dist/src/memory/context-pack.js +21 -16
- package/dist/src/memory/context-pack.js.map +1 -1
- package/dist/src/memory/embedding-worker.d.ts +8 -3
- package/dist/src/memory/embedding-worker.d.ts.map +1 -1
- package/dist/src/memory/embedding-worker.js +42 -28
- package/dist/src/memory/embedding-worker.js.map +1 -1
- package/dist/src/memory/observational.d.ts +8 -3
- package/dist/src/memory/observational.d.ts.map +1 -1
- package/dist/src/memory/observational.js +10 -10
- package/dist/src/memory/observational.js.map +1 -1
- package/dist/src/memory/pglite.d.ts +57 -17
- package/dist/src/memory/pglite.d.ts.map +1 -1
- package/dist/src/memory/pglite.js +153 -140
- package/dist/src/memory/pglite.js.map +1 -1
- package/dist/src/memory/recall.d.ts +9 -2
- package/dist/src/memory/recall.d.ts.map +1 -1
- package/dist/src/memory/recall.js +23 -20
- package/dist/src/memory/recall.js.map +1 -1
- package/dist/src/memory/session.d.ts +78 -0
- package/dist/src/memory/session.d.ts.map +1 -0
- package/dist/src/memory/session.js +198 -0
- package/dist/src/memory/session.js.map +1 -0
- package/dist/src/memory/storage.d.ts +29 -15
- package/dist/src/memory/storage.d.ts.map +1 -1
- package/dist/src/memory/storage.js +80 -54
- package/dist/src/memory/storage.js.map +1 -1
- package/dist/src/turn-runner/tools.d.ts +8 -6
- package/dist/src/turn-runner/tools.d.ts.map +1 -1
- package/dist/src/turn-runner/tools.js +3 -3
- package/dist/src/turn-runner/tools.js.map +1 -1
- package/dist/src/turn-runner/turn-runner.d.ts +17 -6
- package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
- package/dist/src/turn-runner/turn-runner.js +41 -13
- package/dist/src/turn-runner/turn-runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -18,6 +18,23 @@ export interface OpenPGliteOptions {
|
|
|
18
18
|
cause: unknown;
|
|
19
19
|
}) => void;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when the cross-process open-lock could not be acquired within
|
|
23
|
+
* the caller's wait budget. `MemorySession` catches this and degrades
|
|
24
|
+
* gracefully (skips the op, warns once) so a second duet CLI does not
|
|
25
|
+
* crash when a long-running first CLI holds the memory db.
|
|
26
|
+
*/
|
|
27
|
+
export declare class MemoryLockTimeoutError extends Error {
|
|
28
|
+
readonly dataDir: string;
|
|
29
|
+
readonly holderPid: number;
|
|
30
|
+
readonly budgetMs: number;
|
|
31
|
+
constructor(dataDir: string, holderPid: number, budgetMs: number);
|
|
32
|
+
}
|
|
33
|
+
export type TryAcquireOpenLockResult = {
|
|
34
|
+
lockPath: string;
|
|
35
|
+
} | {
|
|
36
|
+
holderPid: number;
|
|
37
|
+
};
|
|
21
38
|
/**
|
|
22
39
|
* Opens a PGlite database at `path`, recovering from three failure modes that
|
|
23
40
|
* would otherwise leave the agent unable to start:
|
|
@@ -29,24 +46,33 @@ export interface OpenPGliteOptions {
|
|
|
29
46
|
* by running the init hook as a probe. The bad directory is renamed
|
|
30
47
|
* to `<path>.corrupted-<iso-timestamp>` so the user can recover it
|
|
31
48
|
* later, and a fresh database is opened in its place.
|
|
32
|
-
* 3.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* an in-process refcounted handle plus an `O_EXCL` lock file scoped
|
|
36
|
-
* to the dataDir.
|
|
49
|
+
* 3. A cross-process open-lock so two duet CLIs cannot both call
|
|
50
|
+
* `PGlite.create` on the same fresh dataDir and corrupt each
|
|
51
|
+
* other's migrations.
|
|
37
52
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
53
|
+
* Throws synchronously-via-reject if the lock is held by a live foreign
|
|
54
|
+
* process. Callers that need to wait or degrade gracefully should use
|
|
55
|
+
* `MemorySession` instead, which polls `tryAcquireOpenLock`.
|
|
41
56
|
*/
|
|
42
57
|
export declare function openPGlite(path: string, options?: OpenPGliteOptions): Promise<PGlite>;
|
|
58
|
+
/**
|
|
59
|
+
* Open the database assuming the caller already holds `lockPath` for
|
|
60
|
+
* the dataDir. On a quarantine recovery the directory is renamed
|
|
61
|
+
* aside, the old lockPath disappears with it, and this returns the
|
|
62
|
+
* freshly-reacquired lockPath. `MemorySession` uses this so its
|
|
63
|
+
* poll-acquire loop owns the lock lifecycle end-to-end.
|
|
64
|
+
*/
|
|
65
|
+
export declare function openPGliteHoldingLock(path: string, options: OpenPGliteOptions, lockPath: string): Promise<{
|
|
66
|
+
db: PGlite;
|
|
67
|
+
lockPath: string;
|
|
68
|
+
}>;
|
|
43
69
|
/**
|
|
44
70
|
* Open a managed PGlite handle without running migrations or quarantining
|
|
45
71
|
* on read failure. Used by the recovery tooling to read rows out of a
|
|
46
72
|
* previously-quarantined `memory.db.corrupted-*` directory.
|
|
47
73
|
*
|
|
48
|
-
* Does not participate in the
|
|
49
|
-
*
|
|
74
|
+
* Does not participate in the open-lock machinery; the caller owns the
|
|
75
|
+
* returned instance and is responsible for `close()`.
|
|
50
76
|
*/
|
|
51
77
|
export declare function openForRecovery(path: string): Promise<PGlite>;
|
|
52
78
|
/**
|
|
@@ -55,19 +81,33 @@ export declare function openForRecovery(path: string): Promise<PGlite>;
|
|
|
55
81
|
*/
|
|
56
82
|
export declare function quarantineDataDirectory(path: string): string;
|
|
57
83
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
* Try once to acquire the cross-process open-lock at
|
|
85
|
+
* `<dataDir>/.duet-open.lock`. Returns `{ lockPath }` on success
|
|
86
|
+
* (including after a stale-pid takeover) or `{ holderPid }` when a live
|
|
87
|
+
* foreign process still holds the lock. Never throws on contention so
|
|
88
|
+
* callers can poll and retry without try/catch.
|
|
89
|
+
*/
|
|
90
|
+
export declare function tryAcquireOpenLock(dataDir: string): TryAcquireOpenLockResult;
|
|
91
|
+
/**
|
|
92
|
+
* Acquire the cross-process open-lock, throwing if a live foreign
|
|
93
|
+
* process holds it. Used by `openPGlite` for the fast/strict path;
|
|
94
|
+
* polling callers should use `tryAcquireOpenLock` instead.
|
|
65
95
|
*/
|
|
66
96
|
export declare function acquireOpenLock(dataDir: string): string;
|
|
97
|
+
/**
|
|
98
|
+
* Release a lock previously returned by `tryAcquireOpenLock` /
|
|
99
|
+
* `acquireOpenLock`. Idempotent and safe to call if the file has
|
|
100
|
+
* already disappeared.
|
|
101
|
+
*/
|
|
102
|
+
export declare function releaseOpenLock(lockPath: string): void;
|
|
67
103
|
/**
|
|
68
104
|
* If `dataDir` contains a `postmaster.pid` whose PID is not a live process,
|
|
69
105
|
* remove the lock so PGlite can start. If the PID is alive, throw with a clear
|
|
70
106
|
* message rather than letting PGlite abort opaquely. Exported for testing.
|
|
71
107
|
*/
|
|
72
108
|
export declare function clearStalePostmasterLock(dataDir: string): void;
|
|
109
|
+
export declare const __testing: {
|
|
110
|
+
heldLockPaths: Set<string>;
|
|
111
|
+
resolvePath: (...paths: string[]) => string;
|
|
112
|
+
};
|
|
73
113
|
//# sourceMappingURL=pglite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pglite.d.ts","sourceRoot":"","sources":["../../../src/memory/pglite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AA+B9C,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CACpE;
|
|
1
|
+
{"version":3,"file":"pglite.d.ts","sourceRoot":"","sources":["../../../src/memory/pglite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AA+B9C,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CACpE;AAED;;;;;GAKG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAUjE;AAED,MAAM,MAAM,wBAAwB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAyBpF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAU/F;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B3C;AAiDD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,wBAAwB,CA8C5E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOvD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOtD;AAkBD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAkB9D;AA+BD,eAAO,MAAM,SAAS;;;CAGrB,CAAC"}
|
|
@@ -17,24 +17,44 @@ const MEMORY_EXTENSIONS = { vector };
|
|
|
17
17
|
* processes only proceed past O_EXCL if that pid is no longer alive.
|
|
18
18
|
*/
|
|
19
19
|
const OPEN_LOCK_FILE = ".duet-open.lock";
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Thrown when the cross-process open-lock could not be acquired within
|
|
22
|
+
* the caller's wait budget. `MemorySession` catches this and degrades
|
|
23
|
+
* gracefully (skips the op, warns once) so a second duet CLI does not
|
|
24
|
+
* crash when a long-running first CLI holds the memory db.
|
|
25
|
+
*/
|
|
26
|
+
export class MemoryLockTimeoutError extends Error {
|
|
27
|
+
dataDir;
|
|
28
|
+
holderPid;
|
|
29
|
+
budgetMs;
|
|
30
|
+
constructor(dataDir, holderPid, budgetMs) {
|
|
31
|
+
super(`Timed out after ${budgetMs}ms waiting for the memory db open-lock at ${dataDir} ` +
|
|
32
|
+
`(held by pid ${holderPid}).`);
|
|
33
|
+
this.name = "MemoryLockTimeoutError";
|
|
34
|
+
this.dataDir = dataDir;
|
|
35
|
+
this.holderPid = holderPid;
|
|
36
|
+
this.budgetMs = budgetMs;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Process-global registry of open-lock files currently held by this
|
|
41
|
+
* process. Used by the exit handler to drop locks on crash so the next
|
|
42
|
+
* launch (and concurrent duet CLIs) are not blocked behind a stale
|
|
43
|
+
* lock pointing at a no-longer-living pid.
|
|
44
|
+
*/
|
|
45
|
+
const heldLockPaths = new Set();
|
|
21
46
|
let exitHandlerInstalled = false;
|
|
22
47
|
function installExitCleanup() {
|
|
23
48
|
if (exitHandlerInstalled)
|
|
24
49
|
return;
|
|
25
50
|
exitHandlerInstalled = true;
|
|
26
|
-
// Last-chance cleanup: if the process exits before every caller's
|
|
27
|
-
// `close()` runs, drop any locks we still hold so the next launch
|
|
28
|
-
// can open the database without tripping the stale-lock check.
|
|
29
51
|
process.on("exit", () => {
|
|
30
|
-
for (const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Best-effort on exit; nothing to do if the file is already gone.
|
|
37
|
-
}
|
|
52
|
+
for (const lockPath of heldLockPaths) {
|
|
53
|
+
try {
|
|
54
|
+
unlinkSync(lockPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Best-effort on exit; nothing to do if the file is already gone.
|
|
38
58
|
}
|
|
39
59
|
}
|
|
40
60
|
});
|
|
@@ -50,118 +70,47 @@ function installExitCleanup() {
|
|
|
50
70
|
* by running the init hook as a probe. The bad directory is renamed
|
|
51
71
|
* to `<path>.corrupted-<iso-timestamp>` so the user can recover it
|
|
52
72
|
* later, and a fresh database is opened in its place.
|
|
53
|
-
* 3.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* an in-process refcounted handle plus an `O_EXCL` lock file scoped
|
|
57
|
-
* to the dataDir.
|
|
73
|
+
* 3. A cross-process open-lock so two duet CLIs cannot both call
|
|
74
|
+
* `PGlite.create` on the same fresh dataDir and corrupt each
|
|
75
|
+
* other's migrations.
|
|
58
76
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
77
|
+
* Throws synchronously-via-reject if the lock is held by a live foreign
|
|
78
|
+
* process. Callers that need to wait or degrade gracefully should use
|
|
79
|
+
* `MemorySession` instead, which polls `tryAcquireOpenLock`.
|
|
62
80
|
*/
|
|
63
81
|
export async function openPGlite(path, options = {}) {
|
|
64
82
|
installExitCleanup();
|
|
65
|
-
const
|
|
66
|
-
const existing = sharedHandles.get(key);
|
|
67
|
-
if (existing) {
|
|
68
|
-
existing.refs++;
|
|
69
|
-
try {
|
|
70
|
-
return await existing.promise;
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
decrementAndMaybeDelete(key);
|
|
74
|
-
throw error;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const entry = {
|
|
78
|
-
promise: undefined,
|
|
79
|
-
refs: 1,
|
|
80
|
-
};
|
|
81
|
-
entry.promise = openWithLock(path, options, entry).then((db) => installRefcountedClose(db, key, entry));
|
|
82
|
-
sharedHandles.set(key, entry);
|
|
83
|
+
const lockPath = acquireOpenLock(path);
|
|
83
84
|
try {
|
|
84
|
-
|
|
85
|
+
const opened = await openPGliteHoldingLock(path, options, lockPath);
|
|
86
|
+
return installLockReleasingClose(opened.db, opened.lockPath);
|
|
85
87
|
}
|
|
86
88
|
catch (error) {
|
|
87
|
-
|
|
89
|
+
releaseOpenLock(lockPath);
|
|
88
90
|
throw error;
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* the
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* keeps private-field access working.
|
|
94
|
+
* Open the database assuming the caller already holds `lockPath` for
|
|
95
|
+
* the dataDir. On a quarantine recovery the directory is renamed
|
|
96
|
+
* aside, the old lockPath disappears with it, and this returns the
|
|
97
|
+
* freshly-reacquired lockPath. `MemorySession` uses this so its
|
|
98
|
+
* poll-acquire loop owns the lock lifecycle end-to-end.
|
|
98
99
|
*/
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
db.close = async () => {
|
|
102
|
-
const current = sharedHandles.get(key);
|
|
103
|
-
if (!current || current.refs <= 0)
|
|
104
|
-
return;
|
|
105
|
-
current.refs--;
|
|
106
|
-
if (current.refs > 0)
|
|
107
|
-
return;
|
|
108
|
-
sharedHandles.delete(key);
|
|
109
|
-
try {
|
|
110
|
-
await originalClose();
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
if (entry.lockPath) {
|
|
114
|
-
bestEffortUnlink(entry.lockPath);
|
|
115
|
-
entry.lockPath = undefined;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
return db;
|
|
120
|
-
}
|
|
121
|
-
function bestEffortUnlink(path) {
|
|
122
|
-
try {
|
|
123
|
-
unlinkSync(path);
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
if (error.code !== "ENOENT")
|
|
127
|
-
throw error;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function decrementAndMaybeDelete(key) {
|
|
131
|
-
const entry = sharedHandles.get(key);
|
|
132
|
-
if (!entry)
|
|
133
|
-
return;
|
|
134
|
-
entry.refs--;
|
|
135
|
-
if (entry.refs > 0)
|
|
136
|
-
return;
|
|
137
|
-
sharedHandles.delete(key);
|
|
138
|
-
if (entry.lockPath) {
|
|
139
|
-
try {
|
|
140
|
-
unlinkSync(entry.lockPath);
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
// Lock may never have been created if we threw before acquireOpenLock returned.
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async function openWithLock(path, options, entry) {
|
|
100
|
+
export async function openPGliteHoldingLock(path, options, lockPath) {
|
|
101
|
+
installExitCleanup();
|
|
148
102
|
mkdirSync(dirname(path), { recursive: true });
|
|
149
103
|
clearStalePostmasterLock(path);
|
|
150
|
-
entry.lockPath = acquireOpenLock(path);
|
|
151
104
|
try {
|
|
152
105
|
const db = await openAndProbe(path, options.init);
|
|
153
|
-
|
|
154
|
-
return db;
|
|
106
|
+
return { db, lockPath };
|
|
155
107
|
}
|
|
156
108
|
catch (error) {
|
|
157
109
|
if (!isExistingDirectory(path))
|
|
158
110
|
throw error;
|
|
159
111
|
// Release the lock before renaming the directory aside — the lock
|
|
160
112
|
// file lives inside that directory and would be moved with it.
|
|
161
|
-
|
|
162
|
-
bestEffortUnlink(entry.lockPath);
|
|
163
|
-
entry.lockPath = undefined;
|
|
164
|
-
}
|
|
113
|
+
releaseOpenLock(lockPath);
|
|
165
114
|
const backupPath = quarantineDataDirectory(path);
|
|
166
115
|
if (options.onRecover) {
|
|
167
116
|
options.onRecover({ backupPath, cause: error });
|
|
@@ -171,12 +120,34 @@ async function openWithLock(path, options, entry) {
|
|
|
171
120
|
console.warn(`[duet] memory database at ${path} could not be opened (${reason}). ` +
|
|
172
121
|
`Moved aside to ${backupPath} and starting fresh.`);
|
|
173
122
|
}
|
|
174
|
-
|
|
123
|
+
const fresh = acquireOpenLock(path);
|
|
175
124
|
const db = await openAndProbe(path, options.init);
|
|
176
|
-
|
|
177
|
-
return db;
|
|
125
|
+
return { db, lockPath: fresh };
|
|
178
126
|
}
|
|
179
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Wrap `db.close` so the cross-process lock is released alongside the
|
|
130
|
+
* handle. PGlite uses private class fields internally, so a Proxy
|
|
131
|
+
* around the instance breaks every method that touches one — method
|
|
132
|
+
* replacement preserves identity and keeps private-field access
|
|
133
|
+
* working.
|
|
134
|
+
*/
|
|
135
|
+
function installLockReleasingClose(db, lockPath) {
|
|
136
|
+
const originalClose = db.close.bind(db);
|
|
137
|
+
let released = false;
|
|
138
|
+
db.close = async () => {
|
|
139
|
+
if (released)
|
|
140
|
+
return;
|
|
141
|
+
released = true;
|
|
142
|
+
try {
|
|
143
|
+
await originalClose();
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
releaseOpenLock(lockPath);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
return db;
|
|
150
|
+
}
|
|
180
151
|
async function openAndProbe(path, init) {
|
|
181
152
|
// PGlite.create is the only API path that lets us register loadable
|
|
182
153
|
// extensions at construction time — the bare `new PGlite(path)` form
|
|
@@ -206,8 +177,8 @@ async function openAndProbe(path, init) {
|
|
|
206
177
|
* on read failure. Used by the recovery tooling to read rows out of a
|
|
207
178
|
* previously-quarantined `memory.db.corrupted-*` directory.
|
|
208
179
|
*
|
|
209
|
-
* Does not participate in the
|
|
210
|
-
*
|
|
180
|
+
* Does not participate in the open-lock machinery; the caller owns the
|
|
181
|
+
* returned instance and is responsible for `close()`.
|
|
211
182
|
*/
|
|
212
183
|
export async function openForRecovery(path) {
|
|
213
184
|
clearStalePostmasterLock(path);
|
|
@@ -224,38 +195,22 @@ export function quarantineDataDirectory(path) {
|
|
|
224
195
|
return backup;
|
|
225
196
|
}
|
|
226
197
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* Throws with a clear message when the lock is held by a live foreign
|
|
233
|
-
* process so the caller fails fast instead of double-opening the dataDir.
|
|
198
|
+
* Try once to acquire the cross-process open-lock at
|
|
199
|
+
* `<dataDir>/.duet-open.lock`. Returns `{ lockPath }` on success
|
|
200
|
+
* (including after a stale-pid takeover) or `{ holderPid }` when a live
|
|
201
|
+
* foreign process still holds the lock. Never throws on contention so
|
|
202
|
+
* callers can poll and retry without try/catch.
|
|
234
203
|
*/
|
|
235
|
-
export function
|
|
204
|
+
export function tryAcquireOpenLock(dataDir) {
|
|
236
205
|
mkdirSync(dataDir, { recursive: true });
|
|
237
206
|
const lockPath = join(dataDir, OPEN_LOCK_FILE);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
fd = openSync(lockPath, "wx");
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
if (error.code === "EEXIST")
|
|
247
|
-
return undefined;
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
writeSync(fd, `${process.pid}\n`);
|
|
252
|
-
}
|
|
253
|
-
finally {
|
|
254
|
-
closeSync(fd);
|
|
207
|
+
const created = createLockFile(lockPath);
|
|
208
|
+
if (created) {
|
|
209
|
+
heldLockPaths.add(created);
|
|
210
|
+
return { lockPath: created };
|
|
255
211
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
function stealStaleLock(dataDir, lockPath) {
|
|
212
|
+
// Lock file existed. If a live foreign process owns it, report back
|
|
213
|
+
// without taking it; otherwise treat it as stale and steal it.
|
|
259
214
|
const contents = tryReadFile(lockPath) ?? "";
|
|
260
215
|
const firstLine = contents.split("\n", 1)[0]?.trim() ?? "";
|
|
261
216
|
const holderPid = Number.parseInt(firstLine, 10);
|
|
@@ -263,8 +218,7 @@ function stealStaleLock(dataDir, lockPath) {
|
|
|
263
218
|
holderPid > 0 &&
|
|
264
219
|
holderPid !== process.pid &&
|
|
265
220
|
isProcessAlive(holderPid)) {
|
|
266
|
-
|
|
267
|
-
"Stop that process before opening the database.");
|
|
221
|
+
return { holderPid };
|
|
268
222
|
}
|
|
269
223
|
// Stale (crashed prior run) or our own pid from a partially-cleaned
|
|
270
224
|
// failed open. Replace atomically — unlink-then-create-with-O_EXCL.
|
|
@@ -275,11 +229,64 @@ function stealStaleLock(dataDir, lockPath) {
|
|
|
275
229
|
if (error.code !== "ENOENT")
|
|
276
230
|
throw error;
|
|
277
231
|
}
|
|
278
|
-
const
|
|
279
|
-
if (!
|
|
232
|
+
const recreated = createLockFile(lockPath);
|
|
233
|
+
if (!recreated) {
|
|
234
|
+
// Another opener won the race after our stale-lock takeover. Report
|
|
235
|
+
// its pid (if live) so the caller can poll again.
|
|
236
|
+
const afterContents = tryReadFile(lockPath) ?? "";
|
|
237
|
+
const after = Number.parseInt(afterContents.split("\n", 1)[0]?.trim() ?? "", 10);
|
|
238
|
+
if (Number.isFinite(after) && after > 0 && after !== process.pid && isProcessAlive(after)) {
|
|
239
|
+
return { holderPid: after };
|
|
240
|
+
}
|
|
280
241
|
throw new Error(`Failed to acquire open lock at ${lockPath}: another opener won the race after the stale-lock takeover.`);
|
|
281
242
|
}
|
|
282
|
-
|
|
243
|
+
heldLockPaths.add(recreated);
|
|
244
|
+
return { lockPath: recreated };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Acquire the cross-process open-lock, throwing if a live foreign
|
|
248
|
+
* process holds it. Used by `openPGlite` for the fast/strict path;
|
|
249
|
+
* polling callers should use `tryAcquireOpenLock` instead.
|
|
250
|
+
*/
|
|
251
|
+
export function acquireOpenLock(dataDir) {
|
|
252
|
+
const result = tryAcquireOpenLock(dataDir);
|
|
253
|
+
if ("lockPath" in result)
|
|
254
|
+
return result.lockPath;
|
|
255
|
+
throw new Error(`PGlite data directory ${dataDir} is locked by another duet process (pid ${result.holderPid}). ` +
|
|
256
|
+
"Stop that process before opening the database.");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Release a lock previously returned by `tryAcquireOpenLock` /
|
|
260
|
+
* `acquireOpenLock`. Idempotent and safe to call if the file has
|
|
261
|
+
* already disappeared.
|
|
262
|
+
*/
|
|
263
|
+
export function releaseOpenLock(lockPath) {
|
|
264
|
+
heldLockPaths.delete(lockPath);
|
|
265
|
+
try {
|
|
266
|
+
unlinkSync(lockPath);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
if (error.code !== "ENOENT")
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function createLockFile(lockPath) {
|
|
274
|
+
let fd;
|
|
275
|
+
try {
|
|
276
|
+
fd = openSync(lockPath, "wx");
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
if (error.code === "EEXIST")
|
|
280
|
+
return undefined;
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
writeSync(fd, `${process.pid}\n`);
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
closeSync(fd);
|
|
288
|
+
}
|
|
289
|
+
return lockPath;
|
|
283
290
|
}
|
|
284
291
|
/**
|
|
285
292
|
* If `dataDir` contains a `postmaster.pid` whose PID is not a live process,
|
|
@@ -330,4 +337,10 @@ function isProcessAlive(pid) {
|
|
|
330
337
|
return error.code === "EPERM";
|
|
331
338
|
}
|
|
332
339
|
}
|
|
340
|
+
// Internal: lets tests reach into the held-lock registry to assert
|
|
341
|
+
// exit cleanup behavior without exporting it on the public surface.
|
|
342
|
+
export const __testing = {
|
|
343
|
+
heldLockPaths,
|
|
344
|
+
resolvePath,
|
|
345
|
+
};
|
|
333
346
|
//# sourceMappingURL=pglite.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pglite.js","sourceRoot":"","sources":["../../../src/memory/pglite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACrD,OAAO,EACL,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,UAAU,EACV,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,EAAE,MAAM,EAAW,CAAC;AAE9C;;;;GAIG;AACH,MAAM,cAAc,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"pglite.js","sourceRoot":"","sources":["../../../src/memory/pglite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACrD,OAAO,EACL,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,UAAU,EACV,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,EAAE,MAAM,EAAW,CAAC;AAE9C;;;;GAIG;AACH,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAmBzC;;;;;GAKG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,OAAO,CAAS;IAChB,SAAS,CAAS;IAClB,QAAQ,CAAS;IAC1B,YAAY,OAAe,EAAE,SAAiB,EAAE,QAAgB;QAC9D,KAAK,CACH,mBAAmB,QAAQ,6CAA6C,OAAO,GAAG;YAChF,gBAAgB,SAAS,IAAI,CAChC,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAID;;;;;GAKG;AACH,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;AAExC,IAAI,oBAAoB,GAAG,KAAK,CAAC;AACjC,SAAS,kBAAkB;IACzB,IAAI,oBAAoB;QAAE,OAAO;IACjC,oBAAoB,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACtB,KAAK,MAAM,QAAQ,IAAI,aAAa,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,UAAU,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,kEAAkE;YACpE,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,UAA6B,EAAE;IAC5E,kBAAkB,EAAE,CAAC;IACrB,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QACpE,OAAO,yBAAyB,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC1B,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAY,EACZ,OAA0B,EAC1B,QAAgB;IAEhB,kBAAkB,EAAE,CAAC;IACrB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAAE,MAAM,KAAK,CAAC;QAE5C,kEAAkE;QAClE,+DAA+D;QAC/D,eAAe,CAAC,QAAQ,CAAC,CAAC;QAE1B,MAAM,UAAU,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,OAAO,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACpF,OAAO,CAAC,IAAI,CACV,6BAA6B,IAAI,yBAAyB,MAAM,KAAK;gBACnE,kBAAkB,UAAU,sBAAsB,CACrD,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,yBAAyB,CAAC,EAAU,EAAE,QAAgB;IAC7D,MAAM,aAAa,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,EAAE,CAAC,KAAK,GAAG,KAAK,IAAI,EAAE;QACpB,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,aAAa,EAAE,CAAC;QACxB,CAAC;gBAAS,CAAC;YACT,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC;IACF,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,IAAY,EACZ,IAAiD;IAEjD,oEAAoE;IACpE,qEAAqE;IACrE,qEAAqE;IACrE,iEAAiE;IACjE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC,CAAC;IACjF,IAAI,CAAC;QACH,IAAI,IAAI;YAAE,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,0EAA0E;QAC1E,uEAAuE;QACvE,+DAA+D;QAC/D,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,GAAG,IAAI,cAAc,KAAK,EAAE,CAAC;IAC5C,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,OAAO,EAAE,CAAC;QACZ,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC/B,CAAC;IAED,oEAAoE;IACpE,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC7C,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACjD,IACE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC1B,SAAS,GAAG,CAAC;QACb,SAAS,KAAK,OAAO,CAAC,GAAG;QACzB,cAAc,CAAC,SAAS,CAAC,EACzB,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,CAAC;IACvB,CAAC;IAED,oEAAoE;IACpE,oEAAoE;IACpE,IAAI,CAAC;QACH,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;IACtE,CAAC;IACD,MAAM,SAAS,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,oEAAoE;QACpE,kDAAkD;QAClD,MAAM,aAAa,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACjF,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,KAAK,OAAO,CAAC,GAAG,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1F,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kCAAkC,QAAQ,8DAA8D,CACzG,CAAC;IACJ,CAAC;IACD,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,UAAU,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC;IACjD,MAAM,IAAI,KAAK,CACb,yBAAyB,OAAO,2CAA2C,MAAM,CAAC,SAAS,KAAK;QAC9F,gDAAgD,CACnD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;IACtE,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,IAAI,EAAU,CAAC;IACf,IAAI,CAAC;QACH,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACzE,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,SAAS,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC;QAAE,OAAO;IAE1C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO;IAEnC,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAE3C,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,yBAAyB,OAAO,wCAAwC,GAAG,KAAK;YAC9E,gDAAgD,CACnD,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,QAAQ,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACrE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACzE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,GAAW;IACjC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,oEAAoE;AACpE,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,aAAa;IACb,WAAW;CACZ,CAAC"}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import type { PGlite } from "@electric-sql/pglite";
|
|
2
1
|
import type { Observation } from "../types/memory.js";
|
|
3
2
|
import type { EmbedFn } from "./embedding.js";
|
|
3
|
+
import type { MemorySession } from "./session.js";
|
|
4
4
|
export type RecallScope = "session" | "global" | "all";
|
|
5
5
|
export interface RecallMemoryOptions {
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Memory session that owns the PGlite handle and cross-process lock.
|
|
8
|
+
* The recall call wraps every query in a single `withDb` so keyword,
|
|
9
|
+
* vector, and hydration all run against the same open. When the lock
|
|
10
|
+
* cannot be acquired, recall returns an empty result instead of
|
|
11
|
+
* throwing so the tool surface degrades gracefully.
|
|
12
|
+
*/
|
|
13
|
+
session: MemorySession;
|
|
7
14
|
/**
|
|
8
15
|
* Embedding callable. When omitted (or when it throws) the vector
|
|
9
16
|
* path is skipped silently and recall falls back to keyword-only.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recall.d.ts","sourceRoot":"","sources":["../../../src/memory/recall.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recall.d.ts","sourceRoot":"","sources":["../../../src/memory/recall.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAwC,MAAM,oBAAoB,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAkClD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,OAAO,EAAE,aAAa,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,0FAA0F;IAC1F,qBAAqB,EAAE,OAAO,CAAC;IAC/B,mGAAmG;IACnG,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoC5F;AAED,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAsGD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE,GAAG,MAAM,EAAE,CAqBzE"}
|
|
@@ -36,27 +36,30 @@ export async function recallMemory(options) {
|
|
|
36
36
|
if (scope === "session" && !options.sessionId) {
|
|
37
37
|
return { observations: [], vectorSearchAttempted: false, vectorSearchSucceeded: false };
|
|
38
38
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
const result = await options.session.withDb(async (db) => {
|
|
40
|
+
const keywordHits = await keywordSearch(db, options.query, scope, options.sessionId);
|
|
41
|
+
let vectorAttempted = false;
|
|
42
|
+
let vectorHits = [];
|
|
43
|
+
if (options.embed) {
|
|
44
|
+
vectorAttempted = true;
|
|
45
|
+
try {
|
|
46
|
+
vectorHits = await vectorSearch(db, options.embed, options.query, scope, options.sessionId);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Embedding unavailable, network blip, or empty index — drop
|
|
50
|
+
// back to keyword-only. Caller surfaces the degraded mode.
|
|
51
|
+
vectorHits = [];
|
|
52
|
+
}
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
54
|
+
const fusedIds = reciprocalRankFusion([keywordHits, vectorHits]).slice(0, limit);
|
|
55
|
+
const observations = await hydrate(db, fusedIds);
|
|
56
|
+
return {
|
|
57
|
+
observations,
|
|
58
|
+
vectorSearchAttempted: vectorAttempted,
|
|
59
|
+
vectorSearchSucceeded: vectorHits.length > 0,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
return result ?? { observations: [], vectorSearchAttempted: false, vectorSearchSucceeded: false };
|
|
60
63
|
}
|
|
61
64
|
async function keywordSearch(db, query, scope, sessionId) {
|
|
62
65
|
const trimmed = query.trim();
|