@poncho-ai/harness 0.35.0 → 0.36.0

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 (57) hide show
  1. package/.turbo/turbo-build.log +6 -5
  2. package/.turbo/turbo-test.log +15169 -0
  3. package/CHANGELOG.md +18 -0
  4. package/dist/chunk-MCKGQKYU.js +15 -0
  5. package/dist/dist-3KMQR4IO.js +27092 -0
  6. package/dist/index.d.ts +485 -29
  7. package/dist/index.js +2839 -2114
  8. package/dist/isolate-5MISBSUK.js +733 -0
  9. package/dist/isolate-5R6762YA.js +605 -0
  10. package/dist/isolate-KUZ5NOPG.js +727 -0
  11. package/dist/isolate-LOL3T7RA.js +729 -0
  12. package/dist/isolate-N22X4TCE.js +740 -0
  13. package/dist/isolate-T7WXM7IL.js +1490 -0
  14. package/dist/isolate-TCWTUVG4.js +1532 -0
  15. package/dist/isolate-WFOLANOB.js +768 -0
  16. package/package.json +22 -3
  17. package/scripts/migrate-to-engine.mjs +556 -0
  18. package/src/config.ts +106 -1
  19. package/src/harness.ts +226 -91
  20. package/src/index.ts +5 -0
  21. package/src/isolate/bindings.ts +206 -0
  22. package/src/isolate/bundler.ts +179 -0
  23. package/src/isolate/index.ts +10 -0
  24. package/src/isolate/polyfills.ts +796 -0
  25. package/src/isolate/run-code-tool.ts +220 -0
  26. package/src/isolate/runtime.ts +286 -0
  27. package/src/isolate/type-stubs.ts +196 -0
  28. package/src/memory.ts +129 -198
  29. package/src/reminder-store.ts +3 -237
  30. package/src/secrets-store.ts +2 -91
  31. package/src/state.ts +11 -1302
  32. package/src/storage/engine.ts +106 -0
  33. package/src/storage/index.ts +59 -0
  34. package/src/storage/memory-engine.ts +588 -0
  35. package/src/storage/postgres-engine.ts +139 -0
  36. package/src/storage/schema.ts +145 -0
  37. package/src/storage/sql-dialect.ts +963 -0
  38. package/src/storage/sqlite-engine.ts +99 -0
  39. package/src/storage/store-adapters.ts +100 -0
  40. package/src/todo-tools.ts +1 -136
  41. package/src/upload-store.ts +1 -0
  42. package/src/vfs/bash-manager.ts +120 -0
  43. package/src/vfs/bash-tool.ts +59 -0
  44. package/src/vfs/create-bash-fs.ts +32 -0
  45. package/src/vfs/edit-file-tool.ts +72 -0
  46. package/src/vfs/index.ts +5 -0
  47. package/src/vfs/poncho-fs-adapter.ts +267 -0
  48. package/src/vfs/protected-fs.ts +177 -0
  49. package/src/vfs/read-file-tool.ts +103 -0
  50. package/src/vfs/write-file-tool.ts +49 -0
  51. package/test/harness.test.ts +30 -36
  52. package/test/isolate-vfs.test.ts +453 -0
  53. package/test/isolate.test.ts +252 -0
  54. package/test/state.test.ts +4 -27
  55. package/test/storage-engine.test.ts +250 -0
  56. package/test/vfs.test.ts +242 -0
  57. package/src/kv-store.ts +0 -216
