@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.
- package/.turbo/turbo-build.log +6 -5
- package/.turbo/turbo-test.log +15169 -0
- package/CHANGELOG.md +18 -0
- package/dist/chunk-MCKGQKYU.js +15 -0
- package/dist/dist-3KMQR4IO.js +27092 -0
- package/dist/index.d.ts +485 -29
- package/dist/index.js +2839 -2114
- package/dist/isolate-5MISBSUK.js +733 -0
- package/dist/isolate-5R6762YA.js +605 -0
- package/dist/isolate-KUZ5NOPG.js +727 -0
- package/dist/isolate-LOL3T7RA.js +729 -0
- package/dist/isolate-N22X4TCE.js +740 -0
- package/dist/isolate-T7WXM7IL.js +1490 -0
- package/dist/isolate-TCWTUVG4.js +1532 -0
- package/dist/isolate-WFOLANOB.js +768 -0
- package/package.json +22 -3
- package/scripts/migrate-to-engine.mjs +556 -0
- package/src/config.ts +106 -1
- package/src/harness.ts +226 -91
- package/src/index.ts +5 -0
- package/src/isolate/bindings.ts +206 -0
- package/src/isolate/bundler.ts +179 -0
- package/src/isolate/index.ts +10 -0
- package/src/isolate/polyfills.ts +796 -0
- package/src/isolate/run-code-tool.ts +220 -0
- package/src/isolate/runtime.ts +286 -0
- package/src/isolate/type-stubs.ts +196 -0
- package/src/memory.ts +129 -198
- package/src/reminder-store.ts +3 -237
- package/src/secrets-store.ts +2 -91
- package/src/state.ts +11 -1302
- package/src/storage/engine.ts +106 -0
- package/src/storage/index.ts +59 -0
- package/src/storage/memory-engine.ts +588 -0
- package/src/storage/postgres-engine.ts +139 -0
- package/src/storage/schema.ts +145 -0
- package/src/storage/sql-dialect.ts +963 -0
- package/src/storage/sqlite-engine.ts +99 -0
- package/src/storage/store-adapters.ts +100 -0
- package/src/todo-tools.ts +1 -136
- package/src/upload-store.ts +1 -0
- package/src/vfs/bash-manager.ts +120 -0
- package/src/vfs/bash-tool.ts +59 -0
- package/src/vfs/create-bash-fs.ts +32 -0
- package/src/vfs/edit-file-tool.ts +72 -0
- package/src/vfs/index.ts +5 -0
- package/src/vfs/poncho-fs-adapter.ts +267 -0
- package/src/vfs/protected-fs.ts +177 -0
- package/src/vfs/read-file-tool.ts +103 -0
- package/src/vfs/write-file-tool.ts +49 -0
- package/test/harness.test.ts +30 -36
- package/test/isolate-vfs.test.ts +453 -0
- package/test/isolate.test.ts +252 -0
- package/test/state.test.ts +4 -27
- package/test/storage-engine.test.ts +250 -0
- package/test/vfs.test.ts +242 -0
- 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
|
+
];
|