@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,242 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { Bash } from "just-bash";
3
+ import { InMemoryEngine } from "../src/storage/memory-engine.js";
4
+ import { PonchoFsAdapter } from "../src/vfs/poncho-fs-adapter.js";
5
+ import { ProtectedFs } from "../src/vfs/protected-fs.js";
6
+ import { createBashFs } from "../src/vfs/create-bash-fs.js";
7
+ import { BashEnvironmentManager } from "../src/vfs/bash-manager.js";
8
+
9
+ const MB = 1024 * 1024;
10
+ const LIMITS = { maxFileSize: 10 * MB, maxTotalStorage: 100 * MB };
11
+
12
+ describe("PonchoFsAdapter", () => {
13
+ it("implements IFileSystem contract with InMemoryEngine", async () => {
14
+ const engine = new InMemoryEngine("test");
15
+ await engine.initialize();
16
+ const adapter = new PonchoFsAdapter(engine, "t1", LIMITS);
17
+
18
+ // writeFile + readFile
19
+ await adapter.writeFile("/hello.txt", "world");
20
+ const content = await adapter.readFile("/hello.txt");
21
+ expect(content).toBe("world");
22
+
23
+ // readFileBuffer
24
+ const buf = await adapter.readFileBuffer("/hello.txt");
25
+ expect(new TextDecoder().decode(buf)).toBe("world");
26
+
27
+ // exists
28
+ expect(await adapter.exists("/hello.txt")).toBe(true);
29
+ expect(await adapter.exists("/nope.txt")).toBe(false);
30
+
31
+ // stat
32
+ const stat = await adapter.stat("/hello.txt");
33
+ expect(stat.isFile).toBe(true);
34
+ expect(stat.size).toBe(5);
35
+
36
+ // mkdir + readdir
37
+ await adapter.mkdir("/mydir", { recursive: true });
38
+ await adapter.writeFile("/mydir/a.txt", "a");
39
+ await adapter.writeFile("/mydir/b.txt", "b");
40
+ const entries = await adapter.readdir("/mydir");
41
+ expect(entries.sort()).toEqual(["a.txt", "b.txt"]);
42
+
43
+ // cp
44
+ await adapter.cp("/mydir/a.txt", "/mydir/copy.txt");
45
+ expect(await adapter.readFile("/mydir/copy.txt")).toBe("a");
46
+
47
+ // mv
48
+ await adapter.mv("/mydir/copy.txt", "/mydir/moved.txt");
49
+ expect(await adapter.exists("/mydir/copy.txt")).toBe(false);
50
+ expect(await adapter.readFile("/mydir/moved.txt")).toBe("a");
51
+
52
+ // rm
53
+ await adapter.rm("/mydir/moved.txt");
54
+ expect(await adapter.exists("/mydir/moved.txt")).toBe(false);
55
+
56
+ // getAllPaths
57
+ const paths = adapter.getAllPaths();
58
+ expect(paths).toContain("/hello.txt");
59
+
60
+ // chmod
61
+ await adapter.chmod("/hello.txt", 0o755);
62
+ const stat2 = await adapter.stat("/hello.txt");
63
+ expect(stat2.mode).toBe(0o755);
64
+
65
+ await engine.close();
66
+ });
67
+
68
+ it("enforces file size limits", async () => {
69
+ const engine = new InMemoryEngine("test");
70
+ await engine.initialize();
71
+ const adapter = new PonchoFsAdapter(engine, "t1", { maxFileSize: 10, maxTotalStorage: 1000 });
72
+
73
+ await expect(adapter.writeFile("/big.txt", "x".repeat(20))).rejects.toThrow("File too large");
74
+ await engine.close();
75
+ });
76
+ });
77
+
78
+ describe("ProtectedFs", () => {
79
+ it("blocks writes to protected paths", async () => {
80
+ const engine = new InMemoryEngine("test");
81
+ await engine.initialize();
82
+ const inner = new PonchoFsAdapter(engine, "t1", LIMITS);
83
+ const protectedFs = new ProtectedFs(inner);
84
+
85
+ // Write to .env should be blocked (guard throws synchronously)
86
+ expect(() => protectedFs.writeFile(".env", "SECRET=bad")).toThrow("Permission denied");
87
+ expect(() => protectedFs.writeFile(".env.local", "SECRET=bad")).toThrow("Permission denied");
88
+ expect(() => protectedFs.writeFile(".git/config", "bad")).toThrow("Permission denied");
89
+ expect(() => protectedFs.writeFile("node_modules/foo", "bad")).toThrow("Permission denied");
90
+
91
+ // Write to normal paths should work
92
+ await protectedFs.writeFile("/src/index.ts", "console.log('ok')");
93
+ const content = await protectedFs.readFile("/src/index.ts");
94
+ expect(content).toBe("console.log('ok')");
95
+
96
+ // Reads from protected paths should work
97
+ await inner.writeFile("/.env", "SECRET=ok");
98
+ const secret = await protectedFs.readFile("/.env");
99
+ expect(secret).toBe("SECRET=ok");
100
+
101
+ await engine.close();
102
+ });
103
+ });
104
+
105
+ describe("bash + VFS integration", () => {
106
+ it("executes bash commands against the VFS", async () => {
107
+ const engine = new InMemoryEngine("test");
108
+ await engine.initialize();
109
+ const manager = new BashEnvironmentManager(engine, LIMITS, null);
110
+ const bash = manager.getOrCreate("t1");
111
+
112
+ // Write via bash
113
+ const writeResult = await bash.exec('echo "hello from bash" > /greeting.txt');
114
+ expect(writeResult.exitCode).toBe(0);
115
+
116
+ // Read via bash
117
+ const catResult = await bash.exec("cat /greeting.txt");
118
+ expect(catResult.stdout.trim()).toBe("hello from bash");
119
+
120
+ // Data processing pipeline
121
+ await bash.exec('echo -e "3\\n1\\n2" > /numbers.txt');
122
+ const sortResult = await bash.exec("cat /numbers.txt | sort -n");
123
+ expect(sortResult.stdout.trim()).toBe("1\n2\n3");
124
+
125
+ // Files persist across exec calls
126
+ const lsResult = await bash.exec("ls /");
127
+ expect(lsResult.stdout).toContain("greeting.txt");
128
+ expect(lsResult.stdout).toContain("numbers.txt");
129
+
130
+ // Verify data is in the engine (not just in bash memory)
131
+ const adapter = manager.getAdapter("t1");
132
+ const content = await adapter.readFile("/greeting.txt");
133
+ expect(content.trim()).toBe("hello from bash");
134
+
135
+ manager.destroyAll();
136
+ await engine.close();
137
+ });
138
+
139
+ it("creates production filesystem without /project mount", () => {
140
+ const engine = new InMemoryEngine("test");
141
+ const adapter = new PonchoFsAdapter(engine, "t1", LIMITS);
142
+
143
+ // null workingDir = production mode, no project mount
144
+ const fs = createBashFs(adapter, null);
145
+ expect(fs).toBe(adapter); // Should return adapter directly
146
+ });
147
+
148
+ it("enables curl when network config is provided", async () => {
149
+ const engine = new InMemoryEngine("test");
150
+ await engine.initialize();
151
+ const manager = new BashEnvironmentManager(engine, LIMITS, null, undefined, {
152
+ dangerouslyAllowAll: true,
153
+ });
154
+ const bash = manager.getOrCreate("t1");
155
+
156
+ // curl should be registered as a command when network is configured.
157
+ // Fetching a known URL and writing to VFS:
158
+ const result = await bash.exec("curl -s -o /test.txt https://example.com");
159
+ expect(result.exitCode).toBe(0);
160
+
161
+ // Verify the file was written to VFS
162
+ const adapter = manager.getAdapter("t1");
163
+ expect(await adapter.exists("/test.txt")).toBe(true);
164
+ const content = await adapter.readFile("/test.txt");
165
+ expect(content.length).toBeGreaterThan(0);
166
+ expect(content).toContain("Example Domain");
167
+
168
+ manager.destroyAll();
169
+ await engine.close();
170
+ });
171
+
172
+ it("blocks curl when no network config is provided", async () => {
173
+ const engine = new InMemoryEngine("test");
174
+ await engine.initialize();
175
+ const manager = new BashEnvironmentManager(engine, LIMITS, null);
176
+ const bash = manager.getOrCreate("t1");
177
+
178
+ const result = await bash.exec("curl https://example.com");
179
+ // curl should either not be found or be blocked
180
+ expect(result.exitCode).not.toBe(0);
181
+
182
+ manager.destroyAll();
183
+ await engine.close();
184
+ });
185
+
186
+ it("restricts commands when whitelist is provided", async () => {
187
+ const engine = new InMemoryEngine("test");
188
+ await engine.initialize();
189
+ const manager = new BashEnvironmentManager(engine, LIMITS, null, {
190
+ commands: ["echo", "cat"],
191
+ });
192
+ const bash = manager.getOrCreate("t1");
193
+
194
+ // Allowed commands work
195
+ const echoResult = await bash.exec('echo "hello"');
196
+ expect(echoResult.exitCode).toBe(0);
197
+ expect(echoResult.stdout.trim()).toBe("hello");
198
+
199
+ // Disallowed commands fail
200
+ const rmResult = await bash.exec("rm /some-file");
201
+ expect(rmResult.exitCode).not.toBe(0);
202
+
203
+ manager.destroyAll();
204
+ await engine.close();
205
+ });
206
+
207
+ it("enforces execution limits", async () => {
208
+ const engine = new InMemoryEngine("test");
209
+ await engine.initialize();
210
+ const manager = new BashEnvironmentManager(engine, LIMITS, null, {
211
+ executionLimits: { maxLoopIterations: 5 },
212
+ });
213
+ const bash = manager.getOrCreate("t1");
214
+
215
+ // A loop that exceeds the limit should fail
216
+ const result = await bash.exec("for i in $(seq 1 100); do echo $i; done");
217
+ expect(result.exitCode).not.toBe(0);
218
+
219
+ manager.destroyAll();
220
+ await engine.close();
221
+ });
222
+
223
+ it("injects environment variables", async () => {
224
+ const engine = new InMemoryEngine("test");
225
+ await engine.initialize();
226
+ const manager = new BashEnvironmentManager(engine, LIMITS, null, {
227
+ env: { MY_VAR: "hello_world", TZ: "UTC" },
228
+ });
229
+ const bash = manager.getOrCreate("t1");
230
+
231
+ const result = await bash.exec("echo $MY_VAR");
232
+ expect(result.exitCode).toBe(0);
233
+ expect(result.stdout.trim()).toBe("hello_world");
234
+
235
+ const tzResult = await bash.exec("echo $TZ");
236
+ expect(tzResult.exitCode).toBe(0);
237
+ expect(tzResult.stdout.trim()).toBe("UTC");
238
+
239
+ manager.destroyAll();
240
+ await engine.close();
241
+ });
242
+ });
@@ -1,6 +0,0 @@
1
-
2
- > @poncho-ai/harness@0.11.2 lint /Users/cesar/Dev/latitude/poncho-ai/packages/harness
3
- > eslint src/
4
-
5
- sh: eslint: command not found
6
-  ELIFECYCLE  Command failed.