@poncho-ai/harness 0.11.2 → 0.12.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
@@ -412,10 +412,313 @@ var createWriteTool = (workingDir) => defineTool({
412
412
 
413
413
  // src/harness.ts
414
414
  import { randomUUID as randomUUID3 } from "crypto";
415
+ import { getTextContent } from "@poncho-ai/sdk";
416
+
417
+ // src/upload-store.ts
418
+ import { createHash as createHash2 } from "crypto";
419
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile3, rm } from "fs/promises";
420
+ import { createRequire } from "module";
421
+ import { resolve as resolve5 } from "path";
422
+ var tryImport = async (mod, workingDir) => {
423
+ try {
424
+ return await import(
425
+ /* webpackIgnore: true */
426
+ mod
427
+ );
428
+ } catch {
429
+ if (workingDir) {
430
+ const require2 = createRequire(resolve5(workingDir, "package.json"));
431
+ const resolved = require2.resolve(mod);
432
+ return await import(
433
+ /* webpackIgnore: true */
434
+ resolved
435
+ );
436
+ }
437
+ throw new Error(`Cannot find module "${mod}"`);
438
+ }
439
+ };
440
+ var PONCHO_UPLOAD_SCHEME = "poncho-upload://";
441
+ var CachedUploadStore = class {
442
+ inner;
443
+ cache = /* @__PURE__ */ new Map();
444
+ maxEntries;
445
+ ttlMs;
446
+ constructor(inner, maxEntries = 64, ttlMs = 10 * 60 * 1e3) {
447
+ this.inner = inner;
448
+ this.maxEntries = maxEntries;
449
+ this.ttlMs = ttlMs;
450
+ }
451
+ async put(key, data, mediaType) {
452
+ const ref = `${PONCHO_UPLOAD_SCHEME}${key}`;
453
+ const now2 = Date.now();
454
+ this.cache.set(ref, { data, ts: now2 });
455
+ this.cache.set(key, { data, ts: now2 });
456
+ this.evict();
457
+ this.inner.put(key, data, mediaType).catch((err) => {
458
+ console.error("[poncho] background upload failed:", err instanceof Error ? err.message : err);
459
+ });
460
+ return ref;
461
+ }
462
+ async get(urlOrKey) {
463
+ const cached = this.cache.get(urlOrKey);
464
+ if (cached && Date.now() - cached.ts < this.ttlMs) {
465
+ return cached.data;
466
+ }
467
+ return this.inner.get(urlOrKey);
468
+ }
469
+ async delete(urlOrKey) {
470
+ this.cache.delete(urlOrKey);
471
+ return this.inner.delete(urlOrKey);
472
+ }
473
+ evict() {
474
+ if (this.cache.size <= this.maxEntries) return;
475
+ let oldest;
476
+ let oldestTs = Infinity;
477
+ for (const [k, v] of this.cache) {
478
+ if (v.ts < oldestTs) {
479
+ oldestTs = v.ts;
480
+ oldest = k;
481
+ }
482
+ }
483
+ if (oldest) this.cache.delete(oldest);
484
+ }
485
+ };
486
+ var deriveUploadKey = (data, mediaType) => {
487
+ const hash = createHash2("sha256").update(data).digest("hex").slice(0, 24);
488
+ const ext = mimeToExt(mediaType);
489
+ return `${hash}${ext}`;
490
+ };
491
+ var MIME_EXT_MAP = {
492
+ "image/jpeg": ".jpg",
493
+ "image/png": ".png",
494
+ "image/gif": ".gif",
495
+ "image/webp": ".webp",
496
+ "image/svg+xml": ".svg",
497
+ "application/pdf": ".pdf",
498
+ "text/plain": ".txt",
499
+ "text/csv": ".csv",
500
+ "text/html": ".html",
501
+ "application/json": ".json",
502
+ "video/mp4": ".mp4",
503
+ "video/webm": ".webm",
504
+ "audio/mpeg": ".mp3",
505
+ "audio/wav": ".wav"
506
+ };
507
+ var mimeToExt = (mediaType) => MIME_EXT_MAP[mediaType] ?? `.${mediaType.split("/").pop() ?? "bin"}`;
508
+ var LocalUploadStore = class {
509
+ uploadsDir;
510
+ constructor(workingDir) {
511
+ this.uploadsDir = resolve5(workingDir, ".poncho", "uploads");
512
+ }
513
+ async put(_key, data, mediaType) {
514
+ const key = deriveUploadKey(data, mediaType);
515
+ const filePath = resolve5(this.uploadsDir, key);
516
+ await mkdir2(this.uploadsDir, { recursive: true });
517
+ await writeFile3(filePath, data);
518
+ return `${PONCHO_UPLOAD_SCHEME}${key}`;
519
+ }
520
+ async get(urlOrKey) {
521
+ const key = urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME) ? urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length) : urlOrKey;
522
+ return readFile4(resolve5(this.uploadsDir, key));
523
+ }
524
+ async delete(urlOrKey) {
525
+ const key = urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME) ? urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length) : urlOrKey;
526
+ await rm(resolve5(this.uploadsDir, key), { force: true });
527
+ }
528
+ };
529
+ var VercelBlobUploadStore = class {
530
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
531
+ sdk;
532
+ workingDir;
533
+ access;
534
+ constructor(workingDir, access3 = "public") {
535
+ this.workingDir = workingDir;
536
+ this.access = access3;
537
+ }
538
+ async loadSdk() {
539
+ if (this.sdk) return this.sdk;
540
+ try {
541
+ this.sdk = await tryImport("@vercel/blob", this.workingDir);
542
+ return this.sdk;
543
+ } catch {
544
+ throw new Error(
545
+ 'uploads: vercel-blob provider requires the "@vercel/blob" package. Install it with: pnpm add @vercel/blob'
546
+ );
547
+ }
548
+ }
549
+ async put(key, data, mediaType) {
550
+ const sdk = await this.loadSdk();
551
+ await sdk.put(key, data, {
552
+ access: this.access,
553
+ contentType: mediaType,
554
+ addRandomSuffix: false,
555
+ allowOverwrite: true
556
+ });
557
+ return `${PONCHO_UPLOAD_SCHEME}${key}`;
558
+ }
559
+ async get(urlOrKey) {
560
+ let pathname = urlOrKey;
561
+ if (urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME)) {
562
+ pathname = urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length);
563
+ } else if (urlOrKey.startsWith("https://") || urlOrKey.startsWith("http://")) {
564
+ pathname = new URL(urlOrKey).pathname.slice(1);
565
+ }
566
+ if (this.access === "private") {
567
+ const sdk2 = await this.loadSdk();
568
+ const result = await sdk2.get(pathname, { access: "private" });
569
+ if (!result || result.statusCode !== 200) {
570
+ throw new Error(`uploads: failed to fetch private blob "${pathname}": ${result?.statusCode ?? "not found"}`);
571
+ }
572
+ const chunks = [];
573
+ const reader = result.stream.getReader();
574
+ for (; ; ) {
575
+ const { done, value } = await reader.read();
576
+ if (done) break;
577
+ chunks.push(value);
578
+ }
579
+ return Buffer.concat(chunks);
580
+ }
581
+ const sdk = await this.loadSdk();
582
+ const blob = await sdk.head(pathname);
583
+ const response = await fetch(blob.url);
584
+ if (!response.ok) {
585
+ throw new Error(`uploads: failed to fetch blob "${pathname}": ${response.status}`);
586
+ }
587
+ return Buffer.from(await response.arrayBuffer());
588
+ }
589
+ async delete(urlOrKey) {
590
+ const sdk = await this.loadSdk();
591
+ await sdk.del(urlOrKey);
592
+ }
593
+ };
594
+ var S3UploadStore = class {
595
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
596
+ s3Sdk;
597
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
598
+ presignerSdk;
599
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
600
+ client;
601
+ bucket;
602
+ region;
603
+ endpoint;
604
+ workingDir;
605
+ constructor(bucket, region, endpoint, workingDir) {
606
+ this.bucket = bucket;
607
+ this.region = region;
608
+ this.endpoint = endpoint;
609
+ this.workingDir = workingDir;
610
+ }
611
+ async ensureClient() {
612
+ if (this.client) return;
613
+ try {
614
+ this.s3Sdk = await tryImport("@aws-sdk/client-s3", this.workingDir);
615
+ this.presignerSdk = await tryImport("@aws-sdk/s3-request-presigner", this.workingDir);
616
+ } catch {
617
+ throw new Error(
618
+ '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'
619
+ );
620
+ }
621
+ this.client = new this.s3Sdk.S3Client({
622
+ region: this.region ?? process.env.AWS_REGION ?? "us-east-1",
623
+ ...this.endpoint ? { endpoint: this.endpoint, forcePathStyle: true } : {}
624
+ });
625
+ }
626
+ async put(key, data, mediaType) {
627
+ await this.ensureClient();
628
+ await this.client.send(
629
+ new this.s3Sdk.PutObjectCommand({
630
+ Bucket: this.bucket,
631
+ Key: key,
632
+ Body: data,
633
+ ContentType: mediaType
634
+ })
635
+ );
636
+ const url = await this.presignerSdk.getSignedUrl(
637
+ this.client,
638
+ new this.s3Sdk.GetObjectCommand({ Bucket: this.bucket, Key: key }),
639
+ { expiresIn: 7 * 24 * 60 * 60 }
640
+ );
641
+ return url;
642
+ }
643
+ async get(urlOrKey) {
644
+ if (urlOrKey.startsWith("https://") || urlOrKey.startsWith("http://")) {
645
+ const response = await fetch(urlOrKey);
646
+ if (!response.ok) {
647
+ throw new Error(`uploads: failed to fetch S3 object at ${urlOrKey}: ${response.status}`);
648
+ }
649
+ return Buffer.from(await response.arrayBuffer());
650
+ }
651
+ await this.ensureClient();
652
+ const result = await this.client.send(
653
+ new this.s3Sdk.GetObjectCommand({ Bucket: this.bucket, Key: urlOrKey })
654
+ );
655
+ if (!result.Body) throw new Error(`uploads: empty body for S3 key ${urlOrKey}`);
656
+ return Buffer.from(await result.Body.transformToByteArray());
657
+ }
658
+ async delete(urlOrKey) {
659
+ await this.ensureClient();
660
+ const key = urlOrKey.startsWith("https://") ? new URL(urlOrKey).pathname.slice(1) : urlOrKey;
661
+ await this.client.send(
662
+ new this.s3Sdk.DeleteObjectCommand({ Bucket: this.bucket, Key: key })
663
+ );
664
+ }
665
+ };
666
+ var warn = (msg) => {
667
+ console.warn(`[poncho] \u26A0 ${msg}`);
668
+ };
669
+ var createUploadStore = async (config, workingDir) => {
670
+ const provider = config?.provider ?? "local";
671
+ if (provider === "vercel-blob") {
672
+ if (!process.env.BLOB_READ_WRITE_TOKEN) {
673
+ warn(
674
+ "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."
675
+ );
676
+ return new LocalUploadStore(workingDir);
677
+ }
678
+ const store = new VercelBlobUploadStore(workingDir, config?.access ?? "public");
679
+ try {
680
+ await store.loadSdk();
681
+ console.log("[poncho] uploads: using vercel-blob store");
682
+ return new CachedUploadStore(store);
683
+ } catch {
684
+ warn(
685
+ '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'
686
+ );
687
+ return new LocalUploadStore(workingDir);
688
+ }
689
+ }
690
+ if (provider === "s3") {
691
+ const bucket = config?.bucket ?? process.env.PONCHO_UPLOADS_BUCKET;
692
+ if (!process.env.AWS_ACCESS_KEY_ID || !bucket) {
693
+ const missing = !process.env.AWS_ACCESS_KEY_ID ? "AWS_ACCESS_KEY_ID" : "bucket (config.uploads.bucket or PONCHO_UPLOADS_BUCKET)";
694
+ warn(
695
+ `uploads: s3 configured but ${missing} not found in environment. Falling back to local filesystem.`
696
+ );
697
+ return new LocalUploadStore(workingDir);
698
+ }
699
+ const store = new S3UploadStore(
700
+ bucket,
701
+ config?.region ?? process.env.AWS_REGION,
702
+ config?.endpoint ?? process.env.PONCHO_UPLOADS_ENDPOINT,
703
+ workingDir
704
+ );
705
+ try {
706
+ await store.ensureClient();
707
+ console.log(`[poncho] uploads: using s3 store (bucket: ${bucket})`);
708
+ return new CachedUploadStore(store);
709
+ } catch {
710
+ warn(
711
+ "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"
712
+ );
713
+ return new LocalUploadStore(workingDir);
714
+ }
715
+ }
716
+ return new LocalUploadStore(workingDir);
717
+ };
415
718
 
