@felan-ai/felan 0.12.7 → 0.12.9
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/NOTICE +4 -0
- package/README.md +86 -4
- package/dist/application.d.ts.map +1 -1
- package/dist/application.js +5 -1
- package/dist/application.js.map +1 -1
- package/dist/extensions.d.ts +9 -2
- package/dist/extensions.d.ts.map +1 -1
- package/dist/extensions.js +9 -1
- package/dist/extensions.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lock.d.ts +15 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +169 -0
- package/dist/lock.js.map +1 -0
- package/dist/memory/control.d.ts +8 -0
- package/dist/memory/control.d.ts.map +1 -0
- package/dist/memory/control.js +106 -0
- package/dist/memory/control.js.map +1 -0
- package/dist/memory/coordinator.d.ts +38 -0
- package/dist/memory/coordinator.d.ts.map +1 -0
- package/dist/memory/coordinator.js +396 -0
- package/dist/memory/coordinator.js.map +1 -0
- package/dist/memory/dreamer.d.ts +41 -0
- package/dist/memory/dreamer.d.ts.map +1 -0
- package/dist/memory/dreamer.js +531 -0
- package/dist/memory/dreamer.js.map +1 -0
- package/dist/memory/lease.d.ts +13 -0
- package/dist/memory/lease.d.ts.map +1 -0
- package/dist/memory/lease.js +188 -0
- package/dist/memory/lease.js.map +1 -0
- package/dist/memory/project.d.ts +7 -0
- package/dist/memory/project.d.ts.map +1 -0
- package/dist/memory/project.js +41 -0
- package/dist/memory/project.js.map +1 -0
- package/dist/memory/store.d.ts +52 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +344 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +68 -10
- package/dist/runtime.js.map +1 -1
- package/dist/settings.d.ts +3 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +61 -27
- package/dist/settings.js.map +1 -1
- package/dist/startup-header.d.ts +1 -0
- package/dist/startup-header.d.ts.map +1 -1
- package/dist/startup-header.js +40 -0
- package/dist/startup-header.js.map +1 -1
- package/dist/subagents/agent-navigator.d.ts.map +1 -1
- package/dist/subagents/agent-navigator.js +11 -2
- package/dist/subagents/agent-navigator.js.map +1 -1
- package/dist/subagents/agent-transcript.d.ts.map +1 -1
- package/dist/subagents/agent-transcript.js +14 -2
- package/dist/subagents/agent-transcript.js.map +1 -1
- package/dist/subagents/catalog.d.ts.map +1 -1
- package/dist/subagents/catalog.js +4 -2
- package/dist/subagents/catalog.js.map +1 -1
- package/dist/subagents/host.d.ts +6 -0
- package/dist/subagents/host.d.ts.map +1 -1
- package/dist/subagents/host.js +11 -7
- package/dist/subagents/host.js.map +1 -1
- package/dist/tool-activity/presentation.d.ts.map +1 -1
- package/dist/tool-activity/presentation.js +205 -4
- package/dist/tool-activity/presentation.js.map +1 -1
- package/dist/tool-activity/runtime-view.d.ts +1 -0
- package/dist/tool-activity/runtime-view.d.ts.map +1 -1
- package/dist/tool-activity/runtime-view.js +13 -2
- package/dist/tool-activity/runtime-view.js.map +1 -1
- package/dist/tool-activity/state.d.ts +3 -1
- package/dist/tool-activity/state.d.ts.map +1 -1
- package/dist/tool-activity/state.js +6 -1
- package/dist/tool-activity/state.js.map +1 -1
- package/package.json +8 -7
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, readFile, readdir, rename, rm, utimes, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import { acquireLocalFileLock } from '../lock.js';
|
|
5
|
+
const DEFAULT_STALE_MS = 30_000;
|
|
6
|
+
const DEFAULT_UPDATE_MS = 10_000;
|
|
7
|
+
export async function acquireLocalMemoryLease(projectDirectory, options = {}) {
|
|
8
|
+
const target = join(projectDirectory, 'writer');
|
|
9
|
+
const gateTarget = join(projectDirectory, 'writer.gate');
|
|
10
|
+
await mkdir(dirname(target), { recursive: true });
|
|
11
|
+
await writeFile(target, '', { flag: 'a', mode: 0o600 });
|
|
12
|
+
await writeFile(gateTarget, '', { flag: 'a', mode: 0o600 });
|
|
13
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
14
|
+
let gateLock;
|
|
15
|
+
try {
|
|
16
|
+
gateLock = await acquireLocalFileLock(gateTarget, {
|
|
17
|
+
realpath: false,
|
|
18
|
+
stale: staleMs,
|
|
19
|
+
update: options.updateMs ?? DEFAULT_UPDATE_MS,
|
|
20
|
+
retries: 0,
|
|
21
|
+
preservePathOnRelease: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (isLocked(error))
|
|
26
|
+
return undefined;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
let fileLock;
|
|
30
|
+
try {
|
|
31
|
+
await recoverStaleLegacyLock(target, staleMs);
|
|
32
|
+
fileLock = await acquireLocalFileLock(target, {
|
|
33
|
+
realpath: false,
|
|
34
|
+
stale: staleMs,
|
|
35
|
+
update: options.updateMs ?? DEFAULT_UPDATE_MS,
|
|
36
|
+
retries: 0,
|
|
37
|
+
preservePathOnRelease: true,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
await gateLock.release().catch(() => { });
|
|
42
|
+
await gateLock.retire(staleMs);
|
|
43
|
+
if (isLocked(error))
|
|
44
|
+
return undefined;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
const token = randomUUID();
|
|
48
|
+
const acquiredAt = new Date().toISOString();
|
|
49
|
+
const ownerPath = `${target}.owner.${token}.json`;
|
|
50
|
+
const temporaryOwnerPath = `${ownerPath}.${token}.tmp`;
|
|
51
|
+
const compromiseController = new AbortController();
|
|
52
|
+
const onCompromise = () => {
|
|
53
|
+
compromiseController.abort(new Error('Memory writer lease was lost'));
|
|
54
|
+
};
|
|
55
|
+
gateLock.compromised.addEventListener('abort', onCompromise, { once: true });
|
|
56
|
+
fileLock.compromised.addEventListener('abort', onCompromise, { once: true });
|
|
57
|
+
try {
|
|
58
|
+
await writeFile(temporaryOwnerPath, `${JSON.stringify({ token, acquiredAt, pid: process.pid })}\n`, {
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
mode: 0o600,
|
|
61
|
+
flag: 'wx',
|
|
62
|
+
});
|
|
63
|
+
await rename(temporaryOwnerPath, ownerPath);
|
|
64
|
+
gateLock.throwIfCompromised('Memory writer lease was lost during acquisition');
|
|
65
|
+
fileLock.throwIfCompromised('Memory writer lease was lost during acquisition');
|
|
66
|
+
if (!(await gateLock.isCurrent()) || !(await fileLock.isCurrent())) {
|
|
67
|
+
throw new Error('Memory writer lease was lost during acquisition');
|
|
68
|
+
}
|
|
69
|
+
await garbageCollectOwnerFiles(target, ownerPath);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
await rm(temporaryOwnerPath, { force: true }).catch(() => { });
|
|
73
|
+
await removeOwnerIfToken(ownerPath, token);
|
|
74
|
+
await fileLock.release().catch(() => { });
|
|
75
|
+
await fileLock.retire(staleMs);
|
|
76
|
+
gateLock.compromised.removeEventListener('abort', onCompromise);
|
|
77
|
+
fileLock.compromised.removeEventListener('abort', onCompromise);
|
|
78
|
+
await gateLock.release().catch(() => { });
|
|
79
|
+
await gateLock.retire(staleMs);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
let released = false;
|
|
83
|
+
return {
|
|
84
|
+
token,
|
|
85
|
+
acquiredAt,
|
|
86
|
+
compromised: compromiseController.signal,
|
|
87
|
+
async verify() {
|
|
88
|
+
if (released || fileLock.isCompromised())
|
|
89
|
+
return false;
|
|
90
|
+
if (gateLock.isCompromised())
|
|
91
|
+
return false;
|
|
92
|
+
if (!(await gateLock.isCurrent()) || !(await fileLock.isCurrent()))
|
|
93
|
+
return false;
|
|
94
|
+
try {
|
|
95
|
+
const owner = JSON.parse(await readFile(ownerPath, 'utf8'));
|
|
96
|
+
return owner.token === token;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
async release() {
|
|
103
|
+
if (released)
|
|
104
|
+
return;
|
|
105
|
+
released = true;
|
|
106
|
+
let ownsOwnerRecord = false;
|
|
107
|
+
try {
|
|
108
|
+
const owner = JSON.parse(await readFile(ownerPath, 'utf8'));
|
|
109
|
+
ownsOwnerRecord = owner.token === token;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// A missing owner record is fenced and must not release a possible successor lock.
|
|
113
|
+
}
|
|
114
|
+
if (ownsOwnerRecord)
|
|
115
|
+
await rm(ownerPath, { force: true }).catch(() => { });
|
|
116
|
+
// LocalFileLock guards the underlying directory identity before rmdir, so
|
|
117
|
+
// releasing a fenced handle stops its heartbeat without removing a successor.
|
|
118
|
+
await fileLock.release().catch(() => { });
|
|
119
|
+
await fileLock.retire(staleMs);
|
|
120
|
+
gateLock.compromised.removeEventListener('abort', onCompromise);
|
|
121
|
+
fileLock.compromised.removeEventListener('abort', onCompromise);
|
|
122
|
+
await gateLock.release().catch(() => { });
|
|
123
|
+
await gateLock.retire(staleMs);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function garbageCollectOwnerFiles(target, currentOwnerPath) {
|
|
128
|
+
const directory = dirname(target);
|
|
129
|
+
const prefix = `${basename(target)}.owner.`;
|
|
130
|
+
let entries;
|
|
131
|
+
try {
|
|
132
|
+
entries = await readdir(directory);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (isMissing(error))
|
|
136
|
+
return;
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
await Promise.all(entries
|
|
140
|
+
.filter((entry) => entry.startsWith(prefix) && join(directory, entry) !== currentOwnerPath)
|
|
141
|
+
.map((entry) => rm(join(directory, entry), { force: true })));
|
|
142
|
+
}
|
|
143
|
+
async function recoverStaleLegacyLock(target, staleMs) {
|
|
144
|
+
const lockPath = `${target}.lock`;
|
|
145
|
+
const ownerPath = join(lockPath, 'owner.json');
|
|
146
|
+
const effectiveStaleMs = Math.max(staleMs, 2_000);
|
|
147
|
+
const initial = await safeLstat(lockPath);
|
|
148
|
+
if (!initial?.isDirectory() || initial.mtimeMs >= Date.now() - effectiveStaleMs)
|
|
149
|
+
return;
|
|
150
|
+
const owner = await safeLstat(ownerPath);
|
|
151
|
+
if (!owner?.isFile())
|
|
152
|
+
return;
|
|
153
|
+
const confirmed = await safeLstat(lockPath);
|
|
154
|
+
if (!confirmed?.isDirectory()
|
|
155
|
+
|| confirmed.mtimeMs !== initial.mtimeMs
|
|
156
|
+
|| confirmed.mtimeMs >= Date.now() - effectiveStaleMs)
|
|
157
|
+
return;
|
|
158
|
+
await rm(ownerPath, { force: true });
|
|
159
|
+
const retiredAt = new Date(Date.now() - effectiveStaleMs - 1_000);
|
|
160
|
+
await utimes(lockPath, retiredAt, retiredAt).catch(() => { });
|
|
161
|
+
}
|
|
162
|
+
async function removeOwnerIfToken(ownerPath, token) {
|
|
163
|
+
try {
|
|
164
|
+
const owner = JSON.parse(await readFile(ownerPath, 'utf8'));
|
|
165
|
+
if (owner.token === token)
|
|
166
|
+
await rm(ownerPath, { force: true });
|
|
167
|
+
}
|
|
168
|
+
catch { }
|
|
169
|
+
}
|
|
170
|
+
async function safeLstat(path) {
|
|
171
|
+
try {
|
|
172
|
+
return await lstat(path);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (isMissing(error))
|
|
176
|
+
return undefined;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function isLocked(error) {
|
|
181
|
+
return typeof error === 'object'
|
|
182
|
+
&& error !== null
|
|
183
|
+
&& Reflect.get(error, 'code') === 'ELOCKED';
|
|
184
|
+
}
|
|
185
|
+
function isMissing(error) {
|
|
186
|
+
return typeof error === 'object' && error !== null && Reflect.get(error, 'code') === 'ENOENT';
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.js","sourceRoot":"","sources":["../../src/memory/lease.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface LocalMemoryProject {
|
|
2
|
+
readonly canonicalRoot: string;
|
|
3
|
+
readonly key: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function resolveLocalMemoryProject(cwd: string): Promise<LocalMemoryProject>;
|
|
6
|
+
export declare function localMemoryProjectDirectory(agentDir: string, project: LocalMemoryProject): string;
|
|
7
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/memory/project.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAQxF;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAEjG"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { realpath } from 'node:fs/promises';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
export async function resolveLocalMemoryProject(cwd) {
|
|
8
|
+
const canonicalCwd = await canonicalPath(cwd);
|
|
9
|
+
const gitRoot = await discoverGitRoot(canonicalCwd);
|
|
10
|
+
const canonicalRoot = await canonicalPath(gitRoot ?? canonicalCwd);
|
|
11
|
+
return {
|
|
12
|
+
canonicalRoot,
|
|
13
|
+
key: createHash('sha256').update(canonicalRoot, 'utf8').digest('hex'),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function localMemoryProjectDirectory(agentDir, project) {
|
|
17
|
+
return resolve(agentDir, 'memory', 'v1', 'projects', project.key);
|
|
18
|
+
}
|
|
19
|
+
async function discoverGitRoot(cwd) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await execFileAsync('git', ['-C', cwd, 'rev-parse', '--show-toplevel'], {
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
timeout: 5_000,
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
});
|
|
26
|
+
const root = result.stdout.trim();
|
|
27
|
+
return root.length > 0 ? root : undefined;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function canonicalPath(path) {
|
|
34
|
+
try {
|
|
35
|
+
return await realpath(resolve(path));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return resolve(path);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/memory/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAO1C,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,GAAW;IACzD,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,YAAY,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,MAAM,aAAa,CAAC,OAAO,IAAI,YAAY,CAAC,CAAC;IACnE,OAAO;QACL,aAAa;QACb,GAAG,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KACtE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,QAAgB,EAAE,OAA2B;IACvF,OAAO,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;AACpE,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE;YACrF,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type MemoryArtifact, type MemorySnapshot, type SessionCheckpoint } from '@felan-ai/ext-memory';
|
|
2
|
+
import type { LocalMemoryProject } from './project.js';
|
|
3
|
+
import { type LocalMemoryLease } from './lease.js';
|
|
4
|
+
declare const STATE_VERSION: 1;
|
|
5
|
+
export interface StoredCheckpoint {
|
|
6
|
+
readonly checkpoint: SessionCheckpoint;
|
|
7
|
+
readonly recordedAt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ProcessedCheckpoint extends StoredCheckpoint {
|
|
10
|
+
readonly processedAt: string;
|
|
11
|
+
readonly memoryFingerprint: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LocalMemoryState {
|
|
14
|
+
readonly version: typeof STATE_VERSION;
|
|
15
|
+
readonly memoryFingerprint: string;
|
|
16
|
+
readonly pending: Readonly<Record<string, StoredCheckpoint>>;
|
|
17
|
+
readonly processed: Readonly<Record<string, ProcessedCheckpoint>>;
|
|
18
|
+
readonly updatedAt: string;
|
|
19
|
+
}
|
|
20
|
+
export interface LocalMemoryProcessingSnapshot {
|
|
21
|
+
readonly artifact: MemoryArtifact;
|
|
22
|
+
readonly fingerprint: string;
|
|
23
|
+
readonly checkpoints: readonly StoredCheckpoint[];
|
|
24
|
+
readonly state: LocalMemoryState;
|
|
25
|
+
}
|
|
26
|
+
export interface LocalMemoryStoreOptions {
|
|
27
|
+
readonly memoryPath?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class LocalMemoryStore {
|
|
30
|
+
#private;
|
|
31
|
+
readonly agentDir: string;
|
|
32
|
+
readonly project: LocalMemoryProject;
|
|
33
|
+
readonly projectDirectory: string;
|
|
34
|
+
readonly currentDirectory: string;
|
|
35
|
+
readonly stagingDirectory: string;
|
|
36
|
+
readonly statePath: string;
|
|
37
|
+
readonly projectionName = ".memory";
|
|
38
|
+
constructor(agentDir: string, project: LocalMemoryProject, options?: LocalMemoryStoreOptions);
|
|
39
|
+
initialize(): Promise<void>;
|
|
40
|
+
readCurrent(): Promise<MemorySnapshot>;
|
|
41
|
+
projectTo(sessionStorageRoot: string, snapshot?: MemorySnapshot): Promise<MemorySnapshot>;
|
|
42
|
+
recordCheckpoint(checkpoint: SessionCheckpoint): Promise<boolean>;
|
|
43
|
+
status(): Promise<LocalMemoryState>;
|
|
44
|
+
processingSnapshot(maxSessions?: number): Promise<LocalMemoryProcessingSnapshot>;
|
|
45
|
+
createStagingDirectory(): Promise<string>;
|
|
46
|
+
commit(lease: LocalMemoryLease, baseFingerprint: string, artifact: MemoryArtifact, processed: readonly StoredCheckpoint[]): Promise<string>;
|
|
47
|
+
clearStaging(): Promise<void>;
|
|
48
|
+
get memoryPath(): string;
|
|
49
|
+
private readState;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,cAAc,EAEnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG5E,QAAA,MAAM,aAAa,EAAG,CAAU,CAAC;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAC3D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,aAAa,CAAC;IACvC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,qBAAa,gBAAgB;;IASzB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,OAAO,EAAE,kBAAkB;IATtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,aAAa;gBAIzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,EACpC,OAAO,GAAE,uBAA4B;IASjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B,WAAW,IAAI,OAAO,CAAC,cAAc,CAAC;IAOtC,SAAS,CACb,kBAAkB,EAAE,MAAM,EAC1B,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,cAAc,CAAC;IAQpB,gBAAgB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBjE,MAAM,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAInC,kBAAkB,CAAC,WAAW,SAAI,GAAG,OAAO,CAAC,6BAA6B,CAAC;IAY3E,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IAOzC,MAAM,CACV,KAAK,EAAE,gBAAgB,EACvB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,OAAO,CAAC,MAAM,CAAC;IAiEZ,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAKnC,IAAI,UAAU,IAAI,MAAM,CAEvB;YAEa,SAAS;CAKxB"}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile, lstat } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { createEmptyMemoryArtifact, createMemoryProjectionSnapshot, createMemorySnapshot, hydrateMemoryDirectory, memoryArtifactFingerprint, readMemoryDirectory, } from '@felan-ai/ext-memory';
|
|
5
|
+
import { localMemoryProjectDirectory } from './project.js';
|
|
6
|
+
import { acquireLocalMemoryLease } from './lease.js';
|
|
7
|
+
import { withLocalFileLock } from '../lock.js';
|
|
8
|
+
const STATE_VERSION = 1;
|
|
9
|
+
export class LocalMemoryStore {
|
|
10
|
+
agentDir;
|
|
11
|
+
project;
|
|
12
|
+
projectDirectory;
|
|
13
|
+
currentDirectory;
|
|
14
|
+
stagingDirectory;
|
|
15
|
+
statePath;
|
|
16
|
+
projectionName = '.memory';
|
|
17
|
+
#memoryPath;
|
|
18
|
+
constructor(agentDir, project, options = {}) {
|
|
19
|
+
this.agentDir = agentDir;
|
|
20
|
+
this.project = project;
|
|
21
|
+
this.projectDirectory = localMemoryProjectDirectory(agentDir, project);
|
|
22
|
+
this.currentDirectory = join(this.projectDirectory, 'current');
|
|
23
|
+
this.stagingDirectory = join(this.projectDirectory, 'staging');
|
|
24
|
+
this.statePath = join(this.projectDirectory, 'state.json');
|
|
25
|
+
this.#memoryPath = options.memoryPath ?? '.memory';
|
|
26
|
+
}
|
|
27
|
+
async initialize() {
|
|
28
|
+
await mkdir(this.projectDirectory, { recursive: true, mode: 0o700 });
|
|
29
|
+
await mkdir(this.stagingDirectory, { recursive: true, mode: 0o700 });
|
|
30
|
+
const recoveryLease = await acquireLocalMemoryLease(this.projectDirectory);
|
|
31
|
+
try {
|
|
32
|
+
await withStateLock(this, async () => {
|
|
33
|
+
if (recoveryLease)
|
|
34
|
+
await recoverTransaction(this);
|
|
35
|
+
const current = await safeLstat(this.currentDirectory);
|
|
36
|
+
if (current?.isSymbolicLink())
|
|
37
|
+
throw new Error('Canonical memory current directory cannot be a symlink');
|
|
38
|
+
if (!current) {
|
|
39
|
+
await hydrateMemoryDirectory(createEmptyMemoryArtifact(this.#memoryPath), this.currentDirectory);
|
|
40
|
+
}
|
|
41
|
+
const artifact = await readMemoryDirectory(this.currentDirectory, { memoryPath: this.#memoryPath });
|
|
42
|
+
const fingerprint = memoryArtifactFingerprint(artifact);
|
|
43
|
+
const existing = await readState(this.statePath);
|
|
44
|
+
if (!existing || existing.memoryFingerprint !== fingerprint) {
|
|
45
|
+
await writeState(this.statePath, {
|
|
46
|
+
version: STATE_VERSION,
|
|
47
|
+
memoryFingerprint: fingerprint,
|
|
48
|
+
pending: existing?.pending ?? {},
|
|
49
|
+
processed: existing?.processed ?? {},
|
|
50
|
+
updatedAt: new Date().toISOString(),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await recoveryLease?.release();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async readCurrent() {
|
|
60
|
+
return withStateLock(this, async () => {
|
|
61
|
+
const artifact = await readMemoryDirectory(this.currentDirectory, { memoryPath: this.#memoryPath });
|
|
62
|
+
return createMemorySnapshot(artifact, this.#memoryPath);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async projectTo(sessionStorageRoot, snapshot) {
|
|
66
|
+
const current = snapshot ?? await this.readCurrent();
|
|
67
|
+
const target = join(sessionStorageRoot, this.projectionName);
|
|
68
|
+
const projection = createMemoryProjectionSnapshot(current, target);
|
|
69
|
+
await hydrateMemoryDirectory(projection, target, { replace: true, memoryPath: target });
|
|
70
|
+
return projection;
|
|
71
|
+
}
|
|
72
|
+
async recordCheckpoint(checkpoint) {
|
|
73
|
+
return withStateLock(this, async () => {
|
|
74
|
+
const state = await this.readState();
|
|
75
|
+
const previousPending = state.pending[checkpoint.sessionId];
|
|
76
|
+
const previousProcessed = state.processed[checkpoint.sessionId];
|
|
77
|
+
if (sameCursor(previousPending?.checkpoint, checkpoint) || sameCursor(previousProcessed?.checkpoint, checkpoint)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const pending = {
|
|
81
|
+
...state.pending,
|
|
82
|
+
[checkpoint.sessionId]: {
|
|
83
|
+
checkpoint,
|
|
84
|
+
recordedAt: new Date().toISOString(),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
await writeState(this.statePath, { ...state, pending, updatedAt: new Date().toISOString() });
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async status() {
|
|
92
|
+
return this.readState();
|
|
93
|
+
}
|
|
94
|
+
async processingSnapshot(maxSessions = 8) {
|
|
95
|
+
return withStateLock(this, async () => {
|
|
96
|
+
const state = await this.readState();
|
|
97
|
+
const artifact = await readMemoryDirectory(this.currentDirectory, { memoryPath: this.#memoryPath });
|
|
98
|
+
const fingerprint = memoryArtifactFingerprint(artifact);
|
|
99
|
+
const checkpoints = Object.values(state.pending)
|
|
100
|
+
.sort((left, right) => left.recordedAt.localeCompare(right.recordedAt))
|
|
101
|
+
.slice(0, maxSessions);
|
|
102
|
+
return { artifact, fingerprint, checkpoints, state };
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async createStagingDirectory() {
|
|
106
|
+
await mkdir(this.stagingDirectory, { recursive: true, mode: 0o700 });
|
|
107
|
+
const path = join(this.stagingDirectory, `run-${randomUUID()}`);
|
|
108
|
+
await mkdir(path, { recursive: true, mode: 0o700 });
|
|
109
|
+
return path;
|
|
110
|
+
}
|
|
111
|
+
async commit(lease, baseFingerprint, artifact, processed) {
|
|
112
|
+
if (!(await lease.verify()))
|
|
113
|
+
throw new Error('Memory writer lease was lost before commit');
|
|
114
|
+
const fingerprint = memoryArtifactFingerprint(artifact);
|
|
115
|
+
const staging = await this.createStagingDirectory();
|
|
116
|
+
const stagedMemory = join(staging, 'memory');
|
|
117
|
+
const previousMemory = join(staging, 'previous');
|
|
118
|
+
const journalPath = join(staging, 'commit.json');
|
|
119
|
+
await hydrateMemoryDirectory(artifact, stagedMemory, { memoryPath: this.#memoryPath });
|
|
120
|
+
await writeFile(journalPath, `${JSON.stringify({
|
|
121
|
+
version: 1,
|
|
122
|
+
baseFingerprint,
|
|
123
|
+
fingerprint,
|
|
124
|
+
processed: processed.map(({ checkpoint }) => checkpoint.sessionId),
|
|
125
|
+
})}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
126
|
+
if (!(await lease.verify()))
|
|
127
|
+
throw new Error('Memory writer lease was lost before replacement');
|
|
128
|
+
try {
|
|
129
|
+
await withStateLock(this, async () => {
|
|
130
|
+
const current = await readMemoryDirectory(this.currentDirectory, { memoryPath: this.#memoryPath });
|
|
131
|
+
if (memoryArtifactFingerprint(current) !== baseFingerprint) {
|
|
132
|
+
throw new Error('Memory changed while the dream was running');
|
|
133
|
+
}
|
|
134
|
+
if (!(await lease.verify()))
|
|
135
|
+
throw new Error('Memory writer lease was lost before replacement');
|
|
136
|
+
await rename(this.currentDirectory, previousMemory);
|
|
137
|
+
try {
|
|
138
|
+
await rename(stagedMemory, this.currentDirectory);
|
|
139
|
+
if (!(await lease.verify()))
|
|
140
|
+
throw new Error('Memory writer lease was lost after replacement');
|
|
141
|
+
const state = await this.readState();
|
|
142
|
+
if (!(await lease.verify()))
|
|
143
|
+
throw new Error('Memory writer lease was lost before state commit');
|
|
144
|
+
if (state.memoryFingerprint !== baseFingerprint)
|
|
145
|
+
throw new Error('Memory state changed while the dream was running');
|
|
146
|
+
const processedAt = new Date().toISOString();
|
|
147
|
+
const pending = { ...state.pending };
|
|
148
|
+
const nextProcessed = { ...state.processed };
|
|
149
|
+
for (const entry of processed) {
|
|
150
|
+
const currentPending = pending[entry.checkpoint.sessionId];
|
|
151
|
+
if (!currentPending || !sameCursor(currentPending.checkpoint, entry.checkpoint))
|
|
152
|
+
continue;
|
|
153
|
+
delete pending[entry.checkpoint.sessionId];
|
|
154
|
+
nextProcessed[entry.checkpoint.sessionId] = {
|
|
155
|
+
...entry,
|
|
156
|
+
processedAt,
|
|
157
|
+
memoryFingerprint: fingerprint,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
await writeState(this.statePath, {
|
|
161
|
+
version: STATE_VERSION,
|
|
162
|
+
memoryFingerprint: fingerprint,
|
|
163
|
+
pending,
|
|
164
|
+
processed: nextProcessed,
|
|
165
|
+
updatedAt: processedAt,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const replacement = await safeLstat(this.currentDirectory);
|
|
170
|
+
if (replacement)
|
|
171
|
+
await rm(this.currentDirectory, { recursive: true, force: true });
|
|
172
|
+
await rename(previousMemory, this.currentDirectory).catch(() => { });
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
await rm(previousMemory, { recursive: true, force: true });
|
|
181
|
+
await rm(staging, { recursive: true, force: true });
|
|
182
|
+
return fingerprint;
|
|
183
|
+
}
|
|
184
|
+
async clearStaging() {
|
|
185
|
+
await rm(this.stagingDirectory, { recursive: true, force: true });
|
|
186
|
+
await mkdir(this.stagingDirectory, { recursive: true, mode: 0o700 });
|
|
187
|
+
}
|
|
188
|
+
get memoryPath() {
|
|
189
|
+
return this.#memoryPath;
|
|
190
|
+
}
|
|
191
|
+
async readState() {
|
|
192
|
+
const state = await readState(this.statePath);
|
|
193
|
+
if (!state)
|
|
194
|
+
throw new Error('Local memory state is missing or invalid');
|
|
195
|
+
return state;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function recoverTransaction(store) {
|
|
199
|
+
const entries = await safeReadDir(store.stagingDirectory);
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
const path = join(store.stagingDirectory, entry);
|
|
202
|
+
const journalPath = join(path, 'commit.json');
|
|
203
|
+
const journal = await readJson(journalPath);
|
|
204
|
+
if (!journal) {
|
|
205
|
+
await rm(path, { recursive: true, force: true });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const current = await safeLstat(store.currentDirectory);
|
|
209
|
+
const memory = join(path, 'memory');
|
|
210
|
+
const previous = join(path, 'previous');
|
|
211
|
+
if (current && typeof journal.fingerprint === 'string'
|
|
212
|
+
&& await fingerprintAt(store.currentDirectory, store.memoryPath) === journal.fingerprint) {
|
|
213
|
+
await applyRecoveredStateUnlocked(store, journal.fingerprint, journal.processed);
|
|
214
|
+
await rm(path, { recursive: true, force: true });
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (!current && await safeLstat(previous)) {
|
|
218
|
+
await rename(previous, store.currentDirectory);
|
|
219
|
+
}
|
|
220
|
+
await rm(path, { recursive: true, force: true });
|
|
221
|
+
void memory;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function applyRecoveredStateUnlocked(store, fingerprint, sessionIds) {
|
|
225
|
+
if (!Array.isArray(sessionIds))
|
|
226
|
+
return;
|
|
227
|
+
const state = await readState(store.statePath);
|
|
228
|
+
if (!state)
|
|
229
|
+
return;
|
|
230
|
+
const pending = { ...state.pending };
|
|
231
|
+
const processed = { ...state.processed };
|
|
232
|
+
const processedAt = new Date().toISOString();
|
|
233
|
+
for (const sessionId of sessionIds) {
|
|
234
|
+
if (typeof sessionId !== 'string')
|
|
235
|
+
continue;
|
|
236
|
+
const entry = pending[sessionId];
|
|
237
|
+
if (!entry)
|
|
238
|
+
continue;
|
|
239
|
+
delete pending[sessionId];
|
|
240
|
+
processed[sessionId] = { ...entry, processedAt, memoryFingerprint: fingerprint };
|
|
241
|
+
}
|
|
242
|
+
await writeState(store.statePath, { ...state, memoryFingerprint: fingerprint, pending, processed, updatedAt: processedAt });
|
|
243
|
+
}
|
|
244
|
+
async function withStateLock(store, operation) {
|
|
245
|
+
await mkdir(dirname(store.statePath), { recursive: true });
|
|
246
|
+
try {
|
|
247
|
+
await writeFile(store.statePath, '{}', { flag: 'wx', mode: 0o600 });
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (!isAlreadyExists(error))
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
return withLocalFileLock(store.statePath, {
|
|
254
|
+
realpath: false,
|
|
255
|
+
retries: { retries: 20, minTimeout: 10, maxTimeout: 100 },
|
|
256
|
+
}, operationWithLock(operation));
|
|
257
|
+
}
|
|
258
|
+
function operationWithLock(operation) {
|
|
259
|
+
return async (lock) => {
|
|
260
|
+
const result = await operation();
|
|
261
|
+
lock.throwIfCompromised();
|
|
262
|
+
return result;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function writeState(path, state) {
|
|
266
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
267
|
+
await writeFile(temporary, `${JSON.stringify(state, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
268
|
+
await rename(temporary, path);
|
|
269
|
+
}
|
|
270
|
+
async function readState(path) {
|
|
271
|
+
try {
|
|
272
|
+
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
273
|
+
if (!isRecord(value) || value.version !== STATE_VERSION || typeof value.memoryFingerprint !== 'string'
|
|
274
|
+
|| !isRecord(value.pending) || !isRecord(value.processed) || typeof value.updatedAt !== 'string')
|
|
275
|
+
return undefined;
|
|
276
|
+
return {
|
|
277
|
+
version: STATE_VERSION,
|
|
278
|
+
memoryFingerprint: value.memoryFingerprint,
|
|
279
|
+
pending: value.pending,
|
|
280
|
+
processed: value.processed,
|
|
281
|
+
updatedAt: value.updatedAt,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (isMissing(error))
|
|
286
|
+
return undefined;
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function fingerprintAt(path, memoryPath) {
|
|
291
|
+
try {
|
|
292
|
+
return memoryArtifactFingerprint(await readMemoryDirectory(path, { memoryPath }));
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (isMissing(error))
|
|
296
|
+
return undefined;
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function readJson(path) {
|
|
301
|
+
try {
|
|
302
|
+
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
303
|
+
return isRecord(value) ? value : undefined;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
if (isMissing(error))
|
|
307
|
+
return undefined;
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async function safeReadDir(path) {
|
|
312
|
+
try {
|
|
313
|
+
const { readdir } = await import('node:fs/promises');
|
|
314
|
+
return await readdir(path);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
if (isMissing(error))
|
|
318
|
+
return [];
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function safeLstat(path) {
|
|
323
|
+
try {
|
|
324
|
+
return await lstat(path);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
if (isMissing(error))
|
|
328
|
+
return undefined;
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function sameCursor(left, right) {
|
|
333
|
+
return left?.leafId === right.leafId && left?.transcriptDigest === right.transcriptDigest;
|
|
334
|
+
}
|
|
335
|
+
function isMissing(error) {
|
|
336
|
+
return typeof error === 'object' && error !== null && Reflect.get(error, 'code') === 'ENOENT';
|
|
337
|
+
}
|
|
338
|
+
function isAlreadyExists(error) {
|
|
339
|
+
return typeof error === 'object' && error !== null && Reflect.get(error, 'code') === 'EEXIST';
|
|
340
|
+
}
|
|
341
|
+
function isRecord(value) {
|
|
342
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
343
|
+
}
|
|
344
|
+
//# sourceMappingURL=store.js.map
|