@poncho-ai/harness 0.43.0 → 0.44.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 +5 -5
- package/CHANGELOG.md +91 -0
- package/dist/index.d.ts +96 -45
- package/dist/index.js +367 -98
- package/package.json +1 -1
- package/src/harness.ts +32 -2
- package/src/orchestrator/run-conversation-turn.ts +2 -2
- package/src/storage/schema.ts +19 -0
- package/src/storage/sql-dialect.ts +11 -3
- package/src/upload-store.ts +27 -0
- package/src/vfs/bash-manager.ts +6 -3
- package/src/vfs/poncho-fs-adapter.ts +333 -51
- package/test/upload-store-decode.test.ts +43 -0
- package/test/vfs.test.ts +111 -0
package/dist/index.js
CHANGED
|
@@ -2340,7 +2340,7 @@ var ponchoDocsTool = defineTool({
|
|
|
2340
2340
|
|
|
2341
2341
|
// src/harness.ts
|
|
2342
2342
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
2343
|
-
import { readFile as
|
|
2343
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2344
2344
|
import { resolve as resolve11 } from "path";
|
|
2345
2345
|
import { defineTool as defineTool12, getTextContent as getTextContent2, createLogger as createLogger6, formatError as fmtErr, url as urlColor } from "@poncho-ai/sdk";
|
|
2346
2346
|
|
|
@@ -2422,6 +2422,23 @@ var deriveUploadKey = (data, mediaType) => {
|
|
|
2422
2422
|
const ext = mimeToExt(mediaType);
|
|
2423
2423
|
return `${hash}${ext}`;
|
|
2424
2424
|
};
|
|
2425
|
+
var DATA_URI_PREFIX = /^data:[^,]*?;base64,/;
|
|
2426
|
+
var decodeFileInputData = async (data) => {
|
|
2427
|
+
if (data.startsWith("http://") || data.startsWith("https://")) {
|
|
2428
|
+
const resp = await fetch(data);
|
|
2429
|
+
if (!resp.ok) {
|
|
2430
|
+
throw new Error(
|
|
2431
|
+
`uploads: failed to fetch file at ${data}: ${resp.status} ${resp.statusText}`
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
2435
|
+
}
|
|
2436
|
+
const match = data.match(DATA_URI_PREFIX);
|
|
2437
|
+
if (match) {
|
|
2438
|
+
return Buffer.from(data.slice(match[0].length), "base64");
|
|
2439
|
+
}
|
|
2440
|
+
return Buffer.from(data, "base64");
|
|
2441
|
+
};
|
|
2425
2442
|
var MIME_EXT_MAP = {
|
|
2426
2443
|
"image/jpeg": ".jpg",
|
|
2427
2444
|
"image/png": ".png",
|
|
@@ -2470,9 +2487,9 @@ var VercelBlobUploadStore = class {
|
|
|
2470
2487
|
sdk;
|
|
2471
2488
|
workingDir;
|
|
2472
2489
|
access;
|
|
2473
|
-
constructor(workingDir,
|
|
2490
|
+
constructor(workingDir, access4 = "public") {
|
|
2474
2491
|
this.workingDir = workingDir;
|
|
2475
|
-
this.access =
|
|
2492
|
+
this.access = access4;
|
|
2476
2493
|
}
|
|
2477
2494
|
async loadSdk() {
|
|
2478
2495
|
if (this.sdk) return this.sdk;
|
|
@@ -3319,6 +3336,25 @@ var migrations = [
|
|
|
3319
3336
|
ON conversations (parent_conversation_id, parent_message_id)
|
|
3320
3337
|
WHERE parent_message_id IS NOT NULL`
|
|
3321
3338
|
]
|
|
3339
|
+
},
|
|
3340
|
+
{
|
|
3341
|
+
version: 7,
|
|
3342
|
+
name: "fix_reminder_scheduled_at_precision",
|
|
3343
|
+
// Postgres maps `REAL` to float4 (4 bytes, ~7 digit precision),
|
|
3344
|
+
// which silently rounds millisecond epochs (13 digits) — every
|
|
3345
|
+
// reminder write+read on Postgres returned a different value than
|
|
3346
|
+
// it stored. SQLite's REAL is float8 (15+ digit precision) so it
|
|
3347
|
+
// was always fine there.
|
|
3348
|
+
//
|
|
3349
|
+
// Convert the Postgres column to BIGINT so future writes are exact
|
|
3350
|
+
// (already-stored rounded values aren't recoverable but they were
|
|
3351
|
+
// never correct in the first place).
|
|
3352
|
+
up: (d) => {
|
|
3353
|
+
if (d === "sqlite") return [];
|
|
3354
|
+
return [
|
|
3355
|
+
`ALTER TABLE reminders ALTER COLUMN scheduled_at TYPE BIGINT USING scheduled_at::bigint`
|
|
3356
|
+
];
|
|
3357
|
+
}
|
|
3322
3358
|
}
|
|
3323
3359
|
];
|
|
3324
3360
|
|
|
@@ -3701,11 +3737,12 @@ var SqlStorageEngine = class {
|
|
|
3701
3737
|
const filterTenant = tenantId !== void 0;
|
|
3702
3738
|
const pattern = `%${query}%`;
|
|
3703
3739
|
const params = [this.agentId, pattern, pattern];
|
|
3740
|
+
const dataMatch = this.dialect.tag === "postgresql" ? "data::text LIKE $3" : "data LIKE $3";
|
|
3704
3741
|
let sql = `SELECT id, title, updated_at, created_at, owner_id, tenant_id,
|
|
3705
3742
|
message_count, parent_conversation_id, parent_message_id,
|
|
3706
3743
|
has_pending_approvals, channel_meta
|
|
3707
3744
|
FROM conversations
|
|
3708
|
-
WHERE agent_id = $1 AND (title LIKE $2 OR
|
|
3745
|
+
WHERE agent_id = $1 AND (title LIKE $2 OR ${dataMatch})`;
|
|
3709
3746
|
if (filterTenant) {
|
|
3710
3747
|
sql += ` AND tenant_id = $4`;
|
|
3711
3748
|
params.push(tid);
|
|
@@ -3981,9 +4018,9 @@ var SqlStorageEngine = class {
|
|
|
3981
4018
|
}
|
|
3982
4019
|
},
|
|
3983
4020
|
deleteFile: async (tenantId, path) => {
|
|
3984
|
-
const
|
|
3985
|
-
if (!
|
|
3986
|
-
if (
|
|
4021
|
+
const stat4 = await this.vfs.stat(tenantId, path);
|
|
4022
|
+
if (!stat4) throw new Error(`ENOENT: no such file or directory, unlink '${path}'`);
|
|
4023
|
+
if (stat4.type === "directory") throw new Error(`EISDIR: illegal operation on a directory, unlink '${path}'`);
|
|
3987
4024
|
await this.executor.run(
|
|
3988
4025
|
rewrite(
|
|
3989
4026
|
"DELETE FROM vfs_entries WHERE agent_id = $1 AND tenant_id = $2 AND path = $3",
|
|
@@ -4223,7 +4260,10 @@ var SqlStorageEngine = class {
|
|
|
4223
4260
|
return {
|
|
4224
4261
|
id: row.id,
|
|
4225
4262
|
task: row.task,
|
|
4226
|
-
|
|
4263
|
+
// Postgres-js returns BIGINT columns as strings to avoid silent
|
|
4264
|
+
// precision loss in JS. Coerce to Number — ms epochs max out at
|
|
4265
|
+
// ~10^16 in year 2286, well under Number.MAX_SAFE_INTEGER (2^53).
|
|
4266
|
+
scheduledAt: Number(row.scheduled_at),
|
|
4227
4267
|
timezone: row.timezone ?? void 0,
|
|
4228
4268
|
status: row.status,
|
|
4229
4269
|
createdAt: new Date(row.created_at).getTime(),
|
|
@@ -4231,7 +4271,7 @@ var SqlStorageEngine = class {
|
|
|
4231
4271
|
ownerId: row.owner_id ?? void 0,
|
|
4232
4272
|
tenantId: tid === DEFAULT_TENANT2 ? null : tid,
|
|
4233
4273
|
recurrence,
|
|
4234
|
-
occurrenceCount: row.occurrence_count ?? 0
|
|
4274
|
+
occurrenceCount: Number(row.occurrence_count ?? 0)
|
|
4235
4275
|
};
|
|
4236
4276
|
}
|
|
4237
4277
|
toUint8Array(value) {
|
|
@@ -4527,6 +4567,9 @@ function createReminderStoreFromEngine(engine) {
|
|
|
4527
4567
|
import { Bash } from "just-bash";
|
|
4528
4568
|
|
|
4529
4569
|
// src/vfs/poncho-fs-adapter.ts
|
|
4570
|
+
import * as nodeFs from "fs/promises";
|
|
4571
|
+
import * as nodeFsSync from "fs";
|
|
4572
|
+
import * as nodePath from "path";
|
|
4530
4573
|
var MIME_MAP = {
|
|
4531
4574
|
".txt": "text/plain",
|
|
4532
4575
|
".html": "text/html",
|
|
@@ -4583,52 +4626,190 @@ var normalize = (path) => {
|
|
|
4583
4626
|
}
|
|
4584
4627
|
return "/" + out.join("/");
|
|
4585
4628
|
};
|
|
4629
|
+
var READ_ONLY_ERROR = (path, op) => new Error(`EROFS: read-only mount, ${op} '${path}'`);
|
|
4586
4630
|
var PonchoFsAdapter = class {
|
|
4587
|
-
constructor(engine, tenantId, limits) {
|
|
4631
|
+
constructor(engine, tenantId, limits, mounts = []) {
|
|
4588
4632
|
this.engine = engine;
|
|
4589
4633
|
this.tenantId = tenantId;
|
|
4590
4634
|
this.limits = limits;
|
|
4635
|
+
this.mounts = mounts.map((m) => {
|
|
4636
|
+
const prefix = m.prefix.endsWith("/") ? m.prefix : m.prefix + "/";
|
|
4637
|
+
return {
|
|
4638
|
+
prefix,
|
|
4639
|
+
prefixNoSlash: prefix.slice(0, -1),
|
|
4640
|
+
source: m.source.replace(/\/+$/, "")
|
|
4641
|
+
};
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
mounts;
|
|
4645
|
+
/** Find which mount, if any, a normalised VFS path falls under.
|
|
4646
|
+
* Returns the relative path within the mount's source dir (empty string
|
|
4647
|
+
* when the path is exactly the mount root). */
|
|
4648
|
+
routeToMount(np) {
|
|
4649
|
+
for (const m of this.mounts) {
|
|
4650
|
+
if (np === m.prefixNoSlash) return { mount: m, relative: "" };
|
|
4651
|
+
if (np.startsWith(m.prefix)) return { mount: m, relative: np.slice(m.prefix.length) };
|
|
4652
|
+
}
|
|
4653
|
+
return null;
|
|
4654
|
+
}
|
|
4655
|
+
/** Treat `np` as a directory and return mount-root segments that should be
|
|
4656
|
+
* listed as virtual subdirectories. E.g. with mount "/system/", reading
|
|
4657
|
+
* "/" returns ["system"]; reading "/system" goes via routeToMount and
|
|
4658
|
+
* serves from local FS instead. */
|
|
4659
|
+
virtualChildrenAt(np) {
|
|
4660
|
+
const dirPrefix = np === "/" ? "/" : np + "/";
|
|
4661
|
+
const out = [];
|
|
4662
|
+
for (const m of this.mounts) {
|
|
4663
|
+
if (m.prefix.startsWith(dirPrefix) && m.prefix !== dirPrefix) {
|
|
4664
|
+
const remaining = m.prefix.slice(dirPrefix.length);
|
|
4665
|
+
const seg = remaining.split("/")[0];
|
|
4666
|
+
if (seg && !out.includes(seg)) out.push(seg);
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
return out;
|
|
4670
|
+
}
|
|
4671
|
+
toLocal(mount, relative) {
|
|
4672
|
+
return nodePath.join(mount.source, relative);
|
|
4673
|
+
}
|
|
4674
|
+
/** Build an FsStat from a node fs.Stats. */
|
|
4675
|
+
toFsStat(s) {
|
|
4676
|
+
return {
|
|
4677
|
+
isFile: s.isFile(),
|
|
4678
|
+
isDirectory: s.isDirectory(),
|
|
4679
|
+
isSymbolicLink: s.isSymbolicLink(),
|
|
4680
|
+
mode: s.mode,
|
|
4681
|
+
size: s.size,
|
|
4682
|
+
mtime: s.mtime
|
|
4683
|
+
};
|
|
4684
|
+
}
|
|
4685
|
+
/** Synthesise a directory stat for a virtual ancestor (e.g. "/system"
|
|
4686
|
+
* when "/system/jobs/" is mounted but "/system" itself isn't a real dir
|
|
4687
|
+
* on disk). Used so `ls /` and `stat /system` work without surprises. */
|
|
4688
|
+
syntheticDirStat() {
|
|
4689
|
+
return {
|
|
4690
|
+
isFile: false,
|
|
4691
|
+
isDirectory: true,
|
|
4692
|
+
isSymbolicLink: false,
|
|
4693
|
+
mode: 493,
|
|
4694
|
+
size: 0,
|
|
4695
|
+
mtime: /* @__PURE__ */ new Date(0)
|
|
4696
|
+
};
|
|
4591
4697
|
}
|
|
4592
4698
|
// --- Reads ---
|
|
4593
4699
|
async readFile(path, _options) {
|
|
4594
|
-
const
|
|
4700
|
+
const np = normalize(path);
|
|
4701
|
+
const route = this.routeToMount(np);
|
|
4702
|
+
if (route) {
|
|
4703
|
+
const buf2 = await nodeFs.readFile(this.toLocal(route.mount, route.relative));
|
|
4704
|
+
return buf2.toString("utf8");
|
|
4705
|
+
}
|
|
4706
|
+
const buf = await this.engine.vfs.readFile(this.tenantId, np);
|
|
4595
4707
|
return new TextDecoder().decode(buf);
|
|
4596
4708
|
}
|
|
4597
4709
|
async readFileBuffer(path) {
|
|
4598
|
-
|
|
4710
|
+
const np = normalize(path);
|
|
4711
|
+
const route = this.routeToMount(np);
|
|
4712
|
+
if (route) {
|
|
4713
|
+
const buf = await nodeFs.readFile(this.toLocal(route.mount, route.relative));
|
|
4714
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
4715
|
+
}
|
|
4716
|
+
return this.engine.vfs.readFile(this.tenantId, np);
|
|
4599
4717
|
}
|
|
4600
4718
|
async exists(path) {
|
|
4601
|
-
const
|
|
4602
|
-
|
|
4719
|
+
const np = normalize(path);
|
|
4720
|
+
const route = this.routeToMount(np);
|
|
4721
|
+
if (route) {
|
|
4722
|
+
try {
|
|
4723
|
+
await nodeFs.access(this.toLocal(route.mount, route.relative));
|
|
4724
|
+
return true;
|
|
4725
|
+
} catch {
|
|
4726
|
+
return false;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
const s = await this.engine.vfs.stat(this.tenantId, np);
|
|
4730
|
+
if (s) return true;
|
|
4731
|
+
return this.virtualChildrenAt(np).length > 0;
|
|
4603
4732
|
}
|
|
4604
4733
|
async stat(path) {
|
|
4605
4734
|
const np = normalize(path);
|
|
4735
|
+
const route = this.routeToMount(np);
|
|
4736
|
+
if (route) {
|
|
4737
|
+
try {
|
|
4738
|
+
const s2 = await nodeFs.stat(this.toLocal(route.mount, route.relative));
|
|
4739
|
+
return this.toFsStat(s2);
|
|
4740
|
+
} catch {
|
|
4741
|
+
throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4606
4744
|
const s = await this.engine.vfs.stat(this.tenantId, np);
|
|
4607
|
-
if (
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4745
|
+
if (s) {
|
|
4746
|
+
return {
|
|
4747
|
+
isFile: s.type === "file",
|
|
4748
|
+
isDirectory: s.type === "directory",
|
|
4749
|
+
isSymbolicLink: s.type === "symlink",
|
|
4750
|
+
mode: s.mode,
|
|
4751
|
+
size: s.size,
|
|
4752
|
+
mtime: new Date(s.updatedAt)
|
|
4753
|
+
};
|
|
4754
|
+
}
|
|
4755
|
+
if (this.virtualChildrenAt(np).length > 0) return this.syntheticDirStat();
|
|
4756
|
+
throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
|
|
4616
4757
|
}
|
|
4617
4758
|
async readdir(path) {
|
|
4618
|
-
const
|
|
4619
|
-
|
|
4759
|
+
const np = normalize(path);
|
|
4760
|
+
const route = this.routeToMount(np);
|
|
4761
|
+
if (route) {
|
|
4762
|
+
return nodeFs.readdir(this.toLocal(route.mount, route.relative));
|
|
4763
|
+
}
|
|
4764
|
+
let engineNames = [];
|
|
4765
|
+
try {
|
|
4766
|
+
const entries = await this.engine.vfs.readdir(this.tenantId, np);
|
|
4767
|
+
engineNames = entries.map((e) => e.name);
|
|
4768
|
+
} catch {
|
|
4769
|
+
}
|
|
4770
|
+
const virtualSegs = this.virtualChildrenAt(np);
|
|
4771
|
+
if (virtualSegs.length === 0) return engineNames;
|
|
4772
|
+
const merged = new Set(engineNames);
|
|
4773
|
+
for (const seg of virtualSegs) merged.add(seg);
|
|
4774
|
+
return Array.from(merged);
|
|
4620
4775
|
}
|
|
4621
4776
|
async readdirWithFileTypes(path) {
|
|
4622
|
-
const
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4777
|
+
const np = normalize(path);
|
|
4778
|
+
const route = this.routeToMount(np);
|
|
4779
|
+
if (route) {
|
|
4780
|
+
const entries = await nodeFs.readdir(this.toLocal(route.mount, route.relative), { withFileTypes: true });
|
|
4781
|
+
return entries.map((e) => ({
|
|
4782
|
+
name: e.name,
|
|
4783
|
+
isFile: e.isFile(),
|
|
4784
|
+
isDirectory: e.isDirectory(),
|
|
4785
|
+
isSymbolicLink: e.isSymbolicLink()
|
|
4786
|
+
}));
|
|
4787
|
+
}
|
|
4788
|
+
let engineEntries = [];
|
|
4789
|
+
try {
|
|
4790
|
+
const entries = await this.engine.vfs.readdir(this.tenantId, np);
|
|
4791
|
+
engineEntries = entries.map((e) => ({
|
|
4792
|
+
name: e.name,
|
|
4793
|
+
isFile: e.type === "file",
|
|
4794
|
+
isDirectory: e.type === "directory",
|
|
4795
|
+
isSymbolicLink: e.type === "symlink"
|
|
4796
|
+
}));
|
|
4797
|
+
} catch {
|
|
4798
|
+
}
|
|
4799
|
+
const virtualSegs = this.virtualChildrenAt(np);
|
|
4800
|
+
if (virtualSegs.length === 0) return engineEntries;
|
|
4801
|
+
const seen = new Set(engineEntries.map((e) => e.name));
|
|
4802
|
+
for (const seg of virtualSegs) {
|
|
4803
|
+
if (!seen.has(seg)) {
|
|
4804
|
+
engineEntries.push({ name: seg, isFile: false, isDirectory: true, isSymbolicLink: false });
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
return engineEntries;
|
|
4629
4808
|
}
|
|
4630
4809
|
// --- Writes ---
|
|
4631
4810
|
async writeFile(path, content, _options) {
|
|
4811
|
+
const np = normalize(path);
|
|
4812
|
+
if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "writeFile");
|
|
4632
4813
|
const buf = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
4633
4814
|
if (buf.byteLength > this.limits.maxFileSize) {
|
|
4634
4815
|
throw new Error(
|
|
@@ -4636,17 +4817,22 @@ var PonchoFsAdapter = class {
|
|
|
4636
4817
|
);
|
|
4637
4818
|
}
|
|
4638
4819
|
const mime = mimeFromExtension(path);
|
|
4639
|
-
await this.engine.vfs.writeFile(this.tenantId,
|
|
4820
|
+
await this.engine.vfs.writeFile(this.tenantId, np, buf, mime);
|
|
4640
4821
|
}
|
|
4641
4822
|
async appendFile(path, content, _options) {
|
|
4823
|
+
const np = normalize(path);
|
|
4824
|
+
if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "appendFile");
|
|
4642
4825
|
const buf = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
4643
|
-
await this.engine.vfs.appendFile(this.tenantId,
|
|
4826
|
+
await this.engine.vfs.appendFile(this.tenantId, np, buf);
|
|
4644
4827
|
}
|
|
4645
4828
|
async mkdir(path, options) {
|
|
4646
|
-
|
|
4829
|
+
const np = normalize(path);
|
|
4830
|
+
if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "mkdir");
|
|
4831
|
+
await this.engine.vfs.mkdir(this.tenantId, np, options?.recursive);
|
|
4647
4832
|
}
|
|
4648
4833
|
async rm(path, options) {
|
|
4649
4834
|
const np = normalize(path);
|
|
4835
|
+
if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "rm");
|
|
4650
4836
|
const s = await this.engine.vfs.stat(this.tenantId, np);
|
|
4651
4837
|
if (!s) {
|
|
4652
4838
|
if (options?.force) return;
|
|
@@ -4662,25 +4848,30 @@ var PonchoFsAdapter = class {
|
|
|
4662
4848
|
async cp(src, dest, options) {
|
|
4663
4849
|
const srcNorm = normalize(src);
|
|
4664
4850
|
const destNorm = normalize(dest);
|
|
4665
|
-
|
|
4851
|
+
if (this.routeToMount(destNorm)) throw READ_ONLY_ERROR(dest, "cp");
|
|
4852
|
+
const srcStat = await this.stat(srcNorm).catch(() => null);
|
|
4666
4853
|
if (!srcStat) throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
|
|
4667
|
-
if (srcStat.
|
|
4854
|
+
if (srcStat.isDirectory) {
|
|
4668
4855
|
if (!options?.recursive) {
|
|
4669
4856
|
throw new Error(`EISDIR: cp -r not specified; omitting directory '${src}'`);
|
|
4670
4857
|
}
|
|
4671
4858
|
await this.engine.vfs.mkdir(this.tenantId, destNorm, true);
|
|
4672
|
-
const entries = await this.
|
|
4673
|
-
for (const
|
|
4674
|
-
await this.cp(`${srcNorm}/${
|
|
4859
|
+
const entries = await this.readdir(srcNorm);
|
|
4860
|
+
for (const name of entries) {
|
|
4861
|
+
await this.cp(`${srcNorm}/${name}`, `${destNorm}/${name}`, options);
|
|
4675
4862
|
}
|
|
4676
4863
|
} else {
|
|
4677
|
-
const content = await this.
|
|
4864
|
+
const content = await this.readFileBuffer(srcNorm);
|
|
4678
4865
|
const mime = mimeFromExtension(destNorm);
|
|
4679
4866
|
await this.engine.vfs.writeFile(this.tenantId, destNorm, content, mime);
|
|
4680
4867
|
}
|
|
4681
4868
|
}
|
|
4682
4869
|
async mv(src, dest) {
|
|
4683
|
-
|
|
4870
|
+
const srcNorm = normalize(src);
|
|
4871
|
+
const destNorm = normalize(dest);
|
|
4872
|
+
if (this.routeToMount(srcNorm)) throw READ_ONLY_ERROR(src, "mv (source)");
|
|
4873
|
+
if (this.routeToMount(destNorm)) throw READ_ONLY_ERROR(dest, "mv (dest)");
|
|
4874
|
+
await this.engine.vfs.rename(this.tenantId, srcNorm, destNorm);
|
|
4684
4875
|
}
|
|
4685
4876
|
// --- Path resolution ---
|
|
4686
4877
|
resolvePath(base, path) {
|
|
@@ -4689,8 +4880,22 @@ var PonchoFsAdapter = class {
|
|
|
4689
4880
|
}
|
|
4690
4881
|
async realpath(path) {
|
|
4691
4882
|
const np = normalize(path);
|
|
4883
|
+
const route = this.routeToMount(np);
|
|
4884
|
+
if (route) {
|
|
4885
|
+
const localResolved = await nodeFs.realpath(this.toLocal(route.mount, route.relative));
|
|
4886
|
+
const localRoot = await nodeFs.realpath(route.mount.source).catch(() => route.mount.source);
|
|
4887
|
+
if (localResolved === localRoot) return route.mount.prefixNoSlash;
|
|
4888
|
+
if (localResolved.startsWith(localRoot + nodePath.sep)) {
|
|
4889
|
+
const rel = localResolved.slice(localRoot.length + 1).split(nodePath.sep).join("/");
|
|
4890
|
+
return `${route.mount.prefix}${rel}`;
|
|
4891
|
+
}
|
|
4892
|
+
return np;
|
|
4893
|
+
}
|
|
4692
4894
|
const s = await this.engine.vfs.lstat(this.tenantId, np);
|
|
4693
|
-
if (!s)
|
|
4895
|
+
if (!s) {
|
|
4896
|
+
if (this.virtualChildrenAt(np).length > 0) return np;
|
|
4897
|
+
throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
|
|
4898
|
+
}
|
|
4694
4899
|
if (s.type === "symlink" && s.symlinkTarget) {
|
|
4695
4900
|
const target = s.symlinkTarget.startsWith("/") ? s.symlinkTarget : normalize(`${np.slice(0, np.lastIndexOf("/"))}/${s.symlinkTarget}`);
|
|
4696
4901
|
return this.realpath(target);
|
|
@@ -4699,39 +4904,92 @@ var PonchoFsAdapter = class {
|
|
|
4699
4904
|
}
|
|
4700
4905
|
// --- Sync: required by just-bash for glob/find ---
|
|
4701
4906
|
getAllPaths() {
|
|
4702
|
-
|
|
4907
|
+
const enginePaths = this.engine.vfs.listAllPaths(this.tenantId);
|
|
4908
|
+
if (this.mounts.length === 0) return enginePaths;
|
|
4909
|
+
const out = new Set(enginePaths);
|
|
4910
|
+
for (const m of this.mounts) {
|
|
4911
|
+
out.add(m.prefixNoSlash);
|
|
4912
|
+
try {
|
|
4913
|
+
const stack = [
|
|
4914
|
+
{ abs: m.source, vfs: m.prefixNoSlash }
|
|
4915
|
+
];
|
|
4916
|
+
while (stack.length > 0) {
|
|
4917
|
+
const { abs, vfs } = stack.pop();
|
|
4918
|
+
let entries;
|
|
4919
|
+
try {
|
|
4920
|
+
entries = nodeFsSync.readdirSync(abs, { withFileTypes: true });
|
|
4921
|
+
} catch {
|
|
4922
|
+
continue;
|
|
4923
|
+
}
|
|
4924
|
+
for (const e of entries) {
|
|
4925
|
+
const childVfs = `${vfs}/${e.name}`;
|
|
4926
|
+
out.add(childVfs);
|
|
4927
|
+
if (e.isDirectory()) {
|
|
4928
|
+
stack.push({ abs: nodePath.join(abs, e.name), vfs: childVfs });
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4932
|
+
} catch {
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
return Array.from(out);
|
|
4703
4936
|
}
|
|
4704
4937
|
// --- Metadata ---
|
|
4705
4938
|
async chmod(path, mode) {
|
|
4706
|
-
|
|
4939
|
+
const np = normalize(path);
|
|
4940
|
+
if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "chmod");
|
|
4941
|
+
await this.engine.vfs.chmod(this.tenantId, np, mode);
|
|
4707
4942
|
}
|
|
4708
4943
|
async utimes(path, _atime, mtime) {
|
|
4709
|
-
|
|
4944
|
+
const np = normalize(path);
|
|
4945
|
+
if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "utimes");
|
|
4946
|
+
await this.engine.vfs.utimes(this.tenantId, np, mtime);
|
|
4710
4947
|
}
|
|
4711
4948
|
// --- Symlinks ---
|
|
4712
4949
|
async symlink(target, linkPath) {
|
|
4713
|
-
|
|
4950
|
+
const np = normalize(linkPath);
|
|
4951
|
+
if (this.routeToMount(np)) throw READ_ONLY_ERROR(linkPath, "symlink");
|
|
4952
|
+
await this.engine.vfs.symlink(this.tenantId, target, np);
|
|
4714
4953
|
}
|
|
4715
4954
|
async link(existingPath, newPath) {
|
|
4716
|
-
const
|
|
4955
|
+
const npNew = normalize(newPath);
|
|
4956
|
+
if (this.routeToMount(npNew)) throw READ_ONLY_ERROR(newPath, "link");
|
|
4957
|
+
const content = await this.readFileBuffer(existingPath);
|
|
4717
4958
|
const mime = mimeFromExtension(newPath);
|
|
4718
|
-
await this.engine.vfs.writeFile(this.tenantId,
|
|
4959
|
+
await this.engine.vfs.writeFile(this.tenantId, npNew, content, mime);
|
|
4719
4960
|
}
|
|
4720
4961
|
async readlink(path) {
|
|
4721
|
-
|
|
4962
|
+
const np = normalize(path);
|
|
4963
|
+
const route = this.routeToMount(np);
|
|
4964
|
+
if (route) {
|
|
4965
|
+
return nodeFs.readlink(this.toLocal(route.mount, route.relative));
|
|
4966
|
+
}
|
|
4967
|
+
return this.engine.vfs.readlink(this.tenantId, np);
|
|
4722
4968
|
}
|
|
4723
4969
|
async lstat(path) {
|
|
4724
4970
|
const np = normalize(path);
|
|
4971
|
+
const route = this.routeToMount(np);
|
|
4972
|
+
if (route) {
|
|
4973
|
+
try {
|
|
4974
|
+
const s2 = await nodeFs.lstat(this.toLocal(route.mount, route.relative));
|
|
4975
|
+
return this.toFsStat(s2);
|
|
4976
|
+
} catch {
|
|
4977
|
+
throw new Error(`ENOENT: no such file or directory, lstat '${path}'`);
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4725
4980
|
const s = await this.engine.vfs.lstat(this.tenantId, np);
|
|
4726
|
-
if (
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4981
|
+
if (s) {
|
|
4982
|
+
return {
|
|
4983
|
+
isFile: s.type === "file",
|
|
4984
|
+
isDirectory: s.type === "directory",
|
|
4985
|
+
isSymbolicLink: s.type === "symlink",
|
|
4986
|
+
mode: s.mode,
|
|
4987
|
+
size: s.size,
|
|
4988
|
+
mtime: new Date(s.updatedAt)
|
|
4989
|
+
};
|
|
4990
|
+
}
|
|
4991
|
+
if (this.virtualChildrenAt(np).length > 0) return this.syntheticDirStat();
|
|
4992
|
+
throw new Error(`ENOENT: no such file or directory, lstat '${path}'`);
|
|
4735
4993
|
}
|
|
4736
4994
|
};
|
|
4737
4995
|
|
|
@@ -4908,21 +5166,23 @@ function toBashOptions(cfg, network) {
|
|
|
4908
5166
|
return opts;
|
|
4909
5167
|
}
|
|
4910
5168
|
var BashEnvironmentManager = class {
|
|
4911
|
-
constructor(engine, limits, workingDir, bashConfig, network) {
|
|
5169
|
+
constructor(engine, limits, workingDir, bashConfig, network, virtualMounts = []) {
|
|
4912
5170
|
this.engine = engine;
|
|
4913
5171
|
this.limits = limits;
|
|
4914
5172
|
this.workingDir = workingDir;
|
|
4915
5173
|
this.bashOptions = toBashOptions(bashConfig, network);
|
|
5174
|
+
this.virtualMounts = virtualMounts;
|
|
4916
5175
|
}
|
|
4917
5176
|
environments = /* @__PURE__ */ new Map();
|
|
4918
5177
|
filesystems = /* @__PURE__ */ new Map();
|
|
4919
5178
|
workingDir;
|
|
4920
5179
|
bashOptions;
|
|
5180
|
+
virtualMounts;
|
|
4921
5181
|
/** Return the combined IFileSystem (VFS + optional /project mount) for a tenant. */
|
|
4922
5182
|
getFs(tenantId) {
|
|
4923
5183
|
let fs = this.filesystems.get(tenantId);
|
|
4924
5184
|
if (!fs) {
|
|
4925
|
-
const adapter = new PonchoFsAdapter(this.engine, tenantId, this.limits);
|
|
5185
|
+
const adapter = new PonchoFsAdapter(this.engine, tenantId, this.limits, this.virtualMounts);
|
|
4926
5186
|
fs = createBashFs(adapter, this.workingDir);
|
|
4927
5187
|
this.filesystems.set(tenantId, fs);
|
|
4928
5188
|
}
|
|
@@ -4942,7 +5202,7 @@ var BashEnvironmentManager = class {
|
|
|
4942
5202
|
return bash;
|
|
4943
5203
|
}
|
|
4944
5204
|
getAdapter(tenantId) {
|
|
4945
|
-
return new PonchoFsAdapter(this.engine, tenantId, this.limits);
|
|
5205
|
+
return new PonchoFsAdapter(this.engine, tenantId, this.limits, this.virtualMounts);
|
|
4946
5206
|
}
|
|
4947
5207
|
/** Refresh the PostgreSQL path cache before a bash.exec() call. */
|
|
4948
5208
|
async refreshPathCache(tenantId) {
|
|
@@ -5061,8 +5321,8 @@ var createReadFileTool = (getFs) => defineTool3({
|
|
|
5061
5321
|
if (!await fs.exists(filePath)) {
|
|
5062
5322
|
throw new Error(`File not found: ${filePath}`);
|
|
5063
5323
|
}
|
|
5064
|
-
const
|
|
5065
|
-
if (
|
|
5324
|
+
const stat4 = await fs.stat(filePath);
|
|
5325
|
+
if (stat4.isDirectory) {
|
|
5066
5326
|
throw new Error(`${filePath} is a directory, not a file`);
|
|
5067
5327
|
}
|
|
5068
5328
|
const mediaType = mimeFromPath(filePath) ?? "application/octet-stream";
|
|
@@ -5114,8 +5374,8 @@ var createEditFileTool = (getFs) => defineTool4({
|
|
|
5114
5374
|
const tenantId = context.tenantId ?? "__default__";
|
|
5115
5375
|
const fs = getFs(tenantId);
|
|
5116
5376
|
if (!await fs.exists(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
5117
|
-
const
|
|
5118
|
-
if (
|
|
5377
|
+
const stat4 = await fs.stat(filePath);
|
|
5378
|
+
if (stat4.isDirectory) throw new Error(`${filePath} is a directory`);
|
|
5119
5379
|
const content = await fs.readFile(filePath);
|
|
5120
5380
|
const first = content.indexOf(oldStr);
|
|
5121
5381
|
if (first === -1) {
|
|
@@ -5610,7 +5870,7 @@ var createTodoTools = (store) => {
|
|
|
5610
5870
|
|
|
5611
5871
|
// src/secrets-store.ts
|
|
5612
5872
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
|
|
5613
|
-
import { mkdir as mkdir3, readFile as
|
|
5873
|
+
import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
5614
5874
|
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
5615
5875
|
function deriveKey(signingKey) {
|
|
5616
5876
|
return createHash3("sha256").update("poncho-secrets-v1:" + signingKey).digest();
|
|
@@ -5656,7 +5916,7 @@ var FileSecretsStore = class {
|
|
|
5656
5916
|
}
|
|
5657
5917
|
async readAll(tenantId) {
|
|
5658
5918
|
try {
|
|
5659
|
-
const raw = await
|
|
5919
|
+
const raw = await readFile6(await this.filePath(tenantId), "utf8");
|
|
5660
5920
|
return JSON.parse(raw);
|
|
5661
5921
|
} catch {
|
|
5662
5922
|
return {};
|
|
@@ -6566,7 +6826,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
6566
6826
|
// src/openai-codex-auth.ts
|
|
6567
6827
|
import { homedir as homedir3 } from "os";
|
|
6568
6828
|
import { dirname as dirname4, resolve as resolve8 } from "path";
|
|
6569
|
-
import { mkdir as mkdir4, readFile as
|
|
6829
|
+
import { mkdir as mkdir4, readFile as readFile7, chmod, writeFile as writeFile5, rm as rm3 } from "fs/promises";
|
|
6570
6830
|
var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
6571
6831
|
var OPENAI_AUTH_ISSUER = "https://auth.openai.com";
|
|
6572
6832
|
var REFRESH_TOKEN_GRACE_MS = 5 * 60 * 1e3;
|
|
@@ -6605,7 +6865,7 @@ var getOpenAICodexAuthFilePath = (config) => {
|
|
|
6605
6865
|
var readOpenAICodexSession = async (config) => {
|
|
6606
6866
|
const filePath = getOpenAICodexAuthFilePath(config);
|
|
6607
6867
|
try {
|
|
6608
|
-
const content = await
|
|
6868
|
+
const content = await readFile7(filePath, "utf8");
|
|
6609
6869
|
const parsed = JSON.parse(content);
|
|
6610
6870
|
if (typeof parsed.refreshToken !== "string" || parsed.refreshToken.length === 0) {
|
|
6611
6871
|
return void 0;
|
|
@@ -6940,7 +7200,7 @@ var createModelProvider = (provider, config) => {
|
|
|
6940
7200
|
};
|
|
6941
7201
|
|
|
6942
7202
|
// src/skill-context.ts
|
|
6943
|
-
import { readFile as
|
|
7203
|
+
import { readFile as readFile8, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
6944
7204
|
import { dirname as dirname5, resolve as resolve9, normalize as normalize2 } from "path";
|
|
6945
7205
|
import YAML3 from "yaml";
|
|
6946
7206
|
import { createLogger as createLogger4 } from "@poncho-ai/sdk";
|
|
@@ -7021,7 +7281,7 @@ var parseSkillFrontmatter = (content) => {
|
|
|
7021
7281
|
};
|
|
7022
7282
|
};
|
|
7023
7283
|
var collectSkillManifests = async (directory) => {
|
|
7024
|
-
const entries = await
|
|
7284
|
+
const entries = await readdir3(directory, { withFileTypes: true });
|
|
7025
7285
|
const files = [];
|
|
7026
7286
|
for (const entry of entries) {
|
|
7027
7287
|
const fullPath = resolve9(directory, entry.name);
|
|
@@ -7029,7 +7289,7 @@ var collectSkillManifests = async (directory) => {
|
|
|
7029
7289
|
let isFile = entry.isFile();
|
|
7030
7290
|
if (entry.isSymbolicLink()) {
|
|
7031
7291
|
try {
|
|
7032
|
-
const s = await
|
|
7292
|
+
const s = await stat2(fullPath);
|
|
7033
7293
|
isDir = s.isDirectory();
|
|
7034
7294
|
isFile = s.isFile();
|
|
7035
7295
|
} catch {
|
|
@@ -7059,7 +7319,7 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
|
|
|
7059
7319
|
const seen = /* @__PURE__ */ new Set();
|
|
7060
7320
|
for (const manifest of allManifests) {
|
|
7061
7321
|
try {
|
|
7062
|
-
const content = await
|
|
7322
|
+
const content = await readFile8(manifest, "utf8");
|
|
7063
7323
|
const parsed = parseSkillFrontmatter(content);
|
|
7064
7324
|
if (parsed && !seen.has(parsed.name)) {
|
|
7065
7325
|
seen.add(parsed.name);
|
|
@@ -7162,7 +7422,7 @@ var mergeSkills = (repoSkills, vfsSkills, onCollision) => {
|
|
|
7162
7422
|
var loadSkillInstructions = async (skill, engine) => {
|
|
7163
7423
|
const raw = skill.source.kind === "vfs" ? decoder.decode(
|
|
7164
7424
|
await requireEngine(engine).vfs.readFile(skill.source.tenantId, skill.skillPath)
|
|
7165
|
-
) : await
|
|
7425
|
+
) : await readFile8(skill.skillPath, "utf8");
|
|
7166
7426
|
const match = raw.match(FRONTMATTER_PATTERN3);
|
|
7167
7427
|
return match ? match[2].trim() : raw.trim();
|
|
7168
7428
|
};
|
|
@@ -7183,7 +7443,7 @@ var readSkillResource = async (skill, relativePath, engine) => {
|
|
|
7183
7443
|
if (!fullPath.startsWith(skill.skillDir)) {
|
|
7184
7444
|
throw new Error("Path escapes the skill directory");
|
|
7185
7445
|
}
|
|
7186
|
-
return await
|
|
7446
|
+
return await readFile8(fullPath, "utf8");
|
|
7187
7447
|
};
|
|
7188
7448
|
var requireEngine = (engine) => {
|
|
7189
7449
|
if (!engine) {
|
|
@@ -7311,8 +7571,8 @@ function convertSchema(schema) {
|
|
|
7311
7571
|
|
|
7312
7572
|
// src/skill-tools.ts
|
|
7313
7573
|
import { defineTool as defineTool9 } from "@poncho-ai/sdk";
|
|
7314
|
-
import { access as
|
|
7315
|
-
import { extname, normalize as normalize3, resolve as resolve10, sep as
|
|
7574
|
+
import { access as access3, readdir as readdir4, stat as stat3 } from "fs/promises";
|
|
7575
|
+
import { extname, normalize as normalize3, resolve as resolve10, sep as sep3 } from "path";
|
|
7316
7576
|
import { pathToFileURL } from "url";
|
|
7317
7577
|
import { createJiti as createJiti2 } from "jiti";
|
|
7318
7578
|
var findSkill = async (options, tenantId, name) => {
|
|
@@ -7497,7 +7757,7 @@ var createSkillTools = (options) => {
|
|
|
7497
7757
|
error: `Script "${resolved2.relativePath}" for skill "${name}" is not allowed by policy.`
|
|
7498
7758
|
};
|
|
7499
7759
|
}
|
|
7500
|
-
await
|
|
7760
|
+
await access3(resolved2.fullPath);
|
|
7501
7761
|
const fn2 = await loadRunnableScriptFunction(resolved2.fullPath);
|
|
7502
7762
|
const output2 = await fn2(payload, {
|
|
7503
7763
|
scope: "skill",
|
|
@@ -7513,7 +7773,7 @@ var createSkillTools = (options) => {
|
|
|
7513
7773
|
error: `Script "${resolved.relativePath}" is not allowed by policy.`
|
|
7514
7774
|
};
|
|
7515
7775
|
}
|
|
7516
|
-
await
|
|
7776
|
+
await access3(resolved.fullPath);
|
|
7517
7777
|
const fn = await loadRunnableScriptFunction(resolved.fullPath);
|
|
7518
7778
|
const output = await fn(payload, {
|
|
7519
7779
|
scope: "agent",
|
|
@@ -7533,7 +7793,7 @@ var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", "
|
|
|
7533
7793
|
var VFS_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".ts", ".mts"]);
|
|
7534
7794
|
var listRepoSkillScripts = async (skill, isScriptAllowed) => {
|
|
7535
7795
|
const scripts = await collectScriptFiles(skill.skillDir);
|
|
7536
|
-
return scripts.map((fullPath) => fullPath.slice(skill.skillDir.length + 1).split(
|
|
7796
|
+
return scripts.map((fullPath) => fullPath.slice(skill.skillDir.length + 1).split(sep3).join("/")).filter((relativePath) => relativePath.toLowerCase() !== "skill.md").map(
|
|
7537
7797
|
(relativePath) => relativePath.includes("/") ? relativePath : `./${relativePath}`
|
|
7538
7798
|
).filter((path) => isScriptAllowed ? isScriptAllowed(skill.name, path) : true).sort();
|
|
7539
7799
|
};
|
|
@@ -7566,7 +7826,7 @@ var listVfsSkillScripts = async (skill, engine, isScriptAllowed) => {
|
|
|
7566
7826
|
return found.filter((path) => isScriptAllowed ? isScriptAllowed(skill.name, path) : true).sort();
|
|
7567
7827
|
};
|
|
7568
7828
|
var collectScriptFiles = async (directory) => {
|
|
7569
|
-
const entries = await
|
|
7829
|
+
const entries = await readdir4(directory, { withFileTypes: true });
|
|
7570
7830
|
const files = [];
|
|
7571
7831
|
for (const entry of entries) {
|
|
7572
7832
|
if (entry.name === "node_modules") continue;
|
|
@@ -7575,7 +7835,7 @@ var collectScriptFiles = async (directory) => {
|
|
|
7575
7835
|
let isFile = entry.isFile();
|
|
7576
7836
|
if (entry.isSymbolicLink()) {
|
|
7577
7837
|
try {
|
|
7578
|
-
const s = await
|
|
7838
|
+
const s = await stat3(fullPath);
|
|
7579
7839
|
isDir = s.isDirectory();
|
|
7580
7840
|
isFile = s.isFile();
|
|
7581
7841
|
} catch {
|
|
@@ -7597,7 +7857,7 @@ var collectScriptFiles = async (directory) => {
|
|
|
7597
7857
|
};
|
|
7598
7858
|
var normalizeScriptPolicyPath = (relativePath) => {
|
|
7599
7859
|
const trimmed = relativePath.trim();
|
|
7600
|
-
const normalized = normalize3(trimmed).split(
|
|
7860
|
+
const normalized = normalize3(trimmed).split(sep3).join("/");
|
|
7601
7861
|
if (normalized.startsWith("/")) {
|
|
7602
7862
|
throw new Error("Script path must be relative and within the allowed directory");
|
|
7603
7863
|
}
|
|
@@ -7611,7 +7871,7 @@ var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
|
|
|
7611
7871
|
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
7612
7872
|
const fullPath = resolve10(baseDir, normalized);
|
|
7613
7873
|
const boundary = resolve10(containmentDir ?? baseDir);
|
|
7614
|
-
if (!fullPath.startsWith(`${boundary}${
|
|
7874
|
+
if (!fullPath.startsWith(`${boundary}${sep3}`) && fullPath !== boundary) {
|
|
7615
7875
|
throw new Error("Script path must stay inside the allowed directory");
|
|
7616
7876
|
}
|
|
7617
7877
|
const extension = extname(fullPath).toLowerCase();
|
|
@@ -8775,6 +9035,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
8775
9035
|
storageEngine;
|
|
8776
9036
|
/** Bash environment manager (creates per-tenant bash instances). */
|
|
8777
9037
|
bashManager;
|
|
9038
|
+
/** Read-only virtual mounts overlaid on the VFS. Empty by default. */
|
|
9039
|
+
virtualMounts = [];
|
|
8778
9040
|
resolveToolAccess(toolName) {
|
|
8779
9041
|
const tools = this.loadedConfig?.tools;
|
|
8780
9042
|
if (!tools) return true;
|
|
@@ -8788,8 +9050,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
8788
9050
|
return true;
|
|
8789
9051
|
}
|
|
8790
9052
|
isToolEnabled(name) {
|
|
8791
|
-
const
|
|
8792
|
-
if (
|
|
9053
|
+
const access4 = this.resolveToolAccess(name);
|
|
9054
|
+
if (access4 === false) return false;
|
|
8793
9055
|
if (name === "write_file" || name === "edit_file" || name === "delete_file" || name === "delete_directory") {
|
|
8794
9056
|
return this.shouldEnableWriteTool();
|
|
8795
9057
|
}
|
|
@@ -8946,6 +9208,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
8946
9208
|
this.storageEngine = options.storageEngine;
|
|
8947
9209
|
this.injectedStorageEngine = true;
|
|
8948
9210
|
}
|
|
9211
|
+
this.virtualMounts = options.virtualMounts ?? [];
|
|
8949
9212
|
if (options.toolDefinitions?.length) {
|
|
8950
9213
|
this.dispatcher.registerMany(options.toolDefinitions);
|
|
8951
9214
|
}
|
|
@@ -9117,6 +9380,10 @@ var AgentHarness = class _AgentHarness {
|
|
|
9117
9380
|
return this.loadedSkills;
|
|
9118
9381
|
}
|
|
9119
9382
|
const effectiveTenant = tenantId || "__default__";
|
|
9383
|
+
const engineWithRefresh = this.storageEngine;
|
|
9384
|
+
if (typeof engineWithRefresh.refreshPathCache === "function") {
|
|
9385
|
+
await engineWithRefresh.refreshPathCache(effectiveTenant);
|
|
9386
|
+
}
|
|
9120
9387
|
const fingerprint = this.computeVfsSkillFingerprint(effectiveTenant);
|
|
9121
9388
|
const cached = this.skillCache.get(effectiveTenant);
|
|
9122
9389
|
if (cached && cached.fingerprint === fingerprint) {
|
|
@@ -9367,7 +9634,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
9367
9634
|
}
|
|
9368
9635
|
try {
|
|
9369
9636
|
const agentFilePath = resolve11(this.workingDir, "AGENT.md");
|
|
9370
|
-
const rawContent = await
|
|
9637
|
+
const rawContent = await readFile9(agentFilePath, "utf8");
|
|
9371
9638
|
if (rawContent === this.agentFileFingerprint) {
|
|
9372
9639
|
return false;
|
|
9373
9640
|
}
|
|
@@ -9440,7 +9707,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
9440
9707
|
}
|
|
9441
9708
|
} else {
|
|
9442
9709
|
const agentFilePath = resolve11(this.workingDir, "AGENT.md");
|
|
9443
|
-
const agentRawContent = await
|
|
9710
|
+
const agentRawContent = await readFile9(agentFilePath, "utf8");
|
|
9444
9711
|
this.parsedAgent = parseAgentMarkdown(agentRawContent);
|
|
9445
9712
|
this.agentFileFingerprint = agentRawContent;
|
|
9446
9713
|
const identity = await ensureAgentIdentity(this.workingDir);
|
|
@@ -9487,7 +9754,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
9487
9754
|
{ maxFileSize, maxTotalStorage },
|
|
9488
9755
|
bashWorkingDir,
|
|
9489
9756
|
config?.bash,
|
|
9490
|
-
config?.network
|
|
9757
|
+
config?.network,
|
|
9758
|
+
this.virtualMounts
|
|
9491
9759
|
);
|
|
9492
9760
|
this.registerIfMissing(createBashTool(this.bashManager));
|
|
9493
9761
|
const getFs = (tenantId) => this.bashManager.getFs(tenantId);
|
|
@@ -9585,9 +9853,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
9585
9853
|
await writeFile6(filePath, json, "utf8");
|
|
9586
9854
|
},
|
|
9587
9855
|
async load() {
|
|
9588
|
-
const { readFile:
|
|
9856
|
+
const { readFile: readFile10 } = await import("fs/promises");
|
|
9589
9857
|
try {
|
|
9590
|
-
return await
|
|
9858
|
+
return await readFile10(filePath, "utf8");
|
|
9591
9859
|
} catch {
|
|
9592
9860
|
return void 0;
|
|
9593
9861
|
}
|
|
@@ -9606,9 +9874,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
9606
9874
|
await writeFile6(filePath, json, "utf8");
|
|
9607
9875
|
},
|
|
9608
9876
|
async load() {
|
|
9609
|
-
const { readFile:
|
|
9877
|
+
const { readFile: readFile10 } = await import("fs/promises");
|
|
9610
9878
|
try {
|
|
9611
|
-
return await
|
|
9879
|
+
return await readFile10(filePath, "utf8");
|
|
9612
9880
|
} catch {
|
|
9613
9881
|
return void 0;
|
|
9614
9882
|
}
|
|
@@ -9622,12 +9890,12 @@ var AgentHarness = class _AgentHarness {
|
|
|
9622
9890
|
let browserMod;
|
|
9623
9891
|
try {
|
|
9624
9892
|
const { existsSync } = await import("fs");
|
|
9625
|
-
const { join, dirname: dirname6 } = await import("path");
|
|
9893
|
+
const { join: join2, dirname: dirname6 } = await import("path");
|
|
9626
9894
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
9627
9895
|
let searchDir = this.workingDir;
|
|
9628
9896
|
let entryPath;
|
|
9629
9897
|
for (; ; ) {
|
|
9630
|
-
const candidate =
|
|
9898
|
+
const candidate = join2(searchDir, "node_modules", "@poncho-ai", "browser", "dist", "index.js");
|
|
9631
9899
|
if (existsSync(candidate)) {
|
|
9632
9900
|
entryPath = candidate;
|
|
9633
9901
|
break;
|
|
@@ -9948,7 +10216,7 @@ ${this.skillFingerprint}`;
|
|
|
9948
10216
|
];
|
|
9949
10217
|
for (const file of input.files) {
|
|
9950
10218
|
if (this.uploadStore) {
|
|
9951
|
-
const buf =
|
|
10219
|
+
const buf = await decodeFileInputData(file.data);
|
|
9952
10220
|
const key = deriveUploadKey(buf, file.mediaType);
|
|
9953
10221
|
const ref = await this.uploadStore.put(key, buf, file.mediaType);
|
|
9954
10222
|
parts.push({
|
|
@@ -13063,7 +13331,7 @@ var runConversationTurn = async (opts) => {
|
|
|
13063
13331
|
if (opts.files && opts.files.length > 0 && opts.harness.uploadStore) {
|
|
13064
13332
|
const uploadedParts = await Promise.all(
|
|
13065
13333
|
opts.files.map(async (f) => {
|
|
13066
|
-
const buf =
|
|
13334
|
+
const buf = await decodeFileInputData(f.data);
|
|
13067
13335
|
const key = deriveUploadKey(buf, f.mediaType);
|
|
13068
13336
|
const ref = await opts.harness.uploadStore.put(key, buf, f.mediaType);
|
|
13069
13337
|
return {
|
|
@@ -13417,6 +13685,7 @@ export {
|
|
|
13417
13685
|
createTurnDraftState,
|
|
13418
13686
|
createUploadStore,
|
|
13419
13687
|
createWriteTool,
|
|
13688
|
+
decodeFileInputData,
|
|
13420
13689
|
defaultAgentDefinition,
|
|
13421
13690
|
defineTool13 as defineTool,
|
|
13422
13691
|
deleteOpenAICodexSession,
|