416
719
  // 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";
720
+ import { mkdir as mkdir3, readFile as readFile5, rename, writeFile as writeFile4 } from "fs/promises";
721
+ import { dirname as dirname2, resolve as resolve6 } from "path";
419
722
  import { defineTool as defineTool2 } from "@poncho-ai/sdk";
420
723
  var DEFAULT_MAIN_MEMORY = {
421
724
  content: "",
@@ -423,9 +726,9 @@ var DEFAULT_MAIN_MEMORY = {
423
726
  };
424
727
  var LOCAL_MEMORY_FILE = "memory.json";
425
728
  var writeJsonAtomic = async (filePath, payload) => {
426
- await mkdir2(dirname2(filePath), { recursive: true });
729
+ await mkdir3(dirname2(filePath), { recursive: true });
427
730
  const tmpPath = `${filePath}.tmp`;
428
- await writeFile3(tmpPath, JSON.stringify(payload, null, 2), "utf8");
731
+ await writeFile4(tmpPath, JSON.stringify(payload, null, 2), "utf8");
429
732
  await rename(tmpPath, filePath);
430
733
  };
431
734
  var scoreText = (text, query) => {
@@ -487,7 +790,7 @@ var FileMainMemoryStore = class {
487
790
  return;
488
791
  }
489
792
  const identity = await ensureAgentIdentity(this.workingDir);
490
- this.filePath = resolve5(getAgentStoreDirectory(identity), LOCAL_MEMORY_FILE);
793
+ this.filePath = resolve6(getAgentStoreDirectory(identity), LOCAL_MEMORY_FILE);
491
794
  }
492
795
  isExpired(updatedAt) {
493
796
  return typeof this.ttlMs === "number" && Date.now() - updatedAt > this.ttlMs;
@@ -499,7 +802,7 @@ var FileMainMemoryStore = class {
499
802
  }
500
803
  this.loaded = true;
501
804
  try {
502
- const raw = await readFile4(this.filePath, "utf8");
805
+ const raw = await readFile5(this.filePath, "utf8");
503
806
  const parsed = JSON.parse(raw);
504
807
  const content = typeof parsed.main?.content === "string" ? parsed.main.content : "";
505
808
  const updatedAt = typeof parsed.main?.updatedAt === "number" ? parsed.main.updatedAt : 0;
@@ -1385,8 +1688,8 @@ var createModelProvider = (provider) => {
1385
1688
  };
1386
1689
 
1387
1690
  // 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";
1691
+ import { readFile as readFile6, readdir as readdir2, stat } from "fs/promises";
1692
+ import { dirname as dirname3, resolve as resolve7, normalize } from "path";
1390
1693
  import YAML3 from "yaml";
1391
1694
  var DEFAULT_SKILL_DIRS = ["skills"];
1392
1695
  var resolveSkillDirs = (workingDir, extraPaths) => {
@@ -1398,7 +1701,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
1398
1701
  }
1399
1702
  }
1400
1703
  }
1401
- return dirs.map((d) => resolve6(workingDir, d));
1704
+ return dirs.map((d) => resolve7(workingDir, d));
1402
1705
  };
