@poncho-ai/harness 0.20.13 → 0.21.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
@@ -406,6 +406,1143 @@ var loadPonchoConfig = async (workingDir) => {
406
406
  import { mkdir, readdir, readFile as readFile3, rm, unlink, writeFile as writeFile2 } from "fs/promises";
407
407
  import { dirname, resolve as resolve4, sep } from "path";
408
408
  import { defineTool } from "@poncho-ai/sdk";
409
+
410
+ // src/generated/poncho-docs.ts
411
+ var PONCHO_DOCS = {
412
+ "api": `# HTTP API & Client SDK
413
+
414
+ > Back to [README](../README.md)
415
+
416
+ Your deployed agent exposes these endpoints:
417
+
418
+ | Endpoint | Use case |
419
+ |----------|----------|
420
+ | \`GET /health\` | Health checks for load balancers |
421
+ | \`GET /api/docs\` | Interactive API documentation (Scalar) |
422
+ | \`GET /api/openapi.json\` | OpenAPI 3.1 spec (machine-readable) |
423
+ | \`GET /api/auth/session\` | Session status + CSRF token for browser clients |
424
+ | \`POST /api/auth/login\` | Passphrase login for browser sessions |
425
+ | \`POST /api/auth/logout\` | End browser session |
426
+ | \`GET /api/conversations\` | List stored conversations |
427
+ | \`POST /api/conversations\` | Create conversation |
428
+ | \`GET /api/conversations/:conversationId\` | Get conversation transcript |
429
+ | \`PATCH /api/conversations/:conversationId\` | Rename conversation |
430
+ | \`DELETE /api/conversations/:conversationId\` | Delete conversation |
431
+ | \`POST /api/conversations/:conversationId/messages\` | Stream a new assistant response |
432
+ | \`GET /api/conversations/:conversationId/events\` | Attach to live SSE stream |
433
+ | \`POST /api/conversations/:conversationId/stop\` | Cancel an in-flight run |
434
+ | \`POST /api/approvals/:approvalId\` | Resolve tool approval request |
435
+ | \`GET /api/uploads/:key\` | Retrieve uploaded file |
436
+ | \`GET\\|POST /api/cron/:jobName\` | Trigger a cron job (see [Cron Jobs](../README.md#cron-jobs)) |
437
+ | \`GET /api/browser/status\` | Browser session status (when browser is enabled) |
438
+ | \`GET /api/browser/stream\` | SSE stream of live browser viewport frames |
439
+ | \`POST /api/browser/input\` | Send input events to the browser session |
440
+ | \`POST /api/browser/navigate\` | Navigate the browser to a URL |
441
+
442
+ > **Tip:** Visit \`/api/docs\` on any running agent for interactive API documentation with request examples and full schema details.
443
+
444
+ ## POST /api/conversations/:conversationId/messages (streaming)
445
+
446
+ \`\`\`bash
447
+ curl -N -X POST https://my-agent.vercel.app/api/conversations/<conversation-id>/messages \\
448
+ -H "Content-Type: application/json" \\
449
+ -d '{"message": "Write a hello world function"}'
450
+ \`\`\`
451
+
452
+ Response: Server-Sent Events (\`run:started\`, \`model:chunk\`, \`tool:*\`, \`run:completed\`).
453
+
454
+ On serverless deployments with \`PONCHO_MAX_DURATION\` set, the \`run:completed\` event may
455
+ include \`continuation: true\` in \`result\`, indicating the agent stopped early due to a
456
+ platform timeout and the client should send another message (e.g., \`"Continue"\`) on the
457
+ same conversation to resume.
458
+
459
+ ## Build a custom chat UI
460
+
461
+ You can build your own chat frontend by calling Poncho's conversation endpoints directly.
462
+
463
+ Typical UI flow:
464
+
465
+ 1. Create a conversation: \`POST /api/conversations\`
466
+ 2. Send a message and stream events: \`POST /api/conversations/:conversationId/messages\`
467
+ 3. Append \`model:chunk\` events into the in-progress assistant message
468
+ 4. Render \`tool:*\` events as activity status
469
+ 5. Finalize on \`run:completed\` (or handle \`run:error\` / \`run:cancelled\`)
470
+ 6. Reload full transcript on refresh: \`GET /api/conversations/:conversationId\`
471
+
472
+ Minimal browser example (SSE parsing):
473
+
474
+ \`\`\`typescript
475
+ async function streamMessage(conversationId: string, message: string) {
476
+ const response = await fetch(
477
+ \`/api/conversations/\${encodeURIComponent(conversationId)}/messages\`,
478
+ {
479
+ method: "POST",
480
+ headers: { "Content-Type": "application/json" },
481
+ body: JSON.stringify({ message }),
482
+ credentials: "include", // keep for session auth
483
+ },
484
+ );
485
+
486
+ if (!response.ok || !response.body) {
487
+ throw new Error(\`Streaming request failed: HTTP \${response.status}\`);
488
+ }
489
+
490
+ const reader = response.body.getReader();
491
+ const decoder = new TextDecoder();
492
+ let buffer = "";
493
+
494
+ while (true) {
495
+ const { done, value } = await reader.read();
496
+ if (done) break;
497
+
498
+ buffer += decoder.decode(value, { stream: true });
499
+ const frames = buffer.split("\\n\\n");
500
+ buffer = frames.pop() ?? "";
501
+
502
+ for (const frame of frames) {
503
+ const lines = frame
504
+ .split("\\n")
505
+ .map((line) => line.trim())
506
+ .filter(Boolean);
507
+
508
+ const eventLine = lines.find((line) => line.startsWith("event:"));
509
+ const dataLine = lines.find((line) => line.startsWith("data:"));
510
+ if (!eventLine || !dataLine) continue;
511
+
512
+ const eventName = eventLine.slice("event:".length).trim();
513
+ const payload = JSON.parse(dataLine.slice("data:".length).trim());
514
+
515
+ if (eventName === "model:chunk") {
516
+ // Append payload.content to the active assistant message
517
+ } else if (eventName === "tool:started") {
518
+ // Show "running tool" activity
519
+ } else if (eventName === "tool:completed") {
520
+ // Mark tool activity as complete
521
+ } else if (eventName === "run:completed") {
522
+ // Finalize assistant message
523
+ } else if (eventName === "run:error" || eventName === "run:cancelled") {
524
+ // Show interrupted/error state in UI
525
+ }
526
+ }
527
+ }
528
+ }
529
+ \`\`\`
530
+
531
+ Useful optional endpoints for richer UIs:
532
+
533
+ - \`POST /api/conversations/:conversationId/stop\` with \`{ "runId": "<run-id>" }\` to cancel an in-flight run
534
+ - \`GET /api/conversations/:conversationId/events\` to attach/re-attach to a live event stream
535
+ - \`POST /api/approvals/:approvalId\` with \`{ "approved": true|false }\` to resolve \`tool:approval:required\`
536
+
537
+ Auth notes for custom frontends:
538
+
539
+ - Browser session mode: \`GET /api/auth/session\`, then \`POST /api/auth/login\`, and send \`x-csrf-token\` on mutating requests.
540
+ - API token mode: send \`Authorization: Bearer <PONCHO_AUTH_TOKEN>\` on API requests.
541
+
542
+ ## Headless mode (API-only)
543
+
544
+ If you're building your own frontend or using the agent purely as an API, disable the built-in Web UI:
545
+
546
+ \`\`\`javascript
547
+ // poncho.config.js
548
+ export default {
549
+ webUi: false,
550
+ }
551
+ \`\`\`
552
+
553
+ When \`webUi\` is \`false\`:
554
+ - The built-in chat UI at \`/\` is disabled (returns 404).
555
+ - All \`/api/*\` endpoints, \`/health\`, and \`/api/docs\` continue to work normally.
556
+ - Messaging adapter routes (e.g., Slack) are unaffected.
557
+
558
+ This is useful for API-only deployments where a separate frontend (e.g., a Next.js app) calls the Poncho API via a backend-for-frontend pattern.
559
+
560
+ ## TypeScript/JavaScript Client
561
+
562
+ Install the client SDK for type-safe access:
563
+
564
+ \`\`\`bash
565
+ npm install @poncho-ai/client
566
+ \`\`\`
567
+
568
+ \`\`\`typescript
569
+ import { AgentClient } from '@poncho-ai/client'
570
+
571
+ const agent = new AgentClient({
572
+ url: 'https://my-agent.vercel.app',
573
+ apiKey: 'your-api-key' // Optional, if auth enabled
574
+ })
575
+
576
+ // Create and send in one call (conversation() returns a stateful helper)
577
+ const conv = agent.conversation()
578
+ const first = await conv.send('What is 2 + 2?')
579
+ console.log(first.result.response)
580
+ const followUp = await conv.send('And what is 3 + 3?')
581
+
582
+ // Or manage conversations explicitly
583
+ const created = await agent.createConversation({ title: 'Session' })
584
+ const response = await agent.sendMessage(created.conversationId, 'What is 2 + 2?')
585
+ console.log(response.result.response)
586
+
587
+ // Send with file attachments
588
+ await agent.sendMessage(created.conversationId, 'Describe this image', {
589
+ files: [{ data: base64Data, mediaType: 'image/png', filename: 'photo.png' }],
590
+ })
591
+
592
+ // One-shot run (creates a conversation automatically)
593
+ const result = await agent.run({ task: 'Write a haiku about coding' })
594
+
595
+ // Stream events (creates a conversation automatically)
596
+ for await (const event of agent.stream({ task: 'Write a story' })) {
597
+ if (event.type === 'model:chunk') process.stdout.write(event.content)
598
+ }
599
+
600
+ // List, get, delete conversations
601
+ const conversations = await agent.listConversations()
602
+ const transcript = await agent.getConversation(created.conversationId)
603
+ await agent.deleteConversation(created.conversationId)
604
+ \`\`\`
605
+
606
+ ## Multi-turn Conversations
607
+
608
+ Conversations are persisted and keyed by \`conversationId\`.
609
+
610
+ Typical flow:
611
+
612
+ 1. \`POST /api/conversations\`
613
+ 2. \`POST /api/conversations/:conversationId/messages\`
614
+ 3. Repeat step 2 for follow-up turns
615
+ 4. \`GET /api/conversations/:conversationId\` to fetch full transcript
616
+
617
+ ## File Attachments
618
+
619
+ Agents support multimodal inputs \u2014 attach files to any message via the Web UI, API, or client SDK.
620
+
621
+ ### Web UI
622
+
623
+ Click the attach button (paperclip icon), drag files onto the chat, or paste from your clipboard. Attached files appear as previews above the composer before sending.
624
+
625
+ ### HTTP API
626
+
627
+ Send files as \`multipart/form-data\` or as base64-encoded JSON:
628
+
629
+ \`\`\`bash
630
+ # multipart/form-data
631
+ curl -N -X POST "http://localhost:3000/api/conversations/<id>/messages" \\
632
+ -F "message=Describe this image" \\
633
+ -F "files=@screenshot.png"
634
+
635
+ # JSON with base64
636
+ curl -N -X POST "http://localhost:3000/api/conversations/<id>/messages" \\
637
+ -H "Content-Type: application/json" \\
638
+ -d '{
639
+ "message": "Describe this image",
640
+ "files": [{
641
+ "data": "<base64-encoded>",
642
+ "mediaType": "image/png",
643
+ "filename": "screenshot.png"
644
+ }]
645
+ }'
646
+ \`\`\`
647
+
648
+ ### Client SDK
649
+
650
+ \`\`\`typescript
651
+ const response = await agent.sendMessage(conversationId, 'Describe this image', {
652
+ files: [{ data: base64Data, mediaType: 'image/png', filename: 'screenshot.png' }],
653
+ })
654
+ \`\`\`
655
+
656
+ ### Upload storage
657
+
658
+ By default, uploaded files are stored on the local filesystem. For production deployments, configure a cloud upload provider:
659
+
660
+ \`\`\`javascript
661
+ // poncho.config.js
662
+ export default {
663
+ uploads: {
664
+ provider: 'vercel-blob', // 'local' | 'vercel-blob' | 's3'
665
+ access: 'public', // vercel-blob access mode (default: 'public')
666
+ },
667
+ // Or S3-compatible storage:
668
+ uploads: {
669
+ provider: 's3',
670
+ bucket: 'my-agent-uploads',
671
+ region: 'us-east-1',
672
+ endpoint: 'https://s3.amazonaws.com', // optional, for S3-compatible services
673
+ },
674
+ }
675
+ \`\`\`
676
+
677
+ Environment variables for upload providers:
678
+
679
+ | Provider | Required env vars |
680
+ |----------|-------------------|
681
+ | \`vercel-blob\` | \`BLOB_READ_WRITE_TOKEN\` |
682
+ | \`s3\` | \`AWS_ACCESS_KEY_ID\`, \`AWS_SECRET_ACCESS_KEY\`, \`PONCHO_UPLOADS_BUCKET\` (or \`uploads.bucket\` in config) |
683
+ `,
684
+ "features": `# Platform Features
685
+
686
+ > Back to [README](../README.md)
687
+
688
+ ## Web UI
689
+
690
+ The built-in web UI at \`http://localhost:3000\` provides a full-featured chat interface:
691
+
692
+ - **Conversation sidebar**: create, switch between, rename, and delete conversations. Each conversation has a persistent URL (\`/c/:conversationId\`).
693
+ - **Streaming responses**: assistant text and tool activity stream in real time with structured sections (text blocks, tool call groups).
694
+ - **Context window progress**: a circular ring around the send button shows how much of the model's context window is used. The ring updates as the model responds and as tool results come in, with warning (70%) and critical (90%) color thresholds. Context usage is persisted per conversation so the ring stays accurate when switching between conversations.
695
+ - **File attachments**: attach images, PDFs, text files, and more via the attach button, drag-and-drop, or clipboard paste. Supported types include images, video, PDF, CSV, JSON, HTML, and plain text (up to 25 MB each).
696
+ - **Image lightbox**: click any image in the chat to view it full-size.
697
+ - **Tool approval**: when a tool requires approval, the UI shows an inline Approve/Deny prompt.
698
+ - **Stop streaming**: click the send button while a response is streaming to cancel the current run.
699
+ - **Installable PWA**: the web UI includes a manifest and service worker, so it can be installed as a standalone app on desktop and mobile.
700
+
701
+ Disable the built-in UI for API-only deployments by setting \`webUi: false\` in \`poncho.config.js\`.
702
+
703
+ ## Messaging Integrations
704
+
705
+ Connect your Poncho agent to messaging platforms so it responds to @mentions.
706
+
707
+ ### Slack
708
+
709
+ #### 1. Create a Slack App
710
+
711
+ 1. Go to [api.slack.com/apps](https://api.slack.com/apps) and create a new app "From scratch"
712
+ 2. Under **OAuth & Permissions**, add these Bot Token Scopes:
713
+ - \`app_mentions:read\`
714
+ - \`chat:write\`
715
+ - \`reactions:write\`
716
+ 3. Under **Event Subscriptions**, enable events:
717
+ - Set the Request URL to \`https://<your-deployed-agent>/api/messaging/slack\`
718
+ - Subscribe to the \`app_mention\` bot event
719
+ 4. Install the app to your workspace (generates a Bot Token \`xoxb-...\`)
720
+ 5. Copy the **Signing Secret** from the Basic Information page
721
+
722
+ #### 2. Configure your agent
723
+
724
+ Add environment variables to \`.env\`:
725
+
726
+ \`\`\`
727
+ SLACK_BOT_TOKEN=xoxb-...
728
+ SLACK_SIGNING_SECRET=...
729
+ \`\`\`
730
+
731
+ Add messaging to \`poncho.config.js\`:
732
+
733
+ \`\`\`javascript
734
+ export default {
735
+ // ... your existing config ...
736
+ messaging: [
737
+ { platform: 'slack' }
738
+ ]
739
+ }
740
+ \`\`\`
741
+
742
+ #### 3. Deploy
743
+
744
+ The messaging endpoint works on all deployment targets (Vercel, Docker, Fly.io, etc.). For local development with Slack, use a tunnel like [ngrok](https://ngrok.com) to expose your local server.
745
+
746
+ **Vercel deployments:** install \`@vercel/functions\` so Poncho can keep the serverless function alive while the agent processes messages:
747
+
748
+ \`\`\`bash
749
+ npm install @vercel/functions
750
+ \`\`\`
751
+
752
+ This is detected automatically at runtime -- no extra configuration needed.
753
+
754
+ #### How it works
755
+
756
+ - When a user @mentions your bot in Slack, the agent receives the message and responds in the same thread.
757
+ - Each Slack thread maps to a separate Poncho conversation with persistent history.
758
+ - The bot adds an "eyes" reaction while processing and removes it when done.
759
+ - Long responses are automatically split into multiple messages.
760
+
761
+ #### Custom environment variable names
762
+
763
+ If you need different env var names (e.g., running multiple Slack integrations):
764
+
765
+ \`\`\`javascript
766
+ messaging: [
767
+ {
768
+ platform: 'slack',
769
+ botTokenEnv: 'MY_SLACK_BOT_TOKEN',
770
+ signingSecretEnv: 'MY_SLACK_SIGNING_SECRET',
771
+ }
772
+ ]
773
+ \`\`\`
774
+
775
+ ### Telegram
776
+
777
+ #### 1. Create a Telegram Bot
778
+
779
+ 1. Open [Telegram](https://telegram.org) and start a chat with [@BotFather](https://t.me/BotFather)
780
+ 2. Send \`/newbot\` and follow the prompts to create your bot
781
+ 3. Copy the **Bot Token** (looks like \`123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11\`)
782
+
783
+ > **Privacy mode** is enabled by default, which means the bot only receives messages that @mention it in groups. This is the desired behavior -- no changes needed.
784
+
785
+ #### 2. Configure your agent
786
+
787
+ Add environment variables to \`.env\`:
788
+
789
+ \`\`\`
790
+ TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
791
+ TELEGRAM_WEBHOOK_SECRET=my-secret-token
792
+ \`\`\`
793
+
794
+ The webhook secret is optional but recommended. It can be any string up to 256 characters (\`A-Z\`, \`a-z\`, \`0-9\`, \`_\`, \`-\`).
795
+
796
+ Add messaging to \`poncho.config.js\`:
797
+
798
+ \`\`\`javascript
799
+ export default {
800
+ // ... your existing config ...
801
+ messaging: [
802
+ { platform: 'telegram' }
803
+ ]
804
+ }
805
+ \`\`\`
806
+
807
+ #### 3. Set up the webhook
808
+
809
+ After deploying your agent, register the webhook URL with Telegram:
810
+
811
+ \`\`\`bash
812
+ curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" \\
813
+ -H "Content-Type: application/json" \\
814
+ -d '{"url": "https://<your-deployed-agent>/api/messaging/telegram", "secret_token": "<YOUR_WEBHOOK_SECRET>"}'
815
+ \`\`\`
816
+
817
+ Omit the \`secret_token\` field if you're not using a webhook secret.
818
+
819
+ For local development, use a tunnel like [ngrok](https://ngrok.com) to expose your local server, then register the tunnel URL as the webhook.
820
+
821
+ #### 4. Deploy
822
+
823
+ The messaging endpoint works on all deployment targets (Vercel, Docker, Fly.io, etc.).
824
+
825
+ **Vercel deployments:** install \`@vercel/functions\` so Poncho can keep the serverless function alive while the agent processes messages:
826
+
827
+ \`\`\`bash
828
+ npm install @vercel/functions
829
+ \`\`\`
830
+
831
+ #### How it works
832
+
833
+ - **Private chats**: the bot responds to all messages.
834
+ - **Groups**: the bot only responds when @mentioned (e.g., \`@mybot what's the weather?\`). The mention is stripped before the message reaches the agent.
835
+ - **Forum topics**: each topic in a supergroup is treated as a separate conversation.
836
+ - The bot shows a "typing..." indicator while processing.
837
+ - Long responses are automatically split into multiple messages (4096 char limit).
838
+ - **Photos and documents** sent to the bot are forwarded to the agent as file attachments.
839
+ - Use \`/new\` to reset the conversation and start fresh. In groups, use \`/new@botusername\`.
840
+
841
+ #### Restricting access
842
+
843
+ By default any Telegram user can message your bot. To restrict it to specific users, add their numeric Telegram user IDs:
844
+
845
+ \`\`\`javascript
846
+ messaging: [
847
+ {
848
+ platform: 'telegram',
849
+ allowedUserIds: [1056240469, 9876543210],
850
+ }
851
+ ]
852
+ \`\`\`
853
+
854
+ Messages from anyone not on the list are silently ignored. You can find your user ID by messaging [@userinfobot](https://t.me/userinfobot) on Telegram.
855
+
856
+ #### Custom environment variable names
857
+
858
+ If you need different env var names (e.g., running multiple Telegram integrations):
859
+
860
+ \`\`\`javascript
861
+ messaging: [
862
+ {
863
+ platform: 'telegram',
864
+ botTokenEnv: 'MY_TELEGRAM_BOT_TOKEN',
865
+ webhookSecretEnv: 'MY_TELEGRAM_WEBHOOK_SECRET',
866
+ }
867
+ ]
868
+ \`\`\`
869
+
870
+ ### Email (Resend)
871
+
872
+ #### 1. Set up Resend
873
+
874
+ 1. Create an account at [resend.com](https://resend.com) and add your domain
875
+ 2. Enable **Inbound** on your domain in the Resend dashboard
876
+ 3. Create a **Webhook** subscribing to the \`email.received\` event
877
+ - Set the endpoint URL to \`https://<your-deployed-agent>/api/messaging/resend\`
878
+ 4. Copy the webhook **Signing Secret** from the webhook details page
879
+ 5. Create an API key at [resend.com/api-keys](https://resend.com/api-keys)
880
+
881
+ #### 2. Configure your agent
882
+
883
+ Add environment variables to \`.env\`:
884
+
885
+ \`\`\`
886
+ RESEND_API_KEY=re_...
887
+ RESEND_WEBHOOK_SECRET=whsec_...
888
+ RESEND_FROM=Agent <agent@yourdomain.com>
889
+ RESEND_REPLY_TO=support@yourdomain.com # optional
890
+ \`\`\`
891
+
892
+ Add messaging to \`poncho.config.js\`:
893
+
894
+ \`\`\`javascript
895
+ export default {
896
+ // ... your existing config ...
897
+ messaging: [
898
+ { platform: 'resend' }
899
+ ]
900
+ }
901
+ \`\`\`
902
+
903
+ Install the Resend SDK:
904
+
905
+ \`\`\`bash
906
+ npm install resend
907
+ \`\`\`
908
+
909
+ #### 3. Deploy
910
+
911
+ The messaging endpoint works on all deployment targets (Vercel, Docker, Fly.io, etc.). For local development, use a tunnel like [ngrok](https://ngrok.com) to expose your local server and set it as your Resend webhook URL.
912
+
913
+ **Vercel deployments:** install \`@vercel/functions\` so Poncho can keep the serverless function alive while the agent processes messages:
914
+
915
+ \`\`\`bash
916
+ npm install @vercel/functions
917
+ \`\`\`
918
+
919
+ #### How it works
920
+
921
+ - When someone emails your agent's address, the agent receives the message and replies in the same email thread.
922
+ - Each email thread maps to a separate Poncho conversation with persistent history.
923
+ - Email attachments are passed to the agent as file inputs.
924
+ - Agent responses are formatted as HTML emails with proper markdown rendering.
925
+ - Quoted reply content is automatically stripped so the agent only sees the new message.
926
+ - The incoming email's sender and subject are included in the task header (\`From:\` / \`Subject:\`) so the agent knows who sent the email.
927
+
928
+ #### Response modes
929
+
930
+ Resend email supports two response modes:
931
+
932
+ - **\`"auto-reply"\`** (default): The agent's text response is automatically sent back as an email reply. Simple and zero-config.
933
+ - **\`"tool"\`**: Auto-reply is disabled. Instead, the agent gets a \`send_email\` tool with full control over recipients, subject, body, CC/BCC, and threading. Use this for agents that need to send emails to different people, compose custom subjects, or decide whether to reply at all.
934
+
935
+ #### Options
936
+
937
+ \`\`\`javascript
938
+ messaging: [
939
+ {
940
+ platform: 'resend',
941
+ // Optional: restrict who can email the agent
942
+ allowedSenders: ['*@mycompany.com', 'partner@external.com'],
943
+ // Optional: custom env var names
944
+ apiKeyEnv: 'MY_RESEND_API_KEY',
945
+ webhookSecretEnv: 'MY_RESEND_WEBHOOK_SECRET',
946
+ fromEnv: 'MY_RESEND_FROM',
947
+ replyToEnv: 'MY_RESEND_REPLY_TO',
948
+ }
949
+ ]
950
+ \`\`\`
951
+
952
+ **Tool mode** gives the agent explicit email control:
953
+
954
+ \`\`\`javascript
955
+ messaging: [
956
+ {
957
+ platform: 'resend',
958
+ mode: 'tool',
959
+ // Optional: restrict who the agent can email (glob patterns)
960
+ allowedRecipients: ['*@mycompany.com', 'partner@external.com'],
961
+ // Optional: max emails per agent run (default: 10)
962
+ maxSendsPerRun: 5,
963
+ }
964
+ ]
965
+ \`\`\`
966
+
967
+ In tool mode the agent can call \`send_email\` with:
968
+ - \`to\` (required): recipient email addresses
969
+ - \`subject\` (required): email subject
970
+ - \`body\` (required): markdown content (auto-converted to HTML)
971
+ - \`cc\`, \`bcc\` (optional): additional recipients
972
+ - \`in_reply_to\` (optional): message ID for threading as a reply; omit for a standalone email
973
+
974
+ ### Custom Messaging Adapters
975
+
976
+ The \`MessagingAdapter\` interface from \`@poncho-ai/messaging\` is the extension point for adding other messaging platforms (SendGrid, Postmark, Discord, etc.). Implement the interface and wire it with \`AgentBridge\`:
977
+
978
+ \`\`\`typescript
979
+ import { AgentBridge, type MessagingAdapter } from '@poncho-ai/messaging';
980
+ // Shared email utilities for threading (optional, useful for email adapters)
981
+ import { parseReferences, deriveRootMessageId, buildReplyHeaders } from '@poncho-ai/messaging';
982
+ \`\`\`
983
+
984
+ See the \`SlackAdapter\` and \`ResendAdapter\` source code for reference implementations.
985
+
986
+ ## Browser Automation (Experimental)
987
+
988
+ Give your agent the ability to browse the web with a headless Chromium browser. Powered by [\`agent-browser\`](https://github.com/vercel-labs/agent-browser).
989
+
990
+ \`\`\`javascript
991
+ // poncho.config.js
992
+ export default {
993
+ browser: true,
994
+ // or with options:
995
+ browser: {
996
+ viewport: { width: 1280, height: 720 },
997
+ quality: 80,
998
+ everyNthFrame: 2,
999
+ headless: true,
1000
+ profileDir: "~/.poncho/browser-profiles",
1001
+ stealth: true, // Anti-bot-detection (default: true)
1002
+ userAgent: "custom UA", // Override the default stealth user-agent
1003
+ },
1004
+ }
1005
+ \`\`\`
1006
+
1007
+ When \`browser\` is enabled, the agent gets eight tools:
1008
+
1009
+ - \`browser_open\` \u2014 Navigate to a URL. Starts real-time viewport streaming.
1010
+ - \`browser_snapshot\` \u2014 Get the page as a compact accessibility tree with element refs (\`@e1\`, \`@e2\`, ...).
1011
+ - \`browser_click\` \u2014 Click an element by ref.
1012
+ - \`browser_type\` \u2014 Type text into a form field by ref.
1013
+ - \`browser_content\` \u2014 Get the visible text content of the current page as plain text.
1014
+ - \`browser_screenshot\` \u2014 Take a PNG screenshot (sent to the model as an image).
1015
+ - \`browser_scroll\` \u2014 Scroll the page up or down.
1016
+ - \`browser_close\` \u2014 Save session and close the browser.
1017
+
1018
+ The agent uses the snapshot/ref pattern: call \`browser_snapshot\` to get refs, then \`browser_click @e2\` or \`browser_type @e3 "hello"\`. Re-snapshot after each interaction since refs change when the page updates.
1019
+
1020
+ ### Live viewport
1021
+
1022
+ The web UI shows a real-time browser viewport panel alongside the chat when a browser session is active. Frames stream via CDP screencast through the \`/api/browser/stream\` SSE endpoint.
1023
+
1024
+ ### Session persistence
1025
+
1026
+ Browser sessions are stored in profile directories (\`~/.poncho/browser-profiles/<agent-id>/\`). Cookies, localStorage, and other browser state persist across runs, so the agent can pick up where it left off (e.g., staying logged in across cron job runs).
1027
+
1028
+ ### Stealth mode
1029
+
1030
+ Stealth mode is **enabled by default** (\`stealth: true\`) and reduces bot-detection fingerprints so websites treat the browser like a regular user session. It applies:
1031
+
1032
+ - A realistic Chrome user-agent string (matching the host OS)
1033
+ - \`--disable-blink-features=AutomationControlled\` flag
1034
+ - \`navigator.webdriver\` overridden to \`false\`
1035
+ - \`window.chrome\` shim for headless Chromium
1036
+ - Fake \`navigator.plugins\` (3 standard Chrome plugins)
1037
+ - \`navigator.languages\` fallback (\`['en-US', 'en']\`)
1038
+ - WebGL vendor/renderer patched to hide SwiftShader
1039
+ - \`Notification.permission\` patched for headless mode
1040
+ - Browser-level \`--user-agent\` flag (covers Web Workers)
1041
+
1042
+ To disable stealth mode (e.g., for trusted internal sites), set \`stealth: false\`. To use a custom user-agent while keeping other stealth patches, set \`userAgent\`:
1043
+
1044
+ \`\`\`javascript
1045
+ browser: {
1046
+ stealth: true, // default
1047
+ userAgent: "MyBot/1.0", // overrides the auto-detected Chrome UA
1048
+ }
1049
+ \`\`\`
1050
+
1051
+ ### Setup
1052
+
1053
+ Install the browser package in your agent project:
1054
+
1055
+ \`\`\`bash
1056
+ pnpm add @poncho-ai/browser
1057
+ \`\`\`
1058
+
1059
+ Then set \`browser: true\` in \`poncho.config.js\`. Chromium is downloaded automatically via a \`postinstall\` hook (skipped when \`CI\` or \`SERVERLESS\` env vars are set).
1060
+
1061
+ **Serverless deployments** (Lambda, Vercel, etc.): use \`@sparticuz/chromium\` instead of the bundled Chromium:
1062
+
1063
+ \`\`\`bash
1064
+ pnpm add @sparticuz/chromium
1065
+ \`\`\`
1066
+
1067
+ \`\`\`javascript
1068
+ // poncho.config.js
1069
+ import chromium from "@sparticuz/chromium";
1070
+
1071
+ export default {
1072
+ browser: {
1073
+ executablePath: await chromium.executablePath(),
1074
+ headless: true,
1075
+ },
1076
+ };
1077
+ \`\`\`
1078
+
1079
+ ## Subagents
1080
+
1081
+ Poncho agents can spawn recursive copies of themselves as **subagents**. Each subagent runs in its own independent conversation with full access to the agent's tools and skills. The parent agent controls the subagent lifecycle and receives results directly.
1082
+
1083
+ Subagents are useful when an agent needs to parallelize work, delegate a subtask, or isolate a line of investigation without polluting the main conversation context.
1084
+
1085
+ ### How it works
1086
+
1087
+ When the agent decides to use a subagent, it calls \`spawn_subagent\` with a task description. The subagent runs to completion and the result is returned to the parent \u2014 the call is **blocking**, so the parent waits for the subagent to finish before continuing.
1088
+
1089
+ The parent can also send follow-up messages to existing subagents with \`message_subagent\`, stop a running subagent with \`stop_subagent\`, or list all its subagents with \`list_subagents\`.
1090
+
1091
+ ### Available tools
1092
+
1093
+ | Tool | Description |
1094
+ |------|-------------|
1095
+ | \`spawn_subagent\` | Create a new subagent with a task. Blocks until the subagent completes and returns the result. |
1096
+ | \`message_subagent\` | Send a follow-up message to an existing subagent. Blocks until it responds. |
1097
+ | \`stop_subagent\` | Stop a running subagent. |
1098
+ | \`list_subagents\` | List all subagents for the current conversation with their IDs, tasks, and statuses. |
1099
+
1100
+ ### Limits
1101
+
1102
+ - **Max depth**: 3 levels of nesting (an agent can spawn a subagent, which can spawn another, but no deeper).
1103
+ - **Max concurrent**: 5 subagents per parent conversation.
1104
+
1105
+ ### Memory isolation
1106
+
1107
+ Subagents have **read-only** access to the parent agent's persistent memory. They can recall information but cannot modify the main memory document. This prevents subagents from accidentally overwriting each other's memory updates.
1108
+
1109
+ ### Approvals
1110
+
1111
+ When a subagent invokes a tool that requires approval, the approval request is **tunneled to the parent conversation**. You'll see the approval prompt inline in the parent's message thread with a label indicating which subagent is asking. The parent conversation also shows an orange dot in the sidebar while any subagent is waiting for approval.
1112
+
1113
+ ### Web UI
1114
+
1115
+ In the web UI, subagent conversations appear **nested under their parent** in the sidebar (tree-style indentation). Clicking a subagent conversation shows it in read-only mode \u2014 you can view the full context but cannot send messages, since the parent agent controls the subagent.
1116
+
1117
+ When the parent conversation is active, \`spawn_subagent\` tool calls in the tool activity timeline are clickable links that navigate to the subagent's conversation.
1118
+
1119
+ ## Persistent Memory
1120
+
1121
+ When \`memory.enabled\` is true in \`poncho.config.js\`, the harness enables a simple memory model:
1122
+
1123
+ - A single persistent main memory document is loaded at run start and interpolated into the system prompt under \`## Persistent Memory\`.
1124
+ - \`memory_main_update\` can replace or append to that document. The tool description instructs the model to proactively evaluate each turn whether durable memory should be updated.
1125
+ - \`conversation_recall\` can search recent prior conversations (keyword scoring) when historical context is relevant (\`as we discussed\`, \`last time\`, etc.).
1126
+
1127
+ \`\`\`javascript
1128
+ // poncho.config.js
1129
+ export default {
1130
+ storage: {
1131
+ provider: 'local', // 'local' | 'memory' | 'redis' | 'upstash' | 'dynamodb'
1132
+ memory: {
1133
+ enabled: true,
1134
+ maxRecallConversations: 20, // Bounds conversation_recall scan size
1135
+ },
1136
+ },
1137
+ }
1138
+ \`\`\`
1139
+
1140
+ Available memory tools:
1141
+
1142
+ - \`memory_main_get\`
1143
+ - \`memory_main_update\`
1144
+ - \`conversation_recall\`
1145
+ `,
1146
+ "configuration": `# Configuration & Security
1147
+
1148
+ > Back to [README](../README.md)
1149
+
1150
+ ## Credential Pattern
1151
+
1152
+ All credentials in \`poncho.config.js\` use **env var name** fields (\`*Env\` suffix). The config specifies *which* environment variable to read \u2014 never the secret value itself. Every \`*Env\` field has a sensible default, so you only need to set the field when your env var name differs from the convention:
1153
+
1154
+ | Config field | Default env var | Purpose |
1155
+ |---|---|---|
1156
+ | \`providers.anthropic.apiKeyEnv\` | \`ANTHROPIC_API_KEY\` | Anthropic model API key |
1157
+ | \`providers.openai.apiKeyEnv\` | \`OPENAI_API_KEY\` | OpenAI model API key |
1158
+ | \`auth.tokenEnv\` | \`PONCHO_AUTH_TOKEN\` | Auth passphrase / bearer token |
1159
+ | \`storage.urlEnv\` | \`UPSTASH_REDIS_REST_URL\` / \`REDIS_URL\` | Storage connection URL |
1160
+ | \`storage.tokenEnv\` | \`UPSTASH_REDIS_REST_TOKEN\` | Upstash REST token |
1161
+ | \`telemetry.latitude.apiKeyEnv\` | \`LATITUDE_API_KEY\` | Latitude API key |
1162
+ | \`telemetry.latitude.projectIdEnv\` | \`LATITUDE_PROJECT_ID\` | Latitude project ID |
1163
+ | \`messaging[].botTokenEnv\` | \`SLACK_BOT_TOKEN\` | Slack bot token |
1164
+ | \`messaging[].signingSecretEnv\` | \`SLACK_SIGNING_SECRET\` | Slack signing secret |
1165
+ | \`messaging[].botTokenEnv\` | \`TELEGRAM_BOT_TOKEN\` | Telegram bot token |
1166
+ | \`messaging[].webhookSecretEnv\` | \`TELEGRAM_WEBHOOK_SECRET\` | Telegram webhook secret (optional) |
1167
+ | \`messaging[].apiKeyEnv\` | \`RESEND_API_KEY\` | Resend API key |
1168
+ | \`messaging[].webhookSecretEnv\` | \`RESEND_WEBHOOK_SECRET\` | Resend webhook signing secret |
1169
+ | \`messaging[].fromEnv\` | \`RESEND_FROM\` | Resend sender address |
1170
+ | \`messaging[].replyToEnv\` | \`RESEND_REPLY_TO\` | Resend reply-to address (optional) |
1171
+ | \`mcp[].auth.tokenEnv\` | *(user-defined)* | MCP server bearer token |
1172
+
1173
+ ## Config File Reference (\`poncho.config.js\`)
1174
+
1175
+ \`\`\`javascript
1176
+ export default {
1177
+ // Custom harness (default: @poncho-ai/harness)
1178
+ harness: '@poncho-ai/harness', // or './my-harness.js'
1179
+
1180
+ // MCP servers (remote)
1181
+ mcp: [
1182
+ // Remote: Connect to external server
1183
+ {
1184
+ name: 'github',
1185
+ url: 'https://mcp.example.com/github',
1186
+ auth: { type: 'bearer', tokenEnv: 'GITHUB_TOKEN' },
1187
+ }
1188
+ ],
1189
+
1190
+ // Extra directories to scan for skills (skills/ is always scanned)
1191
+ skillPaths: ['.cursor/skills'],
1192
+
1193
+ // Tool access: true (available), false (disabled), 'approval' (requires human approval)
1194
+ // Any tool name works \u2014 harness tools, adapter tools (send_email), MCP tools, etc.
1195
+ tools: {
1196
+ list_directory: true, // available (default)
1197
+ read_file: true, // available (default)
1198
+ write_file: true, // gated by environment for writes
1199
+ delete_file: 'approval', // requires human approval
1200
+ delete_directory: 'approval', // requires human approval
1201
+ send_email: 'approval', // requires human approval
1202
+ byEnvironment: {
1203
+ production: {
1204
+ write_file: false, // disable writes in production
1205
+ delete_file: false, // disable deletes in production
1206
+ delete_directory: false, // disable deletes in production
1207
+ send_email: 'approval', // keep approval in production
1208
+ },
1209
+ development: {
1210
+ send_email: true, // skip approval in dev
1211
+ },
1212
+ },
1213
+ },
1214
+
1215
+ // Authentication (protects both Web UI and API)
1216
+ auth: {
1217
+ required: true,
1218
+ type: 'bearer', // 'bearer' | 'header' | 'custom'
1219
+ // tokenEnv: 'PONCHO_AUTH_TOKEN', // env var name (default)
1220
+ },
1221
+ // When auth.required is true:
1222
+ // - Web UI: users enter the passphrase (value of PONCHO_AUTH_TOKEN env var)
1223
+ // - API: clients include Authorization: Bearer <token> header
1224
+
1225
+ // Model provider API key env var overrides (optional)
1226
+ providers: {
1227
+ // anthropic: { apiKeyEnv: 'ANTHROPIC_API_KEY' }, // default
1228
+ // openai: { apiKeyEnv: 'OPENAI_API_KEY' }, // default
1229
+ },
1230
+
1231
+ // Unified storage (preferred). Replaces separate \`state\` and \`memory\` blocks.
1232
+ // Credentials are read from env vars; override the var name with *Env fields.
1233
+ storage: {
1234
+ provider: 'upstash', // 'local' | 'memory' | 'redis' | 'upstash' | 'dynamodb'
1235
+ // urlEnv: 'UPSTASH_REDIS_REST_URL', // default (falls back to KV_REST_API_URL)
1236
+ // tokenEnv: 'UPSTASH_REDIS_REST_TOKEN', // default (falls back to KV_REST_API_TOKEN)
1237
+ ttl: {
1238
+ conversations: 3600, // seconds
1239
+ memory: 0, // 0/undefined means no expiration
1240
+ },
1241
+ memory: {
1242
+ enabled: true,
1243
+ maxRecallConversations: 20, // Bounds conversation_recall scan size
1244
+ },
1245
+ },
1246
+
1247
+ // Telemetry destination
1248
+ telemetry: {
1249
+ enabled: true,
1250
+ otlp: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
1251
+ // Or use Latitude (reads from LATITUDE_API_KEY and LATITUDE_PROJECT_ID env vars by default)
1252
+ latitude: {
1253
+ // apiKeyEnv: 'LATITUDE_API_KEY', // default
1254
+ // projectIdEnv: 'LATITUDE_PROJECT_ID', // default
1255
+ path: 'your/prompt-path', // optional, defaults to agent name
1256
+ },
1257
+ },
1258
+
1259
+ // Messaging platform integrations
1260
+ messaging: [
1261
+ { platform: 'slack' }, // Uses SLACK_BOT_TOKEN + SLACK_SIGNING_SECRET
1262
+ // { platform: 'slack', botTokenEnv: 'MY_BOT_TOKEN' }, // Custom env var names
1263
+ { platform: 'telegram' }, // Uses TELEGRAM_BOT_TOKEN (+ optional TELEGRAM_WEBHOOK_SECRET)
1264
+ // { platform: 'telegram', botTokenEnv: 'MY_TG_TOKEN' }, // Custom env var names
1265
+ { platform: 'resend' }, // Uses RESEND_API_KEY + RESEND_WEBHOOK_SECRET + RESEND_FROM
1266
+ // { platform: 'resend', mode: 'tool', replyToEnv: 'RESEND_REPLY_TO' }, // Tool mode with custom reply-to
1267
+ ],
1268
+
1269
+ // File upload storage (default: local filesystem)
1270
+ uploads: {
1271
+ provider: 'local', // 'local' | 'vercel-blob' | 's3'
1272
+ // access: 'public', // vercel-blob access mode
1273
+ // bucket: 'my-uploads', // S3 bucket name
1274
+ // region: 'us-east-1', // S3 region
1275
+ // endpoint: '...', // S3-compatible endpoint
1276
+ },
1277
+
1278
+ // Browser automation (requires @poncho-ai/browser)
1279
+ // browser: true,
1280
+ // browser: {
1281
+ // viewport: { width: 1280, height: 720 },
1282
+ // quality: 80,
1283
+ // everyNthFrame: 2,
1284
+ // headless: true,
1285
+ // profileDir: '~/.poncho/browser-profiles',
1286
+ // executablePath: '/path/to/chromium',
1287
+ // stealth: true, // Anti-bot-detection (default: true)
1288
+ // userAgent: 'custom UA', // Override the default stealth user-agent
1289
+ // },
1290
+
1291
+ // Headless mode: disable the built-in Web UI (API-only)
1292
+ // webUi: false,
1293
+
1294
+ }
1295
+ \`\`\`
1296
+
1297
+ \`provider: 'local'\` stores runtime state under \`~/.poncho/store\` (or \`/tmp/.poncho/store\` on serverless runtimes), scoped by both agent name and stable agent id:
1298
+
1299
+ \`\`\`text
1300
+ ~/.poncho/store
1301
+ \u2514\u2500\u2500 my-agent--agent_01f4f5d7e9c7432da51f8c6b9e2b1a0c
1302
+ \u251C\u2500\u2500 memory.json
1303
+ \u251C\u2500\u2500 state.json
1304
+ \u251C\u2500\u2500 onboarding-state.json
1305
+ \u2514\u2500\u2500 conversations
1306
+ \u251C\u2500\u2500 index.json
1307
+ \u251C\u2500\u2500 20260217T154233Z--conv_01j9x8a12bcd.json
1308
+ \u2514\u2500\u2500 20260218T101004Z--conv_01j9x8b45efg.json
1309
+ \`\`\`
1310
+
1311
+ Remote storage keys are namespaced and versioned, for example \`poncho:v1:<agentId>:...\`.
1312
+
1313
+ ## Environment Variables
1314
+
1315
+ | Variable | Required | Description |
1316
+ |----------|----------|-------------|
1317
+ | \`ANTHROPIC_API_KEY\` | Yes* | Claude API key |
1318
+ | \`OPENAI_API_KEY\` | No | OpenAI API key (if using OpenAI) |
1319
+ | \`PONCHO_AUTH_TOKEN\` | No | Unified auth token (Web UI passphrase + API Bearer token) |
1320
+ | \`OTEL_EXPORTER_OTLP_ENDPOINT\` | No | Telemetry destination |
1321
+ | \`LATITUDE_API_KEY\` | No | Latitude dashboard integration |
1322
+ | \`LATITUDE_PROJECT_ID\` | No | Latitude project identifier for capture traces |
1323
+ | \`LATITUDE_PATH\` | No | Latitude prompt path for grouping traces |
1324
+ | \`KV_REST_API_URL\` | No | Upstash REST URL (Vercel Marketplace naming) |
1325
+ | \`KV_REST_API_TOKEN\` | No | Upstash REST write token (Vercel Marketplace naming) |
1326
+ | \`UPSTASH_REDIS_REST_URL\` | No | Upstash REST URL (direct Upstash naming) |
1327
+ | \`UPSTASH_REDIS_REST_TOKEN\` | No | Upstash REST write token (direct Upstash naming) |
1328
+ | \`REDIS_URL\` | No | For Redis state storage |
1329
+ | \`SLACK_BOT_TOKEN\` | No | Slack Bot Token (for messaging integration) |
1330
+ | \`SLACK_SIGNING_SECRET\` | No | Slack Signing Secret (for messaging integration) |
1331
+ | \`TELEGRAM_BOT_TOKEN\` | No | Telegram Bot Token (from @BotFather) |
1332
+ | \`TELEGRAM_WEBHOOK_SECRET\` | No | Telegram webhook secret token (optional) |
1333
+ | \`RESEND_API_KEY\` | No | Resend API key (for email messaging) |
1334
+ | \`RESEND_WEBHOOK_SECRET\` | No | Resend webhook signing secret |
1335
+ | \`RESEND_FROM\` | No | Sender address for email replies |
1336
+ | \`RESEND_REPLY_TO\` | No | Reply-to address for outgoing emails (optional) |
1337
+ | \`BLOB_READ_WRITE_TOKEN\` | No | Vercel Blob token (for \`uploads.provider: 'vercel-blob'\`) |
1338
+ | \`PONCHO_UPLOADS_BUCKET\` | No | S3 bucket name (for \`uploads.provider: 's3'\`) |
1339
+
1340
+ *Required if using Anthropic models (default).
1341
+
1342
+ ## Observability
1343
+
1344
+ ### Local development
1345
+
1346
+ Logs print to console:
1347
+
1348
+ \`\`\`
1349
+ [event] run:started {"type":"run:started","runId":"run_abc123","agentId":"my-agent"}
1350
+ [event] tool:started {"type":"tool:started","tool":"read_file","input":{"path":"README.md"}}
1351
+ [event] tool:completed {"type":"tool:completed","tool":"read_file","duration":45,"output":{"path":"README.md","content":"..."}}
1352
+ [event] run:completed {"type":"run:completed","runId":"run_abc123","result":{"status":"completed","response":"...","steps":3,"tokens":{"input":1500,"output":840}}}
1353
+ \`\`\`
1354
+
1355
+ ### Production telemetry
1356
+
1357
+ Send events to your observability stack:
1358
+
1359
+ \`\`\`bash
1360
+ # Environment variable
1361
+ OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com
1362
+ \`\`\`
1363
+
1364
+ Or configure in code:
1365
+
1366
+ \`\`\`javascript
1367
+ // poncho.config.js
1368
+ export default {
1369
+ telemetry: {
1370
+ otlp: 'https://otel.example.com',
1371
+ // Or custom handler
1372
+ handler: async (event) => {
1373
+ await sendToMyLoggingService(event)
1374
+ }
1375
+ }
1376
+ }
1377
+ \`\`\`
1378
+
1379
+ ### Latitude integration (optional)
1380
+
1381
+ Send traces to [Latitude](https://latitude.so) for a dashboard with cost tracking and prompt management:
1382
+
1383
+ \`\`\`bash
1384
+ LATITUDE_API_KEY=lat_xxx
1385
+ LATITUDE_PROJECT_ID=123
1386
+ LATITUDE_PATH=agents/my-agent/run
1387
+ \`\`\`
1388
+
1389
+ Or configure via \`poncho.config.js\`:
1390
+
1391
+ \`\`\`javascript
1392
+ telemetry: {
1393
+ latitude: {
1394
+ // apiKeyEnv: 'LATITUDE_API_KEY', // default
1395
+ // projectIdEnv: 'LATITUDE_PROJECT_ID', // default
1396
+ path: 'your/prompt-path',
1397
+ },
1398
+ }
1399
+ \`\`\`
1400
+
1401
+ ## Security
1402
+
1403
+ ### Protect your endpoint
1404
+
1405
+ Enable authentication to secure both the Web UI and API:
1406
+
1407
+ \`\`\`javascript
1408
+ // poncho.config.js
1409
+ export default {
1410
+ auth: {
1411
+ required: true,
1412
+ type: 'bearer' // Default: validates against PONCHO_AUTH_TOKEN
1413
+ }
1414
+ }
1415
+ \`\`\`
1416
+
1417
+ \`\`\`bash
1418
+ # .env
1419
+ PONCHO_AUTH_TOKEN=your-secret-token-here
1420
+ \`\`\`
1421
+
1422
+ With \`auth.required: true\`:
1423
+ - **Web UI**: Users must enter \`PONCHO_AUTH_TOKEN\` as the passphrase to login
1424
+ - **API**: Clients must include \`Authorization: Bearer <PONCHO_AUTH_TOKEN>\` header
1425
+
1426
+ For custom validation:
1427
+
1428
+ \`\`\`javascript
1429
+ // poncho.config.js
1430
+ export default {
1431
+ auth: {
1432
+ required: true,
1433
+ type: 'custom',
1434
+ validate: async (token) => {
1435
+ // Custom logic: check database, verify JWT, etc.
1436
+ return token === process.env.PONCHO_AUTH_TOKEN
1437
+ }
1438
+ }
1439
+ }
1440
+ \`\`\`
1441
+
1442
+ ### Require approval for dangerous tools
1443
+
1444
+ Use \`approval-required\` in your \`AGENT.md\` or \`SKILL.md\` frontmatter to gate specific tools:
1445
+
1446
+ \`\`\`yaml
1447
+ ---
1448
+ allowed-tools:
1449
+ - mcp:linear/*
1450
+ approval-required:
1451
+ - mcp:linear/list_initiatives
1452
+ ---
1453
+ \`\`\`
1454
+
1455
+ When a gated tool is called, the harness emits a \`tool:approval:required\` event and pauses until approval is granted or denied. In the web UI and interactive CLI, the user is prompted before execution continues.
1456
+ `,
1457
+ "troubleshooting": `# Error Handling & Troubleshooting
1458
+
1459
+ > Back to [README](../README.md)
1460
+
1461
+ ## Error Types
1462
+
1463
+ These error codes appear in \`run:error\` SSE events:
1464
+
1465
+ | Code | Description |
1466
+ |------|-------------|
1467
+ | \`MAX_STEPS_EXCEEDED\` | Agent hit the step limit without completing |
1468
+ | \`TIMEOUT\` | Agent exceeded the timeout |
1469
+ | \`MODEL_TIMEOUT\` | Model API call timed out |
1470
+ | \`MODEL_ERROR\` | Model API returned an error |
1471
+ | \`CONTENT_FILTER\` | Response blocked by the provider's content filter |
1472
+ | \`AUTH_ERROR\` | Authentication failed |
1473
+
1474
+ ## Tool Errors Are Recoverable
1475
+
1476
+ When a tool fails, the error is sent back to the model via a \`tool:error\` event. The model can retry with different parameters, try a different tool, or ask the user for help:
1477
+
1478
+ \`\`\`
1479
+ event: tool:error
1480
+ data: {"tool": "fetch-url", "error": "Connection timeout", "recoverable": true}
1481
+
1482
+ event: model:chunk
1483
+ data: {"content": "I couldn't fetch that URL. Let me try a different approach..."}
1484
+ \`\`\`
1485
+
1486
+ ## Fatal Errors End the Run
1487
+
1488
+ Timeout, max steps, or model API errors end the run immediately:
1489
+
1490
+ \`\`\`
1491
+ event: run:error
1492
+ data: {"runId": "run_abc", "error": {"code": "TIMEOUT", "message": "Run exceeded 60 second timeout"}}
1493
+ \`\`\`
1494
+
1495
+ ## Handle Errors in Your Client
1496
+
1497
+ \`\`\`typescript
1498
+ const agent = new AgentClient({ url: 'https://my-agent.vercel.app' })
1499
+
1500
+ try {
1501
+ const result = await agent.run({ task: 'Do something' })
1502
+ } catch (error) {
1503
+ console.error('Agent run failed:', error.message)
1504
+ }
1505
+ \`\`\`
1506
+
1507
+ ## Troubleshooting
1508
+
1509
+ ### "ANTHROPIC_API_KEY not set"
1510
+
1511
+ Make sure you have a \`.env\` file with your API key:
1512
+
1513
+ \`\`\`bash
1514
+ echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
1515
+ \`\`\`
1516
+
1517
+ ### "MCP server failed to connect"
1518
+
1519
+ Check that:
1520
+ 1. A remote MCP server is configured (\`poncho mcp list\`)
1521
+ 2. The MCP URL is correct and reachable (\`http://\` or \`https://\`)
1522
+ 3. Required environment variables/secrets are set
1523
+ 4. Any required auth headers/tokens expected by the remote server are configured
1524
+
1525
+ ### Agent keeps running forever
1526
+
1527
+ Set execution limits:
1528
+
1529
+ \`\`\`yaml
1530
+ ---
1531
+ limits:
1532
+ maxSteps: 20
1533
+ timeout: 60 # 1 minute (in seconds)
1534
+ ---
1535
+ \`\`\`
1536
+
1537
+ ### Vercel deploy issues
1538
+
1539
+ - After upgrading \`@poncho-ai/cli\`, re-run \`poncho build vercel --force\` to refresh generated deploy files.
1540
+ - If Vercel fails during \`pnpm install\` due to a lockfile mismatch, run \`pnpm install --no-frozen-lockfile\` locally and commit \`pnpm-lock.yaml\`.
1541
+ - Deploy from the project root: \`vercel deploy --prod\`.
1542
+ `
1543
+ };
1544
+
1545
+ // src/default-tools.ts
409
1546
  var resolveSafePath = (workingDir, inputPath) => {
410
1547
  const base = resolve4(workingDir);
411
1548
  const target = resolve4(base, inputPath);
@@ -534,6 +1671,31 @@ var createDeleteDirectoryTool = (workingDir) => defineTool({
534
1671
  return { path, deleted: true };
535
1672
  }
536
1673
  });
1674
+ var PONCHO_DOCS_TOPICS = Object.keys(PONCHO_DOCS);
1675
+ var ponchoDocsTool = defineTool({
1676
+ name: "poncho_docs",
1677
+ description: `Read detailed Poncho framework documentation by topic. Available topics: ${PONCHO_DOCS_TOPICS.join(", ")}.`,
1678
+ inputSchema: {
1679
+ type: "object",
1680
+ properties: {
1681
+ topic: {
1682
+ type: "string",
1683
+ enum: PONCHO_DOCS_TOPICS,
1684
+ description: "Documentation topic to read"
1685
+ }
1686
+ },
1687
+ required: ["topic"],
1688
+ additionalProperties: false
1689
+ },
1690
+ handler: async (input) => {
1691
+ const topic = typeof input.topic === "string" ? input.topic : "";
1692
+ const content = PONCHO_DOCS[topic];
1693
+ if (!content) {
1694
+ return { error: `Unknown topic "${topic}". Available: ${PONCHO_DOCS_TOPICS.join(", ")}` };
1695
+ }
1696
+ return { topic, content };
1697
+ }
1698
+ });
537
1699
 
538
1700
  // src/harness.ts
539
1701
  import { randomUUID as randomUUID3 } from "crypto";
@@ -3141,7 +4303,15 @@ Since all fields have defaults, you only need to specify \`*Env\` when your env
3141
4303
  - If shell/CLI access is unavailable, ask the user to run needed commands and provide exact copy-paste commands.
3142
4304
  - For setup, skills, MCP, auth, storage, telemetry, or "how do I..." questions, proactively read \`README.md\` with \`read_file\` before answering.
3143
4305
  - Prefer quoting concrete commands and examples from \`README.md\` over guessing.
3144
- - Keep edits minimal, preserve unrelated settings/code, and summarize what changed.`;
4306
+ - Keep edits minimal, preserve unrelated settings/code, and summarize what changed.
4307
+
4308
+ ## Detailed Documentation
4309
+
4310
+ For topics not covered above, use the \`poncho_docs\` tool to load full documentation on demand:
4311
+ - \`api\` \u2014 HTTP API endpoints, SSE events, TypeScript client SDK, file attachments, upload providers
4312
+ - \`features\` \u2014 Web UI details, browser automation, subagents, persistent memory, custom messaging adapters
4313
+ - \`configuration\` \u2014 Full config reference, env vars, auth types, storage, telemetry, tool approval
4314
+ - \`troubleshooting\` \u2014 Error codes, recoverable vs fatal errors, common issues and fixes`;
3145
4315
  function extractMediaFromToolOutput(output) {
3146
4316
  const mediaItems = [];
3147
4317
  function walk(node) {
@@ -3251,6 +4421,9 @@ var AgentHarness = class {
3251
4421
  if (this.isToolEnabled("delete_directory")) {
3252
4422
  this.registerIfMissing(createDeleteDirectoryTool(this.workingDir));
3253
4423
  }
4424
+ if (this.environment === "development" && this.isToolEnabled("poncho_docs")) {
4425
+ this.registerIfMissing(ponchoDocsTool);
4426
+ }
3254
4427
  }
3255
4428
  shouldEnableWriteTool() {
3256
4429
  const override = process.env.PONCHO_FS_WRITE?.toLowerCase();
@@ -5867,6 +7040,7 @@ export {
5867
7040
  normalizeScriptPolicyPath,
5868
7041
  parseAgentFile,
5869
7042
  parseAgentMarkdown,
7043
+ ponchoDocsTool,
5870
7044
  readSkillResource,
5871
7045
  renderAgentPrompt,
5872
7046
  resolveAgentIdentity,