@poncho-ai/harness 0.11.2 → 0.13.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
@@ -67,6 +67,59 @@ var matchesSlashPattern = (value, pattern) => {
67
67
  var FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
68
68
  var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
69
69
  var asNumberOrUndefined = (value) => typeof value === "number" ? value : void 0;
70
+ var CRON_EXPRESSION_PATTERN = /^(\S+\s+){4}\S+$/;
71
+ var validateCronExpression = (expr, path) => {
72
+ if (!CRON_EXPRESSION_PATTERN.test(expr.trim())) {
73
+ throw new Error(
74
+ `Invalid cron expression at ${path}: "${expr}". Expected 5-field cron format (minute hour day month weekday).`
75
+ );
76
+ }
77
+ };
78
+ var KNOWN_TIMEZONES = (() => {
79
+ try {
80
+ return new Set(Intl.supportedValuesOf("timeZone"));
81
+ } catch {
82
+ return null;
83
+ }
84
+ })();
85
+ var validateTimezone = (tz, path) => {
86
+ if (KNOWN_TIMEZONES && !KNOWN_TIMEZONES.has(tz)) {
87
+ throw new Error(
88
+ `Invalid timezone at ${path}: "${tz}". Expected an IANA timezone string (e.g. "America/New_York", "UTC").`
89
+ );
90
+ }
91
+ };
92
+ var parseCronJobs = (value) => {
93
+ const raw = asRecord(value);
94
+ const keys = Object.keys(raw);
95
+ if (keys.length === 0) return void 0;
96
+ const jobs = {};
97
+ for (const jobName of keys) {
98
+ const jobValue = asRecord(raw[jobName]);
99
+ const path = `AGENT.md frontmatter cron.${jobName}`;
100
+ if (typeof jobValue.schedule !== "string" || jobValue.schedule.trim() === "") {
101
+ throw new Error(
102
+ `Invalid ${path}: "schedule" is required and must be a non-empty string.`
103
+ );
104
+ }
105
+ if (typeof jobValue.task !== "string" || jobValue.task.trim() === "") {
106
+ throw new Error(
107
+ `Invalid ${path}: "task" is required and must be a non-empty string.`
108
+ );
109
+ }
110
+ validateCronExpression(jobValue.schedule, path);
111
+ const timezone = typeof jobValue.timezone === "string" && jobValue.timezone.trim() ? jobValue.timezone.trim() : void 0;
112
+ if (timezone) {
113
+ validateTimezone(timezone, path);
114
+ }
115
+ jobs[jobName] = {
116
+ schedule: jobValue.schedule.trim(),
117
+ task: jobValue.task,
118
+ timezone
119
+ };
120
+ }
121
+ return jobs;
122
+ };
70
123
  var parseAgentMarkdown = (content) => {
71
124
  const match = content.match(FRONTMATTER_PATTERN);
72
125
  if (!match) {
@@ -144,7 +197,8 @@ var parseAgentMarkdown = (content) => {
144
197
  approvalRequired: approvalRequired.mcp.length > 0 || approvalRequired.scripts.length > 0 ? {
145
198
  mcp: approvalRequired.mcp.length > 0 ? approvalRequired.mcp : void 0,
146
199
  scripts: approvalRequired.scripts.length > 0 ? approvalRequired.scripts : void 0
147
- } : void 0
200
+ } : void 0,
201
+ cron: parseCronJobs(parsed.cron)
148
202
  };
149
203
  return {
150
204
  frontmatter,
@@ -412,10 +466,313 @@ var createWriteTool = (workingDir) => defineTool({
412
466
 
413
467
  // src/harness.ts
414
468
  import { randomUUID as randomUUID3 } from "crypto";
469
+ import { getTextContent } from "@poncho-ai/sdk";
470
+
471
+ // src/upload-store.ts
472
+ import { createHash as createHash2 } from "crypto";
473
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile3, rm } from "fs/promises";
474
+ import { createRequire } from "module";
475
+ import { resolve as resolve5 } from "path";
476
+ var tryImport = async (mod, workingDir) => {
477
+ try {
478
+ return await import(
479
+ /* webpackIgnore: true */
480
+ mod
481
+ );
482
+ } catch {
483
+ if (workingDir) {
484
+ const require2 = createRequire(resolve5(workingDir, "package.json"));
485
+ const resolved = require2.resolve(mod);
486
+ return await import(
487
+ /* webpackIgnore: true */
488
+ resolved
489
+ );
490
+ }
491
+ throw new Error(`Cannot find module "${mod}"`);
492
+ }
493
+ };
494
+ var PONCHO_UPLOAD_SCHEME = "poncho-upload://";
495
+ var CachedUploadStore = class {
496
+ inner;
497
+ cache = /* @__PURE__ */ new Map();
498
+ maxEntries;
499
+ ttlMs;
500
+ constructor(inner, maxEntries = 64, ttlMs = 10 * 60 * 1e3) {
501
+ this.inner = inner;
502
+ this.maxEntries = maxEntries;
503
+ this.ttlMs = ttlMs;
504
+ }
505
+ async put(key, data, mediaType) {
506
+ const ref = `${PONCHO_UPLOAD_SCHEME}${key}`;
507
+ const now2 = Date.now();
508
+ this.cache.set(ref, { data, ts: now2 });
509
+ this.cache.set(key, { data, ts: now2 });
510
+ this.evict();
511
+ this.inner.put(key, data, mediaType).catch((err) => {
512
+ console.error("[poncho] background upload failed:", err instanceof Error ? err.message : err);
513
+ });
514
+ return ref;
515
+ }
516
+ async get(urlOrKey) {
517
+ const cached = this.cache.get(urlOrKey);
518
+ if (cached && Date.now() - cached.ts < this.ttlMs) {
519
+ return cached.data;
520
+ }
521
+ return this.inner.get(urlOrKey);
522
+ }
523
+ async delete(urlOrKey) {
524
+ this.cache.delete(urlOrKey);
525
+ return this.inner.delete(urlOrKey);
526
+ }
527
+ evict() {
528
+ if (this.cache.size <= this.maxEntries) return;
529
+ let oldest;
530
+ let oldestTs = Infinity;
531
+ for (const [k, v] of this.cache) {
532
+ if (v.ts < oldestTs) {
533
+ oldestTs = v.ts;
534
+ oldest = k;
535
+ }
536
+ }
537
+ if (oldest) this.cache.delete(oldest);
538
+ }
539
+ };
540
+ var deriveUploadKey = (data, mediaType) => {
541
+ const hash = createHash2("sha256").update(data).digest("hex").slice(0, 24);
542
+ const ext = mimeToExt(mediaType);
543
+ return `${hash}${ext}`;
544
+ };
545
+ var MIME_EXT_MAP = {
546
+ "image/jpeg": ".jpg",
547
+ "image/png": ".png",
548
+ "image/gif": ".gif",
549
+ "image/webp": ".webp",
550
+ "image/svg+xml": ".svg",
551
+ "application/pdf": ".pdf",
552
+ "text/plain": ".txt",
553
+ "text/csv": ".csv",
554
+ "text/html": ".html",
555
+ "application/json": ".json",
556
+ "video/mp4": ".mp4",
557
+ "video/webm": ".webm",
558
+ "audio/mpeg": ".mp3",
559
+ "audio/wav": ".wav"
560
+ };
561
+ var mimeToExt = (mediaType) => MIME_EXT_MAP[mediaType] ?? `.${mediaType.split("/").pop() ?? "bin"}`;
562
+ var LocalUploadStore = class {
563
+ uploadsDir;
564
+ constructor(workingDir) {
565
+ this.uploadsDir = resolve5(workingDir, ".poncho", "uploads");
566
+ }
567
+ async put(_key, data, mediaType) {
568
+ const key = deriveUploadKey(data, mediaType);
569
+ const filePath = resolve5(this.uploadsDir, key);
570
+ await mkdir2(this.uploadsDir, { recursive: true });
571
+ await writeFile3(filePath, data);
572
+ return `${PONCHO_UPLOAD_SCHEME}${key}`;
573
+ }
574
+ async get(urlOrKey) {
575
+ const key = urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME) ? urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length) : urlOrKey;
576
+ return readFile4(resolve5(this.uploadsDir, key));
577
+ }
578
+ async delete(urlOrKey) {
579
+ const key = urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME) ? urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length) : urlOrKey;
580
+ await rm(resolve5(this.uploadsDir, key), { force: true });
581
+ }
582
+ };
583
+ var VercelBlobUploadStore = class {
584
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
585
+ sdk;
586
+ workingDir;
587
+ access;
588
+ constructor(workingDir, access3 = "public") {
589
+ this.workingDir = workingDir;
590
+ this.access = access3;
591
+ }
592
+ async loadSdk() {
593
+ if (this.sdk) return this.sdk;
594
+ try {
595
+ this.sdk = await tryImport("@vercel/blob", this.workingDir);
596
+ return this.sdk;
597
+ } catch {
598
+ throw new Error(
599
+ 'uploads: vercel-blob provider requires the "@vercel/blob" package. Install it with: pnpm add @vercel/blob'
600
+ );
601
+ }
602
+ }
603
+ async put(key, data, mediaType) {
604
+ const sdk = await this.loadSdk();
605
+ await sdk.put(key, data, {
606
+ access: this.access,
607
+ contentType: mediaType,
608
+ addRandomSuffix: false,
609
+ allowOverwrite: true
610
+ });
611
+ return `${PONCHO_UPLOAD_SCHEME}${key}`;
612
+ }
613
+ async get(urlOrKey) {
614
+ let pathname = urlOrKey;
615
+ if (urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME)) {
616
+ pathname = urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length);
617
+ } else if (urlOrKey.startsWith("https://") || urlOrKey.startsWith("http://")) {
618
+ pathname = new URL(urlOrKey).pathname.slice(1);
619
+ }
620
+ if (this.access === "private") {
621
+ const sdk2 = await this.loadSdk();
622
+ const result = await sdk2.get(pathname, { access: "private" });
623
+ if (!result || result.statusCode !== 200) {
624
+ throw new Error(`uploads: failed to fetch private blob "${pathname}": ${result?.statusCode ?? "not found"}`);
625
+ }
626
+ const chunks = [];
627
+ const reader = result.stream.getReader();
628
+ for (; ; ) {
629
+ const { done, value } = await reader.read();
630
+ if (done) break;
631
+ chunks.push(value);
632
+ }
633
+ return Buffer.concat(chunks);
634
+ }
635
+ const sdk = await this.loadSdk();
636
+ const blob = await sdk.head(pathname);
637
+ const response = await fetch(blob.url);
638
+ if (!response.ok) {
639
+ throw new Error(`uploads: failed to fetch blob "${pathname}": ${response.status}`);
640
+ }
641
+ return Buffer.from(await response.arrayBuffer());
642
+ }
643
+ async delete(urlOrKey) {
644
+ const sdk = await this.loadSdk();
645
+ await sdk.del(urlOrKey);
646
+ }
647
+ };
648
+ var S3UploadStore = class {
649
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
650
+ s3Sdk;
651
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
652
+ presignerSdk;
653
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
654
+ client;
655
+ bucket;
656
+ region;
657
+ endpoint;
658
+ workingDir;
659
+ constructor(bucket, region, endpoint, workingDir) {
660
+ this.bucket = bucket;
661
+ this.region = region;
662
+ this.endpoint = endpoint;
663
+ this.workingDir = workingDir;
664
+ }
665
+ async ensureClient() {
666
+ if (this.client) return;
667
+ try {
668
+ this.s3Sdk = await tryImport("@aws-sdk/client-s3", this.workingDir);
669
+ this.presignerSdk = await tryImport("@aws-sdk/s3-request-presigner", this.workingDir);
670
+ } catch {
671
+ throw new Error(
672
+ 'uploads: s3 provider requires "@aws-sdk/client-s3" and "@aws-sdk/s3-request-presigner". Install with: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner'
673
+ );
674
+ }
675
+ this.client = new this.s3Sdk.S3Client({
676
+ region: this.region ?? process.env.AWS_REGION ?? "us-east-1",
677
+ ...this.endpoint ? { endpoint: this.endpoint, forcePathStyle: true } : {}
678
+ });
679
+ }
680
+ async put(key, data, mediaType) {
681
+ await this.ensureClient();
682
+ await this.client.send(
683
+ new this.s3Sdk.PutObjectCommand({
684
+ Bucket: this.bucket,
685
+ Key: key,
686
+ Body: data,
687
+ ContentType: mediaType
688
+ })
689
+ );
690
+ const url = await this.presignerSdk.getSignedUrl(
691
+ this.client,
692
+ new this.s3Sdk.GetObjectCommand({ Bucket: this.bucket, Key: key }),
693
+ { expiresIn: 7 * 24 * 60 * 60 }
694
+ );
695
+ return url;
696
+ }
697
+ async get(urlOrKey) {
698
+ if (urlOrKey.startsWith("https://") || urlOrKey.startsWith("http://")) {
699
+ const response = await fetch(urlOrKey);
700
+ if (!response.ok) {
701
+ throw new Error(`uploads: failed to fetch S3 object at ${urlOrKey}: ${response.status}`);
702
+ }
703
+ return Buffer.from(await response.arrayBuffer());
704
+ }
705
+ await this.ensureClient();
706
+ const result = await this.client.send(
707
+ new this.s3Sdk.GetObjectCommand({ Bucket: this.bucket, Key: urlOrKey })
708
+ );
709
+ if (!result.Body) throw new Error(`uploads: empty body for S3 key ${urlOrKey}`);
710
+ return Buffer.from(await result.Body.transformToByteArray());
711
+ }
712
+ async delete(urlOrKey) {
713
+ await this.ensureClient();
714
+ const key = urlOrKey.startsWith("https://") ? new URL(urlOrKey).pathname.slice(1) : urlOrKey;
715
+ await this.client.send(
716
+ new this.s3Sdk.DeleteObjectCommand({ Bucket: this.bucket, Key: key })
717
+ );
718
+ }
719
+ };
720
+ var warn = (msg) => {
721
+ console.warn(`[poncho] \u26A0 ${msg}`);
722
+ };
723
+ var createUploadStore = async (config, workingDir) => {
724
+ const provider = config?.provider ?? "local";
725
+ if (provider === "vercel-blob") {
726
+ if (!process.env.BLOB_READ_WRITE_TOKEN) {
727
+ warn(
728
+ "uploads: vercel-blob configured but BLOB_READ_WRITE_TOKEN not found in environment. Falling back to local filesystem.\n Make sure BLOB_READ_WRITE_TOKEN is set in your .env file or environment."
729
+ );
730
+ return new LocalUploadStore(workingDir);
731
+ }
732
+ const store = new VercelBlobUploadStore(workingDir, config?.access ?? "public");
733
+ try {
734
+ await store.loadSdk();
735
+ console.log("[poncho] uploads: using vercel-blob store");
736
+ return new CachedUploadStore(store);
737
+ } catch {
738
+ warn(
739
+ 'uploads: vercel-blob configured but "@vercel/blob" package is not installed. Falling back to local filesystem.\n Run `poncho build <target>` to auto-add it, or install manually: pnpm add @vercel/blob'
740
+ );
741
+ return new LocalUploadStore(workingDir);
742
+ }
743
+ }
744
+ if (provider === "s3") {
745
+ const bucket = config?.bucket ?? process.env.PONCHO_UPLOADS_BUCKET;
746
+ if (!process.env.AWS_ACCESS_KEY_ID || !bucket) {
747
+ const missing = !process.env.AWS_ACCESS_KEY_ID ? "AWS_ACCESS_KEY_ID" : "bucket (config.uploads.bucket or PONCHO_UPLOADS_BUCKET)";
748
+ warn(
749
+ `uploads: s3 configured but ${missing} not found in environment. Falling back to local filesystem.`
750
+ );
751
+ return new LocalUploadStore(workingDir);
752
+ }
753
+ const store = new S3UploadStore(
754
+ bucket,
755
+ config?.region ?? process.env.AWS_REGION,
756
+ config?.endpoint ?? process.env.PONCHO_UPLOADS_ENDPOINT,
757
+ workingDir
758
+ );
759
+ try {
760
+ await store.ensureClient();
761
+ console.log(`[poncho] uploads: using s3 store (bucket: ${bucket})`);
762
+ return new CachedUploadStore(store);
763
+ } catch {
764
+ warn(
765
+ "uploads: s3 configured but AWS SDK packages are not installed. Falling back to local filesystem.\n Run `poncho build <target>` to auto-add them, or install manually: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
766
+ );
767
+ return new LocalUploadStore(workingDir);
768
+ }
769
+ }
770
+ return new LocalUploadStore(workingDir);
771
+ };
415
772
 
