eve 0.44.2 → 0.44.4

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/src/channel/adapter.d.ts +2 -2
  3. package/dist/src/chunks/{use-eve-agent-DpUEwS23.js → use-eve-agent-BJwaMe5k.js} +6 -2
  4. package/dist/src/chunks/{use-eve-agent-BsLJiIbc.js → use-eve-agent-BtsOXv3s.js} +6 -2
  5. package/dist/src/cli/dev/tui/prompt-command-handler.js +1 -1
  6. package/dist/src/cli/dev/tui/prompt-commands.d.ts +1 -1
  7. package/dist/src/cli/dev/tui/prompt-commands.js +1 -1
  8. package/dist/src/cli/dev/tui/setup-commands.d.ts +2 -0
  9. package/dist/src/cli/dev/tui/setup-commands.js +1 -1
  10. package/dist/src/client/eve-agent-store.js +1 -1
  11. package/dist/src/compiler/extension-compatibility.d.ts +2 -2
  12. package/dist/src/compiler/extension-compatibility.js +1 -1
  13. package/dist/src/execution/sandbox/bindings/just-bash-runtime.d.ts +1 -0
  14. package/dist/src/execution/sandbox/bindings/just-bash-runtime.js +1 -1
  15. package/dist/src/execution/sandbox/bindings/just-bash.js +1 -1
  16. package/dist/src/execution/web-fetch/request.js +1 -1
  17. package/dist/src/execution/web-fetch/tool.js +1 -1
  18. package/dist/src/execution/workflow-runtime.js +1 -1
  19. package/dist/src/harness/attachment-staging.js +1 -1
  20. package/dist/src/internal/application/package.js +1 -1
  21. package/dist/src/public/channels/chat-sdk/authorization.d.ts +3 -0
  22. package/dist/src/public/channels/chat-sdk/authorization.js +3 -0
  23. package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +2 -0
  24. package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
  25. package/dist/src/public/channels/eve.js +3 -3
  26. package/dist/src/public/channels/linq/index.d.ts +1 -1
  27. package/dist/src/public/channels/linq/index.js +1 -1
  28. package/dist/src/public/channels/linq/linqChannel.d.ts +3 -1
  29. package/dist/src/public/channels/linq/linqChannel.js +1 -1
  30. package/dist/src/public/channels/photon/index.d.ts +1 -1
  31. package/dist/src/public/channels/photon/index.js +1 -1
  32. package/dist/src/public/channels/photon/photonIMessageChannel.d.ts +3 -1
  33. package/dist/src/public/channels/photon/photonIMessageChannel.js +1 -1
  34. package/dist/src/public/channels/slack/api.d.ts +15 -5
  35. package/dist/src/public/channels/slack/api.js +1 -1
  36. package/dist/src/public/channels/slack/attachments.d.ts +2 -2
  37. package/dist/src/public/channels/slack/attachments.js +1 -1
  38. package/dist/src/public/channels/slack/hitl.d.ts +2 -0
  39. package/dist/src/public/channels/slack/inbound-content.d.ts +4 -0
  40. package/dist/src/public/channels/slack/inbound-content.js +4 -0
  41. package/dist/src/public/channels/slack/inbound.d.ts +8 -1
  42. package/dist/src/public/channels/slack/inbound.js +1 -1
  43. package/dist/src/public/channels/slack/index.d.ts +1 -1
  44. package/dist/src/public/channels/slack/input-response.d.ts +1 -0
  45. package/dist/src/public/channels/slack/input-response.js +1 -1
  46. package/dist/src/public/channels/slack/interaction-cards.d.ts +10 -0
  47. package/dist/src/public/channels/slack/interaction-cards.js +1 -0
  48. package/dist/src/public/channels/slack/interactions.d.ts +1 -0
  49. package/dist/src/public/channels/slack/interactions.js +1 -1
  50. package/dist/src/public/channels/slack/slackChannel.d.ts +6 -1
  51. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  52. package/dist/src/public/sandbox/just-bash-sandbox.d.ts +12 -1
  53. package/dist/src/setup/flows/registry.d.ts +7 -1
  54. package/dist/src/setup/flows/registry.js +1 -1
  55. package/dist/src/setup/scaffold/create/project.js +39 -2
  56. package/dist/src/setup/scaffold/create/web-template.d.ts +4 -4
  57. package/dist/src/setup/scaffold/create/web-template.js +1 -1
  58. package/dist/src/shared/channel-definition.d.ts +5 -1
  59. package/dist/src/svelte/index.js +1 -1
  60. package/dist/src/svelte/use-eve-agent.js +1 -1
  61. package/dist/src/vue/index.js +1 -1
  62. package/dist/src/vue/use-eve-agent.js +1 -1
  63. package/docs/channels/linq.mdx +1 -1
  64. package/docs/channels/photon.mdx +4 -4
  65. package/docs/channels/slack.mdx +18 -3
  66. package/docs/concepts/built-in-tools.md +1 -0
  67. package/docs/getting-started.mdx +1 -0
  68. package/docs/guides/dev-tui.md +26 -15
  69. package/docs/meta.json +1 -1
  70. package/docs/sandbox.mdx +20 -0
  71. package/package.json +2 -2
