@poncho-ai/harness 0.35.0 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +12 -11
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.d.ts +485 -29
  4. package/dist/index.js +2839 -2114
  5. package/dist/isolate-TCWTUVG4.js +1532 -0
  6. package/package.json +23 -4
  7. package/scripts/migrate-to-engine.mjs +556 -0
  8. package/src/config.ts +106 -1
  9. package/src/harness.ts +226 -91
  10. package/src/index.ts +5 -0
  11. package/src/isolate/bindings.ts +206 -0
  12. package/src/isolate/bundler.ts +179 -0
  13. package/src/isolate/index.ts +10 -0
  14. package/src/isolate/polyfills.ts +796 -0
  15. package/src/isolate/run-code-tool.ts +220 -0
  16. package/src/isolate/runtime.ts +286 -0
  17. package/src/isolate/type-stubs.ts +196 -0
  18. package/src/memory.ts +129 -198
  19. package/src/reminder-store.ts +3 -237
  20. package/src/secrets-store.ts +2 -91
  21. package/src/state.ts +11 -1302
  22. package/src/storage/engine.ts +106 -0
  23. package/src/storage/index.ts +59 -0
  24. package/src/storage/memory-engine.ts +588 -0
  25. package/src/storage/postgres-engine.ts +139 -0
  26. package/src/storage/schema.ts +145 -0
  27. package/src/storage/sql-dialect.ts +963 -0
  28. package/src/storage/sqlite-engine.ts +99 -0
  29. package/src/storage/store-adapters.ts +100 -0
  30. package/src/todo-tools.ts +1 -136
  31. package/src/upload-store.ts +1 -0
  32. package/src/vfs/bash-manager.ts +120 -0
  33. package/src/vfs/bash-tool.ts +59 -0
  34. package/src/vfs/create-bash-fs.ts +32 -0
  35. package/src/vfs/edit-file-tool.ts +72 -0
  36. package/src/vfs/index.ts +5 -0
  37. package/src/vfs/poncho-fs-adapter.ts +267 -0
  38. package/src/vfs/protected-fs.ts +177 -0
  39. package/src/vfs/read-file-tool.ts +103 -0
  40. package/src/vfs/write-file-tool.ts +49 -0
  41. package/test/harness.test.ts +30 -36
  42. package/test/isolate-vfs.test.ts +453 -0
  43. package/test/isolate.test.ts +252 -0
  44. package/test/state.test.ts +4 -27
  45. package/test/storage-engine.test.ts +250 -0
  46. package/test/vfs.test.ts +242 -0
  47. package/.turbo/turbo-lint.log +0 -6
  48. package/.turbo/turbo-test.log +0 -11931
  49. package/src/kv-store.ts +0 -216
