@peakflo/agent-harness 0.0.0-stage → 0.2.1-rc.3
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/LICENSE +21 -0
- package/README.md +38 -2
- package/dist/adapters/acp-adapter.d.ts +177 -0
- package/dist/adapters/acp-adapter.js +1732 -0
- package/dist/adapters/claude-code-adapter.d.ts +171 -0
- package/dist/adapters/claude-code-adapter.js +1918 -0
- package/dist/adapters/codex-adapter.d.ts +67 -0
- package/dist/adapters/codex-adapter.js +681 -0
- package/dist/adapters/codex-app-server-adapter.d.ts +105 -0
- package/dist/adapters/codex-app-server-adapter.js +1309 -0
- package/dist/adapters/coding-agent-adapter.d.ts +289 -0
- package/dist/adapters/coding-agent-adapter.js +31 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.js +35 -0
- package/dist/adapters/model-gateway.d.ts +84 -0
- package/dist/adapters/model-gateway.js +270 -0
- package/dist/adapters/opencode-adapter.d.ts +403 -0
- package/dist/adapters/opencode-adapter.js +2710 -0
- package/dist/adapters/opencode-config.d.ts +41 -0
- package/dist/adapters/opencode-config.js +147 -0
- package/dist/adapters/opencode-server-policy.d.ts +82 -0
- package/dist/adapters/opencode-server-policy.js +148 -0
- package/dist/adapters/pi-adapter.d.ts +168 -0
- package/dist/adapters/pi-adapter.js +1468 -0
- package/dist/capabilities.d.ts +58 -0
- package/dist/capabilities.js +161 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +231 -0
- package/dist/contract.d.ts +273 -0
- package/dist/contract.js +357 -0
- package/dist/control-plane.d.ts +178 -0
- package/dist/control-plane.js +431 -0
- package/dist/credential-store.d.ts +110 -0
- package/dist/credential-store.js +181 -0
- package/dist/enrol/browser-enrol.d.ts +85 -0
- package/dist/enrol/browser-enrol.js +387 -0
- package/dist/github-command-guards.d.ts +20 -0
- package/dist/github-command-guards.js +330 -0
- package/dist/github-proxy-certificates.d.ts +25 -0
- package/dist/github-proxy-certificates.js +145 -0
- package/dist/github-proxy.d.ts +37 -0
- package/dist/github-proxy.js +476 -0
- package/dist/github-workspace.d.ts +55 -0
- package/dist/github-workspace.js +537 -0
- package/dist/harness.d.ts +333 -0
- package/dist/harness.js +1414 -0
- package/dist/health.d.ts +45 -0
- package/dist/health.js +116 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +123 -0
- package/dist/model-probe.d.ts +96 -0
- package/dist/model-probe.js +195 -0
- package/dist/options.d.ts +15 -0
- package/dist/options.js +212 -0
- package/dist/ports-default.d.ts +212 -0
- package/dist/ports-default.js +333 -0
- package/dist/ports.d.ts +436 -0
- package/dist/ports.js +27 -0
- package/dist/runtime/attachments.d.ts +92 -0
- package/dist/runtime/attachments.js +377 -0
- package/dist/runtime/child-stream-guards.d.ts +45 -0
- package/dist/runtime/child-stream-guards.js +111 -0
- package/dist/runtime/conversation.d.ts +87 -0
- package/dist/runtime/conversation.js +156 -0
- package/dist/runtime/lease-renewal.d.ts +169 -0
- package/dist/runtime/lease-renewal.js +433 -0
- package/dist/runtime/mcp-auth-proxy.d.ts +82 -0
- package/dist/runtime/mcp-auth-proxy.js +300 -0
- package/dist/runtime/permission-mode.d.ts +116 -0
- package/dist/runtime/permission-mode.js +146 -0
- package/dist/runtime/projection.d.ts +71 -0
- package/dist/runtime/projection.js +211 -0
- package/dist/runtime/session-runner.d.ts +520 -0
- package/dist/runtime/session-runner.js +2122 -0
- package/dist/shared/reasoning-effort.d.ts +11 -0
- package/dist/shared/reasoning-effort.js +20 -0
- package/dist/store/conversation-store.d.ts +100 -0
- package/dist/store/conversation-store.js +305 -0
- package/dist/store/sqlite-store.d.ts +120 -0
- package/dist/store/sqlite-store.js +346 -0
- package/dist/types.d.ts +952 -0
- package/dist/types.js +55 -0
- package/dist/version.d.ts +32 -0
- package/dist/version.js +77 -0
- package/package.json +53 -4
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* THE HARNESS TRANSCRIPT STORE.
|
|
4
|
+
*
|
|
5
|
+
* Decision: KEEP THE SQLITE STORE. A runner that holds its working state only
|
|
6
|
+
* in memory loses a turn to its own crash, and the crash is exactly when the
|
|
7
|
+
* transcript matters. The control plane stays the source of truth via
|
|
8
|
+
* `POST /ingest`; this table is WORKING STATE — what has been produced, and
|
|
9
|
+
* what has been acknowledged — so a restarted harness can resume a lease
|
|
10
|
+
* instead of replaying a turn the user already paid for.
|
|
11
|
+
*
|
|
12
|
+
* WHY `node:sqlite` AND NOT `better-sqlite3`, which 20x uses:
|
|
13
|
+
*
|
|
14
|
+
* `pf-bootstrap` installs the harness with `--ignore-scripts` (a Phase 0
|
|
15
|
+
* hardening decision: an npm install script runs arbitrary code as the harness
|
|
16
|
+
* user before any of our code is reached). `better-sqlite3` is a NATIVE module
|
|
17
|
+
* whose binary is fetched or built by `prebuild-install` — an install script.
|
|
18
|
+
* Depending on it would therefore break the one boot path Phase 0 actually
|
|
19
|
+
* proved, and it would break it silently, at runtime, on a customer VM.
|
|
20
|
+
*
|
|
21
|
+
* `node:sqlite` ships INSIDE Node 22 (unflagged from 22.13). It costs no
|
|
22
|
+
* dependency, no native build, no install script and no prebuild matrix, and
|
|
23
|
+
* the sandbox image is already `node:22-bookworm-slim`. On an older Node the
|
|
24
|
+
* module is simply absent and `openTranscriptStore` falls back to memory, which
|
|
25
|
+
* costs crash-resumption and nothing else.
|
|
26
|
+
*
|
|
27
|
+
* THE SEVEN INVARIANTS are ported from 20x `src/main/database.ts` (schema at
|
|
28
|
+
* :1225, upsert at :2118, delta at :2179, snapshot at :2209) and each is
|
|
29
|
+
* asserted individually in the tests. They are NOT approximated: the control
|
|
30
|
+
* plane implements the same seven, and a transcript written by a runner must be
|
|
31
|
+
* byte-for-byte the transcript the desktop would have written.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.MemoryTranscriptStore = exports.SqliteTranscriptStore = void 0;
|
|
35
|
+
exports.openTranscriptStore = openTranscriptStore;
|
|
36
|
+
const SCHEMA = `
|
|
37
|
+
CREATE TABLE IF NOT EXISTS transcript_parts (
|
|
38
|
+
session_id TEXT NOT NULL,
|
|
39
|
+
part_id TEXT NOT NULL,
|
|
40
|
+
seq INTEGER NOT NULL,
|
|
41
|
+
role TEXT NOT NULL DEFAULT 'system',
|
|
42
|
+
content TEXT NOT NULL DEFAULT '',
|
|
43
|
+
part_type TEXT,
|
|
44
|
+
tool TEXT,
|
|
45
|
+
payload TEXT,
|
|
46
|
+
created_at INTEGER NOT NULL,
|
|
47
|
+
updated_at INTEGER NOT NULL,
|
|
48
|
+
-- Monotonic change cursor, bumped on every insert AND every content
|
|
49
|
+
-- update, so a reader can ask for "everything changed since rev N" and
|
|
50
|
+
-- catch both new parts and streaming edits to existing ones.
|
|
51
|
+
rev INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
PRIMARY KEY (session_id, part_id)
|
|
53
|
+
);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_tp_session_seq ON transcript_parts(session_id, seq);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_tp_session_rev ON transcript_parts(session_id, rev);
|
|
56
|
+
`;
|
|
57
|
+
function errorMessage(error) {
|
|
58
|
+
return error instanceof Error ? error.message : String(error);
|
|
59
|
+
}
|
|
60
|
+
function toStoredPart(row) {
|
|
61
|
+
return {
|
|
62
|
+
partId: row.part_id,
|
|
63
|
+
seq: row.seq,
|
|
64
|
+
role: row.role || 'system',
|
|
65
|
+
content: row.content,
|
|
66
|
+
partType: row.part_type ?? '',
|
|
67
|
+
tool: row.tool,
|
|
68
|
+
payload: row.payload ? JSON.parse(row.payload) : null,
|
|
69
|
+
createdAt: row.created_at,
|
|
70
|
+
updatedAt: row.updated_at,
|
|
71
|
+
rev: row.rev
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/* -------------------------------------------------------------------------- */
|
|
75
|
+
/* SQLite implementation */
|
|
76
|
+
/* -------------------------------------------------------------------------- */
|
|
77
|
+
class SqliteTranscriptStore {
|
|
78
|
+
db;
|
|
79
|
+
constructor(db) {
|
|
80
|
+
this.db = db;
|
|
81
|
+
// WAL so a reader (a health probe, a debug dump) never blocks the agent
|
|
82
|
+
// loop's writes. A transcript is written on every streaming delta.
|
|
83
|
+
try {
|
|
84
|
+
this.db.exec('PRAGMA journal_mode = WAL;');
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// A filesystem that cannot do WAL (some container overlays) still works
|
|
88
|
+
// in the default rollback journal. Not worth failing a boot over.
|
|
89
|
+
}
|
|
90
|
+
this.db.exec(SCHEMA);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* INVARIANTS 1-5, in one transaction.
|
|
94
|
+
*
|
|
95
|
+
* The transaction is explicit BEGIN/COMMIT because `node:sqlite` has no
|
|
96
|
+
* `db.transaction()` wrapper of the kind better-sqlite3 provides. The
|
|
97
|
+
* rollback on failure is what makes invariant 1 true: a batch that fails
|
|
98
|
+
* half-way must leave no parts behind, or a retry would see a transcript that
|
|
99
|
+
* never existed.
|
|
100
|
+
*/
|
|
101
|
+
upsertParts(sessionId, parts) {
|
|
102
|
+
if (parts.length === 0) {
|
|
103
|
+
return { maxRev: this.getMaxRev(sessionId), changedPartIds: [] };
|
|
104
|
+
}
|
|
105
|
+
const nextSeqStmt = this.db.prepare('SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM transcript_parts WHERE session_id = ?');
|
|
106
|
+
const globalRevStmt = this.db.prepare('SELECT COALESCE(MAX(rev), 0) AS m FROM transcript_parts');
|
|
107
|
+
// INVARIANT 2: `seq` is absent from the DO UPDATE list, so an existing part
|
|
108
|
+
// KEEPS the position it was first given. That is precisely how token
|
|
109
|
+
// streaming works — without it a growing text part would jump to the end of
|
|
110
|
+
// the transcript on every single delta.
|
|
111
|
+
//
|
|
112
|
+
// INVARIANT 3: part_type/tool/payload COALESCE against the STORED value, so
|
|
113
|
+
// a partial update (a streaming delta carries content and nothing else)
|
|
114
|
+
// cannot erase metadata that a fuller earlier update established.
|
|
115
|
+
//
|
|
116
|
+
// INVARIANT 4: created_at is likewise absent from the DO UPDATE list. The
|
|
117
|
+
// first write wins, for ever. A later reconcile pass must not restamp
|
|
118
|
+
// history with the time it happened to run.
|
|
119
|
+
const upsertStmt = this.db.prepare(`
|
|
120
|
+
INSERT INTO transcript_parts
|
|
121
|
+
(session_id, part_id, seq, role, content, part_type, tool, payload, created_at, updated_at, rev)
|
|
122
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
123
|
+
ON CONFLICT(session_id, part_id) DO UPDATE SET
|
|
124
|
+
content = excluded.content,
|
|
125
|
+
part_type = COALESCE(excluded.part_type, transcript_parts.part_type),
|
|
126
|
+
tool = COALESCE(excluded.tool, transcript_parts.tool),
|
|
127
|
+
payload = COALESCE(excluded.payload, transcript_parts.payload),
|
|
128
|
+
updated_at = excluded.updated_at,
|
|
129
|
+
rev = excluded.rev
|
|
130
|
+
`);
|
|
131
|
+
const changedPartIds = [];
|
|
132
|
+
let maxRev = 0;
|
|
133
|
+
this.db.exec('BEGIN');
|
|
134
|
+
try {
|
|
135
|
+
let nextSeq = nextSeqStmt.get(sessionId).next;
|
|
136
|
+
// INVARIANT 5: rev is allocated from the GLOBAL maximum and bumped for
|
|
137
|
+
// every changed row, inserts and updates alike.
|
|
138
|
+
let rev = globalRevStmt.get().m;
|
|
139
|
+
const writeNow = Date.now();
|
|
140
|
+
for (const part of parts) {
|
|
141
|
+
if (!part.partId) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
rev += 1;
|
|
145
|
+
upsertStmt.run(sessionId, part.partId, nextSeq, part.role || 'system', part.content || '', part.partType ?? null, part.tool ?? null,
|
|
146
|
+
// `undefined` and `null` both store as NULL, which COALESCE then
|
|
147
|
+
// reads as "leave the stored value alone". The contract's
|
|
148
|
+
// payload-clear semantics live one layer up, in `isPersistablePart`
|
|
149
|
+
// and the ingest path — SQL COALESCE cannot express "explicitly
|
|
150
|
+
// null" and pretending otherwise here would be a lie in the schema.
|
|
151
|
+
part.payload === undefined || part.payload === null
|
|
152
|
+
? null
|
|
153
|
+
: JSON.stringify(part.payload),
|
|
154
|
+
// The part's ORIGINAL time when known, never the write time.
|
|
155
|
+
// Otherwise a bulk replay stamps the whole history within a
|
|
156
|
+
// millisecond of itself and destroys the transcript's chronology.
|
|
157
|
+
typeof part.createdAt === 'number' ? part.createdAt : writeNow, writeNow, rev);
|
|
158
|
+
// seq is consumed per part, matching 20x. A conflicting row ignores the
|
|
159
|
+
// value it was handed, so the counter simply skips — gaps in seq are
|
|
160
|
+
// expected and harmless, since seq is only ever a tiebreaker.
|
|
161
|
+
nextSeq += 1;
|
|
162
|
+
changedPartIds.push(part.partId);
|
|
163
|
+
}
|
|
164
|
+
maxRev = rev;
|
|
165
|
+
this.db.exec('COMMIT');
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
try {
|
|
169
|
+
this.db.exec('ROLLBACK');
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Rolling back a transaction that already aborted is not an error.
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
return { maxRev, changedPartIds };
|
|
177
|
+
}
|
|
178
|
+
/** INVARIANTS 6 and 7. */
|
|
179
|
+
getDelta(sessionId, sinceRev) {
|
|
180
|
+
const rows = this.db
|
|
181
|
+
.prepare(`SELECT * FROM transcript_parts
|
|
182
|
+
WHERE session_id = ? AND rev > ?
|
|
183
|
+
ORDER BY created_at ASC, seq ASC`)
|
|
184
|
+
.all(sessionId, sinceRev);
|
|
185
|
+
// INVARIANT 6: maxRev is PER SESSION. A global maximum would hand a client
|
|
186
|
+
// a cursor that another session had already advanced past, and the client
|
|
187
|
+
// would then never be told about its own next part.
|
|
188
|
+
const max = this.db
|
|
189
|
+
.prepare('SELECT COALESCE(MAX(rev), ?) AS m FROM transcript_parts WHERE session_id = ?')
|
|
190
|
+
.get(sinceRev, sessionId);
|
|
191
|
+
return { parts: rows.map(toStoredPart), maxRev: max.m };
|
|
192
|
+
}
|
|
193
|
+
getParts(sessionId) {
|
|
194
|
+
// INVARIANT 7: ordered by REAL event time, with seq as a stable tiebreaker.
|
|
195
|
+
// Insertion order is NOT chronological once a partial projection has older
|
|
196
|
+
// history backfilled into it.
|
|
197
|
+
const rows = this.db
|
|
198
|
+
.prepare(`SELECT * FROM transcript_parts
|
|
199
|
+
WHERE session_id = ?
|
|
200
|
+
ORDER BY created_at ASC, seq ASC`)
|
|
201
|
+
.all(sessionId);
|
|
202
|
+
return rows.map(toStoredPart);
|
|
203
|
+
}
|
|
204
|
+
getMaxRev(sessionId) {
|
|
205
|
+
const row = this.db
|
|
206
|
+
.prepare('SELECT COALESCE(MAX(rev), 0) AS m FROM transcript_parts WHERE session_id = ?')
|
|
207
|
+
.get(sessionId);
|
|
208
|
+
return row.m;
|
|
209
|
+
}
|
|
210
|
+
deleteParts(sessionId) {
|
|
211
|
+
this.db
|
|
212
|
+
.prepare('DELETE FROM transcript_parts WHERE session_id = ?')
|
|
213
|
+
.run(sessionId);
|
|
214
|
+
}
|
|
215
|
+
close() {
|
|
216
|
+
try {
|
|
217
|
+
this.db.close();
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Closing twice is not an error worth propagating out of a shutdown path.
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
exports.SqliteTranscriptStore = SqliteTranscriptStore;
|
|
225
|
+
/* -------------------------------------------------------------------------- */
|
|
226
|
+
/* In-memory fallback */
|
|
227
|
+
/* -------------------------------------------------------------------------- */
|
|
228
|
+
/**
|
|
229
|
+
* The same seven invariants, without durability.
|
|
230
|
+
*
|
|
231
|
+
* Used when `node:sqlite` is absent, and in tests. It is a REAL implementation
|
|
232
|
+
* rather than a stub: if the two disagreed, every test that used this would be
|
|
233
|
+
* testing something the product does not do. The tests run the same invariant
|
|
234
|
+
* suite against both.
|
|
235
|
+
*/
|
|
236
|
+
class MemoryTranscriptStore {
|
|
237
|
+
bySession = new Map();
|
|
238
|
+
globalRev = 0;
|
|
239
|
+
upsertParts(sessionId, parts) {
|
|
240
|
+
let session = this.bySession.get(sessionId);
|
|
241
|
+
if (!session) {
|
|
242
|
+
session = new Map();
|
|
243
|
+
this.bySession.set(sessionId, session);
|
|
244
|
+
}
|
|
245
|
+
const changedPartIds = [];
|
|
246
|
+
const writeNow = Date.now();
|
|
247
|
+
let nextSeq = Math.max(0, ...Array.from(session.values(), (p) => p.seq)) + 1;
|
|
248
|
+
for (const part of parts) {
|
|
249
|
+
if (!part.partId) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
this.globalRev += 1;
|
|
253
|
+
const existing = session.get(part.partId);
|
|
254
|
+
if (existing) {
|
|
255
|
+
// Invariants 2, 3 and 4: keep seq, keep createdAt, coalesce metadata.
|
|
256
|
+
existing.content = part.content || '';
|
|
257
|
+
existing.partType = part.partType ?? existing.partType;
|
|
258
|
+
existing.tool = part.tool ?? existing.tool;
|
|
259
|
+
existing.payload =
|
|
260
|
+
part.payload === undefined || part.payload === null
|
|
261
|
+
? existing.payload
|
|
262
|
+
: part.payload;
|
|
263
|
+
existing.updatedAt = writeNow;
|
|
264
|
+
existing.rev = this.globalRev;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
session.set(part.partId, {
|
|
268
|
+
partId: part.partId,
|
|
269
|
+
seq: nextSeq,
|
|
270
|
+
role: part.role || 'system',
|
|
271
|
+
content: part.content || '',
|
|
272
|
+
partType: part.partType ?? '',
|
|
273
|
+
tool: part.tool ?? null,
|
|
274
|
+
payload: part.payload ?? null,
|
|
275
|
+
createdAt: typeof part.createdAt === 'number' ? part.createdAt : writeNow,
|
|
276
|
+
updatedAt: writeNow,
|
|
277
|
+
rev: this.globalRev
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
nextSeq += 1;
|
|
281
|
+
changedPartIds.push(part.partId);
|
|
282
|
+
}
|
|
283
|
+
return { maxRev: this.globalRev, changedPartIds };
|
|
284
|
+
}
|
|
285
|
+
getDelta(sessionId, sinceRev) {
|
|
286
|
+
const parts = this.ordered(sessionId).filter((p) => p.rev > sinceRev);
|
|
287
|
+
const all = this.ordered(sessionId);
|
|
288
|
+
const maxRev = all.length ? Math.max(...all.map((p) => p.rev)) : sinceRev;
|
|
289
|
+
return { parts, maxRev };
|
|
290
|
+
}
|
|
291
|
+
getParts(sessionId) {
|
|
292
|
+
return this.ordered(sessionId);
|
|
293
|
+
}
|
|
294
|
+
getMaxRev(sessionId) {
|
|
295
|
+
const all = this.ordered(sessionId);
|
|
296
|
+
return all.length ? Math.max(...all.map((p) => p.rev)) : 0;
|
|
297
|
+
}
|
|
298
|
+
deleteParts(sessionId) {
|
|
299
|
+
this.bySession.delete(sessionId);
|
|
300
|
+
}
|
|
301
|
+
ordered(sessionId) {
|
|
302
|
+
const session = this.bySession.get(sessionId);
|
|
303
|
+
if (!session) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
return Array.from(session.values())
|
|
307
|
+
.map((p) => ({ ...p }))
|
|
308
|
+
.sort((a, b) => a.createdAt - b.createdAt || a.seq - b.seq);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
exports.MemoryTranscriptStore = MemoryTranscriptStore;
|
|
312
|
+
/**
|
|
313
|
+
* Open the durable store, or say plainly why it could not be opened.
|
|
314
|
+
*
|
|
315
|
+
* NEVER THROWS. A harness that cannot open a database is still able to run a
|
|
316
|
+
* turn and push it to the control plane, which is the source of truth; refusing
|
|
317
|
+
* to boot would trade a recoverable degradation for an outage. The caller
|
|
318
|
+
* reports `durable: false` in its health snapshot so the degradation is VISIBLE
|
|
319
|
+
* rather than silent — a fallback nobody can see is how an outage becomes a
|
|
320
|
+
* mystery.
|
|
321
|
+
*/
|
|
322
|
+
function openTranscriptStore(path) {
|
|
323
|
+
let sqlite;
|
|
324
|
+
try {
|
|
325
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require, @typescript-eslint/no-require-imports
|
|
326
|
+
sqlite = require('node:sqlite');
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
return {
|
|
330
|
+
store: new MemoryTranscriptStore(),
|
|
331
|
+
durable: false,
|
|
332
|
+
reason: `node:sqlite unavailable (needs Node >= 22.13): ${errorMessage(error)}`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const db = new sqlite.DatabaseSync(path);
|
|
337
|
+
return { store: new SqliteTranscriptStore(db), durable: true };
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
return {
|
|
341
|
+
store: new MemoryTranscriptStore(),
|
|
342
|
+
durable: false,
|
|
343
|
+
reason: `could not open ${path}: ${errorMessage(error)}`
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|