@@ -1 +1 @@
1
- import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{defaultDeliverResult}from"#channel/adapter.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,buildSlackWorkspaceHandle,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{bindSlackSessionOperations}from"#public/channels/slack/session-operations.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{formatSlackInboundMessage,formatSlackThreadContext}from"#public/channels/slack/model-context.js";import{isPrivateSlackConversation}from"#public/channels/slack/privacy.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest as verifySlackRequest$1}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.continuation?.rekey(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),s=e.onInputResponse??defaultOnInputResponse,c=e.events?.[`authorization.required`],l=e.events?.[`approval.candidate`]??defaultEvents[`approval.candidate`],u=e.events?.[`turn.started`]??defaultEvents[`turn.started`],m={...defaultEvents,...e.events,async"approval.candidate"(e,t,n){let r=t.state.approvalResponderUsers?.[e.responderPrincipalId];if(e.outcome===`pending`&&r!==void 0&&(t.state.pendingApprovalCandidateUsers={...t.state.pendingApprovalCandidateUsers,[e.candidateId]:r}),await l(e,t,n),e.outcome!==`pending`){let n={...t.state.pendingApprovalCandidateUsers};delete n[e.candidateId],t.state.pendingApprovalCandidateUsers=n}},async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await u(e,t,n)},"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":c===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(c)},h=new Set;return defineChannel({kindHint:`slack`,turnPolicy:e.turnPolicy,state:{audience:`unknown`,channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{},pendingApprovalCards:{},pendingApprovalCandidateUsers:{},approvalResponderUsers:{}},fetchFile:n,metadata(e){return{audience:e.audience??`unknown`,channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},deliver(e,t){let n=e.pendingApprovalCards;typeof n==`object`&&n&&(t.state.pendingApprovalCards={...t.state.pendingApprovalCards,...n});let i=e.state?.approvalResponderUsers;return typeof i==`object`&&i&&(t.state.approvalResponderUsers={...t.state.approvalResponderUsers,...i}),defaultDeliverResult(e)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,r)=>{let{waitUntil:i}=r,{from:a,resolveSession:o}=r,c=await verifyInbound(n,e.credentials);return c===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(c,{from:a,resolveSession:o,waitUntil:i},{config:e,onInputResponse:s}):handleEventPost({body:c,from:a,resolveSession:o,waitUntil:i,config:e,uploadPolicy:t,handledEvents:h,headers:n.headers})})],receive(t,{from:n}){return receiveOnSlack(t,{from:n,credentials:e.credentials})},events:m})}function defaultOnInputResponse(e){return{auth:e.defaultAuth}}async function receiveOnSlack(e,t){let n=e.target,r=n.channelId;if(!r||typeof r!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let i=typeof n.threadTs==`string`?n.threadTs:``,a=n.initialMessage;if(a&&i.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let o=i;if(a){let{thread:e}=buildSlackBinding({botToken:t.credentials?.botToken,channelId:r,threadTs:``,teamId:t.teamId}),n={card:a.card};a.fallbackText!==void 0&&(n.fallbackText=a.fallbackText),o=(await e.post(n)).id}let c=o||crypto.randomUUID();return t.from(slackContinuationToken(r,c)).send(e.message,{auth:e.auth,state:{channelId:r,threadTs:o||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null},title:e.title})}function constrainAuthorizationRequired(e){return(t,n,r)=>e(t,{postEphemeral:(e,t)=>n.thread.postEphemeral(e,t),postDirectMessage:(e,t)=>n.thread.postDirectMessage(e,t),state:n.state},r)}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let{config:n}=t,r,i;try{r=parseSlackWebhookBody(t.body,{headers:t.headers}),i=parseSlackEventEnvelope(t.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(r.kind===`url_verification`)return new Response(r.challenge,{status:200,headers:{"content-type":`text/plain`}});if(i===null)return new Response(`ok`);let a=typeof i.api_app_id==`string`?i.api_app_id:void 0,o=slackEventBotUserId(i),dispatch=null,s=null;if(r.kind===`app_mention`||r.kind===`direct_message`){let e=r.kind,i=slackMessageFromWebhookPayload(r);if(i!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},i)){let dispatchMessageWith=r=>()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:r,kind:e,message:i,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}),r=(e===`app_mention`?n.onAppMention:n.onDirectMessage)??n.onMessage;r===void 0?s=dispatchMessageWith(e===`app_mention`?defaultOnAppMention:defaultOnDirectMessage):dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:r,kind:e,message:i,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy})}}if(dispatch===null&&n.onMessage!==void 0){let e=parseMessageEvent(i);e!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},e)&&(o===void 0||!e.text.includes(`<@${o}`))&&(dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:n.onMessage,kind:`channel_message`,message:e,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}))}let c=n.onEvent;if(dispatch===null&&c!==void 0&&(dispatch=()=>dispatchSlackEvent({from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,envelope:i,handler:c})),dispatch??=s,dispatch===null)return new Response(`ok`);let l=i.event_id;if(l){if(t.handledEvents.has(l))return log.warn(`received a duplicate event`,{event_id:l,event_time:i.event_time,retry_num:r.retry?.num??`(null)`,retry_reason:r.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(l,t.handledEvents)}return t.waitUntil(dispatch()),new Response(`ok`)}function isSelfAuthoredSlackMessage(e,t){return e.botUserId!==void 0&&t.author?.userId===e.botUserId||e.appId!==void 0&&t.raw.app_id===e.appId}async function dispatchSlackMessage(e){let t=slackContinuationToken(e.message.channelId,e.message.threadTs),{thread:r,slack:i}=buildSlackBinding({appId:e.appId,botToken:e.credentials?.botToken,botUserId:e.botUserId,channelId:e.message.channelId,threadTs:e.message.threadTs,teamId:e.message.teamId}),a=e.message.author,o={audience:`unknown`,channelId:e.message.channelId,teamId:e.message.teamId??null,threadTs:e.message.threadTs,triggeringUserId:a?.userId??null},c=bindSlackSessionOperations({address:t,defaultAuth:a===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:a.fullName,isBot:a.isBot,teamId:e.message.teamId,threadTs:e.message.threadTs,userId:a.userId,userName:a.userName}),from:e.from,resolveSession:e.resolveSession,state:o}),d,isDMOrPrivateChannel=()=>d??=isPrivateSlackConversation({channelId:e.message.channelId,raw:e.message.raw,request:i.request}),f={...c,isBotMentioned:()=>e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),isDMOrPrivateChannel,isSubscribed:async()=>await c.resolveSession()!==void 0,slack:i,thread:r},p;try{p=await e.handler(f,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId});return}if(p==null)return;let m=await isDMOrPrivateChannel();o.audience=e.kind===`direct_message`||m?`private`:`public`,await deliverSlackMessage({credentials:e.credentials,kind:e.kind,isPrivateConversation:m,message:e.message,result:p,sessionOperations:c,thread:r,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,r=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,i=[],sourceFor=t=>e.from(slackContinuationToken(t.channelId,t.threadTs)),a={cancel:({target:e,turnId:t})=>sourceFor(e).cancel({turnId:t}),clear:({target:e})=>sourceFor(e).clear(),compact:({target:e})=>sourceFor(e).compact(),envelope:e.envelope,reset:({reason:e,target:t})=>sourceFor(t).reset({reason:e}),resolveSession:({target:t})=>e.resolveSession(slackContinuationToken(t.channelId,t.threadTs)),respond:(e,{auth:t,target:n})=>sourceFor(n).respond(e,{auth:t}),send:(t,{auth:n,target:i,title:a})=>receiveOnSlack({auth:n,message:t,target:i,title:a},{from:e.from,credentials:e.credentials,teamId:r,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({botToken:e.credentials?.botToken,teamId:r}),waitUntil(e){i.push(e)}};try{await e.handler(a,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(i)}async function verifyInbound(e,t){try{return await verifySlackRequest$1(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function deliverSlackMessage(e){let{message:t,thread:r}=e;try{let n=formatSlackThreadContext(e.threadContext===void 0?[]:await loadThreadContextMessages(r,t,e.threadContext)),i=await collectInboundFileParts({mention:t,thread:r,policy:e.uploadPolicy}),a=formatSlackInboundMessage({channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},t),o=buildSlackTurnMessage(n===void 0?a:`${n}\n\n${a}`,i),s=e.result.context??[],c=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,l=s.length===0?{auth:e.result.auth,title:c}:{auth:e.result.auth,context:s,title:c};await e.sessionOperations.send(o,l)}catch(r){logError(log,`${e.kind} delivery failed`,r,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
1
+ import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{defaultDeliverResult}from"#channel/adapter.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,buildSlackWorkspaceHandle,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{bindSlackSessionOperations}from"#public/channels/slack/session-operations.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{formatSlackInboundMessage,formatSlackThreadContext}from"#public/channels/slack/model-context.js";import{isPrivateSlackConversation}from"#public/channels/slack/privacy.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest as verifySlackRequest$1}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.continuation?.rekey(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),s=e.onInputResponse??defaultOnInputResponse,c=e.events?.[`authorization.required`],l=e.events?.[`approval.candidate`]??defaultEvents[`approval.candidate`],u=e.events?.[`turn.started`]??defaultEvents[`turn.started`],m={...defaultEvents,...e.events,async"approval.candidate"(e,t,n){let r=t.state.approvalResponderUsers?.[e.responderPrincipalId];if(e.outcome===`pending`&&r!==void 0&&(t.state.pendingApprovalCandidateUsers={...t.state.pendingApprovalCandidateUsers,[e.candidateId]:r}),await l(e,t,n),e.outcome!==`pending`){let n={...t.state.pendingApprovalCandidateUsers};delete n[e.candidateId],t.state.pendingApprovalCandidateUsers=n}},async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await u(e,t,n)},"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":c===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(c)},h=new Set;return defineChannel({kindHint:`slack`,turnPolicy:e.turnPolicy,state:{audience:`unknown`,channelId:null,threadTs:null,teamId:null,installationTeamId:null,triggeringUserId:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{},pendingApprovalCards:{},pendingApprovalCandidateUsers:{},approvalResponderUsers:{}},fetchFile:n,metadata(e){return{audience:e.audience??`unknown`,channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},deliver(e,t){let n=e.pendingApprovalCards;typeof n==`object`&&n&&(t.state.pendingApprovalCards={...t.state.pendingApprovalCards,...n});let i=e.state?.approvalResponderUsers;return typeof i==`object`&&i&&(t.state.approvalResponderUsers={...t.state.approvalResponderUsers,...i}),defaultDeliverResult(e)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,r)=>{let{waitUntil:i}=r,{from:a,resolveSession:o}=r,c=await verifyInbound(n,e.credentials);return c===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(c,{from:a,resolveSession:o,waitUntil:i},{config:e,onInputResponse:s}):handleEventPost({body:c,from:a,resolveSession:o,waitUntil:i,config:e,uploadPolicy:t,handledEvents:h,headers:n.headers})})],receive(t,{from:n}){return receiveOnSlack(t,{from:n,credentials:e.credentials})},events:m})}function defaultOnInputResponse(e){return{auth:e.defaultAuth}}async function receiveOnSlack(e,t){let n=e.target,r=n.channelId;if(!r||typeof r!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let i=typeof n.threadTs==`string`?n.threadTs:``,a=typeof n.installationTeamId==`string`?n.installationTeamId:t.installationTeamId,o=n.initialMessage;if(o&&i.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=i;if(o){let{thread:e}=buildSlackBinding({botToken:t.credentials?.botToken,channelId:r,threadTs:``,installationTeamId:a,teamId:t.teamId}),n={card:o.card};o.fallbackText!==void 0&&(n.fallbackText=o.fallbackText),c=(await e.post(n)).id}let u=c||crypto.randomUUID();return t.from(slackContinuationToken(r,u)).send(e.message,{auth:e.auth,state:{channelId:r,installationTeamId:a??null,threadTs:c||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null},title:e.title})}function constrainAuthorizationRequired(e){return(t,n,r)=>e(t,{postEphemeral:(e,t)=>n.thread.postEphemeral(e,t),postDirectMessage:(e,t)=>n.thread.postDirectMessage(e,t),state:n.state},r)}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let{config:n}=t,r,i;try{r=parseSlackWebhookBody(t.body,{headers:t.headers}),i=parseSlackEventEnvelope(t.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(r.kind===`url_verification`)return new Response(r.challenge,{status:200,headers:{"content-type":`text/plain`}});if(i===null)return new Response(`ok`);let a=typeof i.api_app_id==`string`?i.api_app_id:void 0,o=slackEventBotUserId(i),s=slackEventInstallationTeamId(i),dispatch=null,c=null;if(r.kind===`app_mention`||r.kind===`direct_message`){let e=r.kind,i=slackMessageFromWebhookPayload(r);if(i!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},i)){let dispatchMessageWith=r=>()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:r,installationTeamId:s,kind:e,message:i,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}),r=(e===`app_mention`?n.onAppMention:n.onDirectMessage)??n.onMessage;r===void 0?c=dispatchMessageWith(e===`app_mention`?defaultOnAppMention:defaultOnDirectMessage):dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:r,installationTeamId:s,kind:e,message:i,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy})}}if(dispatch===null&&n.onMessage!==void 0){let e=parseMessageEvent(i);e!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},e)&&(o===void 0||!e.text.includes(`<@${o}`))&&(dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:n.onMessage,installationTeamId:s,kind:`channel_message`,message:e,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}))}let l=n.onEvent;if(dispatch===null&&l!==void 0&&(dispatch=()=>dispatchSlackEvent({from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,envelope:i,handler:l,installationTeamId:s})),dispatch??=c,dispatch===null)return new Response(`ok`);let u=i.event_id;if(u){if(t.handledEvents.has(u))return log.warn(`received a duplicate event`,{event_id:u,event_time:i.event_time,retry_num:r.retry?.num??`(null)`,retry_reason:r.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(u,t.handledEvents)}return t.waitUntil(dispatch()),new Response(`ok`)}function isSelfAuthoredSlackMessage(e,t){return e.botUserId!==void 0&&t.author?.userId===e.botUserId||e.appId!==void 0&&t.raw.app_id===e.appId}async function dispatchSlackMessage(e){let t=slackContinuationToken(e.message.channelId,e.message.threadTs),{thread:r,slack:i}=buildSlackBinding({appId:e.appId,botToken:e.credentials?.botToken,botUserId:e.botUserId,channelId:e.message.channelId,threadTs:e.message.threadTs,installationTeamId:e.installationTeamId,teamId:e.message.teamId}),a=e.message.author,o={audience:`unknown`,channelId:e.message.channelId,installationTeamId:e.installationTeamId??null,teamId:e.message.teamId??null,threadTs:e.message.threadTs,triggeringUserId:a?.userId??null},c=bindSlackSessionOperations({address:t,defaultAuth:a===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:a.fullName,isBot:a.isBot,teamId:e.message.teamId,threadTs:e.message.threadTs,userId:a.userId,userName:a.userName}),from:e.from,resolveSession:e.resolveSession,state:o}),d,isDMOrPrivateChannel=()=>d??=isPrivateSlackConversation({channelId:e.message.channelId,raw:e.message.raw,request:i.request}),f={...c,isBotMentioned:()=>e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),isDMOrPrivateChannel,isSubscribed:async()=>await c.resolveSession()!==void 0,slack:i,thread:r},p;try{p=await e.handler(f,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId});return}if(p==null)return;let m=await isDMOrPrivateChannel();o.audience=e.kind===`direct_message`||m?`private`:`public`,await deliverSlackMessage({credentials:e.credentials,kind:e.kind,isPrivateConversation:m,message:e.message,result:p,sessionOperations:c,thread:r,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,r=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,i=[],sourceFor=t=>e.from(slackContinuationToken(t.channelId,t.threadTs)),a={cancel:({target:e,turnId:t})=>sourceFor(e).cancel({turnId:t}),clear:({target:e})=>sourceFor(e).clear(),compact:({target:e})=>sourceFor(e).compact(),envelope:e.envelope,reset:({reason:e,target:t})=>sourceFor(t).reset({reason:e}),resolveSession:({target:t})=>e.resolveSession(slackContinuationToken(t.channelId,t.threadTs)),respond:(e,{auth:t,target:n})=>sourceFor(n).respond(e,{auth:t}),send:(t,{auth:n,target:i,title:a})=>receiveOnSlack({auth:n,message:t,target:i,title:a},{from:e.from,credentials:e.credentials,installationTeamId:e.installationTeamId,teamId:r,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({botToken:e.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:r}),waitUntil(e){i.push(e)}};try{await e.handler(a,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(i)}async function verifyInbound(e,t){try{return await verifySlackRequest$1(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function deliverSlackMessage(e){let{message:t,thread:r}=e;try{let n=formatSlackThreadContext(e.threadContext===void 0?[]:await loadThreadContextMessages(r,t,e.threadContext)),i=await collectInboundFileParts({mention:t,thread:r,policy:e.uploadPolicy}),a=formatSlackInboundMessage({channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},t),o=buildSlackTurnMessage(n===void 0?a:`${n}\n\n${a}`,i),s=e.result.context??[],c=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,l=s.length===0?{auth:e.result.auth,title:c}:{auth:e.result.auth,context:s,title:c};await e.sessionOperations.send(o,l)}catch(r){logError(log,`${e.kind} delivery failed`,r,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
@@ -1,4 +1,4 @@
1
- import type { IFileSystem } from "just-bash";
1
+ import type { CustomCommand, IFileSystem } from "just-bash";
2
2
  /**
3
3
  * Context passed to a custom just-bash filesystem factory for each live handle.
4
4
  */
