@duetso/agent 0.1.52 → 0.1.54
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/README.md +13 -2
- package/dist/package.json +1 -1
- package/dist/src/cli/help.js +1 -1
- package/dist/src/cli/memory-db.d.ts.map +1 -1
- package/dist/src/cli/memory-db.js +14 -5
- package/dist/src/cli/memory-db.js.map +1 -1
- package/dist/src/cli/memory-tui.js +1 -1
- package/dist/src/cli/memory-tui.js.map +1 -1
- package/dist/src/cli/resume-hint.d.ts +1 -1
- package/dist/src/cli/resume-hint.d.ts.map +1 -1
- package/dist/src/cli/resume-hint.js +2 -2
- package/dist/src/cli/resume-hint.js.map +1 -1
- package/dist/src/cli/run.d.ts +1 -1
- package/dist/src/cli/run.d.ts.map +1 -1
- package/dist/src/cli/run.js +17 -8
- package/dist/src/cli/run.js.map +1 -1
- package/dist/src/memory/context-pack.d.ts +29 -0
- package/dist/src/memory/context-pack.d.ts.map +1 -0
- package/dist/src/memory/context-pack.js +40 -0
- package/dist/src/memory/context-pack.js.map +1 -0
- package/dist/src/memory/embedding-worker.d.ts +26 -0
- package/dist/src/memory/embedding-worker.d.ts.map +1 -0
- package/dist/src/memory/embedding-worker.js +145 -0
- package/dist/src/memory/embedding-worker.js.map +1 -0
- package/dist/src/memory/embedding.d.ts +59 -0
- package/dist/src/memory/embedding.d.ts.map +1 -0
- package/dist/src/memory/embedding.js +126 -0
- package/dist/src/memory/embedding.js.map +1 -0
- package/dist/src/memory/loader.d.ts +81 -0
- package/dist/src/memory/loader.d.ts.map +1 -0
- package/dist/src/memory/loader.js +162 -0
- package/dist/src/memory/loader.js.map +1 -0
- package/dist/src/memory/migrations.d.ts +54 -0
- package/dist/src/memory/migrations.d.ts.map +1 -0
- package/dist/src/memory/migrations.js +199 -0
- package/dist/src/memory/migrations.js.map +1 -0
- package/dist/src/memory/observational.d.ts +18 -0
- package/dist/src/memory/observational.d.ts.map +1 -1
- package/dist/src/memory/observational.js +63 -14
- package/dist/src/memory/observational.js.map +1 -1
- package/dist/src/memory/pglite.d.ts +5 -5
- package/dist/src/memory/pglite.d.ts.map +1 -1
- package/dist/src/memory/pglite.js +20 -6
- package/dist/src/memory/pglite.js.map +1 -1
- package/dist/src/memory/recall.d.ts +49 -0
- package/dist/src/memory/recall.d.ts.map +1 -0
- package/dist/src/memory/recall.js +185 -0
- package/dist/src/memory/recall.js.map +1 -0
- package/dist/src/memory/storage.d.ts +36 -1
- package/dist/src/memory/storage.d.ts.map +1 -1
- package/dist/src/memory/storage.js +68 -14
- package/dist/src/memory/storage.js.map +1 -1
- package/dist/src/memory/store.d.ts +33 -1
- package/dist/src/memory/store.d.ts.map +1 -1
- package/dist/src/memory/store.js +22 -16
- package/dist/src/memory/store.js.map +1 -1
- package/dist/src/session/session.d.ts.map +1 -1
- package/dist/src/session/session.js +5 -1
- package/dist/src/session/session.js.map +1 -1
- package/dist/src/tui/app.d.ts +7 -2
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +343 -30
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/autocomplete.d.ts.map +1 -1
- package/dist/src/tui/autocomplete.js +5 -0
- package/dist/src/tui/autocomplete.js.map +1 -1
- package/dist/src/tui/clipboard.d.ts +28 -0
- package/dist/src/tui/clipboard.d.ts.map +1 -0
- package/dist/src/tui/clipboard.js +95 -0
- package/dist/src/tui/clipboard.js.map +1 -0
- package/dist/src/tui/history.d.ts +14 -1
- package/dist/src/tui/history.d.ts.map +1 -1
- package/dist/src/tui/history.js +21 -2
- package/dist/src/tui/history.js.map +1 -1
- package/dist/src/tui/recent-sessions.d.ts +51 -0
- package/dist/src/tui/recent-sessions.d.ts.map +1 -0
- package/dist/src/tui/recent-sessions.js +150 -0
- package/dist/src/tui/recent-sessions.js.map +1 -0
- package/dist/src/tui/starters.d.ts +92 -0
- package/dist/src/tui/starters.d.ts.map +1 -0
- package/dist/src/tui/starters.js +196 -0
- package/dist/src/tui/starters.js.map +1 -0
- package/dist/src/tui/theme.d.ts +2 -2
- package/dist/src/tui/theme.d.ts.map +1 -1
- package/dist/src/tui/theme.js +2 -2
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/transcript-log.d.ts +45 -0
- package/dist/src/tui/transcript-log.d.ts.map +1 -0
- package/dist/src/tui/transcript-log.js +76 -0
- package/dist/src/tui/transcript-log.js.map +1 -0
- package/dist/src/turn-runner/tools.d.ts +28 -1
- package/dist/src/turn-runner/tools.d.ts.map +1 -1
- package/dist/src/turn-runner/tools.js +135 -3
- package/dist/src/turn-runner/tools.js.map +1 -1
- package/dist/src/turn-runner/turn-runner.d.ts +1 -0
- package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
- package/dist/src/turn-runner/turn-runner.js +70 -3
- package/dist/src/turn-runner/turn-runner.js.map +1 -1
- package/dist/src/types/config.d.ts +12 -0
- package/dist/src/types/config.d.ts.map +1 -1
- package/dist/src/types/memory.d.ts +59 -21
- package/dist/src/types/memory.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/src/memory/schema.d.ts +0 -7
- package/dist/src/memory/schema.d.ts.map +0 -1
- package/dist/src/memory/schema.js +0 -21
- package/dist/src/memory/schema.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-worker.js","sourceRoot":"","sources":["../../../src/memory/embedding-worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the Duet embedding endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Routes through the Duet public API (`POST <app>/api/v1/embed`) gated
|
|
5
|
+
* by the user's existing `DUET_API_KEY`. Logged-in users get embeddings
|
|
6
|
+
* for free as a CLI perk; we deliberately do not expose a way to plug
|
|
7
|
+
* in a different provider key here so the runtime stays single-path.
|
|
8
|
+
*
|
|
9
|
+
* Behavior:
|
|
10
|
+
* - Inputs are batched up to `EMBEDDING_BATCH_LIMIT` per HTTP call so
|
|
11
|
+
* a single backfill batch never goes over the request body cap.
|
|
12
|
+
* - 5xx responses and network errors retry with exponential backoff;
|
|
13
|
+
* 4xx responses (auth, malformed) fail fast since retrying will not
|
|
14
|
+
* change the outcome.
|
|
15
|
+
* - When `DUET_API_KEY` is missing the function throws a typed error
|
|
16
|
+
* so callers can degrade to keyword-only retrieval (the recall_memory
|
|
17
|
+
* tool catches it and logs once; the backfill worker logs and
|
|
18
|
+
* sleeps until the user logs in).
|
|
19
|
+
*/
|
|
20
|
+
/** Default embedding model identifier sent with every request. */
|
|
21
|
+
export declare const DEFAULT_EMBEDDING_MODEL = "openai/text-embedding-3-small";
|
|
22
|
+
/** Maximum input strings sent in a single embedding request. */
|
|
23
|
+
export declare const EMBEDDING_BATCH_LIMIT = 100;
|
|
24
|
+
/**
|
|
25
|
+
* Callable shape consumed by the backfill worker and the recall_memory
|
|
26
|
+
* tool. Inputs map 1:1 to output vectors.
|
|
27
|
+
*/
|
|
28
|
+
export type EmbedFn = (inputs: string[]) => Promise<number[][]>;
|
|
29
|
+
export declare class EmbeddingUnavailableError extends Error {
|
|
30
|
+
constructor(message: string);
|
|
31
|
+
}
|
|
32
|
+
export interface CreateEmbeddingClientOptions {
|
|
33
|
+
/** Embedding model identifier sent with each request. */
|
|
34
|
+
model?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Override the API key resolver. Defaults to reading `DUET_API_KEY`
|
|
37
|
+
* from `process.env`. Tests inject a fixed value to exercise the
|
|
38
|
+
* client without touching environment state.
|
|
39
|
+
*/
|
|
40
|
+
apiKey?: string | (() => string | undefined);
|
|
41
|
+
/**
|
|
42
|
+
* Override the base URL. Defaults to `resolveDuetAppBaseUrl()` so the
|
|
43
|
+
* client follows the same staging/production routing as the rest of
|
|
44
|
+
* the CLI.
|
|
45
|
+
*/
|
|
46
|
+
baseUrl?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Override the fetch implementation. Tests use this to assert request
|
|
49
|
+
* shape and stub responses without standing up a real server.
|
|
50
|
+
*/
|
|
51
|
+
fetch?: typeof fetch;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build an `EmbedFn` bound to the Duet embedding endpoint. Constructed
|
|
55
|
+
* once per process and reused for every backfill batch and every
|
|
56
|
+
* `recall_memory` query so connection reuse can amortize TLS setup.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createEmbeddingClient(options?: CreateEmbeddingClientOptions): EmbedFn;
|
|
59
|
+
//# sourceMappingURL=embedding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../../src/memory/embedding.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,kCAAkC,CAAC;AACvE,gEAAgE;AAChE,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAMzC;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEhE,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,4BAA4B;IAC3C,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAmCzF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { resolveDuetAppBaseUrl } from "../lib/duet-app-url.js";
|
|
2
|
+
/**
|
|
3
|
+
* Client for the Duet embedding endpoint.
|
|
4
|
+
*
|
|
5
|
+
* Routes through the Duet public API (`POST <app>/api/v1/embed`) gated
|
|
6
|
+
* by the user's existing `DUET_API_KEY`. Logged-in users get embeddings
|
|
7
|
+
* for free as a CLI perk; we deliberately do not expose a way to plug
|
|
8
|
+
* in a different provider key here so the runtime stays single-path.
|
|
9
|
+
*
|
|
10
|
+
* Behavior:
|
|
11
|
+
* - Inputs are batched up to `EMBEDDING_BATCH_LIMIT` per HTTP call so
|
|
12
|
+
* a single backfill batch never goes over the request body cap.
|
|
13
|
+
* - 5xx responses and network errors retry with exponential backoff;
|
|
14
|
+
* 4xx responses (auth, malformed) fail fast since retrying will not
|
|
15
|
+
* change the outcome.
|
|
16
|
+
* - When `DUET_API_KEY` is missing the function throws a typed error
|
|
17
|
+
* so callers can degrade to keyword-only retrieval (the recall_memory
|
|
18
|
+
* tool catches it and logs once; the backfill worker logs and
|
|
19
|
+
* sleeps until the user logs in).
|
|
20
|
+
*/
|
|
21
|
+
/** Default embedding model identifier sent with every request. */
|
|
22
|
+
export const DEFAULT_EMBEDDING_MODEL = "openai/text-embedding-3-small";
|
|
23
|
+
/** Maximum input strings sent in a single embedding request. */
|
|
24
|
+
export const EMBEDDING_BATCH_LIMIT = 100;
|
|
25
|
+
const ENDPOINT_PATH = "/api/v1/embed";
|
|
26
|
+
const MAX_RETRIES = 3;
|
|
27
|
+
const BASE_BACKOFF_MS = 500;
|
|
28
|
+
export class EmbeddingUnavailableError extends Error {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "EmbeddingUnavailableError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build an `EmbedFn` bound to the Duet embedding endpoint. Constructed
|
|
36
|
+
* once per process and reused for every backfill batch and every
|
|
37
|
+
* `recall_memory` query so connection reuse can amortize TLS setup.
|
|
38
|
+
*/
|
|
39
|
+
export function createEmbeddingClient(options = {}) {
|
|
40
|
+
const model = options.model ?? DEFAULT_EMBEDDING_MODEL;
|
|
41
|
+
const baseUrl = options.baseUrl ?? resolveDuetAppBaseUrl();
|
|
42
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
43
|
+
return async (inputs) => {
|
|
44
|
+
if (inputs.length === 0)
|
|
45
|
+
return [];
|
|
46
|
+
const apiKey = resolveApiKey(options.apiKey);
|
|
47
|
+
if (!apiKey) {
|
|
48
|
+
// Throwing a typed error lets callers branch on graceful
|
|
49
|
+
// degradation without string-matching the message.
|
|
50
|
+
throw new EmbeddingUnavailableError("DUET_API_KEY is not set; embeddings unavailable. Run `duet login` to enable hybrid memory retrieval.");
|
|
51
|
+
}
|
|
52
|
+
const results = [];
|
|
53
|
+
for (let offset = 0; offset < inputs.length; offset += EMBEDDING_BATCH_LIMIT) {
|
|
54
|
+
const slice = inputs.slice(offset, offset + EMBEDDING_BATCH_LIMIT);
|
|
55
|
+
const vectors = await postBatch({
|
|
56
|
+
url: `${baseUrl}${ENDPOINT_PATH}`,
|
|
57
|
+
apiKey,
|
|
58
|
+
model,
|
|
59
|
+
inputs: slice,
|
|
60
|
+
fetchImpl,
|
|
61
|
+
});
|
|
62
|
+
if (vectors.length !== slice.length) {
|
|
63
|
+
throw new Error(`Embedding response length (${vectors.length}) did not match request size (${slice.length})`);
|
|
64
|
+
}
|
|
65
|
+
results.push(...vectors);
|
|
66
|
+
}
|
|
67
|
+
return results;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function postBatch(options) {
|
|
71
|
+
let lastError;
|
|
72
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await options.fetchImpl(options.url, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
78
|
+
"content-type": "application/json",
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ input: options.inputs, model: options.model }),
|
|
81
|
+
});
|
|
82
|
+
if (response.ok) {
|
|
83
|
+
const body = (await response.json());
|
|
84
|
+
return body.embeddings;
|
|
85
|
+
}
|
|
86
|
+
// 4xx errors (auth, malformed input) will not improve on retry;
|
|
87
|
+
// surface them immediately so callers see the real cause.
|
|
88
|
+
if (response.status >= 400 && response.status < 500) {
|
|
89
|
+
const detail = await safeReadText(response);
|
|
90
|
+
throw new EmbeddingUnavailableError(`Embedding endpoint returned ${response.status}: ${detail}`);
|
|
91
|
+
}
|
|
92
|
+
lastError = new Error(`Embedding endpoint returned ${response.status}`);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// Re-throw typed errors immediately; only retry on transport-level failures.
|
|
96
|
+
if (error instanceof EmbeddingUnavailableError)
|
|
97
|
+
throw error;
|
|
98
|
+
lastError = error;
|
|
99
|
+
}
|
|
100
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
101
|
+
await sleep(BASE_BACKOFF_MS * 2 ** attempt);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
throw lastError instanceof Error
|
|
105
|
+
? lastError
|
|
106
|
+
: new Error(`Embedding request failed after ${MAX_RETRIES} attempts`);
|
|
107
|
+
}
|
|
108
|
+
function resolveApiKey(override) {
|
|
109
|
+
if (typeof override === "function")
|
|
110
|
+
return override();
|
|
111
|
+
if (typeof override === "string")
|
|
112
|
+
return override;
|
|
113
|
+
return process.env.DUET_API_KEY;
|
|
114
|
+
}
|
|
115
|
+
async function safeReadText(response) {
|
|
116
|
+
try {
|
|
117
|
+
return await response.text();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return "<unreadable response body>";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function sleep(ms) {
|
|
124
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=embedding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding.js","sourceRoot":"","sources":["../../../src/memory/embedding.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import type { Observation } from "../types/memory.js";
|
|
3
|
+
export interface ScoreInputs {
|
|
4
|
+
/** Wall-clock time at which scoring is happening. Same value across one ranking pass. */
|
|
5
|
+
now: number;
|
|
6
|
+
/** Half-life for the recency exponential decay. Same units as createdAt (milliseconds). */
|
|
7
|
+
halfLifeMs: number;
|
|
8
|
+
/** Multiplier applied when an observation is a reflection rather than a raw observation. */
|
|
9
|
+
reflectionBias: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Combined relevance score used to rank the global layer.
|
|
13
|
+
*
|
|
14
|
+
* score = priorityWeight * recencyDecay * kindBias
|
|
15
|
+
*
|
|
16
|
+
* - priorityWeight: 3 / 2 / 1 for high / medium / low.
|
|
17
|
+
* - recencyDecay: `0.5 ^ (ageMs / halfLifeMs)`. Equals 1.0 at age 0,
|
|
18
|
+
* 0.5 at one half-life, ~0.0625 at four half-lives. Smooth and
|
|
19
|
+
* parameterizable; no cliffs.
|
|
20
|
+
* - kindBias: `reflectionBias` for reflections, 1.0 for raw
|
|
21
|
+
* observations. Default 1.3 keeps reflections preferred at matched
|
|
22
|
+
* priority/recency without shutting raw observations out.
|
|
23
|
+
*
|
|
24
|
+
* Examples at the default 7d half-life and 1.3 reflection bias:
|
|
25
|
+
* - high reflection, 0d: 3 * 1.0 * 1.3 = 3.9
|
|
26
|
+
* - high observation, 0d: 3 * 1.0 * 1.0 = 3.0
|
|
27
|
+
* - medium reflection, 0d: 2 * 1.0 * 1.3 = 2.6
|
|
28
|
+
* - high reflection, 7d: 3 * 0.5 * 1.3 = 1.95
|
|
29
|
+
* - low observation, 30d: 1 * 0.0508 * 1.0 = 0.05 (effectively pruned)
|
|
30
|
+
*/
|
|
31
|
+
export declare function score(observation: Observation, inputs: ScoreInputs): number;
|
|
32
|
+
export interface LoadGlobalPackOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Session whose rows are excluded from the global pack. The current
|
|
35
|
+
* runner's session lives in the local pack at full fidelity, so
|
|
36
|
+
* counting its rows in the global ranking would double-count and
|
|
37
|
+
* push out cross-session signal. Pass `undefined` to include every
|
|
38
|
+
* row (e.g. for the `recall_memory` tool when the user wants
|
|
39
|
+
* unrestricted search).
|
|
40
|
+
*/
|
|
41
|
+
excludeSessionId?: string;
|
|
42
|
+
/** Maximum tokens of `content` packed into the result. Defaults to 8000. */
|
|
43
|
+
tokenBudget?: number;
|
|
44
|
+
/** Recency half-life in milliseconds. */
|
|
45
|
+
recencyHalfLifeMs: number;
|
|
46
|
+
/** Reflection bias multiplier. */
|
|
47
|
+
reflectionBias: number;
|
|
48
|
+
/**
|
|
49
|
+
* Override for `Date.now()`, used by tests so fixture data with
|
|
50
|
+
* fixed `created_at` values produces deterministic scores.
|
|
51
|
+
*/
|
|
52
|
+
now?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Load the highest-scoring rows from every session except the caller's,
|
|
56
|
+
* up to the token budget. Returns rows ordered by descending score so
|
|
57
|
+
* the renderer can preserve the ranking visually.
|
|
58
|
+
*
|
|
59
|
+
* Implementation note: the database query orders by `created_at DESC`
|
|
60
|
+
* (cheap with the `idx_obs_kind_priority_created` index) and a generous
|
|
61
|
+
* over-fetch limit; the actual ranking happens in JS so the recency
|
|
62
|
+
* decay term can be applied without trying to express `0.5 ^ x` in
|
|
63
|
+
* portable SQL. With a 8k-token budget and ~250-token rows this means
|
|
64
|
+
* a worst case of ~32 rows packed; the over-fetch keeps the planner
|
|
65
|
+
* cheap even when the budget is set higher.
|
|
66
|
+
*/
|
|
67
|
+
export declare function loadGlobalPack(db: PGlite, options: LoadGlobalPackOptions): Promise<Observation[]>;
|
|
68
|
+
export interface LoadLocalPackOptions {
|
|
69
|
+
sessionId: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Load every row this session produced, ordered chronologically. The
|
|
73
|
+
* local pack represents the session's own compaction summary — the
|
|
74
|
+
* observations and reflections that replaced earlier transcript — so
|
|
75
|
+
* it is rendered whole regardless of size. Bounding the local layer
|
|
76
|
+
* is the existing observer/reflector pipeline's job: their thresholds
|
|
77
|
+
* keep this set from unbounded growth, and a reflection condenses it
|
|
78
|
+
* back down when it gets too large.
|
|
79
|
+
*/
|
|
80
|
+
export declare function loadLocalPack(db: PGlite, options: LoadLocalPackOptions): Promise<Observation[]>;
|
|
81
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/memory/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAwC,MAAM,oBAAoB,CAAC;AAmC5F,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,GAAG,EAAE,MAAM,CAAC;IACZ,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,KAAK,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAM3E;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,WAAW,EAAE,CAAC,CA6DxB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,WAAW,EAAE,CAAC,CAUxB"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads ranked memory packs for rendering above the message tail.
|
|
3
|
+
*
|
|
4
|
+
* The loader splits durable memory into two layers along a single axis —
|
|
5
|
+
* does the row's session_id match the current runner's session?
|
|
6
|
+
*
|
|
7
|
+
* - Local layer: rows whose session_id equals the current session. The
|
|
8
|
+
* current session's compacted view of itself (observations + the
|
|
9
|
+
* reflection that may have replaced them). Rendered chronologically
|
|
10
|
+
* and unranked because it represents the conversation's own
|
|
11
|
+
* compaction summary; reordering it would lose the time signal.
|
|
12
|
+
*
|
|
13
|
+
* - Global layer: every other session's rows, ranked by
|
|
14
|
+
* `priority * recencyDecay * kindBias`. Reflections rank higher than
|
|
15
|
+
* raw observations through `reflectionBias`. Greedy-fitted to a
|
|
16
|
+
* fixed token budget so the prompt prefix stays bounded regardless
|
|
17
|
+
* of how big the durable memory database has grown.
|
|
18
|
+
*
|
|
19
|
+
* The two layers are intentionally rendered with separate headings so
|
|
20
|
+
* the model can tell what comes from this conversation versus what
|
|
21
|
+
* comes from accumulated cross-session knowledge. The render order is
|
|
22
|
+
* `system prompt -> global -> local -> message history`: the global
|
|
23
|
+
* pack is the most stable prefix (rebuilt only on compaction), local
|
|
24
|
+
* is medium-stability (replaced on each reflection), and the message
|
|
25
|
+
* tail is the only part that grows turn over turn.
|
|
26
|
+
*/
|
|
27
|
+
const PRIORITY_WEIGHT = {
|
|
28
|
+
high: 3,
|
|
29
|
+
medium: 2,
|
|
30
|
+
low: 1,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Combined relevance score used to rank the global layer.
|
|
34
|
+
*
|
|
35
|
+
* score = priorityWeight * recencyDecay * kindBias
|
|
36
|
+
*
|
|
37
|
+
* - priorityWeight: 3 / 2 / 1 for high / medium / low.
|
|
38
|
+
* - recencyDecay: `0.5 ^ (ageMs / halfLifeMs)`. Equals 1.0 at age 0,
|
|
39
|
+
* 0.5 at one half-life, ~0.0625 at four half-lives. Smooth and
|
|
40
|
+
* parameterizable; no cliffs.
|
|
41
|
+
* - kindBias: `reflectionBias` for reflections, 1.0 for raw
|
|
42
|
+
* observations. Default 1.3 keeps reflections preferred at matched
|
|
43
|
+
* priority/recency without shutting raw observations out.
|
|
44
|
+
*
|
|
45
|
+
* Examples at the default 7d half-life and 1.3 reflection bias:
|
|
46
|
+
* - high reflection, 0d: 3 * 1.0 * 1.3 = 3.9
|
|
47
|
+
* - high observation, 0d: 3 * 1.0 * 1.0 = 3.0
|
|
48
|
+
* - medium reflection, 0d: 2 * 1.0 * 1.3 = 2.6
|
|
49
|
+
* - high reflection, 7d: 3 * 0.5 * 1.3 = 1.95
|
|
50
|
+
* - low observation, 30d: 1 * 0.0508 * 1.0 = 0.05 (effectively pruned)
|
|
51
|
+
*/
|
|
52
|
+
export function score(observation, inputs) {
|
|
53
|
+
const priorityWeight = PRIORITY_WEIGHT[observation.priority];
|
|
54
|
+
const ageMs = Math.max(0, inputs.now - observation.createdAt);
|
|
55
|
+
const recencyDecay = Math.pow(0.5, ageMs / inputs.halfLifeMs);
|
|
56
|
+
const kindBias = observation.kind === "reflection" ? inputs.reflectionBias : 1.0;
|
|
57
|
+
return priorityWeight * recencyDecay * kindBias;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Load the highest-scoring rows from every session except the caller's,
|
|
61
|
+
* up to the token budget. Returns rows ordered by descending score so
|
|
62
|
+
* the renderer can preserve the ranking visually.
|
|
63
|
+
*
|
|
64
|
+
* Implementation note: the database query orders by `created_at DESC`
|
|
65
|
+
* (cheap with the `idx_obs_kind_priority_created` index) and a generous
|
|
66
|
+
* over-fetch limit; the actual ranking happens in JS so the recency
|
|
67
|
+
* decay term can be applied without trying to express `0.5 ^ x` in
|
|
68
|
+
* portable SQL. With a 8k-token budget and ~250-token rows this means
|
|
69
|
+
* a worst case of ~32 rows packed; the over-fetch keeps the planner
|
|
70
|
+
* cheap even when the budget is set higher.
|
|
71
|
+
*/
|
|
72
|
+
export async function loadGlobalPack(db, options) {
|
|
73
|
+
const tokenBudget = options.tokenBudget ?? 8000;
|
|
74
|
+
const now = options.now ?? Date.now();
|
|
75
|
+
// Over-fetch by a comfortable factor so the JS-side scorer has enough
|
|
76
|
+
// candidates to find the top-N regardless of priority distribution.
|
|
77
|
+
// 4x the typical row count fitting in the budget keeps the SQL cheap
|
|
78
|
+
// while leaving headroom for a long tail of low-priority rows.
|
|
79
|
+
const overFetchLimit = Math.max(200, Math.ceil(tokenBudget / 50));
|
|
80
|
+
let result;
|
|
81
|
+
if (options.excludeSessionId === undefined) {
|
|
82
|
+
result = await db.query(`SELECT id, created_at, session_id, kind, observed_date, referenced_date,
|
|
83
|
+
relative_date, time_of_day, priority, source_json, content, tags_json
|
|
84
|
+
FROM observations
|
|
85
|
+
ORDER BY created_at DESC
|
|
86
|
+
LIMIT $1`, [overFetchLimit]);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// NULL session_id rows (legacy, pre-sessionId tracking) are kept in
|
|
90
|
+
// the global pool because they cannot match any current session.
|
|
91
|
+
// Without `IS DISTINCT FROM` Postgres would treat NULL = X as NULL,
|
|
92
|
+
// which filters those legacy rows out of every query.
|
|
93
|
+
result = await db.query(`SELECT id, created_at, session_id, kind, observed_date, referenced_date,
|
|
94
|
+
relative_date, time_of_day, priority, source_json, content, tags_json
|
|
95
|
+
FROM observations
|
|
96
|
+
WHERE session_id IS DISTINCT FROM $1
|
|
97
|
+
ORDER BY created_at DESC
|
|
98
|
+
LIMIT $2`, [options.excludeSessionId, overFetchLimit]);
|
|
99
|
+
}
|
|
100
|
+
const candidates = result.rows.map(rowToObservation);
|
|
101
|
+
const scoreInputs = {
|
|
102
|
+
now,
|
|
103
|
+
halfLifeMs: options.recencyHalfLifeMs,
|
|
104
|
+
reflectionBias: options.reflectionBias,
|
|
105
|
+
};
|
|
106
|
+
const ranked = candidates
|
|
107
|
+
.map((observation) => ({ observation, score: score(observation, scoreInputs) }))
|
|
108
|
+
.sort((a, b) => b.score - a.score);
|
|
109
|
+
const packed = [];
|
|
110
|
+
let usedTokens = 0;
|
|
111
|
+
for (const entry of ranked) {
|
|
112
|
+
const tokens = estimateTokens(entry.observation.content);
|
|
113
|
+
if (usedTokens + tokens > tokenBudget) {
|
|
114
|
+
// Skip oversize rows but keep iterating: a smaller row later in
|
|
115
|
+
// the ranking may still fit, which avoids leaving the budget
|
|
116
|
+
// half-empty just because one high-scoring row was a long
|
|
117
|
+
// reflection.
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
packed.push(entry.observation);
|
|
121
|
+
usedTokens += tokens;
|
|
122
|
+
}
|
|
123
|
+
return packed;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Load every row this session produced, ordered chronologically. The
|
|
127
|
+
* local pack represents the session's own compaction summary — the
|
|
128
|
+
* observations and reflections that replaced earlier transcript — so
|
|
129
|
+
* it is rendered whole regardless of size. Bounding the local layer
|
|
130
|
+
* is the existing observer/reflector pipeline's job: their thresholds
|
|
131
|
+
* keep this set from unbounded growth, and a reflection condenses it
|
|
132
|
+
* back down when it gets too large.
|
|
133
|
+
*/
|
|
134
|
+
export async function loadLocalPack(db, options) {
|
|
135
|
+
const result = await db.query(`SELECT id, created_at, session_id, kind, observed_date, referenced_date,
|
|
136
|
+
relative_date, time_of_day, priority, source_json, content, tags_json
|
|
137
|
+
FROM observations
|
|
138
|
+
WHERE session_id = $1
|
|
139
|
+
ORDER BY created_at ASC`, [options.sessionId]);
|
|
140
|
+
return result.rows.map(rowToObservation);
|
|
141
|
+
}
|
|
142
|
+
function rowToObservation(row) {
|
|
143
|
+
return {
|
|
144
|
+
id: row.id,
|
|
145
|
+
createdAt: row.created_at,
|
|
146
|
+
...(row.session_id !== null ? { sessionId: row.session_id } : {}),
|
|
147
|
+
kind: row.kind,
|
|
148
|
+
observedDate: row.observed_date,
|
|
149
|
+
...(row.referenced_date !== null ? { referencedDate: row.referenced_date } : {}),
|
|
150
|
+
...(row.relative_date !== null ? { relativeDate: row.relative_date } : {}),
|
|
151
|
+
...(row.time_of_day !== null ? { timeOfDay: row.time_of_day } : {}),
|
|
152
|
+
priority: row.priority,
|
|
153
|
+
source: JSON.parse(row.source_json),
|
|
154
|
+
content: row.content,
|
|
155
|
+
tags: JSON.parse(row.tags_json),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Rough char-to-token estimate matching the rest of the memory subsystem. */
|
|
159
|
+
function estimateTokens(text) {
|
|
160
|
+
return Math.ceil(text.length / 4);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/memory/loader.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,eAAe,GAAwC;IAC3D,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;CACP,CAAC;AAWF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,KAAK,CAAC,WAAwB,EAAE,MAAmB;IACjE,MAAM,cAAc,GAAG,eAAe,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC;IACjF,OAAO,cAAc,GAAG,YAAY,GAAG,QAAQ,CAAC;AAClD,CAAC;AAyBD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAU,EACV,OAA8B;IAE9B,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC;IAChD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IAEtC,sEAAsE;IACtE,oEAAoE;IACpE,qEAAqE;IACrE,+DAA+D;IAC/D,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC;IAElE,IAAI,MAA6B,CAAC;IAClC,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QAC3C,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CACrB;;;;gBAIU,EACV,CAAC,cAAc,CAAC,CACjB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,oEAAoE;QACpE,iEAAiE;QACjE,oEAAoE;QACpE,sDAAsD;QACtD,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CACrB;;;;;gBAKU,EACV,CAAC,OAAO,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAC3C,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACrD,MAAM,WAAW,GAAgB;QAC/B,GAAG;QACH,UAAU,EAAE,OAAO,CAAC,iBAAiB;QACrC,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC;IACF,MAAM,MAAM,GAAG,UAAU;SACtB,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;SAC/E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAErC,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,UAAU,GAAG,MAAM,GAAG,WAAW,EAAE,CAAC;YACtC,gEAAgE;YAChE,6DAA6D;YAC7D,0DAA0D;YAC1D,cAAc;YACd,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC/B,UAAU,IAAI,MAAM,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAMD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,EAAU,EACV,OAA6B;IAE7B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAC3B;;;;6BAIyB,EACzB,CAAC,OAAO,CAAC,SAAS,CAAC,CACpB,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAC3C,CAAC;AAiBD,SAAS,gBAAgB,CAAC,GAAc;IACtC,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,YAAY,EAAE,GAAG,CAAC,aAAa;QAC/B,GAAG,CAAC,GAAG,CAAC,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChF,GAAG,CAAC,GAAG,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAA0B;QAC5D,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAa;KAC5C,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { PGlite, Transaction } from "@electric-sql/pglite";
|
|
2
|
+
/**
|
|
3
|
+
* Forward-only schema migrations for the observational memory database.
|
|
4
|
+
*
|
|
5
|
+
* Why a real migration framework instead of bare `CREATE TABLE IF NOT
|
|
6
|
+
* EXISTS`: the memory schema is changing shape (adding `session_id` and
|
|
7
|
+
* `kind`, dropping `scope`, reshaping `source_json`) and a user upgrading
|
|
8
|
+
* Duet must have their existing rows transformed in place — dropping the
|
|
9
|
+
* column without rewriting the data would lose information, and there is
|
|
10
|
+
* no `duet memory migrate` CLI step we want to require.
|
|
11
|
+
*
|
|
12
|
+
* Design notes:
|
|
13
|
+
* - Migrations run inside `loadStoredMemory()` and `MemoryDb.open()` so
|
|
14
|
+
* both the runner and the `duet memory` CLI see the same upgraded
|
|
15
|
+
* schema. Failure aborts startup with a clear message; the underlying
|
|
16
|
+
* PGlite directory is also covered by `quarantineDataDirectory()` for
|
|
17
|
+
* unreadable cases (see `pglite.ts`).
|
|
18
|
+
* - Each migration runs inside a single transaction. Postgres DDL is
|
|
19
|
+
* transactional, so a partial failure rolls back to the prior version.
|
|
20
|
+
* - The `schema_version` table records every applied migration with its
|
|
21
|
+
* wall-clock timestamp. This is read once on open to find the current
|
|
22
|
+
* version; we never go backwards.
|
|
23
|
+
* - Forward-only by design. A bad migration is fixed by writing the next
|
|
24
|
+
* migration, not by reverting. This matches gbrain's pattern after
|
|
25
|
+
* they hit "upgrade-wedge" bugs across six schema versions.
|
|
26
|
+
*/
|
|
27
|
+
export interface Migration {
|
|
28
|
+
/** Monotonic version number. Migrations are applied strictly in ascending order. */
|
|
29
|
+
version: number;
|
|
30
|
+
/** One-line summary stored in `schema_version.description` for audit. */
|
|
31
|
+
description: string;
|
|
32
|
+
/** Forward step. Receives a transaction so DDL + DML run atomically. */
|
|
33
|
+
up: (tx: Transaction) => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export interface MigrationResult {
|
|
36
|
+
/** Schema version the database was at before `runMigrations` was called. */
|
|
37
|
+
fromVersion: number;
|
|
38
|
+
/** Schema version the database is at after `runMigrations` returns. */
|
|
39
|
+
toVersion: number;
|
|
40
|
+
/** Versions actually applied this call (empty when already up to date). */
|
|
41
|
+
applied: number[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Bring the database up to the latest known schema version. Idempotent;
|
|
45
|
+
* safe to call on every open. Throws on the first failure with the
|
|
46
|
+
* triggering migration's version in the message so users have a place to
|
|
47
|
+
* look without grepping.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runMigrations(db: PGlite): Promise<MigrationResult>;
|
|
50
|
+
/** Latest version known to this build. Exported for tests. */
|
|
51
|
+
export declare const LATEST_SCHEMA_VERSION: number;
|
|
52
|
+
/** All registered migrations. Exported for tests. */
|
|
53
|
+
export declare const REGISTERED_MIGRATIONS: readonly Migration[];
|
|
54
|
+
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/memory/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,SAAS;IACxB,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAiLD,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAwCxE;AAED,8DAA8D;AAC9D,eAAO,MAAM,qBAAqB,EAAE,MAAwC,CAAC;AAE7E,qDAAqD;AACrD,eAAO,MAAM,qBAAqB,EAAE,SAAS,SAAS,EAAe,CAAC"}
|