feat(chat-workflow): forward Privy JWT as RECOUP_ACCESS_TOKEN (cutover Bundle A.4)
Summary
Fifth and final open-agents → api cutover bundle. The chat UI sends a short-lived Privy JWT in the workflow request body as recoupAccessToken. Today api silently strips it via Zod's default .strip() mode and never plumbs it into the sandbox env, so the recoup-api skill's curl examples can't authenticate.
Production reproducer
Both backends, same prompt: "Use the recoup-api skill to call GET /api/accounts/id and tell me the account_id from the response."
| Today (before this fix) | |
|---|---|
| api prod | recoup-api skill loads ✓. Curl runs with empty $RECOUP_ACCESS_TOKEN → 401. Agent reports "RECOUP_ACCESS_TOKEN is not set — you'll need to sign in." |
| open-agents prod | Skill loads ✓. Curl returns HTTP 200 with the user's account_id (fb678396-…). |
Plumbing (TDD red → green at each layer)
| File | Change |
|---|---|
lib/chat/validateChatWorkflow.ts | Accept recoupAccessToken: z.string().min(1).max(8192).optional() in the body schema (open-agents-shape) |
lib/agent/tools/AgentContext.ts | Add recoupAccessToken?: string field — mirrors open-agents/packages/agent/types.ts:29 |
lib/chat/handleChatWorkflowStream.ts | Conditionally spread recoupAccessToken into agentContext when validator surfaced it |
lib/agent/tools/buildRecoupExecEnv.ts | Inject RECOUP_ACCESS_TOKEN into the sandbox exec env when set on context |
Security
Only forward when the caller sent the field in the body (chat UI path). x-api-key callers don't set it, so their long-lived recoup_sk_… key is never exfiltratable from the sandbox env. Existing risk comment preserved.
Cutover roadmap
| # | Bundle | Status |
|---|---|---|
| 1 | C per-message cost/usage | ✅ merged |
| 2 | B subagent live progress + transcript | ✅ merged |
| 3 | A.7 real cwd in system prompt | ✅ merged |
| 4 | A.6 Anthropic prompt cache control | ✅ merged |
| 5 | A.4 forward Privy JWT as recoupAccessToken | ✅ this PR (last one) |
After this lands, sandbox.recoupable.com (open-agents UI) can switch from sandbox.recoupable.com/api/chat → recoup-api.vercel.app/api/chat/workflow without UI changes.
Test plan
- 5 new unit tests (3 buildRecoupExecEnv + 1 validator + 1 handler); 1 handler omit-when-undefined regression
- Full suite 3144/3144 pass; lint clean
- E2E on preview: re-run the
recoup-apiskill prompt withrecoupAccessTokenin body → expect HTTP 200 + account_id from /api/accounts/id
🤖 Generated with Claude Code
Summary by cubic
Forwards the user's Privy JWT from chat workflow requests into the sandbox as RECOUP_ACCESS_TOKEN so the recoup-api skill can authenticate. Fixes 401s on GET /api/accounts/id and completes cutover Bundle A.4.
- New Features
- Accepts optional
recoupAccessTokenin the body and addsrecoupAccessToken?toAgentContext. handleChatWorkflowStreamforwards it when present;buildRecoupExecEnvinjectsRECOUP_ACCESS_TOKENfor skills likerecoup-api.- Only forwarded for bearer-auth UI requests; never for x-api-key calls (long-lived
recoup_sk_*keys are not exposed).
- Accepts optional
Summary by CodeRabbit
- New Features
- Added support for short-lived token authentication in API integrations. The system now uses temporary bearer tokens for API calls instead of permanent long-lived credentials. This reduces security risk and improves protection against unauthorized access. Updated validation and context handling throughout the application stack to fully support the new authentication method.