@@ -0,0 +1,252 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createIsolateRuntime } from "../src/isolate/runtime.js";
3
+ import type { IsolateBinding } from "../src/config.js";
4
+
5
+ const DEFAULT_CONFIG = { memoryLimit: 64, timeout: 5000, outputLimit: 65536 };
6
+
7
+ describe("IsolateRuntime", () => {
8
+ it("executes basic JavaScript and returns a result", async () => {
9
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
10
+ const res = await runtime.execute("return 1 + 2;", {}, null);
11
+
12
+ expect(res.error).toBeUndefined();
13
+ expect(res.result).toBe(3);
14
+ expect(res.executionTimeMs).toBeGreaterThan(0);
15
+ });
16
+
17
+ it("captures console.log output to stdout", async () => {
18
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
19
+ const res = await runtime.execute(
20
+ `console.log("hello"); console.log("world"); return 42;`,
21
+ {},
22
+ null,
23
+ );
24
+
25
+ expect(res.error).toBeUndefined();
26
+ expect(res.stdout).toBe("hello\nworld");
27
+ expect(res.result).toBe(42);
28
+ });
29
+
30
+ it("captures console.error/warn to stderr", async () => {
31
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
32
+ const res = await runtime.execute(
33
+ `console.error("err"); console.warn("warn");`,
34
+ {},
35
+ null,
36
+ );
37
+
38
+ expect(res.error).toBeUndefined();
39
+ expect(res.stderr).toBe("err\nwarn");
40
+ });
41
+
42
+ it("serializes non-string console arguments as JSON", async () => {
43
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
44
+ const res = await runtime.execute(
45
+ `console.log({ a: 1 }); console.log([1, 2, 3]);`,
46
+ {},
47
+ null,
48
+ );
49
+
50
+ expect(res.stdout).toContain('"a": 1');
51
+ expect(res.stdout).toContain("[");
52
+ });
53
+
54
+ it("returns error with line number for runtime errors", async () => {
55
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
56
+ const res = await runtime.execute(
57
+ `const x = 1;\nconst y = 2;\nthrow new Error("boom");`,
58
+ {},
59
+ null,
60
+ );
61
+
62
+ expect(res.error).toBeDefined();
63
+ expect(res.error!.message).toBe("boom");
64
+ expect(res.error!.name).toBe("Error");
65
+ });
66
+
67
+ it("handles syntax errors from V8", async () => {
68
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
69
+ const res = await runtime.execute("const x = {;", {}, null);
70
+
71
+ expect(res.error).toBeDefined();
72
+ expect(res.error!.message).toBeTruthy();
73
+ });
74
+
75
+ it("times out long-running code", async () => {
76
+ const runtime = createIsolateRuntime({ ...DEFAULT_CONFIG, timeout: 100 });
77
+ const res = await runtime.execute("while (true) {}", {}, null);
78
+
79
+ expect(res.error).toBeDefined();
80
+ expect(res.error!.message).toMatch(/timed out|timeout|Script execution/i);
81
+ });
82
+
83
+ it("truncates output at the configured limit", async () => {
84
+ const runtime = createIsolateRuntime({
85
+ ...DEFAULT_CONFIG,
86
+ outputLimit: 50,
87
+ });
88
+ const res = await runtime.execute(
89
+ `for (let i = 0; i < 100; i++) console.log("line " + i);`,
90
+ {},
91
+ null,
92
+ );
93
+
94
+ expect(res.stdout).toContain("[output truncated at 50 bytes]");
95
+ });
96
+
97
+ it("supports async code with await", async () => {
98
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
99
+ const res = await runtime.execute(
100
+ `const result = await Promise.resolve(99); return result;`,
101
+ {},
102
+ null,
103
+ );
104
+
105
+ expect(res.error).toBeUndefined();
106
+ expect(res.result).toBe(99);
107
+ });
108
+
109
+ it("handles abort signal (already aborted)", async () => {
110
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
111
+ const controller = new AbortController();
112
+ controller.abort();
113
+
114
+ const res = await runtime.execute("return 1;", {}, null, controller.signal);
115
+
116
+ expect(res.error).toBeDefined();
117
+ expect(res.error!.message).toBe("Execution cancelled");
118
+ });
119
+
120
+ it("handles abort signal during execution", async () => {
121
+ const runtime = createIsolateRuntime({ ...DEFAULT_CONFIG, timeout: 10000 });
122
+ const controller = new AbortController();
123
+
124
+ // Abort after a short delay
125
+ setTimeout(() => controller.abort(), 50);
126
+
127
+ const res = await runtime.execute(
128
+ "while (true) {}",
129
+ {},
130
+ null,
131
+ controller.signal,
132
+ );
133
+
134
+ expect(res.error).toBeDefined();
135
+ expect(res.error!.message).toMatch(/cancelled|disposed/i);
136
+ });
137
+ });
138
+
139
+ describe("IsolateRuntime bindings", () => {
140
+ it("calls async bindings and returns results", async () => {
141
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
142
+ const bindings: Record<string, IsolateBinding> = {
143
+ get_greeting: {
144
+ description: "Returns a greeting",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: { name: { type: "string" } },
148
+ required: ["name"],
149
+ },
150
+ handler: async (input) => `Hello, ${input.name}!`,
151
+ },
152
+ };
153
+
154
+ const res = await runtime.execute(
155
+ `const msg = await get_greeting({ name: "World" }); return msg;`,
156
+ bindings,
157
+ null,
158
+ );
159
+
160
+ expect(res.error).toBeUndefined();
161
+ expect(res.result).toBe("Hello, World!");
162
+ });
163
+
164
+ it("handles binding errors gracefully", async () => {
165
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
166
+ const bindings: Record<string, IsolateBinding> = {
167
+ fail: {
168
+ description: "Always fails",
169
+ inputSchema: { type: "object" },
170
+ handler: async () => {
171
+ throw new Error("binding error");
172
+ },
173
+ },
174
+ };
175
+
176
+ const res = await runtime.execute(
177
+ `try { await fail({}); } catch (e) { return e.message; }`,
178
+ bindings,
179
+ null,
180
+ );
181
+
182
+ expect(res.error).toBeUndefined();
183
+ expect(res.result).toBe("binding error");
184
+ });
185
+
186
+ it("supports multiple bindings in the same execution", async () => {
187
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
188
+ const bindings: Record<string, IsolateBinding> = {
189
+ add: {
190
+ description: "Add two numbers",
191
+ inputSchema: { type: "object" },
192
+ handler: async (input) =>
193
+ (input.a as number) + (input.b as number),
194
+ },
195
+ multiply: {
196
+ description: "Multiply two numbers",
197
+ inputSchema: { type: "object" },
198
+ handler: async (input) =>
199
+ (input.a as number) * (input.b as number),
200
+ },
201
+ };
202
+
203
+ const res = await runtime.execute(
204
+ `const sum = await add({ a: 3, b: 4 }); const prod = await multiply({ a: sum, b: 2 }); return prod;`,
205
+ bindings,
206
+ null,
207
+ );
208
+
209
+ expect(res.error).toBeUndefined();
210
+ expect(res.result).toBe(14);
211
+ });
212
+ });
213
+
214
+ describe("IsolateRuntime preamble", () => {
215
+ it("evaluates library preamble before user code", async () => {
216
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
217
+ const preamble = `var __lib_mylib = { greet: function(n) { return "Hi " + n; } };
218
+ var __modules = { mylib: __lib_mylib };
219
+ function require(name) {
220
+ if (!__modules[name]) throw new Error('Module "' + name + '" not found');
221
+ return __modules[name];
222
+ }`;
223
+
224
+ const res = await runtime.execute(
225
+ `const lib = require("mylib"); return lib.greet("Test");`,
226
+ {},
227
+ preamble,
228
+ );
229
+
230
+ expect(res.error).toBeUndefined();
231
+ expect(res.result).toBe("Hi Test");
232
+ });
233
+
234
+ it("preamble errors don't affect user code line numbers", async () => {
235
+ const runtime = createIsolateRuntime(DEFAULT_CONFIG);
236
+ // A valid preamble with many lines
237
+ const preamble = Array(50).fill("var _unused = 0;").join("\n");
238
+
239
+ const res = await runtime.execute(
240
+ `const x = 1;\nthrow new Error("line2");`,
241
+ {},
242
+ preamble,
243
+ );
244
+
245
+ expect(res.error).toBeDefined();
246
+ expect(res.error!.message).toBe("line2");
247
+ // Line should refer to the user code, not the preamble
248
+ if (res.error!.line !== undefined) {
249
+ expect(res.error!.line).toBeLessThanOrEqual(3);
250
+ }
251
+ });
252
+ });
@@ -1,9 +1,5 @@
1
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join, resolve } from "node:path";
4
1
  import { describe, expect, it } from "vitest";