416
773
  // src/memory.ts
417
- import { mkdir as mkdir2, readFile as readFile4, rename, writeFile as writeFile3 } from "fs/promises";
418
- import { dirname as dirname2, resolve as resolve5 } from "path";
774
+ import { mkdir as mkdir3, readFile as readFile5, rename, writeFile as writeFile4 } from "fs/promises";
775
+ import { dirname as dirname2, resolve as resolve6 } from "path";
419
776
  import { defineTool as defineTool2 } from "@poncho-ai/sdk";
420
777
  var DEFAULT_MAIN_MEMORY = {
421
778
  content: "",
@@ -423,9 +780,9 @@ var DEFAULT_MAIN_MEMORY = {
423
780
  };
424
781
  var LOCAL_MEMORY_FILE = "memory.json";
425
782
  var writeJsonAtomic = async (filePath, payload) => {
426
- await mkdir2(dirname2(filePath), { recursive: true });
783
+ await mkdir3(dirname2(filePath), { recursive: true });
427
784
  const tmpPath = `${filePath}.tmp`;
428
- await writeFile3(tmpPath, JSON.stringify(payload, null, 2), "utf8");
785
+ await writeFile4(tmpPath, JSON.stringify(payload, null, 2), "utf8");
429
786
  await rename(tmpPath, filePath);
430
787
  };
431
788
  var scoreText = (text, query) => {
@@ -487,7 +844,7 @@ var FileMainMemoryStore = class {
487
844
  return;
488
845
  }
489
846
  const identity = await ensureAgentIdentity(this.workingDir);
490
- this.filePath = resolve5(getAgentStoreDirectory(identity), LOCAL_MEMORY_FILE);
847
+ this.filePath = resolve6(getAgentStoreDirectory(identity), LOCAL_MEMORY_FILE);
491
848
  }
492
849
  isExpired(updatedAt) {
493
850
  return typeof this.ttlMs === "number" && Date.now() - updatedAt > this.ttlMs;
@@ -499,7 +856,7 @@ var FileMainMemoryStore = class {
499
856
  }
500
857
  this.loaded = true;
501
858
  try {
502
- const raw = await readFile4(this.filePath, "utf8");
859
+ const raw = await readFile5(this.filePath, "utf8");
503
860
  const parsed = JSON.parse(raw);
504
861
  const content = typeof parsed.main?.content === "string" ? parsed.main.content : "";
505
862
  const updatedAt = typeof parsed.main?.updatedAt === "number" ? parsed.main.updatedAt : 0;
@@ -1385,8 +1742,8 @@ var createModelProvider = (provider) => {
1385
1742
  };
1386
1743
 
1387
1744
  // src/skill-context.ts
1388
- import { readFile as readFile5, readdir as readdir2, stat } from "fs/promises";
1389
- import { dirname as dirname3, resolve as resolve6, normalize } from "path";
1745
+ import { readFile as readFile6, readdir as readdir2, stat } from "fs/promises";
1746
+ import { dirname as dirname3, resolve as resolve7, normalize } from "path";
1390
1747
  import YAML3 from "yaml";
1391
1748
  var DEFAULT_SKILL_DIRS = ["skills"];
1392
1749
  var resolveSkillDirs = (workingDir, extraPaths) => {
@@ -1398,7 +1755,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
1398
1755
  }
1399
1756
  }
1400
1757
  }
1401
- return dirs.map((d) => resolve6(workingDir, d));
1758
+ return dirs.map((d) => resolve7(workingDir, d));
1402
1759
  };