@@ -26,6 +26,17 @@ export interface JustBashSandboxCreateOptions {
26
26
  * actionable install error instead. Defaults to `true`.
27
27
  */
28
28
  readonly autoInstall?: boolean;
29
+ /**
30
+ * Registers trusted host application code as commands in each live
31
+ * just-bash interpreter. Custom commands can participate in normal shell
32
+ * composition, including pipelines and redirections.
33
+ *
34
+ * Custom commands are not available during template prewarming. They run in
35
+ * eve's host process, outside the virtual filesystem security boundary, and
36
+ * are responsible for validating their own inputs and cleaning up host
37
+ * resources.
38
+ */
39
+ readonly customCommands?: ReadonlyArray<CustomCommand>;
29
40
  /**
30
41
  * Composes the filesystem used by each live sandbox handle. The factory is
31
42
  * called when a handle opens with eve's durable default filesystem; return a
@@ -27,10 +27,16 @@ export declare class RegistryFlowFailedError extends Error {
27
27
  kind: "done";
28
28
  }>);
29
29
  }
30
- /** Runs the categorized interactive registry catalog used by the dev TUI's `/add`. */
30
+ /**
31
+ * Runs the categorized interactive registry catalog used by the dev TUI's
32
+ * `/add`. `initialAddress` — `/add channel/slack` — skips the category and
33
+ * search screens and opens that item's confirmation directly; backing out of it
34
+ * lands on the same category hub the browser starts from.
35
+ */
31
36
  export declare function runRegistryFlow(input: {
32
37
  appRoot: string;
33
38
  prompter: Prompter;
34
39
  signal?: AbortSignal;
40
+ initialAddress?: string;
35
41
  deps?: Partial<RegistryFlowDeps>;
36
42
  }): Promise<RegistryFlowResult>;
@@ -1 +1 @@
1
- import{createRegistrySession}from"./registry-session.js";import{runDeployFlow}from"#setup/flows/deploy.js";import{WizardCancelledError}from"#setup/step.js";import{detectDeployment}from"#setup/project-resolution.js";import{withSpinner}from"#setup/with-spinner.js";const ADDRESS_PREFIX=`address:`,BACK=`action:back`,DONE=`action:done`,ALL=`category:all`,REGISTRY_CATEGORIES=[{value:`category:channel`,prefix:`channel/`,label:`Channels`,hint:`Where people talk to your agent — Web, Slack, Discord, Teams`,browseLabel:`Browse channels`},{value:`category:connection`,prefix:`connection/`,label:`MCP connections`,hint:`Connect services like Linear, Notion, GitHub, and Vercel`,browseLabel:`Browse MCP connections`},{value:`category:extension`,prefix:`extension/`,label:`Extensions`,hint:`Add browser automation, memory, and developer tools`,browseLabel:`Browse extensions`},{value:`category:instrumentation`,prefix:`instrumentation/`,label:`Observability`,hint:`Trace, evaluate, and monitor your agent`,browseLabel:`Browse observability integrations`}];var RegistryFlowFailedError=class extends Error{completed;constructor(e,t){super(e instanceof Error?e.message:String(e),{cause:e}),this.name=`RegistryFlowFailedError`,this.completed=t}};function itemLabel(e){return e.title===void 0?(e.name.split(`/`).at(-1)??e.name).split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `):e.title}function itemRows(e){return e.map((e,t)=>({value:`item:${t}`,label:itemLabel(e),hint:e.description??e.type}))}function categoryRows(){return[...REGISTRY_CATEGORIES.map(e=>({value:e.value,label:e.label,hint:e.hint})),{value:ALL,label:`Browse all`,hint:`Search every integration or enter an item address`},{value:DONE,label:`Return to chat`,trailingAction:!0}]}function categoryFor(e){return REGISTRY_CATEGORIES.find(t=>t.value===e)}function itemsForCategory(e,t){if(t===ALL)return e;let n=categoryFor(t);return n===void 0?e:e.filter(e=>e.address.startsWith(n.prefix)||e.name.startsWith(n.prefix))}function stringArray(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function itemSource(e){if(e.startsWith(`@`))return e.split(`/`)[0]??e;if(/^https?:\/\//u.test(e))try{return new URL(e).host}catch{return e}return`Vercel`}function manifestRecord(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:{}}function summarizeDetails(e,t=3){let n=e.slice(0,t),r=e.length-n.length;return r>0?`${n.join(`, `)} … (+${r} more)`:n.join(`, `)}function itemDetails(e,t){let n=[{label:`Source`,value:e.source}],r=[...stringArray(t.dependencies),...stringArray(t.devDependencies),...stringArray(t.registryDependencies)];r.length>0&&n.push({label:`Packages`,value:summarizeDetails(r)});let i=t.envVars;if(typeof i==`object`&&i&&!Array.isArray(i)){let e=Object.keys(i);e.length>0&&n.push({label:`Environment`,value:summarizeDetails(e)})}let a=(Array.isArray(t.files)?t.files:[]).flatMap(e=>{if(typeof e!=`object`||!e||Array.isArray(e))return[];let t=e.target;return typeof t==`string`?[t]:[]});return a.length>0&&n.push({label:`Files`,value:summarizeDetails(a)}),n}async function inspectItem(e,t,n,r,a,o){let s=a??manifestRecord(await withSpinner(e,`Loading registry item…`,()=>t.getRegistryItemManifest(n,r.address)));for(;;){let i={message:typeof s.title==`string`?s.title:r.name,metadata:itemDetails(r,s),options:[{value:`add`,label:`Add to project`},{value:`back`,label:`Back`}]},a=typeof s.description==`string`?s.description:r.description;if(a!==void 0&&(i.description=a),await e.select(i)===`back`)return{kind:`back`};let c=e.log.spinner?.(`Installing ${itemLabel(r)} and dependencies…`);try{c?.stop();let install=()=>t.installRegistryItem(n,r.address,{silent:!0,prompter:e,signal:o});return{kind:`added`,...await(e.withExclusiveTerminal?.(install)??install())}}finally{c?.stop()}}}async function resolveAddressItem(e,t,n,r){let a=manifestRecord(await withSpinner(e,`Loading registry item…`,()=>t.getRegistryItemManifest(n,r))),o={address:r,name:typeof a.name==`string`?a.name:r,source:itemSource(r)};return typeof a.type==`string`&&(o.type=a.type),typeof a.description==`string`&&(o.description=a.description),{item:o,manifest:a}}async function runRegistryFlow(c){let l;(c.deps?.browseRegistryCatalog===void 0||c.deps.getRegistryItemManifest===void 0||c.deps.installRegistryItem===void 0)&&(l=await import(`#cli/commands/registry.js`));let u={browseRegistryCatalog:c.deps?.browseRegistryCatalog??l.browseRegistryCatalog,detectDeployment:c.deps?.detectDeployment??detectDeployment,getRegistryItemManifest:c.deps?.getRegistryItemManifest??l.getRegistryItemManifest,installRegistryItem:c.deps?.installRegistryItem??l.installRegistryItem,runDeployFlow:c.deps?.runDeployFlow??runDeployFlow},d=[],f=createRegistrySession(u);try{for(;;){c.signal?.throwIfAborted();let e=await withSpinner(c.prompter,`Loading registry…`,()=>u.browseRegistryCatalog(c.appRoot));d=[...d,...e.errors.map(e=>({tone:`warning`,text:`${e.registry}: ${e.message}`}))];let t=await c.prompter.select({message:`Add an integration`,options:categoryRows(),hintLayout:`inline`,notices:d});if(d=[],t===DONE)return f.result();let n=itemsForCategory(e.items,t),r=itemRows(n);r.push({value:BACK,label:`Back`,trailingAction:!0});let l=await c.prompter.select({message:categoryFor(t)?.browseLabel??`Browse integrations`,options:r,search:!0,placeholder:`Search integrations or enter an item address`,searchAction:{label:e=>`Add “${e}”`,value:e=>`${ADDRESS_PREFIX}${e.trim()}`},hintLayout:`inline`});if(l===BACK)continue;let p=l.startsWith(ADDRESS_PREFIX)?await resolveAddressItem(c.prompter,u,c.appRoot,l.slice(8)):{item:n[Number(l.slice(5))]};if(p.item===void 0)throw Error(`The selected registry item is no longer available.`);let m=await inspectItem(c.prompter,u,c.appRoot,p.item,`manifest`in p?p.manifest:void 0,c.signal);if(m.kind!==`added`)continue;f.add(p.item.address,itemLabel(p.item),m.output,m.setup);let h=await f.continueAfterInstall({appRoot:c.appRoot,prompter:c.prompter,signal:c.signal});if(h!==`add-more`)return h}}catch(e){if(e instanceof WizardCancelledError)return{kind:`cancelled`};let t=f.result();throw t.addedItems.length>0?new RegistryFlowFailedError(e,t):e}}export{RegistryFlowFailedError,runRegistryFlow};
1
+ import{createRegistrySession}from"./registry-session.js";import{runDeployFlow}from"#setup/flows/deploy.js";import{WizardCancelledError}from"#setup/step.js";import{detectDeployment}from"#setup/project-resolution.js";import{withSpinner}from"#setup/with-spinner.js";const ADDRESS_PREFIX=`address:`,BACK=`action:back`,DONE=`action:done`,ALL=`category:all`,REGISTRY_CATEGORIES=[{value:`category:channel`,prefix:`channel/`,label:`Channels`,hint:`Where people talk to your agent — Web, Slack, Discord, Teams`,browseLabel:`Browse channels`},{value:`category:connection`,prefix:`connection/`,label:`MCP connections`,hint:`Connect services like Linear, Notion, GitHub, and Vercel`,browseLabel:`Browse MCP connections`},{value:`category:extension`,prefix:`extension/`,label:`Extensions`,hint:`Add browser automation, memory, and developer tools`,browseLabel:`Browse extensions`},{value:`category:instrumentation`,prefix:`instrumentation/`,label:`Observability`,hint:`Trace, evaluate, and monitor your agent`,browseLabel:`Browse observability integrations`}];var RegistryFlowFailedError=class extends Error{completed;constructor(e,t){super(e instanceof Error?e.message:String(e),{cause:e}),this.name=`RegistryFlowFailedError`,this.completed=t}};function itemLabel(e){return e.title===void 0?(e.name.split(`/`).at(-1)??e.name).split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `):e.title}function itemRows(e){return e.map((e,t)=>({value:`item:${t}`,label:itemLabel(e),hint:e.description??e.type}))}function categoryRows(){return[...REGISTRY_CATEGORIES.map(e=>({value:e.value,label:e.label,hint:e.hint})),{value:ALL,label:`Browse all`,hint:`Search every integration or enter an item address`},{value:DONE,label:`Return to chat`,trailingAction:!0}]}function categoryFor(e){return REGISTRY_CATEGORIES.find(t=>t.value===e)}function itemsForCategory(e,t){if(t===ALL)return e;let n=categoryFor(t);return n===void 0?e:e.filter(e=>e.address.startsWith(n.prefix)||e.name.startsWith(n.prefix))}function stringArray(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function itemSource(e){if(e.startsWith(`@`))return e.split(`/`)[0]??e;if(/^https?:\/\//u.test(e))try{return new URL(e).host}catch{return e}return`Vercel`}function manifestRecord(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:{}}function summarizeDetails(e,t=3){let n=e.slice(0,t),r=e.length-n.length;return r>0?`${n.join(`, `)} … (+${r} more)`:n.join(`, `)}function itemDetails(e,t){let n=[{label:`Source`,value:e.source}],r=[...stringArray(t.dependencies),...stringArray(t.devDependencies),...stringArray(t.registryDependencies)];r.length>0&&n.push({label:`Packages`,value:summarizeDetails(r)});let i=t.envVars;if(typeof i==`object`&&i&&!Array.isArray(i)){let e=Object.keys(i);e.length>0&&n.push({label:`Environment`,value:summarizeDetails(e)})}let a=(Array.isArray(t.files)?t.files:[]).flatMap(e=>{if(typeof e!=`object`||!e||Array.isArray(e))return[];let t=e.target;return typeof t==`string`?[t]:[]});return a.length>0&&n.push({label:`Files`,value:summarizeDetails(a)}),n}async function inspectItem(e,t,n,r,a,o){let s=a??manifestRecord(await withSpinner(e,`Loading registry item…`,()=>t.getRegistryItemManifest(n,r.address)));for(;;){let i={message:typeof s.title==`string`?s.title:r.name,metadata:itemDetails(r,s),options:[{value:`add`,label:`Add to project`},{value:`back`,label:`Back`}]},a=typeof s.description==`string`?s.description:r.description;if(a!==void 0&&(i.description=a),await e.select(i)===`back`)return{kind:`back`};let c=e.log.spinner?.(`Installing ${itemLabel(r)} and dependencies…`);try{c?.stop();let install=()=>t.installRegistryItem(n,r.address,{silent:!0,prompter:e,signal:o});return{kind:`added`,...await(e.withExclusiveTerminal?.(install)??install())}}finally{c?.stop()}}}async function resolveAddressItem(e,t,n,r){let a=manifestRecord(await withSpinner(e,`Loading registry item…`,()=>t.getRegistryItemManifest(n,r))),o={address:r,name:typeof a.name==`string`?a.name:r,source:itemSource(r)};return typeof a.type==`string`&&(o.type=a.type),typeof a.description==`string`&&(o.description=a.description),{item:o,manifest:a}}async function offerItem(e,t,n,r,i){let a=await inspectItem(e.prompter,t,e.appRoot,r,i,e.signal);if(a.kind!==`added`)return;n.add(r.address,itemLabel(r),a.output,a.setup);let o=await n.continueAfterInstall({appRoot:e.appRoot,prompter:e.prompter,signal:e.signal});return o===`add-more`?void 0:o}async function runRegistryFlow(c){let l;(c.deps?.browseRegistryCatalog===void 0||c.deps.getRegistryItemManifest===void 0||c.deps.installRegistryItem===void 0)&&(l=await import(`#cli/commands/registry.js`));let u={browseRegistryCatalog:c.deps?.browseRegistryCatalog??l.browseRegistryCatalog,detectDeployment:c.deps?.detectDeployment??detectDeployment,getRegistryItemManifest:c.deps?.getRegistryItemManifest??l.getRegistryItemManifest,installRegistryItem:c.deps?.installRegistryItem??l.installRegistryItem,runDeployFlow:c.deps?.runDeployFlow??runDeployFlow},d=[],f=createRegistrySession(u),p=c.initialAddress?.trim(),m=p===``?void 0:p;try{for(;;){if(c.signal?.throwIfAborted(),m!==void 0){let e=m;m=void 0;let t=await resolveAddressItem(c.prompter,u,c.appRoot,e),n=await offerItem(c,u,f,t.item,t.manifest);if(n!==void 0)return n;continue}let e=await withSpinner(c.prompter,`Loading registry…`,()=>u.browseRegistryCatalog(c.appRoot));d=[...d,...e.errors.map(e=>({tone:`warning`,text:`${e.registry}: ${e.message}`}))];let t=await c.prompter.select({message:`Add an integration`,options:categoryRows(),hintLayout:`inline`,notices:d});if(d=[],t===DONE)return f.result();let n=itemsForCategory(e.items,t),r=itemRows(n);r.push({value:BACK,label:`Back`,trailingAction:!0});let l=await c.prompter.select({message:categoryFor(t)?.browseLabel??`Browse integrations`,options:r,search:!0,placeholder:`Search integrations or enter an item address`,searchAction:{label:e=>`Add “${e}”`,value:e=>`${ADDRESS_PREFIX}${e.trim()}`},hintLayout:`inline`});if(l===BACK)continue;let p=l.startsWith(ADDRESS_PREFIX)?await resolveAddressItem(c.prompter,u,c.appRoot,l.slice(8)):{item:n[Number(l.slice(5))]};if(p.item===void 0)throw Error(`The selected registry item is no longer available.`);let h=await offerItem(c,u,f,p.item,`manifest`in p?p.manifest:void 0);if(h!==void 0)return h}}catch(e){if(e instanceof WizardCancelledError)return{kind:`cancelled`};let t=f.result();throw t.addedItems.length>0?new RegistryFlowFailedError(e,t):e}}export{RegistryFlowFailedError,runRegistryFlow};
@@ -1,4 +1,4 @@
1
- import{pinnedNodeEngineMajor}from"../../node-engine.js";import{parseChatGptModelSelection}from"../../../shared/chatgpt-model.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{pathExists,writeTextFile}from"../files.js";import{blockingCreateInPlaceEntries}from"../create-in-place.js";import{resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{mkdir,readdir,stat}from"node:fs/promises";import{basename,join,resolve}from"node:path";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,DEFAULT_AI_PACKAGE_VERSION=`^7.0.58`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.3`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.44.2`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e,t){return{"agent/agent.ts":renderAgentTemplate(e,t),"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE}}function renderAgentTemplate(e,n){let r=parseChatGptModelSelection(e);return r===void 0?BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(n)):`import { defineAgent } from "eve";\nimport { chatgpt } from "eve/models/openai";\n\nexport default defineAgent({\n model: chatgpt(${JSON.stringify(r)}),\n${reasoningTemplateLine(n)}});\n`}function reasoningTemplateLine(e){return e===void 0||e===`provider-default`?``:` reasoning: "${e}",\n`}function renderTemplate(e,n){return e===BASE_AGENT_TEMPLATE&&parseChatGptModelSelection(n.model)!==void 0?renderAgentTemplate(n.model,n.reasoning):e.replaceAll(`__EVE_INIT_APP_NAME__`,n.appName).replaceAll(`__EVE_INIT_MODEL__`,n.model).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(n.reasoning)).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(n.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(n.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(n.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,n.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,n.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,n.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,n.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,n.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,n.nodeEngine)}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}const BASE_AGENT_TEMPLATE=`import { defineAgent } from "eve";
1
+ import{pinnedNodeEngineMajor}from"../../node-engine.js";import{parseChatGptModelSelection}from"../../../shared/chatgpt-model.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{pathExists,writeTextFile}from"../files.js";import{blockingCreateInPlaceEntries}from"../create-in-place.js";import{resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{mkdir,readdir,stat}from"node:fs/promises";import{basename,join,resolve}from"node:path";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,DEFAULT_AI_PACKAGE_VERSION=`^7.0.58`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.3`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.44.4`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e,t){return{"agent/agent.ts":renderAgentTemplate(e,t),"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE}}function renderAgentTemplate(e,n){let r=parseChatGptModelSelection(e);return r===void 0?BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(n)):`import { defineAgent } from "eve";\nimport { chatgpt } from "eve/models/openai";\n\nexport default defineAgent({\n model: chatgpt(${JSON.stringify(r)}),\n${reasoningTemplateLine(n)}});\n`}function reasoningTemplateLine(e){return e===void 0||e===`provider-default`?``:` reasoning: "${e}",\n`}function renderTemplate(e,n){return e===BASE_AGENT_TEMPLATE&&parseChatGptModelSelection(n.model)!==void 0?renderAgentTemplate(n.model,n.reasoning):e.replaceAll(`__EVE_INIT_APP_NAME__`,n.appName).replaceAll(`__EVE_INIT_MODEL__`,n.model).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(n.reasoning)).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(n.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(n.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(n.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,n.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,n.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,n.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,n.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,n.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,n.nodeEngine)}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}const BASE_AGENT_TEMPLATE=`import { defineAgent } from "eve";
2
2
 