5
2
  import { createConversationStore, createStateStore } from "../src/state.js";
6
- import { ensureAgentIdentity, getAgentStoreDirectory } from "../src/agent-identity.js";
7
3
 
8
4
  describe("state store factory", () => {
9
5
  it("uses memory provider when explicitly requested", async () => {
@@ -45,29 +41,10 @@ describe("conversation store factory", () => {
45
41
  expect(found?.title).toBe("fallback");
46
42
  });
47
43
 
48
- it("stores local conversations per file with index", async () => {
49
- const dir = await mkdtemp(join(tmpdir(), "poncho-state-local-layout-"));
50
- await writeFile(
51
- join(dir, "AGENT.md"),
52
- `---
53
- name: local-layout-agent
54
- model:
55
- provider: anthropic
56
- name: claude-opus-4-5
57
- ---
58
-
59
- # Local layout
60
- `,
61
- "utf8",
62
- );
63
- const store = createConversationStore({ provider: "local" }, { workingDir: dir });
44
+ it("local provider returns in-memory store (engine handles persistence)", async () => {
45
+ const store = createConversationStore({ provider: "local" });
64
46
  const created = await store.create("owner-c", "layout");
65
- const identity = await ensureAgentIdentity(dir);
66
- const agentDir = getAgentStoreDirectory(identity);
67
- const indexPath = resolve(agentDir, "conversations", "index.json");
68
- const indexContent = await readFile(indexPath, "utf8");
69
- expect(indexContent).toContain(created.conversationId);
70
- expect(indexContent).toContain('"schemaVersion": "v1"');
71
- await rm(agentDir, { recursive: true, force: true });
47
+ const found = await store.get(created.conversationId);
48
+ expect(found?.title).toBe("layout");
72
49
  });
73
50
  });
@@ -0,0 +1,250 @@
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { InMemoryEngine } from "../src/storage/memory-engine.js";
3
+ import { SqliteEngine } from "../src/storage/sqlite-engine.js";
4
+ import { mkdtemp, rm } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import type { StorageEngine } from "../src/storage/engine.js";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Shared test suite that runs against both InMemory and SQLite engines
11
+ // ---------------------------------------------------------------------------
12
+
13
+ function runEngineTests(name: string, factory: () => Promise<{ engine: StorageEngine; cleanup: () => Promise<void> }>) {
14
+ describe(`${name} engine`, () => {
15
+ let engine: StorageEngine;
16
+ let cleanup: () => Promise<void>;
17
+
18
+ beforeEach(async () => {
19
+ const ctx = await factory();
20
+ engine = ctx.engine;
21
+ cleanup = ctx.cleanup;
22
+ });
23
+
24
+ // -- Conversations --
25
+
26
+ describe("conversations", () => {
27
+ it("creates and retrieves a conversation", async () => {
28
+ const conv = await engine.conversations.create("owner1", "Hello", "tenant1");
29
+ expect(conv.title).toBe("Hello");
30
+ expect(conv.ownerId).toBe("owner1");
31
+
32
+ const got = await engine.conversations.get(conv.conversationId);
33
+ expect(got?.title).toBe("Hello");
34
+ });
35
+
36
+ it("lists conversations filtered by tenant", async () => {
37
+ await engine.conversations.create("o", "A", "t1");
38
+ await engine.conversations.create("o", "B", "t2");
39
+
40
+ const t1 = await engine.conversations.list(undefined, "t1");
41
+ expect(t1).toHaveLength(1);
42
+ expect(t1[0].title).toBe("A");
43
+ });
44
+
45
+ it("renames a conversation", async () => {
46
+ const conv = await engine.conversations.create("o", "Old");
47
+ const renamed = await engine.conversations.rename(conv.conversationId, "New");
48
+ expect(renamed?.title).toBe("New");
49
+ });
50
+
51
+ it("deletes a conversation", async () => {
52
+ const conv = await engine.conversations.create("o", "Del");
53
+ const deleted = await engine.conversations.delete(conv.conversationId);
54
+ expect(deleted).toBe(true);
55
+ const got = await engine.conversations.get(conv.conversationId);
56
+ expect(got).toBeUndefined();
57
+ });
58
+
59
+ it("searches conversations by title", async () => {
60
+ await engine.conversations.create("o", "alpha beta");
61
+ await engine.conversations.create("o", "gamma delta");
62
+
63
+ const results = await engine.conversations.search("beta");
64
+ expect(results).toHaveLength(1);
65
+ expect(results[0].title).toBe("alpha beta");
66
+ });
67
+ });
68
+
69
+ // -- Memory --
70
+
71
+ describe("memory", () => {
72
+ it("reads empty memory by default", async () => {
73
+ const mem = await engine.memory.get("t1");
74
+ expect(mem.content).toBe("");
75
+ });
76
+
77
+ it("updates and retrieves memory", async () => {
78
+ await engine.memory.update("remember this", "t1");
79
+ const mem = await engine.memory.get("t1");
80
+ expect(mem.content).toBe("remember this");
81
+ });
82
+
83
+ it("isolates memory by tenant", async () => {
84
+ await engine.memory.update("tenant A", "tA");
85
+ await engine.memory.update("tenant B", "tB");
86
+ expect((await engine.memory.get("tA")).content).toBe("tenant A");
87
+ expect((await engine.memory.get("tB")).content).toBe("tenant B");
88
+ });
89
+ });
90
+
91
+ // -- Todos --
92
+
93
+ describe("todos", () => {
94
+ it("stores and retrieves todos", async () => {
95
+ const items = [
96
+ { id: "1", content: "Buy milk", status: "pending" as const, priority: "medium" as const, createdAt: Date.now(), updatedAt: Date.now() },
97
+ ];
98
+ await engine.todos.set("conv1", items);
99
+ const got = await engine.todos.get("conv1");
100
+ expect(got).toHaveLength(1);
101
+ expect(got[0].content).toBe("Buy milk");
102
+ });
103
+
104
+ it("returns empty array for missing conversation", async () => {
105
+ const got = await engine.todos.get("nonexistent");
106
+ expect(got).toHaveLength(0);
107
+ });
108
+ });
109
+
110
+ // -- Reminders --
111
+
112
+ describe("reminders", () => {
113
+ it("creates and lists reminders", async () => {
114
+ const r = await engine.reminders.create({
115
+ task: "Call dentist",
116
+ scheduledAt: Date.now() + 3600_000,
117
+ conversationId: "conv1",
118
+ });
119
+ expect(r.task).toBe("Call dentist");
120
+
121
+ const list = await engine.reminders.list();
122
+ expect(list).toHaveLength(1);
123
+ });
124
+
125
+ it("cancels a reminder", async () => {
126
+ const r = await engine.reminders.create({
127
+ task: "Cancel me",
128
+ scheduledAt: Date.now(),
129
+ conversationId: "conv1",
130
+ });
131
+ const cancelled = await engine.reminders.cancel(r.id);
132
+ expect(cancelled.status).toBe("cancelled");
133
+ });
134
+ });
135
+
136
+ // -- VFS --
137
+
138
+ describe("vfs", () => {
139
+ it("writes and reads a file", async () => {
140
+ const content = new TextEncoder().encode("hello world");
141
+ await engine.vfs.writeFile("t1", "/test.txt", content);
142
+ const read = await engine.vfs.readFile("t1", "/test.txt");
143
+ expect(new TextDecoder().decode(read)).toBe("hello world");
144
+ });
145
+
146
+ it("creates directories recursively", async () => {
147
+ await engine.vfs.mkdir("t1", "/a/b/c", true);
148
+ const stat = await engine.vfs.stat("t1", "/a/b/c");
149
+ expect(stat?.type).toBe("directory");
150
+ });
151
+
152
+ it("lists directory contents", async () => {
153
+ await engine.vfs.writeFile("t1", "/dir/a.txt", new Uint8Array());
154
+ await engine.vfs.writeFile("t1", "/dir/b.txt", new Uint8Array());
155
+ const entries = await engine.vfs.readdir("t1", "/dir");
156
+ expect(entries.map((e) => e.name).sort()).toEqual(["a.txt", "b.txt"]);
157
+ });
158
+
159
+ it("deletes files and directories", async () => {
160
+ await engine.vfs.writeFile("t1", "/rm-me.txt", new TextEncoder().encode("bye"));
161
+ await engine.vfs.deleteFile("t1", "/rm-me.txt");
162
+ const stat = await engine.vfs.stat("t1", "/rm-me.txt");
163
+ expect(stat).toBeUndefined();
164
+ });
165
+
166
+ it("renames files", async () => {
167
+ await engine.vfs.writeFile("t1", "/old.txt", new TextEncoder().encode("data"));
168
+ await engine.vfs.rename("t1", "/old.txt", "/new.txt");
169
+ const old = await engine.vfs.stat("t1", "/old.txt");
170
+ const nw = await engine.vfs.stat("t1", "/new.txt");
171
+ expect(old).toBeUndefined();
172
+ expect(nw?.type).toBe("file");
173
+ });
174
+
175
+ it("appends to files", async () => {
176
+ await engine.vfs.writeFile("t1", "/append.txt", new TextEncoder().encode("hello"));
177
+ await engine.vfs.appendFile("t1", "/append.txt", new TextEncoder().encode(" world"));
178
+ const read = await engine.vfs.readFile("t1", "/append.txt");
179
+ expect(new TextDecoder().decode(read)).toBe("hello world");
180
+ });
181
+
182
+ it("supports symlinks", async () => {
183
+ await engine.vfs.writeFile("t1", "/target.txt", new TextEncoder().encode("linked"));
184
+ await engine.vfs.symlink("t1", "/target.txt", "/link.txt");
185
+ const target = await engine.vfs.readlink("t1", "/link.txt");
186
+ expect(target).toBe("/target.txt");
187
+ });
188
+
189
+ it("reports usage stats", async () => {
190
+ await engine.vfs.writeFile("t1", "/f1.txt", new TextEncoder().encode("abc"));
191
+ await engine.vfs.writeFile("t1", "/f2.txt", new TextEncoder().encode("defgh"));
192
+ const usage = await engine.vfs.getUsage("t1");
193
+ expect(usage.fileCount).toBe(2);
194
+ expect(usage.totalBytes).toBe(8);
195
+ });
196
+
197
+ it("listAllPaths returns all VFS paths", async () => {
198
+ await engine.vfs.writeFile("t1", "/a.txt", new Uint8Array());
199
+ await engine.vfs.mkdir("t1", "/dir");
200
+ const paths = engine.vfs.listAllPaths("t1");
201
+ expect(paths).toContain("/a.txt");
202
+ expect(paths).toContain("/dir");
203
+ });
204
+
205
+ it("isolates files by tenant", async () => {
206
+ await engine.vfs.writeFile("t1", "/secret.txt", new TextEncoder().encode("t1-data"));
207
+ await engine.vfs.writeFile("t2", "/secret.txt", new TextEncoder().encode("t2-data"));
208
+ const t1 = await engine.vfs.readFile("t1", "/secret.txt");
209
+ const t2 = await engine.vfs.readFile("t2", "/secret.txt");
210
+ expect(new TextDecoder().decode(t1)).toBe("t1-data");
211
+ expect(new TextDecoder().decode(t2)).toBe("t2-data");
212
+ });
213
+ });
214
+
215
+ // Cleanup at the end of each test
216
+ it("cleanup", async () => {
217
+ await engine.close();
218
+ await cleanup();
219
+ });
220
+ });
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // Run against InMemoryEngine
225
+ // ---------------------------------------------------------------------------
226
+
227
+ runEngineTests("InMemory", async () => {
228
+ const engine = new InMemoryEngine("test-agent");
229
+ await engine.initialize();
230
+ return { engine, cleanup: async () => {} };
231
+ });
232
+
233
+ // ---------------------------------------------------------------------------
234
+ // Run against SqliteEngine
235
+ // ---------------------------------------------------------------------------
236
+
237
+ runEngineTests("SQLite", async () => {
238
+ const dir = await mkdtemp(join(tmpdir(), "poncho-sqlite-test-"));
239
+ const engine = new SqliteEngine({
240
+ workingDir: dir,
241
+ agentId: "test-agent",
242
+ });
243
+ await engine.initialize();
244
+ return {
245
+ engine,
246
+ cleanup: async () => {
247
+ await rm(dir, { recursive: true, force: true });
248
+ },
249
+ };
250
+ });