@duetso/agent 0.1.99 → 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.
Files changed (37) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/memory/context-pack.d.ts +2 -2
  3. package/dist/src/memory/context-pack.d.ts.map +1 -1
  4. package/dist/src/memory/context-pack.js +21 -16
  5. package/dist/src/memory/context-pack.js.map +1 -1
  6. package/dist/src/memory/embedding-worker.d.ts +8 -3
  7. package/dist/src/memory/embedding-worker.d.ts.map +1 -1
  8. package/dist/src/memory/embedding-worker.js +42 -28
  9. package/dist/src/memory/embedding-worker.js.map +1 -1
  10. package/dist/src/memory/observational.d.ts +8 -3
  11. package/dist/src/memory/observational.d.ts.map +1 -1
  12. package/dist/src/memory/observational.js +10 -10
  13. package/dist/src/memory/observational.js.map +1 -1
  14. package/dist/src/memory/pglite.d.ts +57 -17
  15. package/dist/src/memory/pglite.d.ts.map +1 -1
  16. package/dist/src/memory/pglite.js +153 -140
  17. package/dist/src/memory/pglite.js.map +1 -1
  18. package/dist/src/memory/recall.d.ts +9 -2
  19. package/dist/src/memory/recall.d.ts.map +1 -1
  20. package/dist/src/memory/recall.js +23 -20
  21. package/dist/src/memory/recall.js.map +1 -1
  22. package/dist/src/memory/session.d.ts +78 -0
  23. package/dist/src/memory/session.d.ts.map +1 -0
  24. package/dist/src/memory/session.js +198 -0
  25. package/dist/src/memory/session.js.map +1 -0
  26. package/dist/src/memory/storage.d.ts +29 -15
  27. package/dist/src/memory/storage.d.ts.map +1 -1
  28. package/dist/src/memory/storage.js +80 -54
  29. package/dist/src/memory/storage.js.map +1 -1
  30. package/dist/src/turn-runner/tools.d.ts +8 -6
  31. package/dist/src/turn-runner/tools.d.ts.map +1 -1
  32. package/dist/src/turn-runner/tools.js +3 -3
  33. package/dist/src/turn-runner/tools.js.map +1 -1
  34. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
  35. package/dist/src/turn-runner/turn-runner.js +14 -6
  36. package/dist/src/turn-runner/turn-runner.js.map +1 -1
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"recall.js","sourceRoot":"","sources":["../../../src/memory/recall.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"recall.js","sourceRoot":"","sources":["../../../src/memory/recall.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+ import { type OpenPGliteOptions } from "./pglite.js";
3
+ export interface MemorySessionOptions {
4
+ /** Absolute path to the PGlite data directory. */
5
+ path: string;
6
+ /**
7
+ * Passed through to `openPGliteHoldingLock` on each open — typically
8
+ * supplies `init` (migrations) and `onRecover` (quarantine surface).
9
+ */
10
+ openOptions: OpenPGliteOptions;
11
+ /**
12
+ * How long to keep the handle open after the last in-flight op
13
+ * resolves. A new `withDb` call within this window reuses the open
14
+ * handle and cancels the pending close. Defaults to 2s.
15
+ */
16
+ idleCloseMs?: number;
17
+ /**
18
+ * Wall-time budget for waiting on the cross-process lock before a
19
+ * single `withDb` call gives up. On exhaustion the call resolves to
20
+ * `undefined` and `onWarn` is invoked once. Defaults to 30s.
21
+ */
22
+ lockWaitBudgetMs?: number;
23
+ /**
24
+ * Invoked once when a `withDb` call is skipped because the lock
25
+ * could not be acquired within `lockWaitBudgetMs`. Lets the host
26
+ * surface a single user-visible warning instead of repeating per
27
+ * skipped op.
28
+ */
29
+ onWarn?: (message: string) => void;
30
+ }
31
+ /**
32
+ * Manages a single PGlite handle for one dataDir across many short
33
+ * bursts of work. Refcounted opens keep the handle (and the
34
+ * cross-process `.duet-open.lock`) alive while any `withDb` is
35
+ * in-flight; an idle timer releases both shortly after the last op
36
+ * resolves so a peer process can step in.
37
+ *
38
+ * Concurrent in-process `withDb` calls share one `PGlite.create`: the
39
+ * first call allocates `this.opening` and every other call awaits the
40
+ * same promise, so two opens cannot race into `init` and double-run
41
+ * migrations against a fresh dataDir.
42
+ */
43
+ export declare class MemorySession {
44
+ private readonly path;
45
+ private readonly openOptions;
46
+ private readonly idleCloseMs;
47
+ private readonly lockWaitBudgetMs;
48
+ private readonly onWarn?;
49
+ private db;
50
+ private lockPath;
51
+ private opening;
52
+ private refs;
53
+ private idleTimer;
54
+ private disposed;
55
+ private disposing;
56
+ constructor(opts: MemorySessionOptions);
57
+ /**
58
+ * Run `fn` against the (lazily-opened) memory database. Returns
59
+ * `undefined` without invoking `fn` when the session is disposed or
60
+ * the lock could not be acquired within the budget; otherwise
61
+ * returns whatever `fn` returns. Errors thrown inside `fn`
62
+ * propagate.
63
+ */
64
+ withDb<T>(fn: (db: PGlite) => Promise<T>): Promise<T | undefined>;
65
+ /**
66
+ * Wait for any in-flight `withDb` to drain, cancel a pending idle
67
+ * close, then close the handle and release the lock. Subsequent
68
+ * `withDb` calls resolve to `undefined`.
69
+ */
70
+ dispose(): Promise<void>;
71
+ private runDispose;
72
+ private ensureOpen;
73
+ private openWithPolling;
74
+ private scheduleIdleClose;
75
+ private cancelIdleClose;
76
+ private closeNow;
77
+ }
78
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/memory/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAEL,KAAK,iBAAiB,EAIvB,MAAM,aAAa,CAAC;AA4BrB,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,WAAW,EAAE,iBAAiB,CAAC;IAC/B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA4B;IAEpD,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,SAAS,CAA4C;IAC7D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAA4B;gBAEjC,IAAI,EAAE,oBAAoB;IAQtC;;;;;;OAMG;IACG,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA0BvE;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAMhB,UAAU;YAYV,UAAU;YAqBV,eAAe;IAwB7B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,eAAe;YAOT,QAAQ;CAevB"}
@@ -0,0 +1,198 @@
1
+ import { MemoryLockTimeoutError, openPGliteHoldingLock, releaseOpenLock, tryAcquireOpenLock, } from "./pglite.js";
2
+ /**
3
+ * Default time the PGlite handle stays open after the last `withDb`
4
+ * call resolves before it is closed and the cross-process lock is
5
+ * released. Keeps short bursts of writes (observer + reflector +
6
+ * embedding upserts in the same turn) on one open without making a
7
+ * sitting CLI permanently hold the lock against a second CLI.
8
+ */
9
+ const DEFAULT_IDLE_CLOSE_MS = 2_000;
10
+ /**
11
+ * Default total wall-time budget for waiting on the cross-process
12
+ * open-lock. Generous enough to outlast a moderate write burst from a
13
+ * concurrent duet CLI but bounded so a wedged peer cannot stall the
14
+ * caller's turn indefinitely; on exhaustion `withDb` resolves to
15
+ * `undefined` and the caller degrades gracefully.
16
+ */
17
+ const DEFAULT_LOCK_WAIT_BUDGET_MS = 30_000;
18
+ /**
19
+ * Exponential backoff schedule used while polling for the open-lock.
20
+ * Doubles from 50ms up to a 1s ceiling so a peer that releases the
21
+ * lock during an idle-close gap is picked up within ~50ms, while a
22
+ * long-running peer does not produce a poll storm.
23
+ */
24
+ const POLL_BACKOFF_MS = [50, 100, 200, 400, 800, 1000];
25
+ /**
26
+ * Manages a single PGlite handle for one dataDir across many short
27
+ * bursts of work. Refcounted opens keep the handle (and the
28
+ * cross-process `.duet-open.lock`) alive while any `withDb` is
29
+ * in-flight; an idle timer releases both shortly after the last op
30
+ * resolves so a peer process can step in.
31
+ *
32
+ * Concurrent in-process `withDb` calls share one `PGlite.create`: the
33
+ * first call allocates `this.opening` and every other call awaits the
34
+ * same promise, so two opens cannot race into `init` and double-run
35
+ * migrations against a fresh dataDir.
36
+ */
37
+ export class MemorySession {
38
+ path;
39
+ openOptions;
40
+ idleCloseMs;
41
+ lockWaitBudgetMs;
42
+ onWarn;
43
+ db;
44
+ lockPath;
45
+ opening;
46
+ refs = 0;
47
+ idleTimer;
48
+ disposed = false;
49
+ disposing;
50
+ constructor(opts) {
51
+ this.path = opts.path;
52
+ this.openOptions = opts.openOptions;
53
+ this.idleCloseMs = opts.idleCloseMs ?? DEFAULT_IDLE_CLOSE_MS;
54
+ this.lockWaitBudgetMs = opts.lockWaitBudgetMs ?? DEFAULT_LOCK_WAIT_BUDGET_MS;
55
+ if (opts.onWarn)
56
+ this.onWarn = opts.onWarn;
57
+ }
58
+ /**
59
+ * Run `fn` against the (lazily-opened) memory database. Returns
60
+ * `undefined` without invoking `fn` when the session is disposed or
61
+ * the lock could not be acquired within the budget; otherwise
62
+ * returns whatever `fn` returns. Errors thrown inside `fn`
63
+ * propagate.
64
+ */
65
+ async withDb(fn) {
66
+ if (this.disposed)
67
+ return undefined;
68
+ this.cancelIdleClose();
69
+ // Increment refs before awaiting the open so a concurrent
70
+ // `dispose()` waits for this call to finish even if the await on
71
+ // `ensureOpen` resolves before fn runs. Without this, dispose
72
+ // could see refs=0, close the handle, and leave fn to run against
73
+ // a closed db.
74
+ this.refs++;
75
+ try {
76
+ const db = await this.ensureOpen();
77
+ if (!db)
78
+ return undefined;
79
+ // Once the handle is open, run fn even if dispose was called
80
+ // while we were waiting on `ensureOpen`. dispose's drain loop
81
+ // waits for refs to reach zero, so the call completes against
82
+ // a still-open db; without this we would silently skip fn even
83
+ // though the caller had already committed to running it.
84
+ return await fn(db);
85
+ }
86
+ finally {
87
+ this.refs--;
88
+ if (this.refs === 0 && !this.disposed) {
89
+ this.scheduleIdleClose();
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * Wait for any in-flight `withDb` to drain, cancel a pending idle
95
+ * close, then close the handle and release the lock. Subsequent
96
+ * `withDb` calls resolve to `undefined`.
97
+ */
98
+ async dispose() {
99
+ if (this.disposing)
100
+ return this.disposing;
101
+ this.disposing = this.runDispose();
102
+ return this.disposing;
103
+ }
104
+ async runDispose() {
105
+ this.disposed = true;
106
+ this.cancelIdleClose();
107
+ // Wait for in-flight withDb fns to settle. Polling is fine here:
108
+ // dispose is rare and refs decrements happen on microtask
109
+ // boundaries, so this loop spins a few times at most.
110
+ while (this.refs > 0) {
111
+ await sleep(5);
112
+ }
113
+ await this.closeNow();
114
+ }
115
+ async ensureOpen() {
116
+ if (this.db)
117
+ return this.db;
118
+ if (this.opening)
119
+ return this.opening;
120
+ this.opening = this.openWithPolling()
121
+ .then((db) => {
122
+ this.db = db;
123
+ return db;
124
+ })
125
+ .catch((error) => {
126
+ if (error instanceof MemoryLockTimeoutError) {
127
+ this.onWarn?.("memory db busy, skipping op");
128
+ return undefined;
129
+ }
130
+ throw error;
131
+ })
132
+ .finally(() => {
133
+ this.opening = undefined;
134
+ });
135
+ return this.opening;
136
+ }
137
+ async openWithPolling() {
138
+ const start = Date.now();
139
+ let attempt = 0;
140
+ let lastHolderPid = 0;
141
+ while (true) {
142
+ const result = tryAcquireOpenLock(this.path);
143
+ if ("lockPath" in result) {
144
+ const opened = await openPGliteHoldingLock(this.path, this.openOptions, result.lockPath);
145
+ this.lockPath = opened.lockPath;
146
+ return opened.db;
147
+ }
148
+ lastHolderPid = result.holderPid;
149
+ const elapsed = Date.now() - start;
150
+ if (elapsed >= this.lockWaitBudgetMs) {
151
+ throw new MemoryLockTimeoutError(this.path, lastHolderPid, this.lockWaitBudgetMs);
152
+ }
153
+ const base = POLL_BACKOFF_MS[Math.min(attempt, POLL_BACKOFF_MS.length - 1)] ?? 1000;
154
+ const remaining = this.lockWaitBudgetMs - elapsed;
155
+ const delay = Math.max(1, Math.min(base, remaining));
156
+ attempt++;
157
+ await sleep(delay);
158
+ }
159
+ }
160
+ scheduleIdleClose() {
161
+ this.cancelIdleClose();
162
+ this.idleTimer = setTimeout(() => {
163
+ this.idleTimer = undefined;
164
+ // Re-check refs in case a new withDb arrived between the timer
165
+ // firing and this callback running.
166
+ if (this.refs > 0 || this.disposed)
167
+ return;
168
+ void this.closeNow();
169
+ }, this.idleCloseMs);
170
+ }
171
+ cancelIdleClose() {
172
+ if (this.idleTimer) {
173
+ clearTimeout(this.idleTimer);
174
+ this.idleTimer = undefined;
175
+ }
176
+ }
177
+ async closeNow() {
178
+ const db = this.db;
179
+ const lockPath = this.lockPath;
180
+ this.db = undefined;
181
+ this.lockPath = undefined;
182
+ if (db) {
183
+ try {
184
+ await db.close();
185
+ }
186
+ catch {
187
+ // PGlite occasionally throws on close after an aborted op;
188
+ // we still need to release the lock so a peer can open.
189
+ }
190
+ }
191
+ if (lockPath)
192
+ releaseOpenLock(lockPath);
193
+ }
194
+ }
195
+ function sleep(ms) {
196
+ return new Promise((resolve) => setTimeout(resolve, ms));
197
+ }
198
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/memory/session.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EAEtB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,2BAA2B,GAAG,MAAM,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AA+BvD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,aAAa;IACP,IAAI,CAAS;IACb,WAAW,CAAoB;IAC/B,WAAW,CAAS;IACpB,gBAAgB,CAAS;IACzB,MAAM,CAA6B;IAE5C,EAAE,CAAqB;IACvB,QAAQ,CAAqB;IAC7B,OAAO,CAA0C;IACjD,IAAI,GAAG,CAAC,CAAC;IACT,SAAS,CAA4C;IACrD,QAAQ,GAAG,KAAK,CAAC;IACjB,SAAS,CAA4B;IAE7C,YAAY,IAA0B;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,qBAAqB,CAAC;QAC7D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,2BAA2B,CAAC;QAC7E,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC7C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CAAI,EAA8B;QAC5C,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QACpC,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,0DAA0D;QAC1D,iEAAiE;QACjE,8DAA8D;QAC9D,kEAAkE;QAClE,eAAe;QACf,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACnC,IAAI,CAAC,EAAE;gBAAE,OAAO,SAAS,CAAC;YAC1B,6DAA6D;YAC7D,8DAA8D;YAC9D,8DAA8D;YAC9D,+DAA+D;YAC/D,yDAAyD;YACzD,OAAO,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACtC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,iEAAiE;QACjE,0DAA0D;QAC1D,sDAAsD;QACtD,OAAO,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QACD,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,IAAI,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;QACtC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE;aAClC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YACX,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,IAAI,KAAK,YAAY,sBAAsB,EAAE,CAAC;gBAC5C,IAAI,CAAC,MAAM,EAAE,CAAC,6BAA6B,CAAC,CAAC;gBAC7C,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QAC3B,CAAC,CAAC,CAAC;QACL,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,eAAe;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,UAAU,IAAI,MAAM,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACzF,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;gBAChC,OAAO,MAAM,CAAC,EAAE,CAAC;YACnB,CAAC;YACD,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC;YACjC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;YACnC,IAAI,OAAO,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACrC,MAAM,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACpF,CAAC;YACD,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;YACpF,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC;YAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;YACrD,OAAO,EAAE,CAAC;YACV,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAC3B,+DAA+D;YAC/D,oCAAoC;YACpC,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ;gBAAE,OAAO;YAC3C,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvB,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC;IAEO,eAAe;QACrB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,2DAA2D;gBAC3D,wDAAwD;YAC1D,CAAC;QACH,CAAC;QACD,IAAI,QAAQ;YAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;CACF;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -1,16 +1,16 @@
1
- import type { PGlite } from "@electric-sql/pglite";
2
1
  import type { Observation, ObservationalMemorySettings } from "../types/memory.js";
3
2
  import type { EmbedFn } from "./embedding.js";
3
+ import { MemorySession } from "./session.js";
4
4
  import type { MemoryContextCache } from "./store.js";
5
- export type MemoryDatabase = PGlite;
6
5
  /**
7
- * Handle returned by `loadStoredMemory`. Callers use `db` directly with
8
- * the helpers in this module there is no in-memory mirror to keep in
9
- * sync, so writes are inline awaits against PGlite. Memory disabled
10
- * leaves `db` undefined and persistence becomes a no-op.
6
+ * Handle returned by `loadStoredMemory`. Callers pass `session` into the
7
+ * storage helpers, which each open the underlying PGlite handle just long
8
+ * enough to run their queries and then hand it back to the idle-close
9
+ * timer so a peer duet CLI can acquire the cross-process lock between
10
+ * bursts. `session` is undefined when memory persistence is disabled.
11
11
  */
12
12
  export interface MemoryPersistenceHandle {
13
- db: MemoryDatabase | undefined;
13
+ session: MemorySession | undefined;
14
14
  embed: EmbedFn | undefined;
15
15
  dispose: () => Promise<void>;
16
16
  }
@@ -35,40 +35,54 @@ export interface LoadStoredMemoryOptions {
35
35
  backupPath: string;
36
36
  cause: unknown;
37
37
  }) => void;
38
+ /**
39
+ * Invoked once when a `withDb` call is skipped because the cross-process
40
+ * open-lock could not be acquired within the wait budget. The runner
41
+ * surfaces this as a single user-visible system warning so two concurrent
42
+ * duet CLIs can coexist (one degrades to no-memory) without spamming the
43
+ * UI per skipped op.
44
+ */
45
+ onWarn?: (message: string) => void;
38
46
  }
39
47
  export declare function loadStoredMemory(memoryPath: string | false | undefined, cwd: string, options?: LoadStoredMemoryOptions): Promise<MemoryPersistenceHandle>;
40
48
  /**
41
49
  * Insert a new observation row, materializing `id`, `createdAt`, and
42
50
  * `lastUsedAt = createdAt`. Returns the populated `Observation` so
43
51
  * callers can refer to the freshly-assigned id (e.g. to keep
44
- * observation-group range markers consistent).
52
+ * observation-group range markers consistent). Returns `undefined`
53
+ * when the cross-process lock could not be acquired within the
54
+ * session's wait budget — writes are silently dropped in that case so
55
+ * a concurrent duet CLI does not crash the foreground turn.
45
56
  */
46
- export declare function appendObservation(db: MemoryDatabase, input: Omit<Observation, "id" | "createdAt" | "lastUsedAt">): Promise<Observation>;
57
+ export declare function appendObservation(session: MemorySession, input: Omit<Observation, "id" | "createdAt" | "lastUsedAt">): Promise<Observation | undefined>;
47
58
  /**
48
59
  * Replace this session's rows with the given list. Used by reflection,
49
60
  * which condenses the session's observation log into one reflection
50
61
  * row. Scoping the delete to `session_id = $1` is what makes the
51
62
  * cross-session global pool durable: another session's rows are never
52
- * touched by the current session's reflection.
63
+ * touched by the current session's reflection. No-op when the
64
+ * cross-process lock could not be acquired.
53
65
  */
54
- export declare function replaceSessionObservations(db: MemoryDatabase, sessionId: string, observations: readonly Observation[]): Promise<void>;
66
+ export declare function replaceSessionObservations(session: MemorySession, sessionId: string, observations: readonly Observation[]): Promise<void>;
55
67
  /**
56
68
  * Read this session's observations in chronological order along with a
57
69
  * rough token count. Used by the observer (to gate reflection on
58
70
  * cumulative session-local observation tokens) and by
59
71
  * `getUnobservedMessageTail` to find the highest message id already
60
- * folded into an observation-group range.
72
+ * folded into an observation-group range. Returns an empty snapshot
73
+ * when the cross-process lock could not be acquired.
61
74
  */
62
75
  export interface SessionObservationsSnapshot {
63
76
  observations: Observation[];
64
77
  estimatedObservationTokens: number;
65
78
  }
66
- export declare function readSessionObservations(db: MemoryDatabase, sessionId: string): Promise<SessionObservationsSnapshot>;
79
+ export declare function readSessionObservations(session: MemorySession, sessionId: string): Promise<SessionObservationsSnapshot>;
67
80
  /**
68
81
  * Bump `last_used_at` for the given ids to `now`. Fire-and-forget
69
82
  * usage signal: if the model's response leaned on a particular
70
83
  * memory, that memory's ranking refreshes so it stays surfaced. No-op
71
- * when `ids` is empty.
84
+ * when `ids` is empty or when the cross-process lock could not be
85
+ * acquired.
72
86
  */
73
- export declare function bumpLastUsed(db: MemoryDatabase, ids: readonly string[], now: number): Promise<void>;
87
+ export declare function bumpLastUsed(session: MemorySession, ids: readonly string[], now: number): Promise<void>;
74
88
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/memory/storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,cAAc,GAAG,SAAS,CAAC;IAC/B,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iGAAiG;IACjG,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,kBAAkB,CAAC;QAC1B,QAAQ,EAAE,2BAA2B,CAAC;QACtC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CACpE;AAED,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,EACtC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAgDlC;AAsBD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,YAAY,CAAC,GAC1D,OAAO,CAAC,WAAW,CAAC,CAStB;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,SAAS,WAAW,EAAE,GACnC,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;;GAMG;AACH,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,0BAA0B,EAAE,MAAM,CAAC;CACpC;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,2BAA2B,CAAC,CAgBtC;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/memory/storage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAI9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IACnC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iGAAiG;IACjG,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,kBAAkB,CAAC;QAC1B,QAAQ,EAAE,2BAA2B,CAAC;QACtC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IACnE;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,EACtC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAwElC;AAMD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,YAAY,CAAC,GAC1D,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAYlC;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,SAAS,WAAW,EAAE,GACnC,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,0BAA0B,EAAE,MAAM,CAAC;CACpC;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,2BAA2B,CAAC,CAmBtC;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -3,14 +3,28 @@ import { join } from "node:path";
3
3
  import { EmbeddingBackfillWorker } from "./embedding-worker.js";
4
4
  import { rebuildMemoryContextPack } from "./context-pack.js";
5
5
  import { runMigrations } from "./migrations.js";
6
- import { openPGlite } from "./pglite.js";
6
+ import { MemorySession } from "./session.js";
7
7
  import { nanoid } from "nanoid";
8
8
  export async function loadStoredMemory(memoryPath, cwd, options = {}) {
9
9
  if (!memoryPath) {
10
10
  const noop = async () => { };
11
- return { db: undefined, embed: undefined, dispose: noop };
11
+ return { session: undefined, embed: undefined, dispose: noop };
12
12
  }
13
- const database = await openMemoryDatabase(resolveMemoryPath(memoryPath, cwd), options.onRecover);
13
+ const resolvedPath = resolveMemoryPath(memoryPath, cwd);
14
+ const session = new MemorySession({
15
+ path: resolvedPath,
16
+ openOptions: {
17
+ // Migrations double as the corruption probe: a failure inside
18
+ // runMigrations propagates through openPGlite's try block, which
19
+ // moves the directory aside and starts fresh rather than wedging
20
+ // the agent behind an opaque PGlite abort.
21
+ init: async (db) => {
22
+ await runMigrations(db);
23
+ },
24
+ ...(options.onRecover ? { onRecover: options.onRecover } : {}),
25
+ },
26
+ ...(options.onWarn ? { onWarn: options.onWarn } : {}),
27
+ });
14
28
  // The backfill worker runs whenever the CLI is up. Observers and
15
29
  // reflectors write rows during turns; embeddings catch up in the
16
30
  // background within a few batches, never blocking the foreground.
@@ -18,20 +32,29 @@ export async function loadStoredMemory(memoryPath, cwd, options = {}) {
18
32
  // and one-shot tools that do not call recall_memory.
19
33
  const worker = options.embed
20
34
  ? new EmbeddingBackfillWorker({
21
- db: database,
35
+ session,
22
36
  embed: options.embed,
23
37
  logPath: options.embeddingLogPath ?? defaultEmbeddingLogPath(),
24
38
  })
25
39
  : undefined;
26
40
  worker?.start();
41
+ // Eager probe: open + run migrations once at startup. This keeps
42
+ // the "fail fast on corruption" property of the old eager loader
43
+ // (an unreadable dataDir is quarantined and onRecover is called
44
+ // before any turn dispatches) without changing the steady-state
45
+ // lazy-open behavior. The handle idle-closes within 2s afterwards
46
+ // unless the contextPack rebuild below grabs it first.
47
+ await session.withDb(async () => { });
27
48
  if (options.contextPack) {
28
49
  // Initial compaction trigger: freeze the rendered memory pack
29
50
  // before the first turn dispatches so the prefix is stable from
30
- // turn 1, not turn 2. Failure here is non-fatal: the pack stays
31
- // empty and the runner can trigger a refresh later.
51
+ // turn 1, not turn 2. Migrations ran during the probe above; the
52
+ // 2s idle-close window means the rebuild typically rides the
53
+ // same open. Failure here is non-fatal: the pack stays empty
54
+ // and the runner can trigger a refresh later.
32
55
  try {
33
56
  await rebuildMemoryContextPack({
34
- db: database,
57
+ session,
35
58
  cache: options.contextPack.cache,
36
59
  settings: options.contextPack.settings,
37
60
  ...(options.contextPack.sessionId !== undefined
@@ -47,87 +70,90 @@ export async function loadStoredMemory(memoryPath, cwd, options = {}) {
47
70
  }
48
71
  const dispose = async () => {
49
72
  await worker?.stop();
50
- await database.close();
73
+ await session.dispose();
51
74
  };
52
- return { db: database, embed: options.embed, dispose };
75
+ return { session, embed: options.embed, dispose };
53
76
  }
54
77
  function defaultEmbeddingLogPath() {
55
78
  return join(homedir(), ".duet", "logs", "memory-backfill.log");
56
79
  }
57
- async function openMemoryDatabase(path, onRecover) {
58
- // Migrations double as the corruption probe: a failure inside
59
- // runMigrations propagates through openPGlite's try block, which
60
- // moves the directory aside and starts fresh rather than wedging
61
- // the agent behind an opaque PGlite abort.
62
- return openPGlite(path, {
63
- init: async (db) => {
64
- await runMigrations(db);
65
- },
66
- ...(onRecover ? { onRecover } : {}),
67
- });
68
- }
69
80
  /**
70
81
  * Insert a new observation row, materializing `id`, `createdAt`, and
71
82
  * `lastUsedAt = createdAt`. Returns the populated `Observation` so
72
83
  * callers can refer to the freshly-assigned id (e.g. to keep
73
- * observation-group range markers consistent).
84
+ * observation-group range markers consistent). Returns `undefined`
85
+ * when the cross-process lock could not be acquired within the
86
+ * session's wait budget — writes are silently dropped in that case so
87
+ * a concurrent duet CLI does not crash the foreground turn.
74
88
  */
75
- export async function appendObservation(db, input) {
89
+ export async function appendObservation(session, input) {
76
90
  const observation = {
77
91
  ...input,
78
92
  id: createMemoryId(),
79
93
  createdAt: Date.now(),
80
94
  lastUsedAt: Date.now(),
81
95
  };
82
- await upsertObservation(db, observation);
83
- return observation;
96
+ const result = await session.withDb(async (db) => {
97
+ await upsertObservation(db, observation);
98
+ return observation;
99
+ });
100
+ return result;
84
101
  }
85
102
  /**
86
103
  * Replace this session's rows with the given list. Used by reflection,
87
104
  * which condenses the session's observation log into one reflection
88
105
  * row. Scoping the delete to `session_id = $1` is what makes the
89
106
  * cross-session global pool durable: another session's rows are never
90
- * touched by the current session's reflection.
107
+ * touched by the current session's reflection. No-op when the
108
+ * cross-process lock could not be acquired.
91
109
  */
92
- export async function replaceSessionObservations(db, sessionId, observations) {
110
+ export async function replaceSessionObservations(session, sessionId, observations) {
93
111
  const ids = observations.map((observation) => observation.id);
94
- await db.transaction(async (tx) => {
95
- if (ids.length === 0) {
96
- await tx.query("DELETE FROM observations WHERE session_id = $1", [sessionId]);
97
- }
98
- else {
99
- await tx.query("DELETE FROM observations WHERE session_id = $1 AND NOT (id = ANY($2::text[]))", [sessionId, ids]);
100
- }
101
- for (const observation of observations) {
102
- await upsertObservation(tx, observation);
103
- }
112
+ await session.withDb(async (db) => {
113
+ await db.transaction(async (tx) => {
114
+ if (ids.length === 0) {
115
+ await tx.query("DELETE FROM observations WHERE session_id = $1", [sessionId]);
116
+ }
117
+ else {
118
+ await tx.query("DELETE FROM observations WHERE session_id = $1 AND NOT (id = ANY($2::text[]))", [sessionId, ids]);
119
+ }
120
+ for (const observation of observations) {
121
+ await upsertObservation(tx, observation);
122
+ }
123
+ });
104
124
  });
105
125
  }
106
- export async function readSessionObservations(db, sessionId) {
107
- const result = await db.query(`SELECT id, created_at, last_used_at, session_id, kind, observed_date, referenced_date,
108
- relative_date, time_of_day, priority, source_json, content, tags_json
109
- FROM observations
110
- WHERE session_id = $1
111
- ORDER BY created_at ASC`, [sessionId]);
112
- const observations = result.rows.map(rowToObservation);
113
- return {
114
- observations,
115
- estimatedObservationTokens: estimateTokens(observations.map((observation) => observation.content).join("\n")),
116
- };
126
+ export async function readSessionObservations(session, sessionId) {
127
+ const result = await session.withDb(async (db) => {
128
+ const queryResult = await db.query(`SELECT id, created_at, last_used_at, session_id, kind, observed_date, referenced_date,
129
+ relative_date, time_of_day, priority, source_json, content, tags_json
130
+ FROM observations
131
+ WHERE session_id = $1
132
+ ORDER BY created_at ASC`, [sessionId]);
133
+ const observations = queryResult.rows.map(rowToObservation);
134
+ return {
135
+ observations,
136
+ estimatedObservationTokens: estimateTokens(observations.map((observation) => observation.content).join("\n")),
137
+ };
138
+ });
139
+ return result ?? { observations: [], estimatedObservationTokens: 0 };
117
140
  }
118
141
  /**
119
142
  * Bump `last_used_at` for the given ids to `now`. Fire-and-forget
120
143
  * usage signal: if the model's response leaned on a particular
121
144
  * memory, that memory's ranking refreshes so it stays surfaced. No-op
122
- * when `ids` is empty.
145
+ * when `ids` is empty or when the cross-process lock could not be
146
+ * acquired.
123
147
  */
124
- export async function bumpLastUsed(db, ids, now) {
148
+ export async function bumpLastUsed(session, ids, now) {
125
149
  if (ids.length === 0)
126
150
  return;
127
- await db.query("UPDATE observations SET last_used_at = $1 WHERE id = ANY($2::text[])", [
128
- now,
129
- ids,
130
- ]);
151
+ await session.withDb(async (db) => {
152
+ await db.query("UPDATE observations SET last_used_at = $1 WHERE id = ANY($2::text[])", [
153
+ now,
154
+ ids,
155
+ ]);
156
+ });
131
157
  }
132
158
  async function upsertObservation(database, observation) {
133
159
  await database.query(`INSERT INTO observations (