3
3
  export default defineAgent({
4
4
  model: "__EVE_INIT_MODEL__",
@@ -13,7 +13,9 @@ __EVE_INIT_REASONING__});
13
13
  },
14
14
  "scripts": {
15
15
  "build": "eve build",
16
+ "deploy": "eve deploy",
16
17
  "dev": "eve dev",
18
+ "eval": "eve eval",
17
19
  "start": "eve start",
18
20
  "typecheck": "tsc"
19
21
  },
@@ -35,7 +37,42 @@ __EVE_INIT_REASONING__});
35
37
  `,AGENT_INSTRUCTIONS_TEMPLATE=`# Identity
36
38
 
37
39
  You are a helpful assistant.
38
- `,SHARED_TEMPLATE_FILES={"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE,"tsconfig.json":`{
40
+ `,SHARED_TEMPLATE_FILES={"README.md":`# __EVE_INIT_APP_NAME__
41
+
42
+ This is an [eve](https://eve.dev) agent bootstrapped with [\`eve init\`](https://eve.dev/docs/reference/cli#eve-init).
43
+
44
+ ## Getting started
45
+
46
+ First, run the development server:
47
+
48
+ \`\`\`bash
49
+ eve dev
50
+ \`\`\`
51
+
52
+ The development TUI opens an interactive session where you can send messages to your agent.
53
+
54
+ Start by editing \`agent/instructions.md\` to define the agent's identity, purpose, tone, and response guidelines. Configure its model and runtime behavior in \`agent/agent.ts\`.
55
+
56
+ Add capabilities under \`agent/\`, including tools, connections, channels, skills, subagents, and schedules. eve reloads your changes as you work.
57
+
58
+ ## Learn more
59
+
60
+ To learn more about eve, explore these resources:
61
+
62
+ - [eve documentation](https://eve.dev/docs) — learn about eve's features and authoring APIs.
63
+ - [Build an Agent tutorial](https://eve.dev/docs/tutorial/first-agent) — build and deploy an agent step by step.
64
+ - [eve on GitHub](https://github.com/vercel/eve) — view the source and contribute.
65
+
66
+ ## Deploy on Vercel
67
+
68
+ Deploy your agent to [Vercel](https://vercel.com) from the project root:
69
+
70
+ \`\`\`bash
71
+ eve deploy
72
+ \`\`\`
73
+
74
+ \`eve deploy\` links a Vercel project if needed and deploys the agent to production. See the [eve deployment documentation](https://eve.dev/docs/guides/deployment/vercel) for authentication, environment variables, and deployment options.
75
+ `,"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE,"tsconfig.json":`{
39
76
  "compilerOptions": {
40
77
  "target": "ES2022",
41
78
  "module": "esnext",
@@ -63,9 +63,9 @@ export declare const WEB_APP_TEMPLATE_PACKAGE_JSON: {
63
63
  };
64
64
  readonly dependencies: {
65
65
  readonly "@radix-ui/react-use-controllable-state": "1.2.2";
66
- readonly "@shikijs/core": "4.1.0";
67
- readonly "@shikijs/engine-javascript": "4.1.0";
68
- readonly "@shikijs/engine-oniguruma": "4.1.0";
66
+ readonly "@shikijs/core": "3.23.0";
67
+ readonly "@shikijs/engine-javascript": "3.23.0";
68
+ readonly "@shikijs/engine-oniguruma": "3.23.0";
69
69
  readonly "@streamdown/cjk": "1.0.3";
70
70
  readonly "@streamdown/code": "1.1.1";
71
71
  readonly "@streamdown/math": "1.0.2";
@@ -81,7 +81,7 @@ export declare const WEB_APP_TEMPLATE_PACKAGE_JSON: {
81
81
  readonly "radix-ui": "1.4.3";
82
82
  readonly react: "19.2.6";
83
83
  readonly "react-dom": "19.2.6";
84
- readonly shiki: "4.1.0";
84
+ readonly shiki: "3.23.0";
85
85
  readonly streamdown: "2.5.0";
86
86
  readonly "tailwind-merge": "3.6.0";
87
87
  readonly tailwindcss: "4.3.0";
@@ -5830,4 +5830,4 @@ export const auth = betterAuth({
5830
5830
  },
5831
5831
  },
5832
5832
  });
5833
- `},WEB_APP_TEMPLATE_PACKAGE_JSON={scripts:{build:`next build`,"build:eve":`eve build`,dev:`next dev`,"dev:eve":`eve dev`,start:`next start`,"start:eve":`eve start`,typecheck:`tsc --noEmit -p tsconfig.json`},dependencies:{"@radix-ui/react-use-controllable-state":`1.2.2`,"@shikijs/core":`4.1.0`,"@shikijs/engine-javascript":`4.1.0`,"@shikijs/engine-oniguruma":`4.1.0`,"@streamdown/cjk":`1.0.3`,"@streamdown/code":`1.1.1`,"@streamdown/math":`1.0.2`,"@streamdown/mermaid":`1.0.2`,"@tailwindcss/postcss":`4.3.0`,"class-variance-authority":`0.7.1`,clsx:`2.1.1`,cmdk:`1.1.1`,"lucide-react":`1.16.0`,motion:`12.40.0`,nanoid:`5.1.11`,next:`16.3.0-preview.6`,"radix-ui":`1.4.3`,react:`19.2.6`,"react-dom":`19.2.6`,shiki:`4.1.0`,streamdown:`2.5.0`,"tailwind-merge":`3.6.0`,tailwindcss:`4.3.0`,"use-stick-to-bottom":`1.1.4`,zod:`4.4.3`},devDependencies:{"@types/node":`26`,"@types/react":`19.2.15`,"@types/react-dom":`19.2.3`,typescript:`6.0.3`}};export{WEB_APP_SIGN_IN_WITH_VERCEL_TEMPLATE_FILES,WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON};
5833
+ `},WEB_APP_TEMPLATE_PACKAGE_JSON={scripts:{build:`next build`,"build:eve":`eve build`,dev:`next dev`,"dev:eve":`eve dev`,start:`next start`,"start:eve":`eve start`,typecheck:`tsc --noEmit -p tsconfig.json`},dependencies:{"@radix-ui/react-use-controllable-state":`1.2.2`,"@shikijs/core":`3.23.0`,"@shikijs/engine-javascript":`3.23.0`,"@shikijs/engine-oniguruma":`3.23.0`,"@streamdown/cjk":`1.0.3`,"@streamdown/code":`1.1.1`,"@streamdown/math":`1.0.2`,"@streamdown/mermaid":`1.0.2`,"@tailwindcss/postcss":`4.3.0`,"class-variance-authority":`0.7.1`,clsx:`2.1.1`,cmdk:`1.1.1`,"lucide-react":`1.16.0`,motion:`12.40.0`,nanoid:`5.1.11`,next:`16.3.0-preview.6`,"radix-ui":`1.4.3`,react:`19.2.6`,"react-dom":`19.2.6`,shiki:`3.23.0`,streamdown:`2.5.0`,"tailwind-merge":`3.6.0`,tailwindcss:`4.3.0`,"use-stick-to-bottom":`1.1.4`,zod:`4.4.3`},devDependencies:{"@types/node":`26`,"@types/react":`19.2.15`,"@types/react-dom":`19.2.3`,typescript:`6.0.3`}};export{WEB_APP_SIGN_IN_WITH_VERCEL_TEMPLATE_FILES,WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON};
@@ -20,7 +20,11 @@ export interface FetchFileResult {
20
20
  readonly mediaType?: string;
21
21
  readonly filename?: string;
22
22
  }
23
- export type FetchFileFunction = (url: string) => Promise<Buffer | FetchFileResult | null>;
23
+ /** Runtime context supplied while resolving a channel-owned file URL. */
24
+ export interface FetchFileContext {
25
+ readonly state: Readonly<Record<string, unknown>>;
26
+ }
27
+ export type FetchFileFunction = (url: string, context?: FetchFileContext) => Promise<Buffer | FetchFileResult | null>;
24
28
  /**
25
29
  * Input passed to a channel's `receive` callback when another channel or
26
30
  * schedule proactively routes a message to it.
@@ -1,3 +1,3 @@
1
- import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-BsLJiIbc.js";
1
+ import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-BtsOXv3s.js";
2
2
 
3
3
  export { defaultMessageReducer, useEveAgent };
@@ -1,3 +1,3 @@
1
- import { t as useEveAgent } from "../chunks/use-eve-agent-BsLJiIbc.js";
1
+ import { t as useEveAgent } from "../chunks/use-eve-agent-BtsOXv3s.js";
2
2
 
3
3
  export { useEveAgent };
@@ -1,3 +1,3 @@
1
- import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-DpUEwS23.js";
1
+ import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-BJwaMe5k.js";
2
2
 
3
3
  export { defaultMessageReducer, useEveAgent };
@@ -1,3 +1,3 @@
1
- import { t as useEveAgent } from "../chunks/use-eve-agent-DpUEwS23.js";
1
+ import { t as useEveAgent } from "../chunks/use-eve-agent-BJwaMe5k.js";
2
2
 
3
3
  export { useEveAgent };
@@ -16,7 +16,7 @@ export default linqChannel({
16
16
  });
17
17
  ```
18
18
 
19
- The default webhook route is `/eve/v1/linq`. The channel verifies forwarded webhooks with same-project Vercel OIDC by default, marks accepted messages as read, and continues the same eve session for every message in a Linq conversation. A new accepted message cooperatively cancels an active turn and steers its replacement turn.
19
+ The default webhook route is `/eve/v1/linq`. The channel verifies forwarded webhooks with same-project Vercel OIDC by default, derives user auth from each message author, marks accepted messages as read, and continues the same eve session for every message in a Linq conversation. A new accepted message cooperatively cancels an active turn and steers its replacement turn.
20
20
 
21
21
  Set `turnPolicy: "queue"` when every response should finish before Linq starts the next message.
22
22
 
@@ -22,10 +22,10 @@ export default photonIMessageChannel({
22
22
  ```
23
23
 
24
24
  The default webhook route is `/eve/v1/photon`. The channel verifies forwarded
25
- webhooks with same-project Vercel OIDC by default, marks accepted messages as
26
- read, and continues the same eve session for every message in an iMessage
27
- conversation. A new accepted message cooperatively cancels an active turn and
28
- steers its replacement turn.
25
+ webhooks with same-project Vercel OIDC by default, derives user auth from each
26
+ message author, marks accepted messages as read, and continues the same eve
27
+ session for every message in an iMessage conversation. A new accepted message
28
+ cooperatively cancels an active turn and steers its replacement turn.
29
29
 
30
30
  Set `turnPolicy: "queue"` when every response should finish before Photon starts the next message.
31
31
 
@@ -33,6 +33,20 @@ export default slackChannel({
33
33
 
34
34
  `connectSlackCredentials` returns `{ botToken, webhookVerifier }`, keeping token rotation, multi-workspace tenancy, and request verification inside Connect rather than your code.
35
35
 
36
+ Function-form bot tokens receive `{ teamId }` when Slack supplies the app installation workspace. Use it to select an installation in a self-managed multi-workspace app. The id can differ from the actor or content workspace for Slack Connect events:
37
+
38
+ ```ts title="agent/channels/slack.ts"
39
+ import { slackChannel } from "eve/channels/slack";
40
+
41
+ export default slackChannel({
42
+ credentials: {
43
+ botToken: ({ teamId }) => loadInstallationToken(teamId),
44
+ },
45
+ });
46
+ ```
47
+
48
+ Literal tokens and zero-argument token functions remain supported. A multi-workspace provider should reject a missing `teamId` instead of selecting a default installation.
49
+
36
50
  ### Manage Slack credentials yourself
37
51
 
38
52
  Use this fallback when the agent does not run on Vercel or Vercel Connect is unavailable. In the setup command, **Use portable credentials** is the environment-variable option; eve adds `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` to `.env.example`.
@@ -357,6 +371,7 @@ import { connectSlackCredentials } from "@vercel/connect/eve";
357
371
  const { botToken } = connectSlackCredentials("slack/my-agent");
358
372
  const response = await callSlackApi({
359
373
  botToken,
374
+ context: { teamId: "T0123456789" },
360
375
  operation: "reactions.get",
361
376
  body: { channel: "C0123456789", timestamp: "1712345678.000100", full: true },
362
377
  });
@@ -364,7 +379,7 @@ if (!response.ok) throw new Error(String(response.error));
364
379
  ```
365
380
 
366
381
  `callSlackApi` resolves function-form tokens (secret managers, Connect
367
- rotation) at call time and form-encodes the body — the only safe default,
382
+ rotation) with the supplied `context` at call time and form-encodes the body — the only safe default,
368
383
  since Slack's JSON support is partial (`conversations.replies` rejects
369
384
  JSON). `resolveSlackBotToken` materializes a `SlackBotToken` to a string
370
385
  when you need the bearer token itself.
@@ -414,7 +429,7 @@ events: {
414
429
 
415
430
  ### Proactive sessions
416
431
 
417
- Start a session without an inbound message through `ctx.to(slack, target).send(message, { auth })` from another channel, or `to(slack, target).send(message, { auth })` from a schedule `run` handler. The proactive target shape is `{ channelId, threadTs?, initialMessage? }`. This sends turn input to the agent; it is not a direct Slack post.
432
+ Start a session without an inbound message through `ctx.to(slack, target).send(message, { auth })` from another channel, or `to(slack, target).send(message, { auth })` from a schedule `run` handler. The proactive target shape is `{ channelId, installationTeamId?, threadTs?, initialMessage? }`. Set `installationTeamId` to the app installation workspace when a function-form bot token must select an installation. This sends turn input to the agent; it is not a direct Slack post.
418
433
 
419
434
  To post without invoking the model, use `ctx.thread.post(...)` in an inbound Slack handler or `channel.thread.post(...)` in an `events` handler. Use `callSlackApi(...)` where no Slack channel context exists. For application-managed retries and deduplication, see [Durable cross-channel notifications](../patterns/durable-cross-channel-notifications).
420
435
 
@@ -422,7 +437,7 @@ To post without invoking the model, use `ctx.thread.post(...)` in an inbound Sla
422
437
 
423
438
  Inbound files behind authenticated Slack URLs are staged with `fetchFile`. See [File uploads](./custom#file-uploads) for the `fetchFile` contract.
424
439
 
425
- Private file downloads require the Slack bot token's `files:read` scope. Add the scope and reinstall the Slack app before sending attachments.
440
+ Private file downloads resolve credentials from the session's app installation workspace and require the Slack bot token's `files:read` scope. Add the scope and reinstall the Slack app before sending attachments.
426
441
 
427
442
  Staged files live in the session sandbox under `/workspace/attachments`. Session history keeps a reference to that path, not a second copy of the Slack file. If the backend can no longer reattach the original sandbox, eve cannot restore the attachment bytes from history; later turns receive a missing-file result. Store files outside the sandbox when the agent must retain them independently of sandbox availability.
428
443
 
@@ -32,6 +32,7 @@ Notes:
32
32
  - **`load_skill`** only pulls instructions into context. It adds no new execution surface, because behavior still comes from the tools the agent already has.
33
33
  - **`connection_search`** surfaces a connection's tools by their qualified name (e.g. `linear__list_issues`), which the model can then call directly. It's registered only when the agent has connections.
34
34
  - **`web_search`** has no local executor; the provider runs it. AI Gateway models use Exa by default. To use Parallel instead, export `webSearch({ provider: "parallel" })` from `agent/tools/web_search.ts`. Direct provider models continue to use their native search implementation. To supply your own implementation, override it with `defineTool()`.
35
+ - **`web_fetch`** follows up to ten redirects, rechecking every destination for SSRF safety. Non-success HTTP responses return a plain-text failure result with the response body when available instead of failing the tool call.
35
36
 
36
37
  Review these default tools before production use. Disable, wrap, restrict, or require approval for any tool that can access the filesystem, network, shell, or sensitive data.
37
38
 
@@ -75,6 +75,7 @@ A minimal agent needs `instructions.md`; `agent.ts` is optional when the default
75
75
 
76
76
  ```text
77
77
  my-agent/
78
+ ├── README.md
78
79
  ├── package.json
79
80
  ├── tsconfig.json
80
81
  ├── agent/
@@ -13,24 +13,35 @@ The transcript remains in your terminal scrollback after you exit. Run `/help` i
13
13
 
14
14
  ## Commands
15
15
 
16
- | Command | Description |
17
- | ------------- | ----------------------------------------------------------------------------------------------------- |
18
- | `/model` | Configure the model and its provider. Pass a model ID to set it directly: `/model provider/model-id`. |
19
- | `/add` | Browse and install channels, MCP connections, extensions, and observability integrations. |
20
- | `/deploy` | Deploy the agent to Vercel production. Links the directory first if needed. |
21
- | `/vc:install` | Install the Vercel CLI. |
22
- | `/vc:login` | Log in to Vercel or restore access to a remote deployment. |
23
- | `/loglevel` | Choose which server and agent logs appear in the transcript. |
24
- | `/traces` | Open the local trace viewer. Pass a trace ID prefix to open a specific trace. |
25
- | `/reset` | Start a fresh session. |
26
- | `/cancel` | Cancel the current turn without discarding settled context. |
27
- | `/clear` | Clear the session's model-message history. `/new` is an alias. |
28
- | `/compact` | Compact the current session's context. |
29
- | `/exit` | Quit the UI. |
30
- | `/help` | List available commands. |
16
+ | Command | Description |
17
+ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `/model` | Configure the model and its provider. Pass a model ID to set it directly: `/model provider/model-id`. |
19
+ | `/add` | Browse and install channels, MCP connections, extensions, and observability integrations. Pass an item address to open it directly: `/add channel/slack`. |
20
+ | `/deploy` | Deploy the agent to Vercel production. Links the directory first if needed. |
21
+ | `/vc:install` | Install the Vercel CLI. |
22
+ | `/vc:login` | Log in to Vercel or restore access to a remote deployment. |
23
+ | `/loglevel` | Choose which server and agent logs appear in the transcript. |
24
+ | `/traces` | Open the local trace viewer. Pass a trace ID prefix to open a specific trace. |
25
+ | `/reset` | Start a fresh session. |
26
+ | `/cancel` | Cancel the current turn without discarding settled context. |
27
+ | `/clear` | Clear the session's model-message history. `/new` is an alias. |
28
+ | `/compact` | Compact the current session's context. |
29
+ | `/exit` | Quit the UI. |
30
+ | `/help` | List available commands. |
31
31
 
32
32
  `/model`, `/add`, `/deploy`, and `/traces` are available when `eve dev` runs locally. They are unavailable when the UI connects to a server with `--url`.
33
33
 
34
+ ## Add an integration
35
+
36
+ Bare `/add` opens the categorized registry browser. Pass an item address — `<category>/<name>`, where category is `channel`, `connection`, `extension`, or `instrumentation` — to skip the category and search screens and open that item directly:
37
+
38
+ ```text
39
+ /add channel/slack
40
+ /add extension/agent-browser
41
+ ```
42
+
43
+ Either way the UI shows the item's details and asks to confirm before installing, then runs the same installation and setup prompts. Choosing **Back** on a directly addressed item returns to the registry browser.
44
+
34
45
  ## Work with the agent
35
46
 
36
47
  Type a message and press `Enter` to send it. When the agent asks a question or requests tool approval, respond in the prompt shown by the UI. Connection authorization can open a browser; keep local `eve dev` running until the browser returns to it.
package/docs/meta.json CHANGED
@@ -34,6 +34,6 @@
34
34
  "patterns",
35
35
  "...reference",
36
36
  "responsible-use",
37
- "[Benchmarks](/evals)"
37
+ "[Benchmarks](/benchmarks)"
38
38
  ]
39
39
  }
package/docs/sandbox.mdx CHANGED
@@ -182,6 +182,26 @@ export default defineSandbox({
182
182
 
183
183
  `justbash()` needs no daemon or VM, but commands run in a simulated bash with a virtual filesystem under `.eve/sandbox-cache/`, with no real binaries (`git`, `node`, package managers) and no network isolation. The `just-bash` package is an optional peer dependency, so `eve dev` installs it into your application automatically when missing (disable with `autoInstall: false`); production processes fail with an actionable install error instead.
184
184
 
185
+ Pass `customCommands` to expose a `just-bash` `CustomCommand` inside each live sandbox session. The command participates in shell pipelines, redirections, and exit-code handling like a built-in command:
186
+
187
+ ```ts title="agent/sandbox.ts"
188
+ import { defineSandbox } from "eve/sandbox";
189
+ import { justbash } from "eve/sandbox/just-bash";
190
+ import { decodeBytesToUtf8, defineCommand } from "just-bash";
191
+
192
+ const uppercase = defineCommand("uppercase", async (_args, context) => ({
193
+ stdout: decodeBytesToUtf8(context.stdin).toUpperCase(),
194
+ stderr: "",
195
+ exitCode: 0,
196
+ }));
197
+
198
+ export default defineSandbox({
199
+ backend: justbash({ customCommands: [uppercase] }),
200
+ });
201
+ ```
202
+
203
+ Custom commands are registered on live and reopened sessions, not during template prewarming. They run trusted host application code and create an explicit bridge from the sandbox to that code. Adapters can use this extension point to expose application APIs such as a Capable App's CLI; the outer eve `bash` tool does not automatically provide per-command Capable approvals.
204
+
185
205
  You can also write your own backend. A `SandboxBackend` is an adapter object with a `name`, a `create`, and an optional `prewarm`. It can point at your own container runner, VM pool, internal sandbox service, or another isolation layer, as long as it returns the `SandboxSession` operations eve needs. Handles returned by `create` implement `shutdown()`, which stops the underlying compute at server shutdown. See the `SandboxBackend*` types on `eve/sandbox`.
186
206
 
187
207
  ## Lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve",
3
- "version": "0.44.2",
3
+ "version": "0.44.4",
4
4
  "private": false,
5
5
  "description": "Filesystem-first framework for durable backend AI agents that run anywhere.",
6
6
  "keywords": [
@@ -381,7 +381,7 @@
381
381
  "@opentelemetry/api": "^1.0.0",
382
382
  "ai": "^7.0.58",
383
383
  "braintrust": "^3.0.0",
384
- "just-bash": "^3.0.0",
384
+ "just-bash": "^3.1.0",
385
385
  "microsandbox": "^0.5.0"
386
386
  },
387
387
  "peerDependenciesMeta": {