@@ -0,0 +1,139 @@
1
+ // ---------------------------------------------------------------------------
2
+ // PostgresEngine – postgres.js backed storage engine.
3
+ // ---------------------------------------------------------------------------
4
+
5
+ import type { QueryExecutor, QueryRow } from "./sql-dialect.js";
6
+ import { SqlStorageEngine, postgresDialect } from "./sql-dialect.js";
7
+
8
+ export class PostgresEngine extends SqlStorageEngine {
9
+ private sql: any; // postgres.js Sql instance
10
+ private readonly urlEnv: string;
11
+ protected readonly executor: QueryExecutor;
12
+
13
+ /** In-memory path cache per tenant for sync getAllPaths(). */
14
+ private pathCache = new Map<string, string[]>();
15
+
16
+ constructor(options: { agentId: string; urlEnv?: string }) {
17
+ super(postgresDialect, options.agentId);
18
+ this.urlEnv = options.urlEnv ?? "DATABASE_URL";
19
+
20
+ this.executor = {
21
+ run: async (sql: string, params?: unknown[]): Promise<void> => {
22
+ await this.query(sql, params);
23
+ },
24
+ get: async <T extends QueryRow = QueryRow>(
25
+ sql: string,
26
+ params?: unknown[],
27
+ ): Promise<T | undefined> => {
28
+ const rows = await this.query(sql, params);
29
+ return (rows[0] as T) ?? undefined;
30
+ },
31
+ all: async <T extends QueryRow = QueryRow>(
32
+ sql: string,
33
+ params?: unknown[],
34
+ ): Promise<T[]> => {
35
+ const rows = await this.query(sql, params);
36
+ return rows as T[];
37
+ },
38
+ exec: async (sql: string): Promise<void> => {
39
+ await this.sql.unsafe(sql);
40
+ },
41
+ transaction: async (fn: () => Promise<void>): Promise<void> => {
42
+ await this.sql.begin(async () => {
43
+ await fn();
44
+ });
45
+ },
46
+ };
47
+ }
48
+
49
+ protected override async onBeforeInit(): Promise<void> {
50
+ const url = process.env[this.urlEnv];
51
+ if (!url) {
52
+ throw new Error(
53
+ `PostgreSQL connection URL not found. Set ${this.urlEnv} environment variable.`,
54
+ );
55
+ }
56
+ const postgres = (await import("postgres")).default;
57
+ this.sql = postgres(url, {
58
+ onnotice: () => {},
59
+ });
60
+ }
61
+
62
+ override async initialize(): Promise<void> {
63
+ await super.initialize();
64
+ this.patchVfs();
65
+ }
66
+
67
+ async close(): Promise<void> {
68
+ await this.sql?.end();
69
+ }
70
+
71
+ /** Refresh the path cache for a tenant (call before bash.exec()). */
72
+ async refreshPathCache(tenantId: string): Promise<void> {
73
+ const rows = await this.query(
74
+ "SELECT path FROM vfs_entries WHERE agent_id = $1 AND tenant_id = $2",
75
+ [this.agentId, tenantId],
76
+ );
77
+ this.pathCache.set(tenantId, rows.map((r: any) => r.path as string));
78
+ }
79
+
80
+ // -----------------------------------------------------------------------
81
+ // Private
82
+ // -----------------------------------------------------------------------
83
+
84
+ private patchVfs(): void {
85
+ this.vfs.listAllPaths = (tenantId: string): string[] => {
86
+ return this.pathCache.get(tenantId) ?? [];
87
+ };
88
+
89
+ const origWrite = this.vfs.writeFile;
90
+ this.vfs.writeFile = async (
91
+ tenantId: string,
92
+ path: string,
93
+ content: Uint8Array,
94
+ mimeType?: string,
95
+ ) => {
96
+ await origWrite(tenantId, path, content, mimeType);
97
+ this.addToPathCache(tenantId, path);
98
+ };
99
+
100
+ const origDelete = this.vfs.deleteFile;
101
+ this.vfs.deleteFile = async (tenantId: string, path: string) => {
102
+ await origDelete(tenantId, path);
103
+ this.removeFromPathCache(tenantId, path);
104
+ };
105
+
106
+ const origRename = this.vfs.rename;
107
+ this.vfs.rename = async (
108
+ tenantId: string,
109
+ oldPath: string,
110
+ newPath: string,
111
+ ) => {
112
+ await origRename(tenantId, oldPath, newPath);
113
+ this.removeFromPathCache(tenantId, oldPath);
114
+ this.addToPathCache(tenantId, newPath);
115
+ };
116
+ }
117
+
118
+ private async query(sql: string, params?: unknown[]): Promise<any[]> {
119
+ if (!params || params.length === 0) {
120
+ return this.sql.unsafe(sql);
121
+ }
122
+ return this.sql.unsafe(sql, params);
123
+ }
124
+
125
+ private addToPathCache(tenantId: string, path: string): void {
126
+ const paths = this.pathCache.get(tenantId);
127
+ if (paths && !paths.includes(path)) {
128
+ paths.push(path);
129
+ }
130
+ }
131
+
132
+ private removeFromPathCache(tenantId: string, path: string): void {
133
+ const paths = this.pathCache.get(tenantId);
134
+ if (paths) {
135
+ const idx = paths.indexOf(path);
136
+ if (idx !== -1) paths.splice(idx, 1);
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,145 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Schema definitions and migrations for SQLite / PostgreSQL engines.
3
+ // ---------------------------------------------------------------------------
4
+
5
+ export interface Migration {
6
+ version: number;
7
+ name: string;
8
+ up: (d: DialectTag) => string[];
9
+ }
10
+
11
+ /** Tag used by migrations to branch on backend. */
12
+ export type DialectTag = "sqlite" | "postgresql";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Migration list
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const migrations: Migration[] = [
19
+ {
20
+ version: 1,
21
+ name: "initial_schema",
22
+ up: (d) => {
23
+ const jsonType = d === "sqlite" ? "TEXT" : "JSONB";
24
+ const blobType = d === "sqlite" ? "BLOB" : "BYTEA";
25
+ const tsDefault = d === "sqlite" ? "datetime('now')" : "NOW()";
26
+ const autoTs = `DEFAULT (${tsDefault})`;
27
+
28
+ return [
29
+ // _migrations (self-bootstrap — created by runner, but listed for clarity)
30
+ `CREATE TABLE IF NOT EXISTS _migrations (
31
+ version INTEGER PRIMARY KEY,
32
+ name TEXT NOT NULL,
33
+ applied_at TIMESTAMP ${autoTs}
34
+ )`,
35
+
36
+ // conversations
37
+ `CREATE TABLE IF NOT EXISTS conversations (
38
+ id TEXT PRIMARY KEY,
39
+ agent_id TEXT NOT NULL,
40
+ tenant_id TEXT NOT NULL DEFAULT '__default__',
41
+ owner_id TEXT NOT NULL DEFAULT 'local-owner',
42
+ title TEXT NOT NULL DEFAULT 'New conversation',
43
+ data ${jsonType} NOT NULL,
44
+ message_count INTEGER NOT NULL DEFAULT 0,
45
+ created_at TIMESTAMP ${autoTs},
46
+ updated_at TIMESTAMP ${autoTs}
47
+ )`,
48
+ `CREATE INDEX IF NOT EXISTS idx_conversations_lookup
49
+ ON conversations (agent_id, tenant_id, owner_id, updated_at DESC)`,
50
+
51
+ // memory
52
+ `CREATE TABLE IF NOT EXISTS memory (
53
+ agent_id TEXT NOT NULL,
54
+ tenant_id TEXT NOT NULL DEFAULT '__default__',
55
+ content TEXT NOT NULL DEFAULT '',
56
+ updated_at TIMESTAMP ${autoTs},
57
+ PRIMARY KEY (agent_id, tenant_id)
58
+ )`,
59
+
60
+ // todos
61
+ `CREATE TABLE IF NOT EXISTS todos (
62
+ agent_id TEXT NOT NULL,
63
+ conversation_id TEXT NOT NULL,
64
+ data ${jsonType} NOT NULL,
65
+ PRIMARY KEY (agent_id, conversation_id)
66
+ )`,
67
+
68
+ // reminders
69
+ `CREATE TABLE IF NOT EXISTS reminders (
70
+ id TEXT PRIMARY KEY,
71
+ agent_id TEXT NOT NULL,
72
+ tenant_id TEXT NOT NULL DEFAULT '__default__',
73
+ owner_id TEXT,
74
+ conversation_id TEXT NOT NULL,
75
+ task TEXT NOT NULL,
76
+ status TEXT NOT NULL DEFAULT 'pending',
77
+ scheduled_at REAL NOT NULL,
78
+ timezone TEXT,
79
+ created_at TIMESTAMP ${autoTs}
80
+ )`,
81
+ `CREATE INDEX IF NOT EXISTS idx_reminders_lookup
82
+ ON reminders (agent_id, tenant_id, status, scheduled_at)`,
83
+
84
+ // vfs_entries
85
+ `CREATE TABLE IF NOT EXISTS vfs_entries (
86
+ agent_id TEXT NOT NULL,
87
+ tenant_id TEXT NOT NULL DEFAULT '__default__',
88
+ path TEXT NOT NULL,
89
+ parent_path TEXT NOT NULL,
90
+ type TEXT NOT NULL DEFAULT 'file',
91
+ content ${blobType},
92
+ symlink_target TEXT,
93
+ mime_type TEXT,
94
+ size INTEGER NOT NULL DEFAULT 0,
95
+ mode INTEGER NOT NULL DEFAULT 438,
96
+ created_at TIMESTAMP ${autoTs},
97
+ updated_at TIMESTAMP ${autoTs},
98
+ PRIMARY KEY (agent_id, tenant_id, path)
99
+ )`,
100
+ `CREATE INDEX IF NOT EXISTS idx_vfs_parent
101
+ ON vfs_entries (agent_id, tenant_id, parent_path)`,
102
+ ];
103
+ },
104
+ },
105
+ {
106
+ version: 2,
107
+ name: "separate_tool_result_archive",
108
+ up: (d) => {
109
+ const jsonType = d === "sqlite" ? "TEXT" : "JSONB";
110
+ return [
111
+ `ALTER TABLE conversations ADD COLUMN tool_result_archive ${jsonType}`,
112
+ ];
113
+ },
114
+ },
115
+ {
116
+ version: 3,
117
+ name: "extract_heavy_fields_from_data_blob",
118
+ up: (d) => {
119
+ const jsonType = d === "sqlite" ? "TEXT" : "JSONB";
120
+ return [
121
+ `ALTER TABLE conversations ADD COLUMN harness_messages ${jsonType}`,
122
+ `ALTER TABLE conversations ADD COLUMN continuation_messages ${jsonType}`,
123
+ ...(d === "sqlite"
124
+ ? [
125
+ `UPDATE conversations SET
126
+ tool_result_archive = json_extract(data, '$._toolResultArchive'),
127
+ harness_messages = json_extract(data, '$._harnessMessages'),
128
+ continuation_messages = json_extract(data, '$._continuationMessages')
129
+ WHERE json_extract(data, '$._toolResultArchive') IS NOT NULL
130
+ OR json_extract(data, '$._harnessMessages') IS NOT NULL`,
131
+ `UPDATE conversations SET data = json_remove(data, '$._toolResultArchive', '$._harnessMessages', '$._continuationMessages')`,
132
+ ]
133
+ : [
134
+ `UPDATE conversations SET
135
+ tool_result_archive = data->'_toolResultArchive',
136
+ harness_messages = data->'_harnessMessages',
137
+ continuation_messages = data->'_continuationMessages'
138
+ WHERE data->'_toolResultArchive' IS NOT NULL
139
+ OR data->'_harnessMessages' IS NOT NULL`,
140
+ `UPDATE conversations SET data = data - '_toolResultArchive' - '_harnessMessages' - '_continuationMessages'`,
141
+ ]),
142
+ ];
143
+ },
144
+ },
145
+ ];