1403
1706
  var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
1404
1707
  var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
@@ -1467,7 +1770,7 @@ var collectSkillManifests = async (directory) => {
1467
1770
  const entries = await readdir2(directory, { withFileTypes: true });
1468
1771
  const files = [];
1469
1772
  for (const entry of entries) {
1470
- const fullPath = resolve6(directory, entry.name);
1773
+ const fullPath = resolve7(directory, entry.name);
1471
1774
  let isDir = entry.isDirectory();
1472
1775
  let isFile = entry.isFile();
1473
1776
  if (entry.isSymbolicLink()) {
@@ -1502,7 +1805,7 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
1502
1805
  const seen = /* @__PURE__ */ new Set();
1503
1806
  for (const manifest of allManifests) {
1504
1807
  try {
1505
- const content = await readFile5(manifest, "utf8");
1808
+ const content = await readFile6(manifest, "utf8");
1506
1809
  const parsed = parseSkillFrontmatter(content);
1507
1810
  if (parsed && !seen.has(parsed.name)) {
1508
1811
  seen.add(parsed.name);
@@ -1547,7 +1850,7 @@ ${xmlSkills}
1547
1850
  };
1548
1851
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1549
1852
  var loadSkillInstructions = async (skill) => {
1550
- const content = await readFile5(skill.skillPath, "utf8");
1853
+ const content = await readFile6(skill.skillPath, "utf8");
1551
1854
  const match = content.match(FRONTMATTER_PATTERN3);
1552
1855
  return match ? match[2].trim() : content.trim();
1553
1856
  };
@@ -1556,11 +1859,11 @@ var readSkillResource = async (skill, relativePath) => {
1556
1859
  if (normalized.startsWith("..") || normalized.startsWith("/")) {
1557
1860
  throw new Error("Path must be relative and within the skill directory");
1558
1861
  }
1559
- const fullPath = resolve6(skill.skillDir, normalized);
1862
+ const fullPath = resolve7(skill.skillDir, normalized);
1560
1863
  if (!fullPath.startsWith(skill.skillDir)) {
1561
1864
  throw new Error("Path escapes the skill directory");
1562
1865
  }
1563
- return await readFile5(fullPath, "utf8");
1866
+ return await readFile6(fullPath, "utf8");
1564
1867
  };
1565
1868
  var MAX_INSTRUCTIONS_PER_SKILL = 1200;
1566
1869
  var loadSkillContext = async (workingDir) => {
@@ -1651,7 +1954,7 @@ function convertSchema(schema) {
1651
1954
  // src/skill-tools.ts
1652
1955
  import { defineTool as defineTool3 } from "@poncho-ai/sdk";
1653
1956
  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";
1957
+ import { extname, normalize as normalize2, resolve as resolve8, sep as sep2 } from "path";
1655
1958
  import { pathToFileURL } from "url";
1656
1959
  import { createJiti as createJiti2 } from "jiti";
1657
1960
  var createSkillTools = (skills, options) => {
@@ -1908,7 +2211,7 @@ var collectScriptFiles = async (directory) => {
1908
2211
  if (entry.name === "node_modules") {
1909
2212
  continue;
1910
2213
  }
1911
- const fullPath = resolve7(directory, entry.name);
2214
+ const fullPath = resolve8(directory, entry.name);
1912
2215
  let isDir = entry.isDirectory();
1913
2216
  let isFile = entry.isFile();
1914
2217
  if (entry.isSymbolicLink()) {
@@ -1947,8 +2250,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
1947
2250
  };
1948
2251
  var resolveScriptPath = (baseDir, relativePath) => {
1949
2252
  const normalized = normalizeScriptPolicyPath(relativePath);
1950
- const fullPath = resolve7(baseDir, normalized);
1951
- if (!fullPath.startsWith(`${resolve7(baseDir)}${sep2}`) && fullPath !== resolve7(baseDir)) {
2253
+ const fullPath = resolve8(baseDir, normalized);
2254
+ if (!fullPath.startsWith(`${resolve8(baseDir)}${sep2}`) && fullPath !== resolve8(baseDir)) {
1952
2255
  throw new Error("Script path must stay inside the allowed directory");
1953
2256
  }
1954
2257
  const extension = extname(fullPath).toLowerCase();
@@ -2283,6 +2586,7 @@ var AgentHarness = class {
2283
2586
  modelProviderInjected;
2284
2587
  dispatcher = new ToolDispatcher();
2285
2588
  approvalHandler;
2589
+ uploadStore;
2286
2590
  skillContextWindow = "";
2287
2591
  memoryStore;
2288
2592
  loadedConfig;
@@ -2345,6 +2649,7 @@ var AgentHarness = class {
2345
2649
  this.modelProviderInjected = !!options.modelProvider;
2346
2650
  this.modelProvider = options.modelProvider ?? createModelProvider("anthropic");
2347
2651
  this.approvalHandler = options.approvalHandler;
2652
+ this.uploadStore = options.uploadStore;
2348
2653
  if (options.toolDefinitions?.length) {
2349
2654
  this.dispatcher.registerMany(options.toolDefinitions);
2350
2655
  }
@@ -2626,9 +2931,9 @@ var AgentHarness = class {
2626
2931
  for await (const event of this.run(input)) {
2627
2932
  eventQueue.push(event);
2628
2933
  if (queueResolve) {
2629
- const resolve9 = queueResolve;
2934
+ const resolve10 = queueResolve;
2630
2935
  queueResolve = null;
2631
- resolve9();
2936
+ resolve10();
2632
2937
  }
2633
2938
  }
2634
2939
  } catch (error) {
@@ -2646,8 +2951,8 @@ var AgentHarness = class {
2646
2951
  if (eventQueue.length > 0) {
2647
2952
  yield eventQueue.shift();
2648
2953
  } else if (!generatorDone) {
2649
- await new Promise((resolve9) => {
2650
- queueResolve = resolve9;
2954
+ await new Promise((resolve10) => {
2955
+ queueResolve = resolve10;
2651
2956
  });
2652
2957
  }
2653
2958
  }
@@ -2671,6 +2976,8 @@ var AgentHarness = class {
2671
2976
  const start = now();
2672
2977
  const maxSteps = agent.frontmatter.limits?.maxSteps ?? 50;
2673
2978
  const timeoutMs = (agent.frontmatter.limits?.timeout ?? 300) * 1e3;
2979
+ const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
2980
+ const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
2674
2981
  const messages = [...input.messages ?? []];
2675
2982
  const events = [];
2676
2983
  const systemPrompt = renderAgentPrompt(agent, {
@@ -2718,11 +3025,42 @@ ${boundedMainMemory.trim()}` : "";
2718
3025
  runId,
2719
3026
  agentId: agent.frontmatter.id ?? agent.frontmatter.name
2720
3027
  });
2721
- messages.push({
2722
- role: "user",
2723
- content: input.task,
2724
- metadata: { timestamp: now(), id: randomUUID3() }
2725
- });
3028
+ if (input.files && input.files.length > 0) {
3029
+ const parts = [
3030
+ { type: "text", text: input.task }
3031
+ ];
3032
+ for (const file of input.files) {
3033
+ if (this.uploadStore) {
3034
+ const buf = Buffer.from(file.data, "base64");
3035
+ const key = deriveUploadKey(buf, file.mediaType);
3036
+ const ref = await this.uploadStore.put(key, buf, file.mediaType);
3037
+ parts.push({
3038
+ type: "file",
3039
+ data: ref,
3040
+ mediaType: file.mediaType,
3041
+ filename: file.filename
3042
+ });
3043
+ } else {
3044
+ parts.push({
3045
+ type: "file",
3046
+ data: file.data,
3047
+ mediaType: file.mediaType,
3048
+ filename: file.filename
3049
+ });
3050
+ }
3051
+ }
3052
+ messages.push({
3053
+ role: "user",
3054
+ content: parts,
3055
+ metadata: { timestamp: now(), id: randomUUID3() }
3056
+ });
3057
+ } else {
3058
+ messages.push({
3059
+ role: "user",
3060
+ content: input.task,
3061
+ metadata: { timestamp: now(), id: randomUUID3() }
3062
+ });
3063
+ }
2726
3064
  let responseText = "";
2727
3065
  let totalInputTokens = 0;
2728
3066
  let totalOutputTokens = 0;
@@ -2744,6 +3082,19 @@ ${boundedMainMemory.trim()}` : "";
2744
3082
  });
2745
3083
  return;
2746
3084
  }
3085
+ if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
3086
+ const result2 = {
3087
+ status: "completed",
3088
+ response: responseText,
3089
+ steps: step - 1,
3090
+ tokens: { input: totalInputTokens, output: totalOutputTokens, cached: 0 },
3091
+ duration: now() - start,
3092
+ continuation: true,
3093
+ maxSteps
3094
+ };
3095
+ yield pushEvent({ type: "run:completed", runId, result: result2 });
3096
+ return;
3097
+ }
2747
3098
  const stepStart = now();
2748
3099
  yield pushEvent({ type: "step:started", step });
2749
3100
  yield pushEvent({ type: "model:request", tokens: 0 });
@@ -2765,102 +3116,182 @@ ${boundedMainMemory.trim()}` : "";
2765
3116
  inputSchema: jsonSchemaToZod(tool.inputSchema)
2766
3117
  };
2767
3118
  }
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 [];
3119
+ const convertMessage = async (msg) => {
3120
+ if (msg.role === "tool") {
3121
+ const textContent = typeof msg.content === "string" ? msg.content : getTextContent(msg);
3122
+ try {
3123
+ const parsed = JSON.parse(textContent);
3124
+ if (!Array.isArray(parsed)) {
3125
+ return [];
3126
+ }
3127
+ const toolResults = parsed.filter((item) => {
3128
+ if (typeof item !== "object" || item === null) {
3129
+ return false;
2775
3130
  }
2776
- const toolResults = parsed.filter((item) => {
2777
- if (typeof item !== "object" || item === null) {
3131
+ const row = item;
3132
+ return typeof row.tool_use_id === "string" && typeof row.tool_name === "string" && typeof row.content === "string";
3133
+ });
3134
+ if (toolResults.length === 0) {
3135
+ return [];
3136
+ }
3137
+ return [{
3138
+ role: "tool",
3139
+ content: toolResults.map((tr) => {
3140
+ if (tr.content.startsWith("Tool error:")) {
3141
+ return {
3142
+ type: "tool-result",
3143
+ toolCallId: tr.tool_use_id,
3144
+ toolName: tr.tool_name,
3145
+ output: { type: "text", value: tr.content }
3146
+ };
3147
+ }
3148
+ try {
3149
+ const resultValue = JSON.parse(tr.content);
3150
+ return {
3151
+ type: "tool-result",
3152
+ toolCallId: tr.tool_use_id,
3153
+ toolName: tr.tool_name,
3154
+ output: { type: "json", value: resultValue }
3155
+ };
3156
+ } catch {
3157
+ return {
3158
+ type: "tool-result",
3159
+ toolCallId: tr.tool_use_id,
3160
+ toolName: tr.tool_name,
3161
+ output: { type: "text", value: tr.content }
3162
+ };
3163
+ }
3164
+ })
3165
+ }];
3166
+ } catch {
3167
+ return [];
3168
+ }
3169
+ }
3170
+ if (msg.role === "assistant") {
3171
+ const assistantText = typeof msg.content === "string" ? msg.content : getTextContent(msg);
3172
+ try {
3173
+ const parsed = JSON.parse(assistantText);
3174
+ if (typeof parsed === "object" && parsed !== null) {
3175
+ const parsedRecord = parsed;
3176
+ if (!Array.isArray(parsedRecord.tool_calls)) {
3177
+ return [{ role: "assistant", content: assistantText }];
3178
+ }
3179
+ const textPart = typeof parsedRecord.text === "string" ? parsedRecord.text : "";
3180
+ const validToolCalls = parsedRecord.tool_calls.filter((tc) => {
3181
+ if (typeof tc !== "object" || tc === null) {
2778
3182
  return false;
2779
3183
  }
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) {
3184
+ const toolCall = tc;
3185
+ return typeof toolCall.id === "string" && typeof toolCall.name === "string" && toolCall.input !== null && typeof toolCall.input === "object";
3186
+ }).map((tc) => ({
3187
+ type: "tool-call",
3188
+ toolCallId: tc.id,
3189
+ toolName: tc.name,
3190
+ input: tc.input
3191
+ }));
3192
+ if (textPart.length === 0 && validToolCalls.length === 0) {
2784
3193
  return [];
2785
3194
  }
2786
3195
  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
- })
3196
+ role: "assistant",
3197
+ content: [
3198
+ ...textPart.length > 0 ? [{ type: "text", text: textPart }] : [],
3199
+ ...validToolCalls
3200
+ ]
2814
3201
  }];
2815
- } catch {
2816
- return [];
2817
3202
  }
3203
+ } catch {
2818
3204
  }
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;
3205
+ return [{ role: "assistant", content: assistantText }];
3206
+ }
3207
+ if (msg.role === "system") {
3208
+ return [{
3209
+ role: "system",
3210
+ content: typeof msg.content === "string" ? msg.content : getTextContent(msg)
3211
+ }];
3212
+ }
3213
+ if (msg.role === "user") {
3214
+ if (typeof msg.content === "string") {
3215
+ return [{ role: "user", content: msg.content }];
3216
+ }
3217
+ const MODEL_IMAGE_TYPES = /* @__PURE__ */ new Set([
3218
+ "image/jpeg",
3219
+ "image/png",
3220
+ "image/gif",
3221
+ "image/webp"
3222
+ ]);
3223
+ const MODEL_FILE_TYPES = /* @__PURE__ */ new Set([
3224
+ "application/pdf"
3225
+ ]);
3226
+ const isTextBasedMime = (mt) => mt.startsWith("text/") || mt === "application/json" || mt === "application/xml" || mt === "application/x-yaml" || mt.endsWith("+json") || mt.endsWith("+xml");
3227
+ const userContent = await Promise.all(
3228
+ msg.content.map(async (part) => {
3229
+ if (part.type === "text") {
3230
+ return { type: "text", text: part.text };
3231
+ }
3232
+ const isSupportedImage = MODEL_IMAGE_TYPES.has(part.mediaType);
3233
+ const isSupportedFile = MODEL_FILE_TYPES.has(part.mediaType);
3234
+ if (!isSupportedImage && !isSupportedFile && isTextBasedMime(part.mediaType)) {
3235
+ let textContent;
3236
+ try {
3237
+ if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
3238
+ const buf = await this.uploadStore.get(part.data);
3239
+ textContent = buf.toString("utf8");
3240
+ } else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
3241
+ const resp = await fetch(part.data);
3242
+ textContent = await resp.text();
3243
+ } else {
3244
+ textContent = Buffer.from(part.data, "base64").toString("utf8");
2831
3245
  }
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 [];
3246
+ } catch {
3247
+ textContent = "(could not read file)";
2842
3248
  }
2843
- return [{
2844
- role: "assistant",
2845
- content: [
2846
- ...textPart.length > 0 ? [{ type: "text", text: textPart }] : [],
2847
- ...validToolCalls
2848
- ]
2849
- }];
3249
+ const label = part.filename ?? part.mediaType;
3250
+ return { type: "text", text: `[File: ${label}]
3251
+ ${textContent}` };
2850
3252
  }
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 [];
3253
+ if (!isSupportedImage && !isSupportedFile) {
3254
+ const label = part.filename ?? part.mediaType;
3255
+ return {
3256
+ type: "text",
3257
+ text: `[Attached file: ${label} (${part.mediaType}) \u2014 this format is not supported by the model and was skipped]`
3258
+ };
3259
+ }
3260
+ let resolvedData;
3261
+ if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
3262
+ const buf = await this.uploadStore.get(part.data);
3263
+ resolvedData = buf.toString("base64");
3264
+ } else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
3265
+ if (this.uploadStore) {
3266
+ const buf = await this.uploadStore.get(part.data);
3267
+ resolvedData = buf.toString("base64");
3268
+ } else {
3269
+ const resp = await fetch(part.data);
3270
+ resolvedData = Buffer.from(await resp.arrayBuffer()).toString("base64");
3271
+ }
3272
+ } else {
3273
+ resolvedData = part.data;
3274
+ }
3275
+ if (isSupportedImage) {
3276
+ return {
3277
+ type: "image",
3278
+ image: resolvedData,
3279
+ mediaType: part.mediaType
3280
+ };
3281
+ }
3282
+ return {
3283
+ type: "file",
3284
+ data: resolvedData,
3285
+ mediaType: part.mediaType,
3286
+ filename: part.filename
3287
+ };
3288
+ })
3289
+ );
3290
+ return [{ role: "user", content: userContent }];
2862
3291
  }
2863
- );
3292
+ return [];
3293
+ };
3294
+ const coreMessages = (await Promise.all(trimMessageWindow(messages).map(convertMessage))).flat();
2864
3295
  const modelName = agent.frontmatter.model?.name ?? "claude-opus-4-5";
2865
3296
  const temperature = agent.frontmatter.model?.temperature ?? 0.2;
2866
3297
  const maxTokens = agent.frontmatter.model?.maxTokens;
@@ -2907,8 +3338,8 @@ ${boundedMainMemory.trim()}` : "";
2907
3338
  let timer;
2908
3339
  const nextChunk = await Promise.race([
2909
3340
  textIterator.next(),
2910
- new Promise((resolve9) => {
2911
- timer = setTimeout(() => resolve9(null), timeout);
3341
+ new Promise((resolve10) => {
3342
+ timer = setTimeout(() => resolve10(null), timeout);
2912
3343
  })
2913
3344
  ]);
2914
3345
  clearTimeout(timer);
@@ -3183,14 +3614,27 @@ ${boundedMainMemory.trim()}` : "";
3183
3614
  return;
3184
3615
  }
3185
3616
  }
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
- };
3617
+ if (softDeadlineMs > 0) {
3618
+ const result = {
3619
+ status: "completed",
3620
+ response: responseText,
3621
+ steps: maxSteps,
3622
+ tokens: { input: totalInputTokens, output: totalOutputTokens, cached: 0 },
3623
+ duration: now() - start,
3624
+ continuation: true,
3625
+ maxSteps
3626
+ };
3627
+ yield pushEvent({ type: "run:completed", runId, result });
3628
+ } else {
3629
+ yield pushEvent({
3630
+ type: "run:error",
3631
+ runId,
3632
+ error: {
3633
+ code: "MAX_STEPS_EXCEEDED",
3634
+ message: `Run reached maximum of ${maxSteps} steps`
3635
+ }
3636
+ });
3637
+ }
3194
3638
  }
3195
3639
  async runToCompletion(input) {
3196
3640
  const events = [];
@@ -3270,8 +3714,8 @@ var LatitudeCapture = class {
3270
3714
 
3271
3715
  // src/state.ts
3272
3716
  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";
3717
+ import { mkdir as mkdir4, readFile as readFile7, readdir as readdir4, rename as rename2, rm as rm2, writeFile as writeFile5 } from "fs/promises";
3718
+ import { dirname as dirname4, resolve as resolve9 } from "path";
3275
3719
  var DEFAULT_OWNER = "local-owner";
3276
3720
  var LOCAL_STATE_FILE = "state.json";
3277
3721
  var CONVERSATIONS_DIRECTORY = "conversations";
@@ -3287,9 +3731,9 @@ var toStoreIdentity = async ({
3287
3731
  return { name: ensured.name, id: agentId };
3288
3732
  };
3289
3733
  var writeJsonAtomic2 = async (filePath, payload) => {
3290
- await mkdir3(dirname4(filePath), { recursive: true });
3734
+ await mkdir4(dirname4(filePath), { recursive: true });
3291
3735
  const tmpPath = `${filePath}.tmp`;
3292
- await writeFile4(tmpPath, JSON.stringify(payload, null, 2), "utf8");
3736
+ await writeFile5(tmpPath, JSON.stringify(payload, null, 2), "utf8");
3293
3737
  await rename2(tmpPath, filePath);
3294
3738
  };
3295
3739
  var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
@@ -3450,8 +3894,8 @@ var FileConversationStore = class {
3450
3894
  agentId: this.agentId
3451
3895
  });
3452
3896
  const agentDir = getAgentStoreDirectory(identity);
3453
- const conversationsDir = resolve8(agentDir, CONVERSATIONS_DIRECTORY);
3454
- const indexPath = resolve8(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
3897
+ const conversationsDir = resolve9(agentDir, CONVERSATIONS_DIRECTORY);
3898
+ const indexPath = resolve9(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
3455
3899
  this.paths = { conversationsDir, indexPath };
3456
3900
  return this.paths;
3457
3901
  }
@@ -3465,9 +3909,9 @@ var FileConversationStore = class {
3465
3909
  }
3466
3910
  async readConversationFile(fileName) {
3467
3911
  const { conversationsDir } = await this.resolvePaths();
3468
- const filePath = resolve8(conversationsDir, fileName);
3912
+ const filePath = resolve9(conversationsDir, fileName);
3469
3913
  try {
3470
- const raw = await readFile6(filePath, "utf8");
3914
+ const raw = await readFile7(filePath, "utf8");
3471
3915
  return JSON.parse(raw);
3472
3916
  } catch {
3473
3917
  return void 0;
@@ -3509,7 +3953,7 @@ var FileConversationStore = class {
3509
3953
  this.loaded = true;
3510
3954
  const { indexPath } = await this.resolvePaths();
3511
3955
  try {
3512
- const raw = await readFile6(indexPath, "utf8");
3956
+ const raw = await readFile7(indexPath, "utf8");
3513
3957
  const parsed = JSON.parse(raw);
3514
3958
  for (const conversation of parsed.conversations ?? []) {
3515
3959
  this.conversations.set(conversation.conversationId, conversation);
@@ -3522,7 +3966,7 @@ var FileConversationStore = class {
3522
3966
  const { conversationsDir } = await this.resolvePaths();
3523
3967
  const existing = this.conversations.get(conversation.conversationId);
3524
3968
  const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
3525
- const filePath = resolve8(conversationsDir, fileName);
3969
+ const filePath = resolve9(conversationsDir, fileName);
3526
3970
  this.writing = this.writing.then(async () => {
3527
3971
  await writeJsonAtomic2(filePath, conversation);
3528
3972
  this.conversations.set(conversation.conversationId, {
@@ -3601,7 +4045,7 @@ var FileConversationStore = class {
3601
4045
  if (removed) {
3602
4046
  this.writing = this.writing.then(async () => {
3603
4047
  if (existing) {
3604
- await rm(resolve8(conversationsDir, existing.fileName), { force: true });
4048
+ await rm2(resolve9(conversationsDir, existing.fileName), { force: true });
3605
4049
  }
3606
4050
  await this.writeIndex();
3607
4051
  });
@@ -3631,7 +4075,7 @@ var FileStateStore = class {
3631
4075
  workingDir: this.workingDir,
3632
4076
  agentId: this.agentId
3633
4077
  });
3634
- this.filePath = resolve8(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
4078
+ this.filePath = resolve9(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
3635
4079
  }
3636
4080
  isExpired(state) {
3637
4081
  return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
@@ -3643,7 +4087,7 @@ var FileStateStore = class {
3643
4087
  }
3644
4088
  this.loaded = true;
3645
4089
  try {
3646
- const raw = await readFile6(this.filePath, "utf8");
4090
+ const raw = await readFile7(this.filePath, "utf8");
3647
4091
  const parsed = JSON.parse(raw);
3648
4092
  for (const state of parsed.states ?? []) {
3649
4093
  this.states.set(state.runId, state);
@@ -4319,9 +4763,13 @@ export {
4319
4763
  InMemoryStateStore,
4320
4764
  LatitudeCapture,
4321
4765
  LocalMcpBridge,
4766
+ LocalUploadStore,
4767
+ PONCHO_UPLOAD_SCHEME,
4768
+ S3UploadStore,
4322
4769
  STORAGE_SCHEMA_VERSION,
4323
4770
  TelemetryEmitter,
4324
4771
  ToolDispatcher,
4772
+ VercelBlobUploadStore,
4325
4773
  buildAgentDirectoryName,
4326
4774
  buildSkillContextWindow,
4327
4775
  createConversationStore,
@@ -4331,8 +4779,10 @@ export {
4331
4779
  createModelProvider,
4332
4780
  createSkillTools,
4333
4781
  createStateStore,
4782
+ createUploadStore,
4334
4783
  createWriteTool,
4335
4784
  defineTool4 as defineTool,
4785
+ deriveUploadKey,
4336
4786
  ensureAgentIdentity,
4337
4787
  generateAgentId,
4338
4788
  getAgentStoreDirectory,