1403
1760
  var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
1404
1761
  var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
@@ -1467,7 +1824,7 @@ var collectSkillManifests = async (directory) => {
1467
1824
  const entries = await readdir2(directory, { withFileTypes: true });
1468
1825
  const files = [];
1469
1826
  for (const entry of entries) {
1470
- const fullPath = resolve6(directory, entry.name);
1827
+ const fullPath = resolve7(directory, entry.name);
1471
1828
  let isDir = entry.isDirectory();
1472
1829
  let isFile = entry.isFile();
1473
1830
  if (entry.isSymbolicLink()) {
@@ -1502,7 +1859,7 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
1502
1859
  const seen = /* @__PURE__ */ new Set();
1503
1860
  for (const manifest of allManifests) {
1504
1861
  try {
1505
- const content = await readFile5(manifest, "utf8");
1862
+ const content = await readFile6(manifest, "utf8");
1506
1863
  const parsed = parseSkillFrontmatter(content);
1507
1864
  if (parsed && !seen.has(parsed.name)) {
1508
1865
  seen.add(parsed.name);
@@ -1547,7 +1904,7 @@ ${xmlSkills}
1547
1904
  };
1548
1905
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1549
1906
  var loadSkillInstructions = async (skill) => {
1550
- const content = await readFile5(skill.skillPath, "utf8");
1907
+ const content = await readFile6(skill.skillPath, "utf8");
1551
1908
  const match = content.match(FRONTMATTER_PATTERN3);
1552
1909
  return match ? match[2].trim() : content.trim();
1553
1910
  };
@@ -1556,11 +1913,11 @@ var readSkillResource = async (skill, relativePath) => {
1556
1913
  if (normalized.startsWith("..") || normalized.startsWith("/")) {
1557
1914
  throw new Error("Path must be relative and within the skill directory");
1558
1915
  }
1559
- const fullPath = resolve6(skill.skillDir, normalized);
1916
+ const fullPath = resolve7(skill.skillDir, normalized);
1560
1917
  if (!fullPath.startsWith(skill.skillDir)) {
1561
1918
  throw new Error("Path escapes the skill directory");
1562
1919
  }
1563
- return await readFile5(fullPath, "utf8");
1920
+ return await readFile6(fullPath, "utf8");
1564
1921
  };
1565
1922
  var MAX_INSTRUCTIONS_PER_SKILL = 1200;
1566
1923
  var loadSkillContext = async (workingDir) => {
@@ -1651,7 +2008,7 @@ function convertSchema(schema) {
1651
2008
  // src/skill-tools.ts
1652
2009
  import { defineTool as defineTool3 } from "@poncho-ai/sdk";
1653
2010
  import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
1654
- import { extname, normalize as normalize2, resolve as resolve7, sep as sep2 } from "path";
2011
+ import { extname, normalize as normalize2, resolve as resolve8, sep as sep2 } from "path";
1655
2012
  import { pathToFileURL } from "url";
1656
2013
  import { createJiti as createJiti2 } from "jiti";
1657
2014
  var createSkillTools = (skills, options) => {
@@ -1908,7 +2265,7 @@ var collectScriptFiles = async (directory) => {
1908
2265
  if (entry.name === "node_modules") {
1909
2266
  continue;
1910
2267
  }
1911
- const fullPath = resolve7(directory, entry.name);
2268
+ const fullPath = resolve8(directory, entry.name);
1912
2269
  let isDir = entry.isDirectory();
1913
2270
  let isFile = entry.isFile();
1914
2271
  if (entry.isSymbolicLink()) {
@@ -1947,8 +2304,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
1947
2304
  };
1948
2305
  var resolveScriptPath = (baseDir, relativePath) => {
1949
2306
  const normalized = normalizeScriptPolicyPath(relativePath);
1950
- const fullPath = resolve7(baseDir, normalized);
1951
- if (!fullPath.startsWith(`${resolve7(baseDir)}${sep2}`) && fullPath !== resolve7(baseDir)) {
2307
+ const fullPath = resolve8(baseDir, normalized);
2308
+ if (!fullPath.startsWith(`${resolve8(baseDir)}${sep2}`) && fullPath !== resolve8(baseDir)) {
1952
2309
  throw new Error("Script path must stay inside the allowed directory");
1953
2310
  }
1954
2311
  const extension = extname(fullPath).toLowerCase();
@@ -2256,6 +2613,25 @@ You can extend your own capabilities by creating custom JavaScript/TypeScript sc
2256
2613
  - Script entries outside \`./scripts/\` must also appear in \`allowed-tools\`.
2257
2614
  - Keep MCP server connection details (\`url\`, auth env vars) in \`poncho.config.js\` only.
2258
2615
 
2616
+ ## Cron Jobs
2617
+
2618
+ Users can define scheduled tasks in \`AGENT.md\` frontmatter:
2619
+
2620
+ \`\`\`yaml
2621
+ cron:
2622
+ daily-report:
2623
+ schedule: "0 9 * * *" # Standard 5-field cron expression
2624
+ timezone: "America/New_York" # Optional IANA timezone (default: UTC)
2625
+ task: "Generate the daily sales report"
2626
+ \`\`\`
2627
+
2628
+ - Each cron job triggers an autonomous agent run with the specified task, creating a fresh conversation.
2629
+ - In \`poncho dev\`, jobs run via an in-process scheduler and appear in the web UI sidebar (prefixed with \`[cron]\`).
2630
+ - For Vercel: \`poncho build vercel\` generates \`vercel.json\` cron entries. Set \`CRON_SECRET\` = \`PONCHO_AUTH_TOKEN\`.
2631
+ - Jobs can also be triggered manually: \`GET /api/cron/<jobName>\`.
2632
+ - To carry context across cron runs, enable memory.
2633
+ - **IMPORTANT**: When adding a new cron job, always PRESERVE all existing cron jobs. Never remove or overwrite existing jobs unless the user explicitly asks you to replace or delete them. Read the full current \`cron:\` block before editing, and append the new job alongside the existing ones.
2634
+
2259
2635
  ## When users ask about customization:
2260
2636
 
2261
2637
  - Explain and edit \`poncho.config.js\` for model/provider, storage+memory, auth, telemetry, and MCP settings.
@@ -2283,6 +2659,7 @@ var AgentHarness = class {
2283
2659
  modelProviderInjected;
2284
2660
  dispatcher = new ToolDispatcher();
2285
2661
  approvalHandler;
2662
+ uploadStore;
2286
2663
  skillContextWindow = "";
2287
2664
  memoryStore;
2288
2665
  loadedConfig;
@@ -2345,10 +2722,14 @@ var AgentHarness = class {
2345
2722
  this.modelProviderInjected = !!options.modelProvider;
2346
2723
  this.modelProvider = options.modelProvider ?? createModelProvider("anthropic");
2347
2724
  this.approvalHandler = options.approvalHandler;
2725
+ this.uploadStore = options.uploadStore;
2348
2726
  if (options.toolDefinitions?.length) {
2349
2727
  this.dispatcher.registerMany(options.toolDefinitions);
2350
2728
  }
2351
2729
  }
2730
+ get frontmatter() {
2731
+ return this.parsedAgent?.frontmatter;
2732
+ }
2352
2733
  listActiveSkills() {
2353
2734
  return [...this.activeSkillNames].sort();
2354
2735
  }
@@ -2626,9 +3007,9 @@ var AgentHarness = class {
2626
3007
  for await (const event of this.run(input)) {
2627
3008
  eventQueue.push(event);
2628
3009
  if (queueResolve) {
2629
- const resolve9 = queueResolve;
3010
+ const resolve10 = queueResolve;
2630
3011
  queueResolve = null;
2631
- resolve9();
3012
+ resolve10();
2632
3013
  }
2633
3014
  }
2634
3015
  } catch (error) {
@@ -2646,8 +3027,8 @@ var AgentHarness = class {
2646
3027
  if (eventQueue.length > 0) {
2647
3028
  yield eventQueue.shift();
2648
3029
  } else if (!generatorDone) {
2649
- await new Promise((resolve9) => {
2650
- queueResolve = resolve9;
3030
+ await new Promise((resolve10) => {
3031
+ queueResolve = resolve10;
2651
3032
  });
2652
3033
  }
2653
3034
  }
@@ -2671,6 +3052,8 @@ var AgentHarness = class {
2671
3052
  const start = now();
2672
3053
  const maxSteps = agent.frontmatter.limits?.maxSteps ?? 50;
2673
3054
  const timeoutMs = (agent.frontmatter.limits?.timeout ?? 300) * 1e3;
3055
+ const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
3056
+ const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
2674
3057
  const messages = [...input.messages ?? []];
2675
3058
  const events = [];
2676
3059
  const systemPrompt = renderAgentPrompt(agent, {
@@ -2718,11 +3101,42 @@ ${boundedMainMemory.trim()}` : "";
2718
3101
  runId,
2719
3102
  agentId: agent.frontmatter.id ?? agent.frontmatter.name
2720
3103
  });
2721
- messages.push({
2722
- role: "user",
2723
- content: input.task,
2724
- metadata: { timestamp: now(), id: randomUUID3() }
2725
- });
3104
+ if (input.files && input.files.length > 0) {
3105
+ const parts = [
3106
+ { type: "text", text: input.task }
3107
+ ];
3108
+ for (const file of input.files) {
3109
+ if (this.uploadStore) {
3110
+ const buf = Buffer.from(file.data, "base64");
3111
+ const key = deriveUploadKey(buf, file.mediaType);
3112
+ const ref = await this.uploadStore.put(key, buf, file.mediaType);
3113
+ parts.push({
3114
+ type: "file",
3115
+ data: ref,
3116
+ mediaType: file.mediaType,
3117
+ filename: file.filename
3118
+ });
3119
+ } else {
3120
+ parts.push({
3121
+ type: "file",
3122
+ data: file.data,
3123
+ mediaType: file.mediaType,
3124
+ filename: file.filename
3125
+ });
3126
+ }
3127
+ }
3128
+ messages.push({
3129
+ role: "user",
3130
+ content: parts,
3131
+ metadata: { timestamp: now(), id: randomUUID3() }
3132
+ });
3133
+ } else {
3134
+ messages.push({
3135
+ role: "user",
3136
+ content: input.task,
3137
+ metadata: { timestamp: now(), id: randomUUID3() }
3138
+ });
3139
+ }
2726
3140
  let responseText = "";
2727
3141
  let totalInputTokens = 0;
2728
3142
  let totalOutputTokens = 0;
@@ -2744,6 +3158,19 @@ ${boundedMainMemory.trim()}` : "";
2744
3158
  });
2745
3159
  return;
2746
3160
  }
3161
+ if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
3162
+ const result2 = {
3163
+ status: "completed",
3164
+ response: responseText,
3165
+ steps: step - 1,
3166
+ tokens: { input: totalInputTokens, output: totalOutputTokens, cached: 0 },
3167
+ duration: now() - start,
3168
+ continuation: true,
3169
+ maxSteps
3170
+ };
3171
+ yield pushEvent({ type: "run:completed", runId, result: result2 });
3172
+ return;
3173
+ }
2747
3174
  const stepStart = now();
2748
3175
  yield pushEvent({ type: "step:started", step });
2749
3176
  yield pushEvent({ type: "model:request", tokens: 0 });
@@ -2765,102 +3192,182 @@ ${boundedMainMemory.trim()}` : "";
2765
3192
  inputSchema: jsonSchemaToZod(tool.inputSchema)
2766
3193
  };
2767
3194
  }
2768
- const coreMessages = trimMessageWindow(messages).flatMap(
2769
- (msg) => {
2770
- if (msg.role === "tool") {
2771
- try {
2772
- const parsed = JSON.parse(msg.content);
2773
- if (!Array.isArray(parsed)) {
2774
- return [];
3195
+ const convertMessage = async (msg) => {
3196
+ if (msg.role === "tool") {
3197
+ const textContent = typeof msg.content === "string" ? msg.content : getTextContent(msg);
3198
+ try {
3199
+ const parsed = JSON.parse(textContent);
3200
+ if (!Array.isArray(parsed)) {
3201
+ return [];
3202
+ }
3203
+ const toolResults = parsed.filter((item) => {
3204
+ if (typeof item !== "object" || item === null) {
3205
+ return false;
2775
3206
  }
2776
- const toolResults = parsed.filter((item) => {
2777
- if (typeof item !== "object" || item === null) {
3207
+ const row = item;
3208
+ return typeof row.tool_use_id === "string" && typeof row.tool_name === "string" && typeof row.content === "string";
3209
+ });
3210
+ if (toolResults.length === 0) {
3211
+ return [];
3212
+ }
3213
+ return [{
3214
+ role: "tool",
3215
+ content: toolResults.map((tr) => {
3216
+ if (tr.content.startsWith("Tool error:")) {
3217
+ return {
3218
+ type: "tool-result",
3219
+ toolCallId: tr.tool_use_id,
3220
+ toolName: tr.tool_name,
3221
+ output: { type: "text", value: tr.content }
3222
+ };
3223
+ }
3224
+ try {
3225
+ const resultValue = JSON.parse(tr.content);
3226
+ return {
3227
+ type: "tool-result",
3228
+ toolCallId: tr.tool_use_id,
3229
+ toolName: tr.tool_name,
3230
+ output: { type: "json", value: resultValue }
3231
+ };
3232
+ } catch {
3233
+ return {
3234
+ type: "tool-result",
3235
+ toolCallId: tr.tool_use_id,
3236
+ toolName: tr.tool_name,
3237
+ output: { type: "text", value: tr.content }
3238
+ };
3239
+ }
3240
+ })
3241
+ }];
3242
+ } catch {
3243
+ return [];
3244
+ }
3245
+ }
3246
+ if (msg.role === "assistant") {
3247
+ const assistantText = typeof msg.content === "string" ? msg.content : getTextContent(msg);
3248
+ try {
3249
+ const parsed = JSON.parse(assistantText);
3250
+ if (typeof parsed === "object" && parsed !== null) {
3251
+ const parsedRecord = parsed;
3252
+ if (!Array.isArray(parsedRecord.tool_calls)) {
3253
+ return [{ role: "assistant", content: assistantText }];
3254
+ }
3255
+ const textPart = typeof parsedRecord.text === "string" ? parsedRecord.text : "";
3256
+ const validToolCalls = parsedRecord.tool_calls.filter((tc) => {
3257
+ if (typeof tc !== "object" || tc === null) {
2778
3258
  return false;
2779
3259
  }
2780
- const row = item;
2781
- return typeof row.tool_use_id === "string" && typeof row.tool_name === "string" && typeof row.content === "string";
2782
- });
2783
- if (toolResults.length === 0) {
3260
+ const toolCall = tc;
3261
+ return typeof toolCall.id === "string" && typeof toolCall.name === "string" && toolCall.input !== null && typeof toolCall.input === "object";
3262
+ }).map((tc) => ({
3263
+ type: "tool-call",
3264
+ toolCallId: tc.id,
3265
+ toolName: tc.name,
3266
+ input: tc.input
3267
+ }));
3268
+ if (textPart.length === 0 && validToolCalls.length === 0) {
2784
3269
  return [];
2785
3270
  }
2786
3271
  return [{
2787
- role: "tool",
2788
- content: toolResults.map((tr) => {
2789
- if (tr.content.startsWith("Tool error:")) {
2790
- return {
2791
- type: "tool-result",
2792
- toolCallId: tr.tool_use_id,
2793
- toolName: tr.tool_name,
2794
- output: { type: "text", value: tr.content }
2795
- };
2796
- }
2797
- try {
2798
- const resultValue = JSON.parse(tr.content);
2799
- return {
2800
- type: "tool-result",
2801
- toolCallId: tr.tool_use_id,
2802
- toolName: tr.tool_name,
2803
- output: { type: "json", value: resultValue }
2804
- };
2805
- } catch {
2806
- return {
2807
- type: "tool-result",
2808
- toolCallId: tr.tool_use_id,
2809
- toolName: tr.tool_name,
2810
- output: { type: "text", value: tr.content }
2811
- };
2812
- }
2813
- })
3272
+ role: "assistant",
3273
+ content: [
3274
+ ...textPart.length > 0 ? [{ type: "text", text: textPart }] : [],
3275
+ ...validToolCalls
3276
+ ]
2814
3277
  }];
2815
- } catch {
2816
- return [];
2817
3278
  }
3279
+ } catch {
2818
3280
  }
2819
- if (msg.role === "assistant") {
2820
- try {
2821
- const parsed = JSON.parse(msg.content);
2822
- if (typeof parsed === "object" && parsed !== null) {
2823
- const parsedRecord = parsed;
2824
- if (!Array.isArray(parsedRecord.tool_calls)) {
2825
- return [{ role: "assistant", content: msg.content }];
2826
- }
2827
- const textPart = typeof parsedRecord.text === "string" ? parsedRecord.text : "";
2828
- const validToolCalls = parsedRecord.tool_calls.filter((tc) => {
2829
- if (typeof tc !== "object" || tc === null) {
2830
- return false;
3281
+ return [{ role: "assistant", content: assistantText }];
3282
+ }
3283
+ if (msg.role === "system") {
3284
+ return [{
3285
+ role: "system",
3286
+ content: typeof msg.content === "string" ? msg.content : getTextContent(msg)
3287
+ }];
3288
+ }
3289
+ if (msg.role === "user") {
3290
+ if (typeof msg.content === "string") {
3291
+ return [{ role: "user", content: msg.content }];
3292
+ }
3293
+ const MODEL_IMAGE_TYPES = /* @__PURE__ */ new Set([
3294
+ "image/jpeg",
3295
+ "image/png",
3296
+ "image/gif",
3297
+ "image/webp"
3298
+ ]);
3299
+ const MODEL_FILE_TYPES = /* @__PURE__ */ new Set([
3300
+ "application/pdf"
3301
+ ]);
3302
+ const isTextBasedMime = (mt) => mt.startsWith("text/") || mt === "application/json" || mt === "application/xml" || mt === "application/x-yaml" || mt.endsWith("+json") || mt.endsWith("+xml");
3303
+ const userContent = await Promise.all(
3304
+ msg.content.map(async (part) => {
3305
+ if (part.type === "text") {
3306
+ return { type: "text", text: part.text };
3307
+ }
3308
+ const isSupportedImage = MODEL_IMAGE_TYPES.has(part.mediaType);
3309
+ const isSupportedFile = MODEL_FILE_TYPES.has(part.mediaType);
3310
+ if (!isSupportedImage && !isSupportedFile && isTextBasedMime(part.mediaType)) {
3311
+ let textContent;
3312
+ try {
3313
+ if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
3314
+ const buf = await this.uploadStore.get(part.data);
3315
+ textContent = buf.toString("utf8");
3316
+ } else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
3317
+ const resp = await fetch(part.data);
3318
+ textContent = await resp.text();
3319
+ } else {
3320
+ textContent = Buffer.from(part.data, "base64").toString("utf8");
2831
3321
  }
2832
- const toolCall = tc;
2833
- return typeof toolCall.id === "string" && typeof toolCall.name === "string" && toolCall.input !== null && typeof toolCall.input === "object";
2834
- }).map((tc) => ({
2835
- type: "tool-call",
2836
- toolCallId: tc.id,
2837
- toolName: tc.name,
2838
- input: tc.input
2839
- }));
2840
- if (textPart.length === 0 && validToolCalls.length === 0) {
2841
- return [];
3322
+ } catch {
3323
+ textContent = "(could not read file)";
2842
3324
  }
2843
- return [{
2844
- role: "assistant",
2845
- content: [
2846
- ...textPart.length > 0 ? [{ type: "text", text: textPart }] : [],
2847
- ...validToolCalls
2848
- ]
2849
- }];
3325
+ const label = part.filename ?? part.mediaType;
3326
+ return { type: "text", text: `[File: ${label}]
3327
+ ${textContent}` };
2850
3328
  }
2851
- } catch {
2852
- }
2853
- return [{ role: "assistant", content: msg.content }];
2854
- }
2855
- if (msg.role === "user" || msg.role === "system") {
2856
- return [{
2857
- role: msg.role,
2858
- content: msg.content
2859
- }];
2860
- }
2861
- return [];
3329
+ if (!isSupportedImage && !isSupportedFile) {
3330
+ const label = part.filename ?? part.mediaType;
3331
+ return {
3332
+ type: "text",
3333
+ text: `[Attached file: ${label} (${part.mediaType}) \u2014 this format is not supported by the model and was skipped]`
3334
+ };
3335
+ }
3336
+ let resolvedData;
3337
+ if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
3338
+ const buf = await this.uploadStore.get(part.data);
3339
+ resolvedData = buf.toString("base64");
3340
+ } else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
3341
+ if (this.uploadStore) {
3342
+ const buf = await this.uploadStore.get(part.data);
3343
+ resolvedData = buf.toString("base64");
3344
+ } else {
3345
+ const resp = await fetch(part.data);
3346
+ resolvedData = Buffer.from(await resp.arrayBuffer()).toString("base64");
3347
+ }
3348
+ } else {
3349
+ resolvedData = part.data;
3350
+ }
3351
+ if (isSupportedImage) {
3352
+ return {
3353
+ type: "image",
3354
+ image: resolvedData,
3355
+ mediaType: part.mediaType
3356
+ };
3357
+ }
3358
+ return {
3359
+ type: "file",
3360
+ data: resolvedData,
3361
+ mediaType: part.mediaType,
3362
+ filename: part.filename
3363
+ };
3364
+ })
3365
+ );
3366
+ return [{ role: "user", content: userContent }];
2862
3367
  }
2863
- );
3368
+ return [];
3369
+ };
3370
+ const coreMessages = (await Promise.all(trimMessageWindow(messages).map(convertMessage))).flat();
2864
3371
  const modelName = agent.frontmatter.model?.name ?? "claude-opus-4-5";
2865
3372
  const temperature = agent.frontmatter.model?.temperature ?? 0.2;
2866
3373
  const maxTokens = agent.frontmatter.model?.maxTokens;
@@ -2907,8 +3414,8 @@ ${boundedMainMemory.trim()}` : "";
2907
3414
  let timer;
2908
3415
  const nextChunk = await Promise.race([
2909
3416
  textIterator.next(),
2910
- new Promise((resolve9) => {
2911
- timer = setTimeout(() => resolve9(null), timeout);
3417
+ new Promise((resolve10) => {
3418
+ timer = setTimeout(() => resolve10(null), timeout);
2912
3419
  })
2913
3420
  ]);
2914
3421
  clearTimeout(timer);
@@ -3183,14 +3690,27 @@ ${boundedMainMemory.trim()}` : "";
3183
3690
  return;
3184
3691
  }
3185
3692
  }
3186
- yield {
3187
- type: "run:error",
3188
- runId,
3189
- error: {
3190
- code: "MAX_STEPS_EXCEEDED",
3191
- message: `Run reached maximum of ${maxSteps} steps`
3192
- }
3193
- };
3693
+ if (softDeadlineMs > 0) {
3694
+ const result = {
3695
+ status: "completed",
3696
+ response: responseText,
3697
+ steps: maxSteps,
3698
+ tokens: { input: totalInputTokens, output: totalOutputTokens, cached: 0 },
3699
+ duration: now() - start,
3700
+ continuation: true,
3701
+ maxSteps
3702
+ };
3703
+ yield pushEvent({ type: "run:completed", runId, result });
3704
+ } else {
3705
+ yield pushEvent({
3706
+ type: "run:error",
3707
+ runId,
3708
+ error: {
3709
+ code: "MAX_STEPS_EXCEEDED",
3710
+ message: `Run reached maximum of ${maxSteps} steps`
3711
+ }
3712
+ });
3713
+ }
3194
3714
  }
3195
3715
  async runToCompletion(input) {
3196
3716
  const events = [];
@@ -3270,8 +3790,8 @@ var LatitudeCapture = class {
3270
3790
 
3271
3791
  // src/state.ts
3272
3792
  import { randomUUID as randomUUID4 } from "crypto";
3273
- import { mkdir as mkdir3, readFile as readFile6, readdir as readdir4, rename as rename2, rm, writeFile as writeFile4 } from "fs/promises";
3274
- import { dirname as dirname4, resolve as resolve8 } from "path";
3793
+ import { mkdir as mkdir4, readFile as readFile7, readdir as readdir4, rename as rename2, rm as rm2, writeFile as writeFile5 } from "fs/promises";
3794
+ import { dirname as dirname4, resolve as resolve9 } from "path";
3275
3795
  var DEFAULT_OWNER = "local-owner";
3276
3796
  var LOCAL_STATE_FILE = "state.json";
3277
3797
  var CONVERSATIONS_DIRECTORY = "conversations";
@@ -3287,9 +3807,9 @@ var toStoreIdentity = async ({
3287
3807
  return { name: ensured.name, id: agentId };
3288
3808
  };
3289
3809
  var writeJsonAtomic2 = async (filePath, payload) => {
3290
- await mkdir3(dirname4(filePath), { recursive: true });
3810
+ await mkdir4(dirname4(filePath), { recursive: true });
3291
3811
  const tmpPath = `${filePath}.tmp`;
3292
- await writeFile4(tmpPath, JSON.stringify(payload, null, 2), "utf8");
3812
+ await writeFile5(tmpPath, JSON.stringify(payload, null, 2), "utf8");
3293
3813
  await rename2(tmpPath, filePath);
3294
3814
  };
3295
3815
  var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
@@ -3450,8 +3970,8 @@ var FileConversationStore = class {
3450
3970
  agentId: this.agentId
3451
3971
  });
3452
3972
  const agentDir = getAgentStoreDirectory(identity);
3453
- const conversationsDir = resolve8(agentDir, CONVERSATIONS_DIRECTORY);
3454
- const indexPath = resolve8(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
3973
+ const conversationsDir = resolve9(agentDir, CONVERSATIONS_DIRECTORY);
3974
+ const indexPath = resolve9(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
3455
3975
  this.paths = { conversationsDir, indexPath };
3456
3976
  return this.paths;
3457
3977
  }
@@ -3465,9 +3985,9 @@ var FileConversationStore = class {
3465
3985
  }
3466
3986
  async readConversationFile(fileName) {
3467
3987
  const { conversationsDir } = await this.resolvePaths();
3468
- const filePath = resolve8(conversationsDir, fileName);
3988
+ const filePath = resolve9(conversationsDir, fileName);
3469
3989
  try {
3470
- const raw = await readFile6(filePath, "utf8");
3990
+ const raw = await readFile7(filePath, "utf8");
3471
3991
  return JSON.parse(raw);
3472
3992
  } catch {
3473
3993
  return void 0;
@@ -3509,7 +4029,7 @@ var FileConversationStore = class {
3509
4029
  this.loaded = true;
3510
4030
  const { indexPath } = await this.resolvePaths();
3511
4031
  try {
3512
- const raw = await readFile6(indexPath, "utf8");
4032
+ const raw = await readFile7(indexPath, "utf8");
3513
4033
  const parsed = JSON.parse(raw);
3514
4034
  for (const conversation of parsed.conversations ?? []) {
3515
4035
  this.conversations.set(conversation.conversationId, conversation);
@@ -3522,7 +4042,7 @@ var FileConversationStore = class {
3522
4042
  const { conversationsDir } = await this.resolvePaths();
3523
4043
  const existing = this.conversations.get(conversation.conversationId);
3524
4044
  const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
3525
- const filePath = resolve8(conversationsDir, fileName);
4045
+ const filePath = resolve9(conversationsDir, fileName);
3526
4046
  this.writing = this.writing.then(async () => {
3527
4047
  await writeJsonAtomic2(filePath, conversation);
3528
4048
  this.conversations.set(conversation.conversationId, {
@@ -3601,7 +4121,7 @@ var FileConversationStore = class {
3601
4121
  if (removed) {
3602
4122
  this.writing = this.writing.then(async () => {
3603
4123
  if (existing) {
3604
- await rm(resolve8(conversationsDir, existing.fileName), { force: true });
4124
+ await rm2(resolve9(conversationsDir, existing.fileName), { force: true });
3605
4125
  }
3606
4126
  await this.writeIndex();
3607
4127
  });
@@ -3631,7 +4151,7 @@ var FileStateStore = class {
3631
4151
  workingDir: this.workingDir,
3632
4152
  agentId: this.agentId
3633
4153
  });
3634
- this.filePath = resolve8(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
4154
+ this.filePath = resolve9(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
3635
4155
  }
3636
4156
  isExpired(state) {
3637
4157
  return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
@@ -3643,7 +4163,7 @@ var FileStateStore = class {
3643
4163
  }
3644
4164
  this.loaded = true;
3645
4165
  try {
3646
- const raw = await readFile6(this.filePath, "utf8");
4166
+ const raw = await readFile7(this.filePath, "utf8");
3647
4167
  const parsed = JSON.parse(raw);
3648
4168
  for (const state of parsed.states ?? []) {
3649
4169
  this.states.set(state.runId, state);
@@ -4319,9 +4839,13 @@ export {
4319
4839
  InMemoryStateStore,
4320
4840
  LatitudeCapture,
4321
4841
  LocalMcpBridge,
4842
+ LocalUploadStore,
4843
+ PONCHO_UPLOAD_SCHEME,
4844
+ S3UploadStore,
4322
4845
  STORAGE_SCHEMA_VERSION,
4323
4846
  TelemetryEmitter,
4324
4847
  ToolDispatcher,
4848
+ VercelBlobUploadStore,
4325
4849
  buildAgentDirectoryName,
4326
4850
  buildSkillContextWindow,
4327
4851
  createConversationStore,
@@ -4331,8 +4855,10 @@ export {
4331
4855
  createModelProvider,
4332
4856
  createSkillTools,
4333
4857
  createStateStore,
4858
+ createUploadStore,
4334
4859
  createWriteTool,
4335
4860
  defineTool4 as defineTool,
4861
+ deriveUploadKey,
4336
4862
  ensureAgentIdentity,
4337
4863
  generateAgentId,
4338
4864
  getAgentStoreDirectory,