@poncho-ai/harness 0.43.1 → 0.45.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/dist/index.js CHANGED
@@ -505,6 +505,13 @@ var compactMessages = async (model, messages, config, options) => {
505
505
  import { access } from "fs/promises";
506
506
  import { resolve as resolve3 } from "path";
507
507
  import { createJiti } from "jiti";
508
+ var normalizeToolAccess = (value) => {
509
+ if (value === "approval") return { access: "approval" };
510
+ if (value && typeof value === "object") {
511
+ return { access: value.access, dispatch: value.dispatch };
512
+ }
513
+ return {};
514
+ };
508
515
  var resolveTtl = (ttl, key) => {
509
516
  if (typeof ttl === "number") {
510
517
  return ttl;
@@ -2340,7 +2347,7 @@ var ponchoDocsTool = defineTool({
2340
2347
 
2341
2348
  // src/harness.ts
2342
2349
  import { randomUUID as randomUUID5 } from "crypto";
2343
- import { readFile as readFile8 } from "fs/promises";
2350
+ import { readFile as readFile9 } from "fs/promises";
2344
2351
  import { resolve as resolve11 } from "path";
2345
2352
  import { defineTool as defineTool12, getTextContent as getTextContent2, createLogger as createLogger6, formatError as fmtErr, url as urlColor } from "@poncho-ai/sdk";
2346
2353
 
@@ -2422,6 +2429,23 @@ var deriveUploadKey = (data, mediaType) => {
2422
2429
  const ext = mimeToExt(mediaType);
2423
2430
  return `${hash}${ext}`;
2424
2431
  };
2432
+ var DATA_URI_PREFIX = /^data:[^,]*?;base64,/;
2433
+ var decodeFileInputData = async (data) => {
2434
+ if (data.startsWith("http://") || data.startsWith("https://")) {
2435
+ const resp = await fetch(data);
2436
+ if (!resp.ok) {
2437
+ throw new Error(
2438
+ `uploads: failed to fetch file at ${data}: ${resp.status} ${resp.statusText}`
2439
+ );
2440
+ }
2441
+ return Buffer.from(await resp.arrayBuffer());
2442
+ }
2443
+ const match = data.match(DATA_URI_PREFIX);
2444
+ if (match) {
2445
+ return Buffer.from(data.slice(match[0].length), "base64");
2446
+ }
2447
+ return Buffer.from(data, "base64");
2448
+ };
2425
2449
  var MIME_EXT_MAP = {
2426
2450
  "image/jpeg": ".jpg",
2427
2451
  "image/png": ".png",
@@ -2470,9 +2494,9 @@ var VercelBlobUploadStore = class {
2470
2494
  sdk;
2471
2495
  workingDir;
2472
2496
  access;
2473
- constructor(workingDir, access3 = "public") {
2497
+ constructor(workingDir, access4 = "public") {
2474
2498
  this.workingDir = workingDir;
2475
- this.access = access3;
2499
+ this.access = access4;
2476
2500
  }
2477
2501
  async loadSdk() {
2478
2502
  if (this.sdk) return this.sdk;
@@ -3319,6 +3343,25 @@ var migrations = [
3319
3343
  ON conversations (parent_conversation_id, parent_message_id)
3320
3344
  WHERE parent_message_id IS NOT NULL`
3321
3345
  ]
3346
+ },
3347
+ {
3348
+ version: 7,
3349
+ name: "fix_reminder_scheduled_at_precision",
3350
+ // Postgres maps `REAL` to float4 (4 bytes, ~7 digit precision),
3351
+ // which silently rounds millisecond epochs (13 digits) — every
3352
+ // reminder write+read on Postgres returned a different value than
3353
+ // it stored. SQLite's REAL is float8 (15+ digit precision) so it
3354
+ // was always fine there.
3355
+ //
3356
+ // Convert the Postgres column to BIGINT so future writes are exact
3357
+ // (already-stored rounded values aren't recoverable but they were
3358
+ // never correct in the first place).
3359
+ up: (d) => {
3360
+ if (d === "sqlite") return [];
3361
+ return [
3362
+ `ALTER TABLE reminders ALTER COLUMN scheduled_at TYPE BIGINT USING scheduled_at::bigint`
3363
+ ];
3364
+ }
3322
3365
  }
3323
3366
  ];
3324
3367
 
@@ -3982,9 +4025,9 @@ var SqlStorageEngine = class {
3982
4025
  }
3983
4026
  },
3984
4027
  deleteFile: async (tenantId, path) => {
3985
- const stat3 = await this.vfs.stat(tenantId, path);
3986
- if (!stat3) throw new Error(`ENOENT: no such file or directory, unlink '${path}'`);
3987
- if (stat3.type === "directory") throw new Error(`EISDIR: illegal operation on a directory, unlink '${path}'`);
4028
+ const stat4 = await this.vfs.stat(tenantId, path);
4029
+ if (!stat4) throw new Error(`ENOENT: no such file or directory, unlink '${path}'`);
4030
+ if (stat4.type === "directory") throw new Error(`EISDIR: illegal operation on a directory, unlink '${path}'`);
3988
4031
  await this.executor.run(
3989
4032
  rewrite(
3990
4033
  "DELETE FROM vfs_entries WHERE agent_id = $1 AND tenant_id = $2 AND path = $3",
@@ -4224,7 +4267,10 @@ var SqlStorageEngine = class {
4224
4267
  return {
4225
4268
  id: row.id,
4226
4269
  task: row.task,
4227
- scheduledAt: row.scheduled_at,
4270
+ // Postgres-js returns BIGINT columns as strings to avoid silent
4271
+ // precision loss in JS. Coerce to Number — ms epochs max out at
4272
+ // ~10^16 in year 2286, well under Number.MAX_SAFE_INTEGER (2^53).
4273
+ scheduledAt: Number(row.scheduled_at),
4228
4274
  timezone: row.timezone ?? void 0,
4229
4275
  status: row.status,
4230
4276
  createdAt: new Date(row.created_at).getTime(),
@@ -4232,7 +4278,7 @@ var SqlStorageEngine = class {
4232
4278
  ownerId: row.owner_id ?? void 0,
4233
4279
  tenantId: tid === DEFAULT_TENANT2 ? null : tid,
4234
4280
  recurrence,
4235
- occurrenceCount: row.occurrence_count ?? 0
4281
+ occurrenceCount: Number(row.occurrence_count ?? 0)
4236
4282
  };
4237
4283
  }
4238
4284
  toUint8Array(value) {
@@ -4528,6 +4574,9 @@ function createReminderStoreFromEngine(engine) {
4528
4574
  import { Bash } from "just-bash";
4529
4575
 
4530
4576
  // src/vfs/poncho-fs-adapter.ts
4577
+ import * as nodeFs from "fs/promises";
4578
+ import * as nodeFsSync from "fs";
4579
+ import * as nodePath from "path";
4531
4580
  var MIME_MAP = {
4532
4581
  ".txt": "text/plain",
4533
4582
  ".html": "text/html",
@@ -4584,52 +4633,190 @@ var normalize = (path) => {
4584
4633
  }
4585
4634
  return "/" + out.join("/");
4586
4635
  };
4636
+ var READ_ONLY_ERROR = (path, op) => new Error(`EROFS: read-only mount, ${op} '${path}'`);
4587
4637
  var PonchoFsAdapter = class {
4588
- constructor(engine, tenantId, limits) {
4638
+ constructor(engine, tenantId, limits, mounts = []) {
4589
4639
  this.engine = engine;
4590
4640
  this.tenantId = tenantId;
4591
4641
  this.limits = limits;
4642
+ this.mounts = mounts.map((m) => {
4643
+ const prefix = m.prefix.endsWith("/") ? m.prefix : m.prefix + "/";
4644
+ return {
4645
+ prefix,
4646
+ prefixNoSlash: prefix.slice(0, -1),
4647
+ source: m.source.replace(/\/+$/, "")
4648
+ };
4649
+ });
4650
+ }
4651
+ mounts;
4652
+ /** Find which mount, if any, a normalised VFS path falls under.
4653
+ * Returns the relative path within the mount's source dir (empty string
4654
+ * when the path is exactly the mount root). */
4655
+ routeToMount(np) {
4656
+ for (const m of this.mounts) {
4657
+ if (np === m.prefixNoSlash) return { mount: m, relative: "" };
4658
+ if (np.startsWith(m.prefix)) return { mount: m, relative: np.slice(m.prefix.length) };
4659
+ }
4660
+ return null;
4661
+ }
4662
+ /** Treat `np` as a directory and return mount-root segments that should be
4663
+ * listed as virtual subdirectories. E.g. with mount "/system/", reading
4664
+ * "/" returns ["system"]; reading "/system" goes via routeToMount and
4665
+ * serves from local FS instead. */
4666
+ virtualChildrenAt(np) {
4667
+ const dirPrefix = np === "/" ? "/" : np + "/";
4668
+ const out = [];
4669
+ for (const m of this.mounts) {
4670
+ if (m.prefix.startsWith(dirPrefix) && m.prefix !== dirPrefix) {
4671
+ const remaining = m.prefix.slice(dirPrefix.length);
4672
+ const seg = remaining.split("/")[0];
4673
+ if (seg && !out.includes(seg)) out.push(seg);
4674
+ }
4675
+ }
4676
+ return out;
4677
+ }
4678
+ toLocal(mount, relative) {
4679
+ return nodePath.join(mount.source, relative);
4680
+ }
4681
+ /** Build an FsStat from a node fs.Stats. */
4682
+ toFsStat(s) {
4683
+ return {
4684
+ isFile: s.isFile(),
4685
+ isDirectory: s.isDirectory(),
4686
+ isSymbolicLink: s.isSymbolicLink(),
4687
+ mode: s.mode,
4688
+ size: s.size,
4689
+ mtime: s.mtime
4690
+ };
4691
+ }
4692
+ /** Synthesise a directory stat for a virtual ancestor (e.g. "/system"
4693
+ * when "/system/jobs/" is mounted but "/system" itself isn't a real dir
4694
+ * on disk). Used so `ls /` and `stat /system` work without surprises. */
4695
+ syntheticDirStat() {
4696
+ return {
4697
+ isFile: false,
4698
+ isDirectory: true,
4699
+ isSymbolicLink: false,
4700
+ mode: 493,
4701
+ size: 0,
4702
+ mtime: /* @__PURE__ */ new Date(0)
4703
+ };
4592
4704
  }
4593
4705
  // --- Reads ---
4594
4706
  async readFile(path, _options) {
4595
- const buf = await this.engine.vfs.readFile(this.tenantId, normalize(path));
4707
+ const np = normalize(path);
4708
+ const route = this.routeToMount(np);
4709
+ if (route) {
4710
+ const buf2 = await nodeFs.readFile(this.toLocal(route.mount, route.relative));
4711
+ return buf2.toString("utf8");
4712
+ }
4713
+ const buf = await this.engine.vfs.readFile(this.tenantId, np);
4596
4714
  return new TextDecoder().decode(buf);
4597
4715
  }
4598
4716
  async readFileBuffer(path) {
4599
- return this.engine.vfs.readFile(this.tenantId, normalize(path));
4717
+ const np = normalize(path);
4718
+ const route = this.routeToMount(np);
4719
+ if (route) {
4720
+ const buf = await nodeFs.readFile(this.toLocal(route.mount, route.relative));
4721
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
4722
+ }
4723
+ return this.engine.vfs.readFile(this.tenantId, np);
4600
4724
  }
4601
4725
  async exists(path) {
4602
- const s = await this.engine.vfs.stat(this.tenantId, normalize(path));
4603
- return s !== void 0;
4726
+ const np = normalize(path);
4727
+ const route = this.routeToMount(np);
4728
+ if (route) {
4729
+ try {
4730
+ await nodeFs.access(this.toLocal(route.mount, route.relative));
4731
+ return true;
4732
+ } catch {
4733
+ return false;
4734
+ }
4735
+ }
4736
+ const s = await this.engine.vfs.stat(this.tenantId, np);
4737
+ if (s) return true;
4738
+ return this.virtualChildrenAt(np).length > 0;
4604
4739
  }
4605
4740
  async stat(path) {
4606
4741
  const np = normalize(path);
4742
+ const route = this.routeToMount(np);
4743
+ if (route) {
4744
+ try {
4745
+ const s2 = await nodeFs.stat(this.toLocal(route.mount, route.relative));
4746
+ return this.toFsStat(s2);
4747
+ } catch {
4748
+ throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
4749
+ }
4750
+ }
4607
4751
  const s = await this.engine.vfs.stat(this.tenantId, np);
4608
- if (!s) throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
4609
- return {
4610
- isFile: s.type === "file",
4611
- isDirectory: s.type === "directory",
4612
- isSymbolicLink: s.type === "symlink",
4613
- mode: s.mode,
4614
- size: s.size,
4615
- mtime: new Date(s.updatedAt)
4616
- };
4752
+ if (s) {
4753
+ return {
4754
+ isFile: s.type === "file",
4755
+ isDirectory: s.type === "directory",
4756
+ isSymbolicLink: s.type === "symlink",
4757
+ mode: s.mode,
4758
+ size: s.size,
4759
+ mtime: new Date(s.updatedAt)
4760
+ };
4761
+ }
4762
+ if (this.virtualChildrenAt(np).length > 0) return this.syntheticDirStat();
4763
+ throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
4617
4764
  }
4618
4765
  async readdir(path) {
4619
- const entries = await this.engine.vfs.readdir(this.tenantId, normalize(path));
4620
- return entries.map((e) => e.name);
4766
+ const np = normalize(path);
4767
+ const route = this.routeToMount(np);
4768
+ if (route) {
4769
+ return nodeFs.readdir(this.toLocal(route.mount, route.relative));
4770
+ }
4771
+ let engineNames = [];
4772
+ try {
4773
+ const entries = await this.engine.vfs.readdir(this.tenantId, np);
4774
+ engineNames = entries.map((e) => e.name);
4775
+ } catch {
4776
+ }
4777
+ const virtualSegs = this.virtualChildrenAt(np);
4778
+ if (virtualSegs.length === 0) return engineNames;
4779
+ const merged = new Set(engineNames);
4780
+ for (const seg of virtualSegs) merged.add(seg);
4781
+ return Array.from(merged);
4621
4782
  }
4622
4783
  async readdirWithFileTypes(path) {
4623
- const entries = await this.engine.vfs.readdir(this.tenantId, normalize(path));
4624
- return entries.map((e) => ({
4625
- name: e.name,
4626
- isFile: e.type === "file",
4627
- isDirectory: e.type === "directory",
4628
- isSymbolicLink: e.type === "symlink"
4629
- }));
4784
+ const np = normalize(path);
4785
+ const route = this.routeToMount(np);
4786
+ if (route) {
4787
+ const entries = await nodeFs.readdir(this.toLocal(route.mount, route.relative), { withFileTypes: true });
4788
+ return entries.map((e) => ({
4789
+ name: e.name,
4790
+ isFile: e.isFile(),
4791
+ isDirectory: e.isDirectory(),
4792
+ isSymbolicLink: e.isSymbolicLink()
4793
+ }));
4794
+ }
4795
+ let engineEntries = [];
4796
+ try {
4797
+ const entries = await this.engine.vfs.readdir(this.tenantId, np);
4798
+ engineEntries = entries.map((e) => ({
4799
+ name: e.name,
4800
+ isFile: e.type === "file",
4801
+ isDirectory: e.type === "directory",
4802
+ isSymbolicLink: e.type === "symlink"
4803
+ }));
4804
+ } catch {
4805
+ }
4806
+ const virtualSegs = this.virtualChildrenAt(np);
4807
+ if (virtualSegs.length === 0) return engineEntries;
4808
+ const seen = new Set(engineEntries.map((e) => e.name));
4809
+ for (const seg of virtualSegs) {
4810
+ if (!seen.has(seg)) {
4811
+ engineEntries.push({ name: seg, isFile: false, isDirectory: true, isSymbolicLink: false });
4812
+ }
4813
+ }
4814
+ return engineEntries;
4630
4815
  }
4631
4816
  // --- Writes ---
4632
4817
  async writeFile(path, content, _options) {
4818
+ const np = normalize(path);
4819
+ if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "writeFile");
4633
4820
  const buf = typeof content === "string" ? new TextEncoder().encode(content) : content;
4634
4821
  if (buf.byteLength > this.limits.maxFileSize) {
4635
4822
  throw new Error(
@@ -4637,17 +4824,22 @@ var PonchoFsAdapter = class {
4637
4824
  );
4638
4825
  }
4639
4826
  const mime = mimeFromExtension(path);
4640
- await this.engine.vfs.writeFile(this.tenantId, normalize(path), buf, mime);
4827
+ await this.engine.vfs.writeFile(this.tenantId, np, buf, mime);
4641
4828
  }
4642
4829
  async appendFile(path, content, _options) {
4830
+ const np = normalize(path);
4831
+ if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "appendFile");
4643
4832
  const buf = typeof content === "string" ? new TextEncoder().encode(content) : content;
4644
- await this.engine.vfs.appendFile(this.tenantId, normalize(path), buf);
4833
+ await this.engine.vfs.appendFile(this.tenantId, np, buf);
4645
4834
  }
4646
4835
  async mkdir(path, options) {
4647
- await this.engine.vfs.mkdir(this.tenantId, normalize(path), options?.recursive);
4836
+ const np = normalize(path);
4837
+ if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "mkdir");
4838
+ await this.engine.vfs.mkdir(this.tenantId, np, options?.recursive);
4648
4839
  }
4649
4840
  async rm(path, options) {
4650
4841
  const np = normalize(path);
4842
+ if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "rm");
4651
4843
  const s = await this.engine.vfs.stat(this.tenantId, np);
4652
4844
  if (!s) {
4653
4845
  if (options?.force) return;
@@ -4663,25 +4855,30 @@ var PonchoFsAdapter = class {
4663
4855
  async cp(src, dest, options) {
4664
4856
  const srcNorm = normalize(src);
4665
4857
  const destNorm = normalize(dest);
4666
- const srcStat = await this.engine.vfs.stat(this.tenantId, srcNorm);
4858
+ if (this.routeToMount(destNorm)) throw READ_ONLY_ERROR(dest, "cp");
4859
+ const srcStat = await this.stat(srcNorm).catch(() => null);
4667
4860
  if (!srcStat) throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
4668
- if (srcStat.type === "directory") {
4861
+ if (srcStat.isDirectory) {
4669
4862
  if (!options?.recursive) {
4670
4863
  throw new Error(`EISDIR: cp -r not specified; omitting directory '${src}'`);
4671
4864
  }
4672
4865
  await this.engine.vfs.mkdir(this.tenantId, destNorm, true);
4673
- const entries = await this.engine.vfs.readdir(this.tenantId, srcNorm);
4674
- for (const entry of entries) {
4675
- await this.cp(`${srcNorm}/${entry.name}`, `${destNorm}/${entry.name}`, options);
4866
+ const entries = await this.readdir(srcNorm);
4867
+ for (const name of entries) {
4868
+ await this.cp(`${srcNorm}/${name}`, `${destNorm}/${name}`, options);
4676
4869
  }
4677
4870
  } else {
4678
- const content = await this.engine.vfs.readFile(this.tenantId, srcNorm);
4871
+ const content = await this.readFileBuffer(srcNorm);
4679
4872
  const mime = mimeFromExtension(destNorm);
4680
4873
  await this.engine.vfs.writeFile(this.tenantId, destNorm, content, mime);
4681
4874
  }
4682
4875
  }
4683
4876
  async mv(src, dest) {
4684
- await this.engine.vfs.rename(this.tenantId, normalize(src), normalize(dest));
4877
+ const srcNorm = normalize(src);
4878
+ const destNorm = normalize(dest);
4879
+ if (this.routeToMount(srcNorm)) throw READ_ONLY_ERROR(src, "mv (source)");
4880
+ if (this.routeToMount(destNorm)) throw READ_ONLY_ERROR(dest, "mv (dest)");
4881
+ await this.engine.vfs.rename(this.tenantId, srcNorm, destNorm);
4685
4882
  }
4686
4883
  // --- Path resolution ---
4687
4884
  resolvePath(base, path) {
@@ -4690,8 +4887,22 @@ var PonchoFsAdapter = class {
4690
4887
  }
4691
4888
  async realpath(path) {
4692
4889
  const np = normalize(path);
4890
+ const route = this.routeToMount(np);
4891
+ if (route) {
4892
+ const localResolved = await nodeFs.realpath(this.toLocal(route.mount, route.relative));
4893
+ const localRoot = await nodeFs.realpath(route.mount.source).catch(() => route.mount.source);
4894
+ if (localResolved === localRoot) return route.mount.prefixNoSlash;
4895
+ if (localResolved.startsWith(localRoot + nodePath.sep)) {
4896
+ const rel = localResolved.slice(localRoot.length + 1).split(nodePath.sep).join("/");
4897
+ return `${route.mount.prefix}${rel}`;
4898
+ }
4899
+ return np;
4900
+ }
4693
4901
  const s = await this.engine.vfs.lstat(this.tenantId, np);
4694
- if (!s) throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
4902
+ if (!s) {
4903
+ if (this.virtualChildrenAt(np).length > 0) return np;
4904
+ throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
4905
+ }
4695
4906
  if (s.type === "symlink" && s.symlinkTarget) {
4696
4907
  const target = s.symlinkTarget.startsWith("/") ? s.symlinkTarget : normalize(`${np.slice(0, np.lastIndexOf("/"))}/${s.symlinkTarget}`);
4697
4908
  return this.realpath(target);
@@ -4700,39 +4911,92 @@ var PonchoFsAdapter = class {
4700
4911
  }
4701
4912
  // --- Sync: required by just-bash for glob/find ---
4702
4913
  getAllPaths() {
4703
- return this.engine.vfs.listAllPaths(this.tenantId);
4914
+ const enginePaths = this.engine.vfs.listAllPaths(this.tenantId);
4915
+ if (this.mounts.length === 0) return enginePaths;
4916
+ const out = new Set(enginePaths);
4917
+ for (const m of this.mounts) {
4918
+ out.add(m.prefixNoSlash);
4919
+ try {
4920
+ const stack = [
4921
+ { abs: m.source, vfs: m.prefixNoSlash }
4922
+ ];
4923
+ while (stack.length > 0) {
4924
+ const { abs, vfs } = stack.pop();
4925
+ let entries;
4926
+ try {
4927
+ entries = nodeFsSync.readdirSync(abs, { withFileTypes: true });
4928
+ } catch {
4929
+ continue;
4930
+ }
4931
+ for (const e of entries) {
4932
+ const childVfs = `${vfs}/${e.name}`;
4933
+ out.add(childVfs);
4934
+ if (e.isDirectory()) {
4935
+ stack.push({ abs: nodePath.join(abs, e.name), vfs: childVfs });
4936
+ }
4937
+ }
4938
+ }
4939
+ } catch {
4940
+ }
4941
+ }
4942
+ return Array.from(out);
4704
4943
  }
4705
4944
  // --- Metadata ---
4706
4945
  async chmod(path, mode) {
4707
- await this.engine.vfs.chmod(this.tenantId, normalize(path), mode);
4946
+ const np = normalize(path);
4947
+ if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "chmod");
4948
+ await this.engine.vfs.chmod(this.tenantId, np, mode);
4708
4949
  }
4709
4950
  async utimes(path, _atime, mtime) {
4710
- await this.engine.vfs.utimes(this.tenantId, normalize(path), mtime);
4951
+ const np = normalize(path);
4952
+ if (this.routeToMount(np)) throw READ_ONLY_ERROR(path, "utimes");
4953
+ await this.engine.vfs.utimes(this.tenantId, np, mtime);
4711
4954
  }
4712
4955
  // --- Symlinks ---
4713
4956
  async symlink(target, linkPath) {
4714
- await this.engine.vfs.symlink(this.tenantId, target, normalize(linkPath));
4957
+ const np = normalize(linkPath);
4958
+ if (this.routeToMount(np)) throw READ_ONLY_ERROR(linkPath, "symlink");
4959
+ await this.engine.vfs.symlink(this.tenantId, target, np);
4715
4960
  }
4716
4961
  async link(existingPath, newPath) {
4717
- const content = await this.engine.vfs.readFile(this.tenantId, normalize(existingPath));
4962
+ const npNew = normalize(newPath);
4963
+ if (this.routeToMount(npNew)) throw READ_ONLY_ERROR(newPath, "link");
4964
+ const content = await this.readFileBuffer(existingPath);
4718
4965
  const mime = mimeFromExtension(newPath);
4719
- await this.engine.vfs.writeFile(this.tenantId, normalize(newPath), content, mime);
4966
+ await this.engine.vfs.writeFile(this.tenantId, npNew, content, mime);
4720
4967
  }
4721
4968
  async readlink(path) {
4722
- return this.engine.vfs.readlink(this.tenantId, normalize(path));
4969
+ const np = normalize(path);
4970
+ const route = this.routeToMount(np);
4971
+ if (route) {
4972
+ return nodeFs.readlink(this.toLocal(route.mount, route.relative));
4973
+ }
4974
+ return this.engine.vfs.readlink(this.tenantId, np);
4723
4975
  }
4724
4976
  async lstat(path) {
4725
4977
  const np = normalize(path);
4978
+ const route = this.routeToMount(np);
4979
+ if (route) {
4980
+ try {
4981
+ const s2 = await nodeFs.lstat(this.toLocal(route.mount, route.relative));
4982
+ return this.toFsStat(s2);
4983
+ } catch {
4984
+ throw new Error(`ENOENT: no such file or directory, lstat '${path}'`);
4985
+ }
4986
+ }
4726
4987
  const s = await this.engine.vfs.lstat(this.tenantId, np);
4727
- if (!s) throw new Error(`ENOENT: no such file or directory, lstat '${path}'`);
4728
- return {
4729
- isFile: s.type === "file",
4730
- isDirectory: s.type === "directory",
4731
- isSymbolicLink: s.type === "symlink",
4732
- mode: s.mode,
4733
- size: s.size,
4734
- mtime: new Date(s.updatedAt)
4735
- };
4988
+ if (s) {
4989
+ return {
4990
+ isFile: s.type === "file",
4991
+ isDirectory: s.type === "directory",
4992
+ isSymbolicLink: s.type === "symlink",
4993
+ mode: s.mode,
4994
+ size: s.size,
4995
+ mtime: new Date(s.updatedAt)
4996
+ };
4997
+ }
4998
+ if (this.virtualChildrenAt(np).length > 0) return this.syntheticDirStat();
4999
+ throw new Error(`ENOENT: no such file or directory, lstat '${path}'`);
4736
5000
  }
4737
5001
  };
4738
5002
 
@@ -4909,21 +5173,23 @@ function toBashOptions(cfg, network) {
4909
5173
  return opts;
4910
5174
  }
4911
5175
  var BashEnvironmentManager = class {
4912
- constructor(engine, limits, workingDir, bashConfig, network) {
5176
+ constructor(engine, limits, workingDir, bashConfig, network, virtualMounts = []) {
4913
5177
  this.engine = engine;
4914
5178
  this.limits = limits;
4915
5179
  this.workingDir = workingDir;
4916
5180
  this.bashOptions = toBashOptions(bashConfig, network);
5181
+ this.virtualMounts = virtualMounts;
4917
5182
  }
4918
5183
  environments = /* @__PURE__ */ new Map();
4919
5184
  filesystems = /* @__PURE__ */ new Map();
4920
5185
  workingDir;
4921
5186
  bashOptions;
5187
+ virtualMounts;
4922
5188
  /** Return the combined IFileSystem (VFS + optional /project mount) for a tenant. */
4923
5189
  getFs(tenantId) {
4924
5190
  let fs = this.filesystems.get(tenantId);
4925
5191
  if (!fs) {
4926
- const adapter = new PonchoFsAdapter(this.engine, tenantId, this.limits);
5192
+ const adapter = new PonchoFsAdapter(this.engine, tenantId, this.limits, this.virtualMounts);
4927
5193
  fs = createBashFs(adapter, this.workingDir);
4928
5194
  this.filesystems.set(tenantId, fs);
4929
5195
  }
@@ -4943,7 +5209,7 @@ var BashEnvironmentManager = class {
4943
5209
  return bash;
4944
5210
  }
4945
5211
  getAdapter(tenantId) {
4946
- return new PonchoFsAdapter(this.engine, tenantId, this.limits);
5212
+ return new PonchoFsAdapter(this.engine, tenantId, this.limits, this.virtualMounts);
4947
5213
  }
4948
5214
  /** Refresh the PostgreSQL path cache before a bash.exec() call. */
4949
5215
  async refreshPathCache(tenantId) {
@@ -5062,8 +5328,8 @@ var createReadFileTool = (getFs) => defineTool3({
5062
5328
  if (!await fs.exists(filePath)) {
5063
5329
  throw new Error(`File not found: ${filePath}`);
5064
5330
  }
5065
- const stat3 = await fs.stat(filePath);
5066
- if (stat3.isDirectory) {
5331
+ const stat4 = await fs.stat(filePath);
5332
+ if (stat4.isDirectory) {
5067
5333
  throw new Error(`${filePath} is a directory, not a file`);
5068
5334
  }
5069
5335
  const mediaType = mimeFromPath(filePath) ?? "application/octet-stream";
@@ -5115,8 +5381,8 @@ var createEditFileTool = (getFs) => defineTool4({
5115
5381
  const tenantId = context.tenantId ?? "__default__";
5116
5382
  const fs = getFs(tenantId);
5117
5383
  if (!await fs.exists(filePath)) throw new Error(`File not found: ${filePath}`);
5118
- const stat3 = await fs.stat(filePath);
5119
- if (stat3.isDirectory) throw new Error(`${filePath} is a directory`);
5384
+ const stat4 = await fs.stat(filePath);
5385
+ if (stat4.isDirectory) throw new Error(`${filePath} is a directory`);
5120
5386
  const content = await fs.readFile(filePath);
5121
5387
  const first = content.indexOf(oldStr);
5122
5388
  if (first === -1) {
@@ -5611,7 +5877,7 @@ var createTodoTools = (store) => {
5611
5877
 
5612
5878
  // src/secrets-store.ts
5613
5879
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
5614
- import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
5880
+ import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
5615
5881
  import { dirname as dirname3, resolve as resolve7 } from "path";
5616
5882
  function deriveKey(signingKey) {
5617
5883
  return createHash3("sha256").update("poncho-secrets-v1:" + signingKey).digest();
@@ -5657,7 +5923,7 @@ var FileSecretsStore = class {
5657
5923
  }
5658
5924
  async readAll(tenantId) {
5659
5925
  try {
5660
- const raw = await readFile5(await this.filePath(tenantId), "utf8");
5926
+ const raw = await readFile6(await this.filePath(tenantId), "utf8");
5661
5927
  return JSON.parse(raw);
5662
5928
  } catch {
5663
5929
  return {};
@@ -6567,7 +6833,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
6567
6833
  // src/openai-codex-auth.ts
6568
6834
  import { homedir as homedir3 } from "os";
6569
6835
  import { dirname as dirname4, resolve as resolve8 } from "path";
6570
- import { mkdir as mkdir4, readFile as readFile6, chmod, writeFile as writeFile5, rm as rm3 } from "fs/promises";
6836
+ import { mkdir as mkdir4, readFile as readFile7, chmod, writeFile as writeFile5, rm as rm3 } from "fs/promises";
6571
6837
  var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
6572
6838
  var OPENAI_AUTH_ISSUER = "https://auth.openai.com";
6573
6839
  var REFRESH_TOKEN_GRACE_MS = 5 * 60 * 1e3;
@@ -6606,7 +6872,7 @@ var getOpenAICodexAuthFilePath = (config) => {
6606
6872
  var readOpenAICodexSession = async (config) => {
6607
6873
  const filePath = getOpenAICodexAuthFilePath(config);
6608
6874
  try {
6609
- const content = await readFile6(filePath, "utf8");
6875
+ const content = await readFile7(filePath, "utf8");
6610
6876
  const parsed = JSON.parse(content);
6611
6877
  if (typeof parsed.refreshToken !== "string" || parsed.refreshToken.length === 0) {
6612
6878
  return void 0;
@@ -6941,7 +7207,7 @@ var createModelProvider = (provider, config) => {
6941
7207
  };
6942
7208
 
6943
7209
  // src/skill-context.ts
6944
- import { readFile as readFile7, readdir as readdir2, stat } from "fs/promises";
7210
+ import { readFile as readFile8, readdir as readdir3, stat as stat2 } from "fs/promises";
6945
7211
  import { dirname as dirname5, resolve as resolve9, normalize as normalize2 } from "path";
6946
7212
  import YAML3 from "yaml";
6947
7213
  import { createLogger as createLogger4 } from "@poncho-ai/sdk";
@@ -7022,7 +7288,7 @@ var parseSkillFrontmatter = (content) => {
7022
7288
  };
7023
7289
  };
7024
7290
  var collectSkillManifests = async (directory) => {
7025
- const entries = await readdir2(directory, { withFileTypes: true });
7291
+ const entries = await readdir3(directory, { withFileTypes: true });
7026
7292
  const files = [];
7027
7293
  for (const entry of entries) {
7028
7294
  const fullPath = resolve9(directory, entry.name);
@@ -7030,7 +7296,7 @@ var collectSkillManifests = async (directory) => {
7030
7296
  let isFile = entry.isFile();
7031
7297
  if (entry.isSymbolicLink()) {
7032
7298
  try {
7033
- const s = await stat(fullPath);
7299
+ const s = await stat2(fullPath);
7034
7300
  isDir = s.isDirectory();
7035
7301
  isFile = s.isFile();
7036
7302
  } catch {
@@ -7060,7 +7326,7 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
7060
7326
  const seen = /* @__PURE__ */ new Set();
7061
7327
  for (const manifest of allManifests) {
7062
7328
  try {
7063
- const content = await readFile7(manifest, "utf8");
7329
+ const content = await readFile8(manifest, "utf8");
7064
7330
  const parsed = parseSkillFrontmatter(content);
7065
7331
  if (parsed && !seen.has(parsed.name)) {
7066
7332
  seen.add(parsed.name);
@@ -7163,7 +7429,7 @@ var mergeSkills = (repoSkills, vfsSkills, onCollision) => {
7163
7429
  var loadSkillInstructions = async (skill, engine) => {
7164
7430
  const raw = skill.source.kind === "vfs" ? decoder.decode(
7165
7431
  await requireEngine(engine).vfs.readFile(skill.source.tenantId, skill.skillPath)
7166
- ) : await readFile7(skill.skillPath, "utf8");
7432
+ ) : await readFile8(skill.skillPath, "utf8");
7167
7433
  const match = raw.match(FRONTMATTER_PATTERN3);
7168
7434
  return match ? match[2].trim() : raw.trim();
7169
7435
  };
@@ -7184,7 +7450,7 @@ var readSkillResource = async (skill, relativePath, engine) => {
7184
7450
  if (!fullPath.startsWith(skill.skillDir)) {
7185
7451
  throw new Error("Path escapes the skill directory");
7186
7452
  }
7187
- return await readFile7(fullPath, "utf8");
7453
+ return await readFile8(fullPath, "utf8");
7188
7454
  };
7189
7455
  var requireEngine = (engine) => {
7190
7456
  if (!engine) {
@@ -7312,8 +7578,8 @@ function convertSchema(schema) {
7312
7578
 
7313
7579
  // src/skill-tools.ts
7314
7580
  import { defineTool as defineTool9 } from "@poncho-ai/sdk";
7315
- import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
7316
- import { extname, normalize as normalize3, resolve as resolve10, sep as sep2 } from "path";
7581
+ import { access as access3, readdir as readdir4, stat as stat3 } from "fs/promises";
7582
+ import { extname, normalize as normalize3, resolve as resolve10, sep as sep3 } from "path";
7317
7583
  import { pathToFileURL } from "url";
7318
7584
  import { createJiti as createJiti2 } from "jiti";
7319
7585
  var findSkill = async (options, tenantId, name) => {
@@ -7498,7 +7764,7 @@ var createSkillTools = (options) => {
7498
7764
  error: `Script "${resolved2.relativePath}" for skill "${name}" is not allowed by policy.`
7499
7765
  };
7500
7766
  }
7501
- await access2(resolved2.fullPath);
7767
+ await access3(resolved2.fullPath);
7502
7768
  const fn2 = await loadRunnableScriptFunction(resolved2.fullPath);
7503
7769
  const output2 = await fn2(payload, {
7504
7770
  scope: "skill",
@@ -7514,7 +7780,7 @@ var createSkillTools = (options) => {
7514
7780
  error: `Script "${resolved.relativePath}" is not allowed by policy.`
7515
7781
  };
7516
7782
  }
7517
- await access2(resolved.fullPath);
7783
+ await access3(resolved.fullPath);
7518
7784
  const fn = await loadRunnableScriptFunction(resolved.fullPath);
7519
7785
  const output = await fn(payload, {
7520
7786
  scope: "agent",
@@ -7534,7 +7800,7 @@ var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", "
7534
7800
  var VFS_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".ts", ".mts"]);
7535
7801
  var listRepoSkillScripts = async (skill, isScriptAllowed) => {
7536
7802
  const scripts = await collectScriptFiles(skill.skillDir);
7537
- return scripts.map((fullPath) => fullPath.slice(skill.skillDir.length + 1).split(sep2).join("/")).filter((relativePath) => relativePath.toLowerCase() !== "skill.md").map(
7803
+ return scripts.map((fullPath) => fullPath.slice(skill.skillDir.length + 1).split(sep3).join("/")).filter((relativePath) => relativePath.toLowerCase() !== "skill.md").map(
7538
7804
  (relativePath) => relativePath.includes("/") ? relativePath : `./${relativePath}`
7539
7805
  ).filter((path) => isScriptAllowed ? isScriptAllowed(skill.name, path) : true).sort();
7540
7806
  };
@@ -7567,7 +7833,7 @@ var listVfsSkillScripts = async (skill, engine, isScriptAllowed) => {
7567
7833
  return found.filter((path) => isScriptAllowed ? isScriptAllowed(skill.name, path) : true).sort();
7568
7834
  };
7569
7835
  var collectScriptFiles = async (directory) => {
7570
- const entries = await readdir3(directory, { withFileTypes: true });
7836
+ const entries = await readdir4(directory, { withFileTypes: true });
7571
7837
  const files = [];
7572
7838
  for (const entry of entries) {
7573
7839
  if (entry.name === "node_modules") continue;
@@ -7576,7 +7842,7 @@ var collectScriptFiles = async (directory) => {
7576
7842
  let isFile = entry.isFile();
7577
7843
  if (entry.isSymbolicLink()) {
7578
7844
  try {
7579
- const s = await stat2(fullPath);
7845
+ const s = await stat3(fullPath);
7580
7846
  isDir = s.isDirectory();
7581
7847
  isFile = s.isFile();
7582
7848
  } catch {
@@ -7598,7 +7864,7 @@ var collectScriptFiles = async (directory) => {
7598
7864
  };
7599
7865
  var normalizeScriptPolicyPath = (relativePath) => {
7600
7866
  const trimmed = relativePath.trim();
7601
- const normalized = normalize3(trimmed).split(sep2).join("/");
7867
+ const normalized = normalize3(trimmed).split(sep3).join("/");
7602
7868
  if (normalized.startsWith("/")) {
7603
7869
  throw new Error("Script path must be relative and within the allowed directory");
7604
7870
  }
@@ -7612,7 +7878,7 @@ var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
7612
7878
  const normalized = normalizeScriptPolicyPath(relativePath);
7613
7879
  const fullPath = resolve10(baseDir, normalized);
7614
7880
  const boundary = resolve10(containmentDir ?? baseDir);
7615
- if (!fullPath.startsWith(`${boundary}${sep2}`) && fullPath !== boundary) {
7881
+ if (!fullPath.startsWith(`${boundary}${sep3}`) && fullPath !== boundary) {
7616
7882
  throw new Error("Script path must stay inside the allowed directory");
7617
7883
  }
7618
7884
  const extension = extname(fullPath).toLowerCase();
@@ -7997,6 +8263,57 @@ var createSubagentTools = (manager) => [
7997
8263
  }
7998
8264
  return { subagents };
7999
8265
  }
8266
+ }),
8267
+ defineTool11({
8268
+ name: "read_subagent",
8269
+ description: "Fetch the conversation transcript of a subagent you spawned. Use this to inspect a subagent's intermediate reasoning, tool calls, or full output -- instead of asking it to repeat its work via message_subagent.\n\nModes:\n- 'final' (default): just the last assistant message. Cheap.\n- 'assistant': all assistant messages, no tool calls/results.\n- 'full': every message including tool calls and results. Can be large.\n\nUse since_index / max_messages to page through long transcripts. Only works on subagents directly spawned by this conversation.",
8270
+ inputSchema: {
8271
+ type: "object",
8272
+ properties: {
8273
+ subagent_id: {
8274
+ type: "string",
8275
+ description: "The subagent ID (from spawn_subagent or list_subagents)."
8276
+ },
8277
+ mode: {
8278
+ type: "string",
8279
+ enum: ["final", "assistant", "full"],
8280
+ description: "How much of the transcript to return. Defaults to 'final'."
8281
+ },
8282
+ since_index: {
8283
+ type: "number",
8284
+ description: "Skip messages before this index (applied after mode filter)."
8285
+ },
8286
+ max_messages: {
8287
+ type: "number",
8288
+ description: "Cap the number of messages returned."
8289
+ }
8290
+ },
8291
+ required: ["subagent_id"],
8292
+ additionalProperties: false
8293
+ },
8294
+ handler: async (input, context) => {
8295
+ const subagentId = typeof input.subagent_id === "string" ? input.subagent_id : "";
8296
+ if (!subagentId) {
8297
+ return { error: "subagent_id is required" };
8298
+ }
8299
+ const parentConversationId = context.conversationId;
8300
+ if (!parentConversationId) {
8301
+ return { error: "no active conversation" };
8302
+ }
8303
+ const rawMode = typeof input.mode === "string" ? input.mode : "final";
8304
+ const mode = rawMode === "assistant" || rawMode === "full" ? rawMode : "final";
8305
+ try {
8306
+ return await manager.getTranscript({
8307
+ subagentId,
8308
+ parentConversationId,
8309
+ mode,
8310
+ sinceIndex: typeof input.since_index === "number" ? input.since_index : void 0,
8311
+ maxMessages: typeof input.max_messages === "number" ? input.max_messages : void 0
8312
+ });
8313
+ } catch (err) {
8314
+ return { error: err instanceof Error ? err.message : String(err) };
8315
+ }
8316
+ }
8000
8317
  })
8001
8318
  ];
8002
8319
 
@@ -8776,6 +9093,8 @@ var AgentHarness = class _AgentHarness {
8776
9093
  storageEngine;
8777
9094
  /** Bash environment manager (creates per-tenant bash instances). */
8778
9095
  bashManager;
9096
+ /** Read-only virtual mounts overlaid on the VFS. Empty by default. */
9097
+ virtualMounts = [];
8779
9098
  resolveToolAccess(toolName) {
8780
9099
  const tools = this.loadedConfig?.tools;
8781
9100
  if (!tools) return true;
@@ -8783,14 +9102,23 @@ var AgentHarness = class _AgentHarness {
8783
9102
  const envOverride = tools.byEnvironment?.[env]?.[toolName];
8784
9103
  if (envOverride !== void 0) return envOverride;
8785
9104
  const flatValue = tools[toolName];
8786
- if (typeof flatValue === "boolean" || flatValue === "approval") return flatValue;
9105
+ if (typeof flatValue === "boolean" || flatValue === "approval" || flatValue !== null && typeof flatValue === "object" && !Array.isArray(flatValue) && // distinguish a ToolAccess object from the nested `defaults` /
9106
+ // `byEnvironment` sibling fields by checking it has only the
9107
+ // expected ToolAccess keys.
9108
+ Object.keys(flatValue).every((k) => k === "access" || k === "dispatch")) {
9109
+ return flatValue;
9110
+ }
8787
9111
  const legacyValue = tools.defaults?.[toolName];
8788
9112
  if (legacyValue !== void 0) return legacyValue;
8789
9113
  return true;
8790
9114
  }
9115
+ /** Returns the normalized {access, dispatch} mode for the tool. */
9116
+ resolveToolMode(toolName) {
9117
+ return normalizeToolAccess(this.resolveToolAccess(toolName));
9118
+ }
8791
9119
  isToolEnabled(name) {
8792
- const access3 = this.resolveToolAccess(name);
8793
- if (access3 === false) return false;
9120
+ const access4 = this.resolveToolAccess(name);
9121
+ if (access4 === false) return false;
8794
9122
  if (name === "write_file" || name === "edit_file" || name === "delete_file" || name === "delete_directory") {
8795
9123
  return this.shouldEnableWriteTool();
8796
9124
  }
@@ -8947,6 +9275,7 @@ var AgentHarness = class _AgentHarness {
8947
9275
  this.storageEngine = options.storageEngine;
8948
9276
  this.injectedStorageEngine = true;
8949
9277
  }
9278
+ this.virtualMounts = options.virtualMounts ?? [];
8950
9279
  if (options.toolDefinitions?.length) {
8951
9280
  this.dispatcher.registerMany(options.toolDefinitions);
8952
9281
  }
@@ -9118,6 +9447,10 @@ var AgentHarness = class _AgentHarness {
9118
9447
  return this.loadedSkills;
9119
9448
  }
9120
9449
  const effectiveTenant = tenantId || "__default__";
9450
+ const engineWithRefresh = this.storageEngine;
9451
+ if (typeof engineWithRefresh.refreshPathCache === "function") {
9452
+ await engineWithRefresh.refreshPathCache(effectiveTenant);
9453
+ }
9121
9454
  const fingerprint = this.computeVfsSkillFingerprint(effectiveTenant);
9122
9455
  const cached = this.skillCache.get(effectiveTenant);
9123
9456
  if (cached && cached.fingerprint === fingerprint) {
@@ -9270,7 +9603,7 @@ var AgentHarness = class _AgentHarness {
9270
9603
  );
9271
9604
  }
9272
9605
  requiresApprovalForToolCall(toolName, input) {
9273
- if (this.resolveToolAccess(toolName) === "approval") {
9606
+ if (this.resolveToolMode(toolName).access === "approval") {
9274
9607
  return true;
9275
9608
  }
9276
9609
  if (toolName === "run_skill_script") {
@@ -9368,7 +9701,7 @@ var AgentHarness = class _AgentHarness {
9368
9701
  }
9369
9702
  try {
9370
9703
  const agentFilePath = resolve11(this.workingDir, "AGENT.md");
9371
- const rawContent = await readFile8(agentFilePath, "utf8");
9704
+ const rawContent = await readFile9(agentFilePath, "utf8");
9372
9705
  if (rawContent === this.agentFileFingerprint) {
9373
9706
  return false;
9374
9707
  }
@@ -9441,7 +9774,7 @@ var AgentHarness = class _AgentHarness {
9441
9774
  }
9442
9775
  } else {
9443
9776
  const agentFilePath = resolve11(this.workingDir, "AGENT.md");
9444
- const agentRawContent = await readFile8(agentFilePath, "utf8");
9777
+ const agentRawContent = await readFile9(agentFilePath, "utf8");
9445
9778
  this.parsedAgent = parseAgentMarkdown(agentRawContent);
9446
9779
  this.agentFileFingerprint = agentRawContent;
9447
9780
  const identity = await ensureAgentIdentity(this.workingDir);
@@ -9488,7 +9821,8 @@ var AgentHarness = class _AgentHarness {
9488
9821
  { maxFileSize, maxTotalStorage },
9489
9822
  bashWorkingDir,
9490
9823
  config?.bash,
9491
- config?.network
9824
+ config?.network,
9825
+ this.virtualMounts
9492
9826
  );
9493
9827
  this.registerIfMissing(createBashTool(this.bashManager));
9494
9828
  const getFs = (tenantId) => this.bashManager.getFs(tenantId);
@@ -9586,9 +9920,9 @@ var AgentHarness = class _AgentHarness {
9586
9920
  await writeFile6(filePath, json, "utf8");
9587
9921
  },
9588
9922
  async load() {
9589
- const { readFile: readFile9 } = await import("fs/promises");
9923
+ const { readFile: readFile10 } = await import("fs/promises");
9590
9924
  try {
9591
- return await readFile9(filePath, "utf8");
9925
+ return await readFile10(filePath, "utf8");
9592
9926
  } catch {
9593
9927
  return void 0;
9594
9928
  }
@@ -9607,9 +9941,9 @@ var AgentHarness = class _AgentHarness {
9607
9941
  await writeFile6(filePath, json, "utf8");
9608
9942
  },
9609
9943
  async load() {
9610
- const { readFile: readFile9 } = await import("fs/promises");
9944
+ const { readFile: readFile10 } = await import("fs/promises");
9611
9945
  try {
9612
- return await readFile9(filePath, "utf8");
9946
+ return await readFile10(filePath, "utf8");
9613
9947
  } catch {
9614
9948
  return void 0;
9615
9949
  }
@@ -9623,12 +9957,12 @@ var AgentHarness = class _AgentHarness {
9623
9957
  let browserMod;
9624
9958
  try {
9625
9959
  const { existsSync } = await import("fs");
9626
- const { join, dirname: dirname6 } = await import("path");
9960
+ const { join: join2, dirname: dirname6 } = await import("path");
9627
9961
  const { pathToFileURL: pathToFileURL2 } = await import("url");
9628
9962
  let searchDir = this.workingDir;
9629
9963
  let entryPath;
9630
9964
  for (; ; ) {
9631
- const candidate = join(searchDir, "node_modules", "@poncho-ai", "browser", "dist", "index.js");
9965
+ const candidate = join2(searchDir, "node_modules", "@poncho-ai", "browser", "dist", "index.js");
9632
9966
  if (existsSync(candidate)) {
9633
9967
  entryPath = candidate;
9634
9968
  break;
@@ -9949,7 +10283,7 @@ ${this.skillFingerprint}`;
9949
10283
  ];
9950
10284
  for (const file of input.files) {
9951
10285
  if (this.uploadStore) {
9952
- const buf = Buffer.from(file.data, "base64");
10286
+ const buf = await decodeFileInputData(file.data);
9953
10287
  const key = deriveUploadKey(buf, file.mediaType);
9954
10288
  const ref = await this.uploadStore.put(key, buf, file.mediaType);
9955
10289
  parts.push({
@@ -10628,6 +10962,7 @@ ${textContent}` };
10628
10962
  const richToolResults = [];
10629
10963
  const approvedCalls = [];
10630
10964
  const approvalNeeded = [];
10965
+ const deviceNeeded = [];
10631
10966
  for (const call of toolCalls) {
10632
10967
  if (isCancelled()) {
10633
10968
  yield emitCancellation();
@@ -10642,6 +10977,13 @@ ${textContent}` };
10642
10977
  name: runtimeToolName,
10643
10978
  input: call.input
10644
10979
  });
10980
+ } else if (this.resolveToolMode(runtimeToolName).dispatch === "device") {
10981
+ deviceNeeded.push({
10982
+ approvalId: `device_${randomUUID5()}`,
10983
+ id: call.id,
10984
+ name: runtimeToolName,
10985
+ input: call.input
10986
+ });
10645
10987
  } else {
10646
10988
  approvedCalls.push({
10647
10989
  id: call.id,
@@ -10690,6 +11032,46 @@ ${textContent}` };
10690
11032
  });
10691
11033
  return;
10692
11034
  }
11035
+ if (deviceNeeded.length > 0) {
11036
+ for (const dn of deviceNeeded) {
11037
+ yield pushEvent({
11038
+ type: "tool:device:required",
11039
+ tool: dn.name,
11040
+ input: dn.input,
11041
+ requestId: dn.approvalId
11042
+ });
11043
+ }
11044
+ const assistantContent2 = JSON.stringify({
11045
+ text: fullText,
11046
+ tool_calls: toolCalls.map((tc) => ({
11047
+ id: tc.id,
11048
+ name: exposedToolNames.get(tc.name) ?? tc.name,
11049
+ input: tc.input
11050
+ }))
11051
+ });
11052
+ const assistantMsg = {
11053
+ role: "assistant",
11054
+ content: assistantContent2,
11055
+ metadata: { timestamp: now(), id: randomUUID5(), step, runId }
11056
+ };
11057
+ const deltaMessages = [...messages.slice(inputMessageCount), assistantMsg];
11058
+ yield pushEvent({
11059
+ type: "tool:device:checkpoint",
11060
+ approvals: deviceNeeded.map((dn) => ({
11061
+ approvalId: dn.approvalId,
11062
+ tool: dn.name,
11063
+ toolCallId: dn.id,
11064
+ input: dn.input
11065
+ })),
11066
+ checkpointMessages: deltaMessages,
11067
+ pendingToolCalls: toolCalls.map((tc) => ({
11068
+ id: tc.id,
11069
+ name: exposedToolNames.get(tc.name) ?? tc.name,
11070
+ input: tc.input
11071
+ }))
11072
+ });
11073
+ return;
11074
+ }
10693
11075
  const batchStart = now();
10694
11076
  if (isCancelled()) {
10695
11077
  yield emitCancellation();
@@ -11703,7 +12085,8 @@ var buildApprovalCheckpoints = ({
11703
12085
  runId,
11704
12086
  checkpointMessages,
11705
12087
  baseMessageCount,
11706
- pendingToolCalls
12088
+ pendingToolCalls,
12089
+ kind = "approval"
11707
12090
  }) => approvals.map((approval) => ({
11708
12091
  approvalId: approval.approvalId,
11709
12092
  runId,
@@ -11712,7 +12095,8 @@ var buildApprovalCheckpoints = ({
11712
12095
  input: approval.input,
11713
12096
  checkpointMessages,
11714
12097
  baseMessageCount,
11715
- pendingToolCalls
12098
+ pendingToolCalls,
12099
+ kind
11716
12100
  }));
11717
12101
  var applyTurnMetadata = (conv, meta, opts = {}) => {
11718
12102
  const {
@@ -13001,6 +13385,48 @@ ${resultBody}`,
13001
13385
  }
13002
13386
  }
13003
13387
  return results;
13388
+ },
13389
+ getTranscript: async (opts) => {
13390
+ const conversation = await this.conversationStore.get(opts.subagentId);
13391
+ if (!conversation) {
13392
+ throw new Error(`Subagent "${opts.subagentId}" not found.`);
13393
+ }
13394
+ if (!conversation.parentConversationId) {
13395
+ throw new Error(`Conversation "${opts.subagentId}" is not a subagent.`);
13396
+ }
13397
+ if (conversation.parentConversationId !== opts.parentConversationId) {
13398
+ throw new Error(`Subagent "${opts.subagentId}" was not spawned by this conversation.`);
13399
+ }
13400
+ const all = conversation.messages;
13401
+ let filtered;
13402
+ if (opts.mode === "final") {
13403
+ let lastAssistant;
13404
+ for (let i = all.length - 1; i >= 0; i--) {
13405
+ if (all[i].role === "assistant") {
13406
+ lastAssistant = all[i];
13407
+ break;
13408
+ }
13409
+ }
13410
+ filtered = lastAssistant ? [lastAssistant] : [];
13411
+ } else if (opts.mode === "assistant") {
13412
+ filtered = all.filter((m) => m.role === "assistant");
13413
+ } else {
13414
+ filtered = all;
13415
+ }
13416
+ const startIndex = Math.max(0, opts.sinceIndex ?? 0);
13417
+ const sliced = filtered.slice(startIndex);
13418
+ const cap = opts.maxMessages !== void 0 && opts.maxMessages >= 0 ? opts.maxMessages : sliced.length;
13419
+ const messages = sliced.slice(0, cap);
13420
+ const truncated = startIndex + messages.length < filtered.length;
13421
+ return {
13422
+ subagentId: conversation.conversationId,
13423
+ task: conversation.subagentMeta?.task ?? conversation.title,
13424
+ status: conversation.subagentMeta?.status ?? "stopped",
13425
+ totalMessages: filtered.length,
13426
+ startIndex,
13427
+ messages,
13428
+ truncated
13429
+ };
13004
13430
  }
13005
13431
  };
13006
13432
  }
@@ -13064,7 +13490,7 @@ var runConversationTurn = async (opts) => {
13064
13490
  if (opts.files && opts.files.length > 0 && opts.harness.uploadStore) {
13065
13491
  const uploadedParts = await Promise.all(
13066
13492
  opts.files.map(async (f) => {
13067
- const buf = Buffer.from(f.data, "base64");
13493
+ const buf = await decodeFileInputData(f.data);
13068
13494
  const key = deriveUploadKey(buf, f.mediaType);
13069
13495
  const ref = await opts.harness.uploadStore.put(key, buf, f.mediaType);
13070
13496
  return {
@@ -13200,7 +13626,33 @@ var runConversationTurn = async (opts) => {
13200
13626
  input: event.input ?? {},
13201
13627
  checkpointMessages: void 0,
13202
13628
  baseMessageCount: historyMessages.length,
13203
- pendingToolCalls: []
13629
+ pendingToolCalls: [],
13630
+ kind: "approval"
13631
+ }
13632
+ ];
13633
+ conversation.updatedAt = Date.now();
13634
+ await opts.conversationStore.update(conversation);
13635
+ }
13636
+ await persistDraft();
13637
+ }
13638
+ if (event.type === "tool:device:required") {
13639
+ const toolText = `- device dispatch \`${event.tool}\``;
13640
+ draft.toolTimeline.push(toolText);
13641
+ draft.currentTools.push(toolText);
13642
+ const existing = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
13643
+ if (!existing.some((a) => a.approvalId === event.requestId)) {
13644
+ conversation.pendingApprovals = [
13645
+ ...existing,
13646
+ {
13647
+ approvalId: event.requestId,
13648
+ runId: latestRunId || conversation.runtimeRunId || "",
13649
+ tool: event.tool,
13650
+ toolCallId: void 0,
13651
+ input: event.input ?? {},
13652
+ checkpointMessages: void 0,
13653
+ baseMessageCount: historyMessages.length,
13654
+ pendingToolCalls: [],
13655
+ kind: "device"
13204
13656
  }
13205
13657
  ];
13206
13658
  conversation.updatedAt = Date.now();
@@ -13215,7 +13667,25 @@ var runConversationTurn = async (opts) => {
13215
13667
  runId: latestRunId,
13216
13668
  checkpointMessages: event.checkpointMessages,
13217
13669
  baseMessageCount: historyMessages.length,
13218
- pendingToolCalls: event.pendingToolCalls
13670
+ pendingToolCalls: event.pendingToolCalls,
13671
+ kind: "approval"
13672
+ });
13673
+ conversation._toolResultArchive = opts.harness.getToolResultArchive(
13674
+ opts.conversationId
13675
+ );
13676
+ conversation.updatedAt = Date.now();
13677
+ await opts.conversationStore.update(conversation);
13678
+ checkpointedRun = true;
13679
+ }
13680
+ if (event.type === "tool:device:checkpoint") {
13681
+ conversation.messages = buildMessages();
13682
+ conversation.pendingApprovals = buildApprovalCheckpoints({
13683
+ approvals: event.approvals,
13684
+ runId: latestRunId,
13685
+ checkpointMessages: event.checkpointMessages,
13686
+ baseMessageCount: historyMessages.length,
13687
+ pendingToolCalls: event.pendingToolCalls,
13688
+ kind: "device"
13219
13689
  });
13220
13690
  conversation._toolResultArchive = opts.harness.getToolResultArchive(
13221
13691
  opts.conversationId
@@ -13418,6 +13888,7 @@ export {
13418
13888
  createTurnDraftState,
13419
13889
  createUploadStore,
13420
13890
  createWriteTool,
13891
+ decodeFileInputData,
13421
13892
  defaultAgentDefinition,
13422
13893
  defineTool13 as defineTool,
13423
13894
  deleteOpenAICodexSession,
@@ -13448,6 +13919,7 @@ export {
13448
13919
  normalizeApprovalCheckpoint,
13449
13920
  normalizeOtlp,
13450
13921
  normalizeScriptPolicyPath,
13922
+ normalizeToolAccess,
13451
13923
  parseAgentFile,
13452
13924
  parseAgentMarkdown,
13453
13925
  parseSkillFrontmatter,