eve 0.54.4 → 0.54.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # eve
2
2
 
3
+ ## 0.54.5
4
+
5
+ ### Patch Changes
6
+
7
+ - c112e13: Self-modifying agents now relay the exact `eve add` command required to finish an interactive registry installation instead of substituting a registry configuration command.
8
+ - 4652b5d: Slack channels can route each input request, including tool approvals and `ctx.ask()` questions, to the shared thread or the triggering user's direct messages with the `approvalChannel` callback. Direct-message requests include a preview of the Slack message that triggered the turn, while the original thread names the reviewer without exposing the request.
9
+ - 7ba0c77: Preserve the text of replies to Telegram approval prompts so typed approval responses resume the pending tool.
10
+ - 956205d: Teach root agents with self-modification to treat named product installation questions as requests to add capabilities, so they delegate registry discovery instead of denying device access or asking what kind of installation the user means.
11
+ - eef5381: Teach self-modification to implement reusable, model-callable actions as authored eve tools instead of standalone scripts, including when the request describes a capability without calling it a tool.
12
+
3
13
  ## 0.54.4
4
14
 
5
15
  ### Patch Changes
@@ -56,7 +56,7 @@
56
56
  "zod": "4.5.4",
57
57
  "zod-validation-error": "5.0.0"
58
58
  },
59
- "scriptHash": "a3e915e08fc1766558749b05b8b4f6bd9f1f9004e88d1d0ad7b2188a44e35fe0",
59
+ "scriptHash": "6dd35b7b0dab12a3395adfefa5f9a84d9e04ce6137d3d64434c468df5419375e",
60
60
  "toolVersions": {
61
61
  "rolldown": "1.2.7",
62
62
  "typescript": "7.0.2"
@@ -1 +1 @@
1
- import{sleep}from"#compiled/@workflow/core/index.js";async function executeSleepTool(e){"use workflow";return await sleep(Math.ceil(e.seconds*1e3)),{waitedSeconds:e.seconds}}executeSleepTool.workflowId=`workflow//eve@0.54.4//executeSleepTool`;export{executeSleepTool};
1
+ import{sleep}from"#compiled/@workflow/core/index.js";async function executeSleepTool(e){"use workflow";return await sleep(Math.ceil(e.seconds*1e3)),{waitedSeconds:e.seconds}}executeSleepTool.workflowId=`workflow//eve@0.54.5//executeSleepTool`;export{executeSleepTool};
@@ -1 +1 @@
1
- import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{fileURLToPath}from"node:url";import{EVE_PACKAGE_NAME}from"#internal/package-name.js";let cachedPackageInfo,cachedPackageLocation;const BUNDLED_FALLBACK_PACKAGE_VERSION=`0.54.4`,WORKFLOW_MODULE_ALIASES={"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`};function resolveFallbackPackageVersion(){return BUNDLED_FALLBACK_PACKAGE_VERSION.startsWith(`__`)?`0.0.0`:BUNDLED_FALLBACK_PACKAGE_VERSION}const FALLBACK_PACKAGE_INFO={name:EVE_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function tryResolveVerifiedPackageRoot(e){try{let t=realpathSync.native(e);return tryReadInstalledPackageInfo(t,EVE_PACKAGE_NAME)===void 0?void 0:dirname(t)}catch{return}}function findNearestVerifiedPackageRoot(e){let t=e;for(;;){let e=tryResolveVerifiedPackageRoot(join(t,`package.json`));if(e!==void 0)return e;let n=dirname(t);if(n===t)return;t=n}}function tryResolveDirectBuildLocation(e){let t=dirname(e);for(;;){if(basename(t)===`dist`){let e=tryResolveVerifiedPackageRoot(join(dirname(t),`package.json`));if(e!==void 0)return{packageBuildRoot:t,packageRoot:e}}let e=dirname(t);if(e===t)return;t=e}}function isSourceCheckout(e){return existsSync(join(e,`src`,`internal`,`application`,`package.ts`))}function tryCreatePackageLocation(e){if(isSourceCheckout(e))return{packageBuildRoot:null,packageRoot:e};let t=join(e,`dist`);if(existsSync(t))return{packageBuildRoot:t,packageRoot:e}}function resolveSelfPackageJsonPath(e){return createRequire(e).resolve(`${EVE_PACKAGE_NAME}/package.json`)}function resolvePackageLocationFromModulePath(e,t=resolveSelfPackageJsonPath){let n=realpathSync.native(e),r=tryResolveDirectBuildLocation(n);if(r!==void 0)return r;let i=findNearestVerifiedPackageRoot(dirname(n));if(i!==void 0&&isSourceCheckout(i))return{packageBuildRoot:null,packageRoot:i};try{let e=tryResolveVerifiedPackageRoot(t(n)),r=e===void 0?void 0:tryCreatePackageLocation(e);if(r!==void 0)return r}catch{}let a=i===void 0?void 0:tryCreatePackageLocation(i);if(a!==void 0)return a;throw Error(`Failed to resolve the eve package root from "${e}".`)}function resolvePackageLocation(){return cachedPackageLocation??=resolvePackageLocationFromModulePath(resolveCurrentModulePath()),cachedPackageLocation}function resolvePackageBuildRoot(){return resolvePackageLocation().packageBuildRoot}function resolvePackageRoot(){return resolvePackageLocation().packageRoot}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageDependencyPath(e){return require.resolve(e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(typeof t.name==`string`&&typeof t.version==`string`)return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),EVE_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${EVE_PACKAGE_NAME}/package.json`),EVE_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function readWorkflowVersionFromManifest(e){let t=e;for(let e of[t.devDependencies,t.dependencies,t.peerDependencies]){let t=e?.[`@workflow/core`];if(typeof t==`string`&&t.trim().length>0)return t}}function resolveExpectedWorkflowVersion(){let e=tryResolvePackageRoot();if(e!==void 0)try{return readWorkflowVersionFromManifest(JSON.parse(readFileSync(join(e,`package.json`),`utf8`)))}catch{}try{return readWorkflowVersionFromManifest(JSON.parse(readFileSync(require.resolve(`${EVE_PACKAGE_NAME}/package.json`),`utf8`)))}catch{return}}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/api`||e===`workflow/runtime`)return resolvePackageSourceFilePath(`src/internal/workflow/runtime.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveExpectedWorkflowVersion,resolveInstalledPackageInfo,resolvePackageCompiledFilePath,resolvePackageDependencyPath,resolvePackageLocationFromModulePath,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
1
+ import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{fileURLToPath}from"node:url";import{EVE_PACKAGE_NAME}from"#internal/package-name.js";let cachedPackageInfo,cachedPackageLocation;const BUNDLED_FALLBACK_PACKAGE_VERSION=`0.54.5`,WORKFLOW_MODULE_ALIASES={"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`};function resolveFallbackPackageVersion(){return BUNDLED_FALLBACK_PACKAGE_VERSION.startsWith(`__`)?`0.0.0`:BUNDLED_FALLBACK_PACKAGE_VERSION}const FALLBACK_PACKAGE_INFO={name:EVE_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function tryResolveVerifiedPackageRoot(e){try{let t=realpathSync.native(e);return tryReadInstalledPackageInfo(t,EVE_PACKAGE_NAME)===void 0?void 0:dirname(t)}catch{return}}function findNearestVerifiedPackageRoot(e){let t=e;for(;;){let e=tryResolveVerifiedPackageRoot(join(t,`package.json`));if(e!==void 0)return e;let n=dirname(t);if(n===t)return;t=n}}function tryResolveDirectBuildLocation(e){let t=dirname(e);for(;;){if(basename(t)===`dist`){let e=tryResolveVerifiedPackageRoot(join(dirname(t),`package.json`));if(e!==void 0)return{packageBuildRoot:t,packageRoot:e}}let e=dirname(t);if(e===t)return;t=e}}function isSourceCheckout(e){return existsSync(join(e,`src`,`internal`,`application`,`package.ts`))}function tryCreatePackageLocation(e){if(isSourceCheckout(e))return{packageBuildRoot:null,packageRoot:e};let t=join(e,`dist`);if(existsSync(t))return{packageBuildRoot:t,packageRoot:e}}function resolveSelfPackageJsonPath(e){return createRequire(e).resolve(`${EVE_PACKAGE_NAME}/package.json`)}function resolvePackageLocationFromModulePath(e,t=resolveSelfPackageJsonPath){let n=realpathSync.native(e),r=tryResolveDirectBuildLocation(n);if(r!==void 0)return r;let i=findNearestVerifiedPackageRoot(dirname(n));if(i!==void 0&&isSourceCheckout(i))return{packageBuildRoot:null,packageRoot:i};try{let e=tryResolveVerifiedPackageRoot(t(n)),r=e===void 0?void 0:tryCreatePackageLocation(e);if(r!==void 0)return r}catch{}let a=i===void 0?void 0:tryCreatePackageLocation(i);if(a!==void 0)return a;throw Error(`Failed to resolve the eve package root from "${e}".`)}function resolvePackageLocation(){return cachedPackageLocation??=resolvePackageLocationFromModulePath(resolveCurrentModulePath()),cachedPackageLocation}function resolvePackageBuildRoot(){return resolvePackageLocation().packageBuildRoot}function resolvePackageRoot(){return resolvePackageLocation().packageRoot}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageDependencyPath(e){return require.resolve(e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(typeof t.name==`string`&&typeof t.version==`string`)return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),EVE_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${EVE_PACKAGE_NAME}/package.json`),EVE_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function readWorkflowVersionFromManifest(e){let t=e;for(let e of[t.devDependencies,t.dependencies,t.peerDependencies]){let t=e?.[`@workflow/core`];if(typeof t==`string`&&t.trim().length>0)return t}}function resolveExpectedWorkflowVersion(){let e=tryResolvePackageRoot();if(e!==void 0)try{return readWorkflowVersionFromManifest(JSON.parse(readFileSync(join(e,`package.json`),`utf8`)))}catch{}try{return readWorkflowVersionFromManifest(JSON.parse(readFileSync(require.resolve(`${EVE_PACKAGE_NAME}/package.json`),`utf8`)))}catch{return}}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/api`||e===`workflow/runtime`)return resolvePackageSourceFilePath(`src/internal/workflow/runtime.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveExpectedWorkflowVersion,resolveInstalledPackageInfo,resolvePackageCompiledFilePath,resolvePackageDependencyPath,resolvePackageLocationFromModulePath,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
@@ -1,6 +1,6 @@
1
1
  import type { SessionAuthContext } from "#channel/types.js";
2
2
  import type { SlackMessage } from "#public/channels/slack/inbound.js";
3
- import type { SlackChannelEvents, SlackChannelInternalEvents, SlackContext, SlackMentionResult } from "#public/channels/slack/slackChannel.js";
3
+ import type { SlackApprovalChannelResolver, SlackChannelEvents, SlackChannelInternalEvents, SlackContext, SlackMentionResult } from "#public/channels/slack/slackChannel.js";
4
4
  /**
5
5
  * Workspace-scoped projection of the Slack actor that produced
6
6
  * `message`, derived into a {@link SessionAuthContext}. Used by both
@@ -29,7 +29,7 @@ export declare function defaultOnDirectMessage(ctx: SlackContext, message: Slack
29
29
  * Slack's 50-block message cap. Override by declaring
30
30
  * `events["input.requested"]`.
31
31
  */
32
- export declare function defaultInputRequestedHandler(): NonNullable<SlackChannelEvents["input.requested"]>;
32
+ export declare function defaultInputRequestedHandler(approvalChannel?: SlackApprovalChannelResolver): NonNullable<SlackChannelEvents["input.requested"]>;
33
33
  /**
34
34
  * Delivers a completed default Slack reply without sending content that
35
35
  * exceeds Slack's native Markdown limit. Long replies stay intact as one
@@ -1,7 +1,7 @@
1
- import{createLogger,extractErrorId,formatErrorHint}from"#internal/logging.js";import{SLACK_MARKDOWN_TEXT_MAX_LENGTH,SLACK_MAX_BLOCKS_PER_MESSAGE,truncateMessageText,truncateTypingStatus}from"#public/channels/slack/limits.js";import{describeActionRequests}from"#public/channels/slack/action-status.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{buildAuthCompletedText,buildAuthEphemeralBlocks,buildAuthRequiredPublicText,formatConnectionDisplayName}from"#public/channels/slack/connections.js";import{buildAnsweredBlocks,renderInputRequestPostParts}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.defaults`),LONG_RESPONSE_NOTICE=`Here's a snippet with the full response.`,reasoningByState=new WeakMap;function extractSemanticErrorSummary(e){if(typeof e.details!=`object`||e.details===null)return null;let t=e.details;if(typeof t.semanticErrorId!=`string`||t.semanticErrorId.length===0||typeof t.name!=`string`||t.name.length===0)return null;let n=typeof t.message==`string`&&t.message.trim().length>0?t.message.trim():e.message?.trim();if(!n)return null;let r=typeof t.hint==`string`&&t.hint.trim().length>0?t.hint.trim():void 0;return r===void 0?{message:n,name:t.name}:{hint:r,message:n,name:t.name}}function formatSemanticErrorBlock(e,t){return[`### ${e.name}`,``,e.message,...e.hint?[``,`**How to fix**`,e.hint]:[],...t?[``,`**Error id:**`,`\`${t}\``]:[]].flatMap(e=>e.split(`
1
+ import{createLogger,extractErrorId,formatErrorHint,logError}from"#internal/logging.js";import{SLACK_MARKDOWN_TEXT_MAX_LENGTH,SLACK_MAX_BLOCKS_PER_MESSAGE,truncateMessageText,truncateTypingStatus}from"#public/channels/slack/limits.js";import{describeActionRequests}from"#public/channels/slack/action-status.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{buildAuthCompletedText,buildAuthEphemeralBlocks,buildAuthRequiredPublicText,formatConnectionDisplayName}from"#public/channels/slack/connections.js";import{buildAnsweredBlocks,decodeHitlActionId,renderInputRequestPostParts}from"#public/channels/slack/hitl.js";import{deliverPrivateInputRequest}from"#public/channels/slack/private-approval-delivery.js";const log=createLogger(`slack.defaults`),LONG_RESPONSE_NOTICE=`Here's a snippet with the full response.`,reasoningByState=new WeakMap;function extractSemanticErrorSummary(e){if(typeof e.details!=`object`||e.details===null)return null;let t=e.details;if(typeof t.semanticErrorId!=`string`||t.semanticErrorId.length===0||typeof t.name!=`string`||t.name.length===0)return null;let n=typeof t.message==`string`&&t.message.trim().length>0?t.message.trim():e.message?.trim();if(!n)return null;let r=typeof t.hint==`string`&&t.hint.trim().length>0?t.hint.trim():void 0;return r===void 0?{message:n,name:t.name}:{hint:r,message:n,name:t.name}}function formatSemanticErrorBlock(e,t){return[`### ${e.name}`,``,e.message,...e.hint?[``,`**How to fix**`,e.hint]:[],...t?[``,`**Error id:**`,`\`${t}\``]:[]].flatMap(e=>e.split(`
2
2
  `)).map(e=>e.length>0?`> ${e}`:`> `).join(`
3
3
  `)}function formatSemanticErrorReply(e){return[`${e.introduction}.`,``,formatSemanticErrorBlock(e.summary,e.errorId),...e.summary.hint?[]:[``,e.nextStep],...e.summary.hint&&e.followUp?[``,e.followUp]:[]].join(`
4
- `)}function blockContainsRequestAction(e,t){if(typeof e!=`object`||!e)return!1;let n=e,r=`eve_input:${t}`,i=`eve_input:tool-approval:${t}`;return[n.actions,n.elements].some(e=>Array.isArray(e)&&e.some(e=>{if(typeof e!=`object`||!e)return!1;let t=e.action_id;return typeof t==`string`&&(t.startsWith(r)||t.startsWith(i))}))}function defaultSlackAuth(e,t){let n=e.author;return n?buildSlackAuthContext({channelId:t.slack.channelId,fullName:n.fullName,isBot:n.isBot,teamId:e.teamId,threadTs:t.slack.threadTs,userId:n.userId,userName:n.userName}):null}async function defaultOnAppMention(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}async function defaultOnDirectMessage(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}function defaultInputRequestedHandler(){return async(e,t,n)=>{for(let n of buildInputRequestPosts(e.requests)){let e=await t.thread.post({blocks:n.blocks,text:n.text});if(!e.id)continue;let r={...t.state.pendingApprovalCards};for(let t of n.requests)t.kind===`tool-approval`&&(r[t.requestId]={messageBlocks:n.blocks,messageTs:e.id});t.state.pendingApprovalCards=r}}}function buildInputRequestPosts(e){let t=[],n=[];for(let r of e){let e=renderInputRequestPostParts(r);e.details&&t.push({...e.details,request:r}),n.push({...e.controls,request:r})}return[...groupInputRequestPostParts(t),...groupInputRequestPostParts(n)]}function groupInputRequestPostParts(e){let t=[];for(let n of e){let e=t.at(-1);e&&e.blocks.length+n.blocks.length<=SLACK_MAX_BLOCKS_PER_MESSAGE?(e.blocks.push(...n.blocks),e.fallbacks.push(n.text),e.requests.push(n.request)):t.push({blocks:[...n.blocks],fallbacks:[n.text],requests:[n.request]})}return t.map(e=>({blocks:e.blocks,requests:e.requests,text:truncateMessageText(e.fallbacks.join(`
5
- `))}))}async function postCompletedSlackReply(e,t){if(t.length<=SLACK_MARKDOWN_TEXT_MAX_LENGTH){await e.thread.post(t);return}let n={data:new Blob([t],{type:`text/markdown`}),filename:`eve-response.md`,mimeType:`text/markdown`},r=e.slack.threadTs.length>0;if(!r&&(!(await e.thread.post(LONG_RESPONSE_NOTICE)).id||e.slack.threadTs.length===0))throw Error(`Slack did not return a thread timestamp for the long response notice.`);await e.slack.uploadFiles([n],{initialComment:r?LONG_RESPONSE_NOTICE:void 0,snippetType:`markdown`})}const defaultEvents={async"approval.candidate"(e,t,n){let r=t.state.pendingApprovalCandidateUsers?.[e.candidateId];if(e.outcome===`pending`&&r!==void 0){await t.thread.postEphemeral(r,`Checking whether you can approve this action…`);return}r!==void 0&&(e.outcome===`rejected`||e.outcome===`failed`)&&await t.thread.postEphemeral(r,e.reason??`We couldn’t verify your approval. Please try again.`)},async"approval.settled"(e,t,n){let r=t.state.pendingApprovalCards??{},i=r[e.requestId];if(i===void 0||t.state.channelId===null)return;let a=e.outcome===`approved`?`Approve`:`Cancel`,o=t.state.approvalResponderUsers?.[e.responderPrincipalId],s=i.messageBlocks.flatMap(t=>{if(!blockContainsRequestAction(t,e.requestId))return[t];if(typeof t!=`object`||!t)return[];let n=t;if(n.type!==`card`)return buildAnsweredBlocks({answerLabel:a,promptBlocks:[],userId:o});let{actions:r,...i}=n;return buildAnsweredBlocks({answerLabel:a,promptBlocks:[i],userId:o})});await t.slack.request(`chat.update`,{blocks:s,channel:t.state.channelId,text:`Answered: ${a}`,ts:i.messageTs});let c={...r};delete c[e.requestId];for(let[e,t]of Object.entries(c))t.messageTs===i.messageTs&&(c[e]={...t,messageBlocks:s});t.state.pendingApprovalCards=c},async"turn.started"(e,t,n){t.state.pendingToolCallMessage=null,t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null,reasoningByState.delete(t.state),await t.thread.startTyping(`Working...`)},async"reasoning.appended"(e,t,n){let r=reasoningByState.get(t.state),i=r?.turnId===e.turnId&&r.stepIndex===e.stepIndex;i||(t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null);let a=(i?r.text:``)+e.reasoningDelta;reasoningByState.set(t.state,{stepIndex:e.stepIndex,text:a,turnId:e.turnId});let o=firstNonEmptyLine(a);if(o===void 0)return;let s=truncateTypingStatus(o),c=t.state.lastReasoningTypingStatus,l=c!=null&&s.startsWith(c)&&s.length>=c.length+4,u=Date.now(),d=t.state.lastReasoningTypingAtMs;if(!l&&d!=null){let e=u-d;if(e>=0&&e<5e3)return}await t.thread.startTyping(s),t.state.lastReasoningTypingAtMs=u,t.state.lastReasoningTypingStatus=s},async"reasoning.completed"(e,t,n){let r=reasoningByState.get(t.state);r?.turnId===e.turnId&&r.stepIndex===e.stepIndex&&(reasoningByState.delete(t.state),t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null)},async"actions.requested"(e,t,n){let r=t.state.pendingToolCallMessage;if(t.state.pendingToolCallMessage=null,r){await t.thread.startTyping(truncateTypingStatus(r));return}await t.thread.startTyping(truncateTypingStatus(describeActionRequests(e.actions)))},async"message.completed"(e,t,n){if(e.finishReason===`tool-calls`){t.state.pendingToolCallMessage=e.message?firstNonEmptyLine(e.message)??null:null;return}if(t.state.pendingToolCallMessage=null,!e.message){await t.thread.startTyping();return}await postCompletedSlackReply(t,e.message)},async"turn.failed"(e,t,n){let r=extractErrorId(e.details),i=extractSemanticErrorSummary(e);if(i!==null){await t.thread.post(formatSemanticErrorReply({errorId:r,introduction:`I hit an error while handling your request`,nextStep:`Please try again, rephrase, or reach out if it keeps failing.`,summary:i}));return}let a=formatErrorHint(e);await t.thread.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...r?[``,`_Error id: \`${r}\`_`]:[]].join(`
4
+ `)}function blockContainsRequestAction(e,t){if(typeof e!=`object`||!e)return!1;let n=e;return[n.actions,n.elements].some(e=>Array.isArray(e)&&e.some(e=>{if(typeof e!=`object`||!e)return!1;let n=e.action_id;return typeof n==`string`&&decodeHitlActionId(n)?.requestId===t}))}function defaultSlackAuth(e,t){let n=e.author;return n?buildSlackAuthContext({channelId:t.slack.channelId,fullName:n.fullName,isBot:n.isBot,teamId:e.teamId,threadTs:t.slack.threadTs,userId:n.userId,userName:n.userName}):null}async function defaultOnAppMention(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}async function defaultOnDirectMessage(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}function defaultInputRequestedHandler(e){return async(t,n,r)=>{let i=[],a=[];for(let n of t.requests)((e===void 0?`thread`:await e(n,r))===`direct-message`?i:a).push(n);await postPublicInputRequests(a,n);for(let e of i){let t=slackUserIdFromAuthContext(r.session.auth.current)??n.state.triggeringUserId;if(!t){log.warn(`direct-message input request not delivered because no reviewer was resolved`,{requestId:e.requestId,sessionId:r.session.id});continue}let i=await deliverPrivateInputRequest({previewMessageTs:n.state.triggeringMessageTs??n.slack.threadTs,request:e,reviewer:t,slack:n.slack});recordApprovalCards(n.state,[e],i);try{await n.thread.post(`Waiting on ${e.kind===`tool-approval`?`approval`:`a response`} from <@${t}>…`)}catch(t){logError(log,`failed to announce private input request`,t,{requestId:e.requestId,sessionId:r.session.id})}}}}async function postPublicInputRequests(e,t){for(let n of buildInputRequestPosts(e)){let e=await t.thread.post({blocks:n.blocks,text:n.text});recordApprovalCards(t.state,n.requests,{messageBlocks:n.blocks,messageTs:e.id})}}function recordApprovalCards(e,t,n){if(!n.messageTs)return;let r={...e.pendingApprovalCards};for(let e of t)e.kind===`tool-approval`&&(r[e.requestId]=n);e.pendingApprovalCards=r}function buildInputRequestPosts(e){let t=[],n=[];for(let r of e){let e=renderInputRequestPostParts(r);e.details&&t.push({...e.details,request:r}),n.push({...e.controls,request:r})}return[...groupInputRequestPostParts(t),...groupInputRequestPostParts(n)]}function groupInputRequestPostParts(e){let t=[];for(let n of e){let e=t.at(-1);e&&e.blocks.length+n.blocks.length<=SLACK_MAX_BLOCKS_PER_MESSAGE?(e.blocks.push(...n.blocks),e.fallbacks.push(n.text),e.requests.push(n.request)):t.push({blocks:[...n.blocks],fallbacks:[n.text],requests:[n.request]})}return t.map(e=>({blocks:e.blocks,requests:e.requests,text:truncateMessageText(e.fallbacks.join(`
5
+ `))}))}async function postCompletedSlackReply(e,t){if(t.length<=SLACK_MARKDOWN_TEXT_MAX_LENGTH){await e.thread.post(t);return}let n={data:new Blob([t],{type:`text/markdown`}),filename:`eve-response.md`,mimeType:`text/markdown`},r=e.slack.threadTs.length>0;if(!r&&(!(await e.thread.post(LONG_RESPONSE_NOTICE)).id||e.slack.threadTs.length===0))throw Error(`Slack did not return a thread timestamp for the long response notice.`);await e.slack.uploadFiles([n],{initialComment:r?LONG_RESPONSE_NOTICE:void 0,snippetType:`markdown`})}const defaultEvents={async"approval.candidate"(e,t,n){let r=t.state.pendingApprovalCandidateUsers?.[e.candidateId];if(e.outcome===`pending`&&r!==void 0){await t.thread.postEphemeral(r,`Checking whether you can approve this action…`);return}r!==void 0&&(e.outcome===`rejected`||e.outcome===`failed`)&&await t.thread.postEphemeral(r,e.reason??`We couldn’t verify your approval. Please try again.`)},async"approval.settled"(e,t,n){let r=t.state.pendingApprovalCards??{},i=r[e.requestId];if(i===void 0)return;let a=i.messageChannelId??t.state.channelId;if(a===null)return;let o=e.outcome===`approved`?`Approve`:`Cancel`,s=t.state.approvalResponderUsers?.[e.responderPrincipalId],c=i.messageBlocks.flatMap(t=>{if(!blockContainsRequestAction(t,e.requestId))return[t];if(typeof t!=`object`||!t)return[];let n=t;if(n.type!==`card`)return buildAnsweredBlocks({answerLabel:o,promptBlocks:[],userId:s});let{actions:r,...i}=n;return buildAnsweredBlocks({answerLabel:o,promptBlocks:[i],userId:s})});await t.slack.request(`chat.update`,{blocks:c,channel:a,text:`Answered: ${o}`,ts:i.messageTs});let l={...r};delete l[e.requestId];for(let[e,t]of Object.entries(l))t.messageTs===i.messageTs&&(l[e]={...t,messageBlocks:c});t.state.pendingApprovalCards=l},async"turn.started"(e,t,n){t.state.pendingToolCallMessage=null,t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null,reasoningByState.delete(t.state),await t.thread.startTyping(`Working...`)},async"reasoning.appended"(e,t,n){let r=reasoningByState.get(t.state),i=r?.turnId===e.turnId&&r.stepIndex===e.stepIndex;i||(t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null);let a=(i?r.text:``)+e.reasoningDelta;reasoningByState.set(t.state,{stepIndex:e.stepIndex,text:a,turnId:e.turnId});let o=firstNonEmptyLine(a);if(o===void 0)return;let s=truncateTypingStatus(o),c=t.state.lastReasoningTypingStatus,l=c!=null&&s.startsWith(c)&&s.length>=c.length+4,u=Date.now(),d=t.state.lastReasoningTypingAtMs;if(!l&&d!=null){let e=u-d;if(e>=0&&e<5e3)return}await t.thread.startTyping(s),t.state.lastReasoningTypingAtMs=u,t.state.lastReasoningTypingStatus=s},async"reasoning.completed"(e,t,n){let r=reasoningByState.get(t.state);r?.turnId===e.turnId&&r.stepIndex===e.stepIndex&&(reasoningByState.delete(t.state),t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null)},async"actions.requested"(e,t,n){let r=t.state.pendingToolCallMessage;if(t.state.pendingToolCallMessage=null,r){await t.thread.startTyping(truncateTypingStatus(r));return}await t.thread.startTyping(truncateTypingStatus(describeActionRequests(e.actions)))},async"message.completed"(e,t,n){if(e.finishReason===`tool-calls`){t.state.pendingToolCallMessage=e.message?firstNonEmptyLine(e.message)??null:null;return}if(t.state.pendingToolCallMessage=null,!e.message){await t.thread.startTyping();return}await postCompletedSlackReply(t,e.message)},async"turn.failed"(e,t,n){let r=extractErrorId(e.details),i=extractSemanticErrorSummary(e);if(i!==null){await t.thread.post(formatSemanticErrorReply({errorId:r,introduction:`I hit an error while handling your request`,nextStep:`Please try again, rephrase, or reach out if it keeps failing.`,summary:i}));return}let a=formatErrorHint(e);await t.thread.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...r?[``,`_Error id: \`${r}\`_`]:[]].join(`
6
6
  `))},async"session.failed"(e,t){let n=extractErrorId(e.details),r=extractSemanticErrorSummary(e);if(r!==null){await t.thread.post(formatSemanticErrorReply({errorId:n,followUp:`Start a new thread to continue — I can't pick this one back up.`,introduction:`This session couldn't recover from an error`,nextStep:`Resolve the issue, then start a new thread — I can't pick this one back up.`,summary:r}));return}let i=formatErrorHint(e);await t.thread.post([`This session couldn't recover from an error${i}.`,``,`Start a new thread to continue — I can't pick this one back up.`,...n?[``,`_Error id: \`${n}\`_`]:[]].join(`
7
7
  `))},async"authorization.required"(e,t,n){let r=e.authorization?.displayName??formatConnectionDisplayName(e.name),i=e.candidateId===void 0?slackUserIdFromAuthContext(n.session.auth.current)??t.state.triggeringUserId??null:t.state.pendingApprovalCandidateUsers?.[e.candidateId]??null,a=e.authorization?.url,o=t.state.pendingAuthMessageTs??{};if(e.candidateId===void 0&&o[e.name]===void 0){let n=buildAuthRequiredPublicText({displayName:r,hasUser:i!==null});try{let r=await t.thread.post(n);r.id&&(t.state.pendingAuthMessageTs={...o,[e.name]:r.id})}catch(t){log.error(`Slack auth public message delivery failed`,{name:e.name,error:t})}}if(i&&a){let n=e.authorization?.userCode;try{await t.thread.postEphemeral(i,{blocks:buildAuthEphemeralBlocks({displayName:r,url:a,userCode:n}),text:n?`Sign in with ${r}: ${a} (code: ${n})`:`Sign in with ${r}: ${a}`})}catch(t){log.error(`Slack auth ephemeral delivery failed`,{name:e.name,error:t})}}},async"authorization.completed"(e,t,n){let r=e.authorization?.displayName??formatConnectionDisplayName(e.name);e.outcome===`authorized`&&e.candidateId===void 0&&await t.thread.startTyping(`Connected to ${r}. Resuming...`);let i=t.state.pendingAuthMessageTs??{},a=i[e.name];if(a===void 0)return;let o=buildAuthCompletedText({displayName:r,outcome:e.outcome,reason:e.reason});try{await t.slack.request(`chat.update`,{channel:t.slack.channelId,ts:a,text:o})}catch(t){log.error(`Slack auth status edit failed`,{name:e.name,error:t})}let s={...i};delete s[e.name],t.state.pendingAuthMessageTs=s}};export{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage,defaultSlackAuth,postCompletedSlackReply};
@@ -40,6 +40,10 @@ export declare const HITL_FREEFORM_MODAL_BLOCK_ID = "eve_freeform_block";
40
40
  * `action_id` of the text input inside the freeform answer modal.
41
41
  */
42
42
  export declare const HITL_FREEFORM_MODAL_ACTION_ID = "eve_freeform_text";
43
+ export interface SlackHitlRoute {
44
+ readonly channelId: string;
45
+ readonly threadTs: string;
46
+ }
43
47
  /**
44
48
  * Subset of one Slack interactivity action the HITL decoder reads.
45
49
  * Mirrors the relevant fields of `SlackInteractionAction`.
@@ -57,8 +61,15 @@ interface SlackHitlAction {
57
61
  * presentation metadata cannot cross the durable session-inbox boundary.
58
62
  */
59
63
  interface DerivedHitlResponse {
60
- readonly kind?: "tool-approval";
61
- readonly response: ValidatedInputResponse;
64
+ kind?: "tool-approval";
65
+ response: ValidatedInputResponse;
66
+ route?: SlackHitlRoute;
67
+ }
68
+ interface DecodedHitlActionId {
69
+ button: boolean;
70
+ kind?: "tool-approval";
71
+ requestId: string;
72
+ route?: SlackHitlRoute;
62
73
  }
63
74
  /**
64
75
  * Decodes one Slack interactivity action into an HITL response, or
@@ -72,6 +83,8 @@ export declare function deriveHitlResponse(action: SlackHitlAction): DerivedHitl
72
83
  * clicks into the HITL path vs. the user-owned `onInteraction` path.
73
84
  */
74
85
  export declare function isHitlAction(actionId: string): boolean;
86
+ /** Decodes the framework-owned identity and optional return route from one action id. */
87
+ export declare function decodeHitlActionId(actionId: string): DecodedHitlActionId | null;
75
88
  /**
76
89
  * Renders one `InputRequest` as Block Kit blocks:
77
90
  *
@@ -90,7 +103,7 @@ export declare function isHitlAction(actionId: string): boolean;
90
103
  *
91
104
  * Always emits at least the prompt section.
92
105
  */
93
- export declare function renderInputRequestBlocks(request: InputRequest): unknown[];
106
+ export declare function renderInputRequestBlocks(request: InputRequest, route?: SlackHitlRoute): unknown[];
94
107
  export interface SlackInputRequestPostPart {
95
108
  readonly blocks: unknown[];
96
109
  readonly text: string;
@@ -100,7 +113,7 @@ export interface SlackInputRequestPostPart {
100
113
  * originating message in `block_actions`, so putting large tool input beside
101
114
  * the buttons makes the callback body grow with model-authored input.
102
115
  */
103
- export declare function renderInputRequestPostParts(request: InputRequest): {
116
+ export declare function renderInputRequestPostParts(request: InputRequest, route?: SlackHitlRoute): {
104
117
  readonly controls: SlackInputRequestPostPart;
105
118
  readonly details?: SlackInputRequestPostPart;
106
119
  };
@@ -122,6 +135,8 @@ export interface HitlFreeformModalMetadata {
122
135
  /** Workspace whose app installation supplies credentials for modal follow-up API calls. */
123
136
  readonly installationTeamId?: string;
124
137
  readonly threadTs: string;
138
+ /** Slack conversation containing the interactive card. */
139
+ readonly messageChannelId?: string;
125
140
  readonly messageTs: string;
126
141
  readonly requestId: string;
127
142
  }
@@ -145,6 +160,7 @@ export declare function isFreeformAction(actionId: string): boolean;
145
160
  * Extracts the requestId from a freeform-answer button's `action_id`.
146
161
  */
147
162
  export declare function freeformRequestIdFromActionId(actionId: string): string | undefined;
163
+ export declare function decodeFreeformHitlActionId(actionId: string): DecodedHitlActionId | null;
148
164
  /**
149
165
  * Renders the "answered" replacement blocks for a previously-posted
150
166
  * HITL card. Preserves the original prompt/detail blocks (so context
@@ -1,4 +1,4 @@
1
- import{parseInputResponse}from"#shared/input.js";import{SLACK_SECTION_TEXT_MAX_LENGTH,truncateCardBodyText,truncateModalTitle,truncatePlainText,truncateSectionText}from"#public/channels/slack/limits.js";const HITL_ACTION_PREFIX=`eve_input:`,HITL_FREEFORM_ACTION_PREFIX=`eve_input_freeform:`,HITL_FREEFORM_MODAL_CALLBACK_ID=`eve_input_freeform_submit`,HITL_FREEFORM_MODAL_BLOCK_ID=`eve_freeform_block`,HITL_FREEFORM_MODAL_ACTION_ID=`eve_freeform_text`,BUTTON_ACTION_ID_RE=/^(?:(?<kind>tool-approval):)?(?<requestId>.+):button:\d+$/u,TOOL_INPUT_SUFFIX="\n```";function deriveHitlResponse(e){if(!e.actionId.startsWith(`eve_input:`))return null;let t=e.actionId.slice(10);if(e.selectedOptionValue!==void 0){let{kind:n,requestId:r}=splitEncodedRequest(t);return r?n===`tool-approval`?{kind:n,response:parseInputResponse({optionId:e.selectedOptionValue,requestId:r})}:{response:parseInputResponse({optionId:e.selectedOptionValue,requestId:r})}:null}if(e.value!==void 0){let n=BUTTON_ACTION_ID_RE.exec(t),r=n?.groups?.requestId;return r?n.groups?.kind===`tool-approval`?{kind:`tool-approval`,response:parseInputResponse({optionId:e.value,requestId:r})}:{response:parseInputResponse({optionId:e.value,requestId:r})}:null}return null}function splitEncodedRequest(e){return e.startsWith(`tool-approval:`)?{kind:`tool-approval`,requestId:e.slice(14)}:{requestId:e}}function isHitlAction(e){return e.startsWith(HITL_ACTION_PREFIX)}function renderInputRequestBlocks(e){let t={text:{text:truncateSectionText(e.prompt),type:`mrkdwn`},type:`section`},n=renderInputRequestDetailBlocks(e),r=`${HITL_ACTION_PREFIX}${e.kind===`tool-approval`?`tool-approval:`:``}${e.requestId}`,i=e.options,a=e.allowFreeform===!0||!i||i.length===0;if(i&&i.length>0&&e.display===`select`){let e=i.length<=6?{type:`radio_buttons`,action_id:r,options:i.map(buildOption)}:{type:`static_select`,action_id:r,options:i.map(buildOption),placeholder:{type:`plain_text`,text:`Choose an option`}};return[t,...n,{type:`actions`,elements:[e]}]}if(i&&i.length>0){let t=renderInputRequestCardBlock(e,r),n=renderToolInputContainerBlock(e);return n===void 0?[t]:[t,n]}return a?[t,...n,{type:`actions`,elements:[{type:`button`,action_id:`${HITL_FREEFORM_ACTION_PREFIX}${e.requestId}`,text:{type:`plain_text`,text:`Type your answer`},style:`primary`,value:e.requestId}]}]:[t]}function renderInputRequestPostParts(e){let t=renderInputRequestBlocks(e),n=t[0];return!isApprovalRequest(e)||!isBlockType(n,`card`)||t.length===1?{controls:{blocks:t,text:formatInputRequestFallbackText(e)}}:{controls:{blocks:[n],text:e.prompt},details:{blocks:t.slice(1),text:formatInputRequestFallbackText(e)}}}function formatInputRequestFallbackText(e){let t=formatToolInputDetails(e);return t===void 0?e.prompt:`${e.prompt}\n${t}`}function buildFreeformModalView(e){let t=e.prompt?truncateModalTitle(e.prompt):`Your answer`,n=e.prompt?[{type:`section`,text:{type:`mrkdwn`,text:truncateSectionText(e.prompt)}}]:[];return{type:`modal`,callback_id:HITL_FREEFORM_MODAL_CALLBACK_ID,private_metadata:JSON.stringify(e.metadata),title:{type:`plain_text`,text:t},submit:{type:`plain_text`,text:`Submit`},close:{type:`plain_text`,text:`Cancel`},blocks:[...n,{type:`input`,block_id:HITL_FREEFORM_MODAL_BLOCK_ID,element:{type:`plain_text_input`,action_id:HITL_FREEFORM_MODAL_ACTION_ID,multiline:!0,placeholder:{type:`plain_text`,text:`Type your answer here...`}},label:{type:`plain_text`,text:`Answer`}}]}}function isFreeformAction(e){return e.startsWith(HITL_FREEFORM_ACTION_PREFIX)}function freeformRequestIdFromActionId(e){if(!isFreeformAction(e))return;let t=e.slice(19);return t.length>0?t:void 0}function buildCardButton(e,t,n){let r={type:`button`,text:{type:`plain_text`,text:truncatePlainText(e.label),emoji:!1},action_id:`${t}:button:${n}`,value:e.id};return(e.style===`primary`||e.style===`danger`)&&(r.style=e.style),r}function buildOption(e){let t={text:{text:truncatePlainText(e.label),type:`plain_text`},value:e.id},n=truncatePlainText(e.description);return n&&n.length>0&&(t.description={text:n,type:`plain_text`}),t}function renderInputRequestCardBlock(e,t){return{type:`card`,body:{type:`mrkdwn`,text:truncateCardBodyText(`*${e.prompt}*`),verbatim:!1},actions:cardButtonOptions(e).map((e,n)=>buildCardButton(e,t,n))}}function cardButtonOptions(e){let t=e.options??[];if(!isApprovalRequest(e))return t.map(toCardButtonOption);let n=t.find(e=>e.id===`approve`),r=t.find(e=>e.id===`cancel`);return!n||!r?t.map(toCardButtonOption):[{id:r.id,label:`Cancel`},{id:n.id,label:`Approve`,style:`primary`}]}function toCardButtonOption(e){let t={id:e.id,label:e.label};return e.style===`primary`||e.style===`danger`?{...t,style:e.style}:t}function buildAnsweredBlocks(e){let t=[];for(let n of e.promptBlocks)n!=null&&t.push(n);let n=truncateWithEllipsis(e.answerLabel,SLACK_SECTION_TEXT_MAX_LENGTH-20-1);return t.push({type:`section`,text:{type:`mrkdwn`,text:`:white_check_mark: *${n}*`}}),e.userId&&e.userId.length>0&&t.push({type:`context`,elements:[{type:`mrkdwn`,text:`Answered by <@${e.userId}>`}]}),t}function renderInputRequestDetailBlocks(e){let t=formatToolInputDetails(e);return t===void 0?[]:[{type:`section`,text:{type:`mrkdwn`,text:t}}]}function renderToolInputContainerBlock(e){let t=formatToolInputContainerText(e);if(t!==void 0)return{type:`container`,title:{type:`plain_text`,text:`Tool input`},is_collapsible:!0,default_collapsed:!1,child_blocks:[{type:`section`,text:{type:`mrkdwn`,text:t}}]}}function formatToolInputContainerText(e){if(!isApprovalRequest(e))return;let t=JSON.stringify(e.action.input,null,2);return t===`{}`?void 0:`\`\`\`
1
+ import{parseInputResponse}from"#shared/input.js";import{SLACK_SECTION_TEXT_MAX_LENGTH,truncateCardBodyText,truncateModalTitle,truncatePlainText,truncateSectionText}from"#public/channels/slack/limits.js";const HITL_ACTION_PREFIX=`eve_input:`,HITL_FREEFORM_ACTION_PREFIX=`eve_input_freeform:`,HITL_FREEFORM_MODAL_CALLBACK_ID=`eve_input_freeform_submit`,HITL_FREEFORM_MODAL_BLOCK_ID=`eve_freeform_block`,HITL_FREEFORM_MODAL_ACTION_ID=`eve_freeform_text`,HITL_ROUTE_PREFIX=`${HITL_ACTION_PREFIX}route:`,BUTTON_ACTION_ID_RE=/^(?:(?<kind>tool-approval):)?(?<requestId>.+):button:\d+$/u,TOOL_INPUT_SUFFIX="\n```";function deriveHitlResponse(e){if(!e.actionId.startsWith(`eve_input:`))return null;let t=decodeHitlActionId(e.actionId);if(t===null)return null;let n=e.selectedOptionValue??e.value;if(n===void 0||e.value!==void 0&&!t.button)return null;let r={response:parseInputResponse({optionId:n,requestId:t.requestId})};return t.kind!==void 0&&(r.kind=t.kind),t.route!==void 0&&(r.route=t.route),r}function splitEncodedRequest(e){return e.startsWith(`tool-approval:`)?{kind:`tool-approval`,requestId:e.slice(14)}:{requestId:e}}function isHitlAction(e){return e.startsWith(HITL_ACTION_PREFIX)}function decodeHitlActionId(e){if(!e.startsWith(`eve_input:`))return null;let t=e.slice(10),n;if(t.startsWith(`route:`)){t=t.slice(6);let e=t.indexOf(`:`),r=t.indexOf(`:`,e+1);if(e<=0||r<=e+1)return null;n={channelId:t.slice(0,e),threadTs:t.slice(e+1,r)},t=t.slice(r+1)}let r=BUTTON_ACTION_ID_RE.exec(t),i=splitEncodedRequest(r?.groups?.requestId??t);if(!i.requestId)return null;let a=r?.groups?.kind===`tool-approval`?`tool-approval`:i.kind,o={button:r!==null,requestId:i.requestId};return a!==void 0&&(o.kind=a),n!==void 0&&(o.route=n),o}function encodeHitlActionId(e,t){let n=`${e.kind===`tool-approval`?`tool-approval:`:``}${e.requestId}`;return t===void 0?`${HITL_ACTION_PREFIX}${n}`:`${HITL_ROUTE_PREFIX}${t.channelId}:${t.threadTs}:${n}`}function renderInputRequestBlocks(e,t){let n={text:{text:truncateSectionText(e.prompt),type:`mrkdwn`},type:`section`},r=renderInputRequestDetailBlocks(e),i=encodeHitlActionId(e,t),a=e.options,o=e.allowFreeform===!0||!a||a.length===0;if(a&&a.length>0&&e.display===`select`){let e=a.length<=6?{type:`radio_buttons`,action_id:i,options:a.map(buildOption)}:{type:`static_select`,action_id:i,options:a.map(buildOption),placeholder:{type:`plain_text`,text:`Choose an option`}};return[n,...r,{type:`actions`,elements:[e]}]}if(a&&a.length>0){let t=renderInputRequestCardBlock(e,i),n=renderToolInputContainerBlock(e);return n===void 0?[t]:[t,n]}if(o){let t=i.replace(HITL_ACTION_PREFIX,HITL_FREEFORM_ACTION_PREFIX);return[n,...r,{type:`actions`,elements:[{type:`button`,action_id:t,text:{type:`plain_text`,text:`Type your answer`},style:`primary`,value:e.requestId}]}]}return[n]}function renderInputRequestPostParts(e,t){let n=renderInputRequestBlocks(e,t),r=n[0];return!isApprovalRequest(e)||!isBlockType(r,`card`)||n.length===1?{controls:{blocks:n,text:formatInputRequestFallbackText(e)}}:{controls:{blocks:[r],text:e.prompt},details:{blocks:n.slice(1),text:formatInputRequestFallbackText(e)}}}function formatInputRequestFallbackText(e){let t=formatToolInputDetails(e);return t===void 0?e.prompt:`${e.prompt}\n${t}`}function buildFreeformModalView(e){let t=e.prompt?truncateModalTitle(e.prompt):`Your answer`,n=e.prompt?[{type:`section`,text:{type:`mrkdwn`,text:truncateSectionText(e.prompt)}}]:[];return{type:`modal`,callback_id:HITL_FREEFORM_MODAL_CALLBACK_ID,private_metadata:JSON.stringify(e.metadata),title:{type:`plain_text`,text:t},submit:{type:`plain_text`,text:`Submit`},close:{type:`plain_text`,text:`Cancel`},blocks:[...n,{type:`input`,block_id:HITL_FREEFORM_MODAL_BLOCK_ID,element:{type:`plain_text_input`,action_id:HITL_FREEFORM_MODAL_ACTION_ID,multiline:!0,placeholder:{type:`plain_text`,text:`Type your answer here...`}},label:{type:`plain_text`,text:`Answer`}}]}}function isFreeformAction(e){return e.startsWith(HITL_FREEFORM_ACTION_PREFIX)}function freeformRequestIdFromActionId(e){return decodeFreeformHitlActionId(e)?.requestId}function decodeFreeformHitlActionId(e){return isFreeformAction(e)?decodeHitlActionId(`${HITL_ACTION_PREFIX}${e.slice(19)}`):null}function buildCardButton(e,t,n){let r={type:`button`,text:{type:`plain_text`,text:truncatePlainText(e.label),emoji:!1},action_id:`${t}:button:${n}`,value:e.id};return(e.style===`primary`||e.style===`danger`)&&(r.style=e.style),r}function buildOption(e){let t={text:{text:truncatePlainText(e.label),type:`plain_text`},value:e.id},n=truncatePlainText(e.description);return n&&n.length>0&&(t.description={text:n,type:`plain_text`}),t}function renderInputRequestCardBlock(e,t){return{type:`card`,body:{type:`mrkdwn`,text:truncateCardBodyText(`*${e.prompt}*`),verbatim:!1},actions:cardButtonOptions(e).map((e,n)=>buildCardButton(e,t,n))}}function cardButtonOptions(e){let t=e.options??[];if(!isApprovalRequest(e))return t.map(toCardButtonOption);let n=t.find(e=>e.id===`approve`),r=t.find(e=>e.id===`cancel`);return!n||!r?t.map(toCardButtonOption):[{id:r.id,label:`Cancel`},{id:n.id,label:`Approve`,style:`primary`}]}function toCardButtonOption(e){let t={id:e.id,label:e.label};return e.style===`primary`||e.style===`danger`?{...t,style:e.style}:t}function buildAnsweredBlocks(e){let t=[];for(let n of e.promptBlocks)n!=null&&t.push(n);let n=truncateWithEllipsis(e.answerLabel,SLACK_SECTION_TEXT_MAX_LENGTH-20-1);return t.push({type:`section`,text:{type:`mrkdwn`,text:`:white_check_mark: *${n}*`}}),e.userId&&e.userId.length>0&&t.push({type:`context`,elements:[{type:`mrkdwn`,text:`Answered by <@${e.userId}>`}]}),t}function renderInputRequestDetailBlocks(e){let t=formatToolInputDetails(e);return t===void 0?[]:[{type:`section`,text:{type:`mrkdwn`,text:t}}]}function renderToolInputContainerBlock(e){let t=formatToolInputContainerText(e);if(t!==void 0)return{type:`container`,title:{type:`plain_text`,text:`Tool input`},is_collapsible:!0,default_collapsed:!1,child_blocks:[{type:`section`,text:{type:`mrkdwn`,text:t}}]}}function formatToolInputContainerText(e){if(!isApprovalRequest(e))return;let t=JSON.stringify(e.action.input,null,2);return t===`{}`?void 0:`\`\`\`
2
2
  ${truncateWithEllipsis(t,SLACK_SECTION_TEXT_MAX_LENGTH-4-4)}${TOOL_INPUT_SUFFIX}`}function formatToolInputDetails(e){if(!isApprovalRequest(e))return;let t=JSON.stringify(e.action.input,null,2);return t===`{}`?void 0:`*Tool input*
3
3
  \`\`\`
4
- ${truncateWithEllipsis(t,SLACK_SECTION_TEXT_MAX_LENGTH-17-4)}${TOOL_INPUT_SUFFIX}`}function truncateWithEllipsis(e,t){if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}function isBlockType(e,t){return typeof e==`object`&&!!e&&e.type===t}function isApprovalRequest(e){return e.kind===`tool-approval`}export{HITL_ACTION_PREFIX,HITL_FREEFORM_ACTION_PREFIX,HITL_FREEFORM_MODAL_ACTION_ID,HITL_FREEFORM_MODAL_BLOCK_ID,HITL_FREEFORM_MODAL_CALLBACK_ID,buildAnsweredBlocks,buildFreeformModalView,deriveHitlResponse,formatInputRequestFallbackText,freeformRequestIdFromActionId,isFreeformAction,isHitlAction,renderInputRequestBlocks,renderInputRequestPostParts};
4
+ ${truncateWithEllipsis(t,SLACK_SECTION_TEXT_MAX_LENGTH-17-4)}${TOOL_INPUT_SUFFIX}`}function truncateWithEllipsis(e,t){if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}function isBlockType(e,t){return typeof e==`object`&&!!e&&e.type===t}function isApprovalRequest(e){return e.kind===`tool-approval`}export{HITL_ACTION_PREFIX,HITL_FREEFORM_ACTION_PREFIX,HITL_FREEFORM_MODAL_ACTION_ID,HITL_FREEFORM_MODAL_BLOCK_ID,HITL_FREEFORM_MODAL_CALLBACK_ID,buildAnsweredBlocks,buildFreeformModalView,decodeFreeformHitlActionId,decodeHitlActionId,deriveHitlResponse,formatInputRequestFallbackText,freeformRequestIdFromActionId,isFreeformAction,isHitlAction,renderInputRequestBlocks,renderInputRequestPostParts};
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{parseInputResponse}from"#shared/input.js";import{buildSlackBinding,buildSlackWorkspaceHandle,resolveSlackBotToken,slackContinuationToken}from"#public/channels/slack/api.js";import{readSlackTextObject}from"#public/channels/slack/inbound-content.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{HITL_FREEFORM_MODAL_ACTION_ID,HITL_FREEFORM_MODAL_BLOCK_ID,HITL_FREEFORM_MODAL_CALLBACK_ID,buildFreeformModalView,deriveHitlResponse,freeformRequestIdFromActionId,isFreeformAction,isHitlAction}from"#public/channels/slack/hitl.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{updateAnsweredFreeformCard,updateAnsweredHitlCard}from"#public/channels/slack/interaction-cards.js";import{approvalResponderStatePatch,authorizeInputResponse}from"#public/channels/slack/input-response.js";import{bindSlackSessionOperations}from"#public/channels/slack/session-operations.js";import{dispatchSlashCommand}from"#public/channels/slack/slash-command.js";const log=createLogger(`slack.interactions`);function parseBlockActionsPayload(e){if(isSharedBlockActionsPayload(e))return parseSharedBlockActionsPayload(e);let t=e,n=t.actions;if(!Array.isArray(n))return null;let r=t.channel?.id,i=t.message,a=i?.thread_ts??i?.ts;if(!r||!a)return null;let o=t.team,s=t.user,c=s.team_id??o?.id,l={id:s.id,username:s.username,name:s.name},u=i?.blocks??[];return{actions:n.map(e=>({actionId:String(e.action_id??``),value:e.value==null?void 0:String(e.value),blockId:e.block_id==null?void 0:String(e.block_id),selectedOptionValue:extractSelectedOptionValue(e),messageTs:i?.ts,label:extractActionLabel(e),user:l})),channelId:r,installationTeamId:readInstallationTeamId(t),threadTs:a,teamId:c,messageBlocks:u}}function isSharedBlockActionsPayload(e){return e.kind===`block_actions`&&Array.isArray(e.actions)}function parseSharedBlockActionsPayload(e){return!e.channelId||!e.threadTs?null:{actions:e.actions.map(t=>({actionId:t.actionId,value:t.value,blockId:t.blockId,selectedOptionValue:t.selectedOptionValue,messageTs:e.messageTs,label:t.label,user:{id:t.user?.id??e.userId,username:t.user?.username,name:t.user?.name}})),channelId:e.channelId,installationTeamId:readInstallationTeamId(e.raw),threadTs:e.threadTs,teamId:e.user?.teamId??e.teamId,messageBlocks:e.messageBlocks??[]}}function extractSelectedOptionValue(e){let t=e.selected_option;return typeof t?.value==`string`?t.value:void 0}function extractActionLabel(e){let t=e.selected_option?.text?.text;if(typeof t==`string`&&t.length>0)return t;let n=e.text?.text;if(typeof n==`string`&&n.length>0)return n}function findPromptBlock(e){return findPromptBlocks(e)[0]}function findPromptBlocks(e){let t=[];for(let n of e){if(typeof n!=`object`||!n)continue;let e=n.type;if(e===`actions`)break;(e===`section`||e===`context`||e===`divider`||e===`image`)&&t.push(n)}return t}function readPromptTextFromBlocks(e){let t=findPromptBlock(e),n=readSlackTextObject(t?.text);return n.length>0?n:void 0}function isObjectRecord(e){return typeof e==`object`&&!!e}function readInstallationTeamId(e){if(!isObjectRecord(e))return;let t=isObjectRecord(e.view)?e.view:void 0,n=isObjectRecord(e.team)?e.team:void 0,r=isObjectRecord(e.user)?e.user:void 0;return[t?.app_installed_team_id,e.app_installed_team_id,n?.id,r?.team_id,t?.team_id].find(e=>typeof e==`string`&&e.length>0)}async function handleInteractionPost(e,t,n){let r=new Response(`ok`,{status:200}),i;try{i=parseSlackWebhookBody(e,{contentType:`application/x-www-form-urlencoded`})}catch{return log.warn(`failed to parse Slack interaction payload`),r}if(i.kind===`view_submission`)return handleViewSubmission(i,t,n);if(i.kind===`slash_command`)return dispatchSlashCommand(i,t,n.config),new Response(null,{status:200});if(i.kind===`unsupported`){let e=parseShortcutPayload(i.raw);return e===null?(log.warn(`unsupported Slack interaction payload ignored`,{type:i.type}),r):(dispatchShortcut(e,readInstallationTeamId(i.raw),t,n),new Response(null,{status:200}))}if(i.kind!==`block_actions`)return log.warn(`unsupported Slack interaction payload ignored`,{type:i.kind}),r;let a=parseBlockActionsPayload(i);if(!a)return r;let o=a.actions.find(e=>isFreeformAction(e.actionId));if(o)return await openFreeformModal({payload:i.raw,interaction:a,freeformAction:o,deps:n}),r;let s=slackContinuationToken(a.channelId,a.threadTs),c=a.actions.flatMap(e=>{let t=deriveHitlResponse(e);return t===null?[]:[{action:e,derived:t}]});if(c.length>0){let e=c[0].action.user;t.waitUntil(dispatchBlockInputResponses({ctx:t,deps:n,interaction:a,submission:{type:`block_actions`,actions:c.map(({action:e})=>e),inputResponses:c.map(({derived:e})=>e.response),messageTs:c[0].action.messageTs,user:e}}))}let l=n.config.onInteraction;if(l){let e=a.actions.filter(e=>!isHitlAction(e.actionId));if(e.length>0){let r=e[0].user,{thread:i,slack:o}=buildSlackBinding({botToken:n.config.credentials?.botToken,channelId:a.channelId,threadTs:a.threadTs,installationTeamId:a.installationTeamId,teamId:a.teamId}),c={...bindSlackSessionOperations({address:s,defaultAuth:buildSlackAuthContext({channelId:a.channelId,teamId:a.teamId,threadTs:a.threadTs,userId:r.id,userName:r.username??r.name}),from:t.from,resolveSession:t.resolveSession,state:{channelId:a.channelId,installationTeamId:a.installationTeamId??null,teamId:a.teamId??null,threadTs:a.threadTs,triggeringUserId:r.id}}),thread:i,slack:o};for(let n of e)t.waitUntil(Promise.resolve(l(n,c)).catch(e=>{log.error(`custom interaction handler failed`,{error:e})}))}}return r}function parseShortcutPayload(e){if(!isObjectRecord(e))return null;let t=e.type;if(t!==`message_action`&&t!==`shortcut`)return null;let n=readRequiredString(e.callback_id),r=readRequiredString(e.trigger_id),i=isObjectRecord(e.user)?e.user:void 0,a=readRequiredString(i?.id);if(n===null||r===null||a===null)return null;let o=isObjectRecord(e.team)?e.team:void 0,s=readOptionalString(i?.team_id)??readOptionalString(o?.id),c={id:a,username:readOptionalString(i?.username),name:readOptionalString(i?.name)};if(t===`shortcut`)return{type:t,callbackId:n,triggerId:r,user:c,teamId:s};let l=isObjectRecord(e.channel)?e.channel:void 0,u=isObjectRecord(e.message)?e.message:void 0,d=readRequiredString(l?.id),f=readRequiredString(u?.ts);return d===null||f===null||u===void 0?null:{type:t,callbackId:n,triggerId:r,user:c,teamId:s,channelId:d,message:{text:typeof u.text==`string`?u.text:``,ts:f,threadTs:readOptionalString(u.thread_ts),userId:readOptionalString(u.user)},responseUrl:readOptionalString(e.response_url)}}function readRequiredString(e){return typeof e==`string`&&e.length>0?e:null}function readOptionalString(e){return typeof e==`string`&&e.length>0?e:void 0}function dispatchShortcut(e,t,n,r){let i=r.config.onShortcut;if(i===void 0){log.warn(`Slack shortcut ignored because onShortcut is not configured`,{type:e.type});return}let a=buildShortcutContext({config:r.config,installationTeamId:t,teamId:e.teamId});dispatchInteractionHook(()=>i(e,a),n,`shortcut handler failed`)}function buildShortcutContext(e){return{slack:buildSlackWorkspaceHandle({botToken:e.config.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:e.teamId})}}function dispatchInteractionHook(e,t,n){t.waitUntil(Promise.resolve().then(e).catch(e=>{log.error(n,{error:e})}))}async function dispatchBlockInputResponses(e){let t=await authorizeInputResponse({channelId:e.interaction.channelId,deps:e.deps,installationTeamId:e.interaction.installationTeamId,submission:e.submission,teamId:e.interaction.teamId,threadTs:e.interaction.threadTs});if(t!==null){try{await e.ctx.from(slackContinuationToken(e.interaction.channelId,e.interaction.threadTs)).respond(e.submission.inputResponses,{auth:t.auth,state:approvalResponderStatePatch(e.submission,t.auth)})}catch(e){log.error(`HITL interaction delivery failed`,{error:e});return}if(!e.submission.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`))try{await updateAnsweredHitlCard(e.interaction,e.deps)}catch(e){log.error(`HITL answered-card update failed`,{error:e})}}}async function openFreeformModal(e){let t=e.payload.trigger_id;if(typeof t!=`string`||t.length===0){log.warn(`freeform button click missing trigger_id — cannot open modal`);return}let n=freeformRequestIdFromActionId(e.freeformAction.actionId)??e.freeformAction.value;if(!n){log.warn(`freeform button click missing requestId`);return}let r=e.freeformAction.messageTs;if(!r){log.warn(`freeform button click missing messageTs`);return}let i={continuationToken:slackContinuationToken(e.interaction.channelId,e.interaction.threadTs),channelId:e.interaction.channelId,installationTeamId:e.interaction.installationTeamId,threadTs:e.interaction.threadTs,messageTs:r,requestId:n},a=readPromptTextFromBlocks(e.interaction.messageBlocks),o=buildFreeformModalView({metadata:i,prompt:a}),s=await resolveSlackBotToken(e.deps.config.credentials?.botToken,{teamId:e.interaction.installationTeamId}),c=await fetch(`https://slack.com/api/views.open`,{method:`POST`,headers:{authorization:`Bearer ${s}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({trigger_id:t,view:o})});c.ok||log.error(`Slack views.open returned non-2xx`,{status:c.status})}async function handleViewSubmission(e,t,n){let r=new Response(null,{status:200});if(e.callbackId!==HITL_FREEFORM_MODAL_CALLBACK_ID)return r;let i;try{i=JSON.parse(e.privateMetadata??``)}catch{return log.warn(`freeform view_submission carries invalid private_metadata`),r}if(!i.continuationToken||!i.requestId||!i.messageTs||!i.channelId||!i.threadTs)return r;let a=e.values?.find(e=>e.blockId===HITL_FREEFORM_MODAL_BLOCK_ID&&e.actionId===HITL_FREEFORM_MODAL_ACTION_ID)?.value??``;if(a.length===0)return r;let o=e.user,s=e.userId,c=o?.teamId??e.teamId??null,l=i.installationTeamId??readInstallationTeamId(e.raw),u={type:`view_submission`,inputResponses:[parseInputResponse({requestId:i.requestId,text:a})],messageTs:i.messageTs,user:{id:s,username:o?.username,name:o?.name}};return t.waitUntil(dispatchViewInputResponse({ctx:t,deps:n,installationTeamId:l,metadata:i,submission:u,teamId:c,text:a})),r}async function dispatchViewInputResponse(e){let t=await authorizeInputResponse({channelId:e.metadata.channelId,deps:e.deps,installationTeamId:e.installationTeamId,submission:e.submission,teamId:e.teamId,threadTs:e.metadata.threadTs});if(t!==null){try{await e.ctx.from(e.metadata.continuationToken).respond(e.submission.inputResponses,{auth:t.auth})}catch(e){log.error(`freeform answer delivery failed`,{error:e});return}try{await updateAnsweredFreeformCard({channelId:e.metadata.channelId,messageTs:e.metadata.messageTs,answerLabel:e.text,userId:e.submission.user.id,installationTeamId:e.installationTeamId,deps:e.deps})}catch(e){log.error(`freeform answered-card update failed`,{error:e})}}}export{handleInteractionPost,parseBlockActionsPayload,parseShortcutPayload};
1
+ import{createLogger}from"#internal/logging.js";import{parseInputResponse}from"#shared/input.js";import{buildSlackBinding,buildSlackWorkspaceHandle,resolveSlackBotToken,slackContinuationToken}from"#public/channels/slack/api.js";import{readSlackTextObject}from"#public/channels/slack/inbound-content.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{HITL_FREEFORM_MODAL_ACTION_ID,HITL_FREEFORM_MODAL_BLOCK_ID,HITL_FREEFORM_MODAL_CALLBACK_ID,buildFreeformModalView,decodeFreeformHitlActionId,deriveHitlResponse,isFreeformAction,isHitlAction}from"#public/channels/slack/hitl.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{updateAnsweredFreeformCard,updateAnsweredHitlCard}from"#public/channels/slack/interaction-cards.js";import{approvalResponderStatePatch,authorizeInputResponse}from"#public/channels/slack/input-response.js";import{bindSlackSessionOperations}from"#public/channels/slack/session-operations.js";import{dispatchSlashCommand}from"#public/channels/slack/slash-command.js";const log=createLogger(`slack.interactions`);function parseBlockActionsPayload(e){if(isSharedBlockActionsPayload(e))return parseSharedBlockActionsPayload(e);let t=e,n=t.actions;if(!Array.isArray(n))return null;let r=t.channel?.id,i=t.message,a=i?.thread_ts??i?.ts;if(!r||!a)return null;let o=t.team,s=t.user,c=s.team_id??o?.id,l={id:s.id,username:s.username,name:s.name},u=i?.blocks??[];return{actions:n.map(e=>({actionId:String(e.action_id??``),value:e.value==null?void 0:String(e.value),blockId:e.block_id==null?void 0:String(e.block_id),selectedOptionValue:extractSelectedOptionValue(e),messageTs:i?.ts,label:extractActionLabel(e),user:l})),channelId:r,installationTeamId:readInstallationTeamId(t),threadTs:a,teamId:c,messageBlocks:u}}function isSharedBlockActionsPayload(e){return e.kind===`block_actions`&&Array.isArray(e.actions)}function parseSharedBlockActionsPayload(e){return!e.channelId||!e.threadTs?null:{actions:e.actions.map(t=>({actionId:t.actionId,value:t.value,blockId:t.blockId,selectedOptionValue:t.selectedOptionValue,messageTs:e.messageTs,label:t.label,user:{id:t.user?.id??e.userId,username:t.user?.username,name:t.user?.name}})),channelId:e.channelId,installationTeamId:readInstallationTeamId(e.raw),threadTs:e.threadTs,teamId:e.user?.teamId??e.teamId,messageBlocks:e.messageBlocks??[]}}function extractSelectedOptionValue(e){let t=e.selected_option;return typeof t?.value==`string`?t.value:void 0}function extractActionLabel(e){let t=e.selected_option?.text?.text;if(typeof t==`string`&&t.length>0)return t;let n=e.text?.text;if(typeof n==`string`&&n.length>0)return n}function findPromptBlock(e){return findPromptBlocks(e)[0]}function findPromptBlocks(e){let t=[];for(let n of e){if(typeof n!=`object`||!n)continue;let e=n.type;if(e===`actions`)break;(e===`section`||e===`context`||e===`divider`||e===`image`)&&t.push(n)}return t}function readPromptTextFromBlocks(e){let t=findPromptBlock(e),n=readSlackTextObject(t?.text);return n.length>0?n:void 0}function isObjectRecord(e){return typeof e==`object`&&!!e}function readInstallationTeamId(e){if(!isObjectRecord(e))return;let t=isObjectRecord(e.view)?e.view:void 0,n=isObjectRecord(e.team)?e.team:void 0,r=isObjectRecord(e.user)?e.user:void 0;return[t?.app_installed_team_id,e.app_installed_team_id,n?.id,r?.team_id,t?.team_id].find(e=>typeof e==`string`&&e.length>0)}async function handleInteractionPost(e,t,n){let r=new Response(`ok`,{status:200}),i;try{i=parseSlackWebhookBody(e,{contentType:`application/x-www-form-urlencoded`})}catch{return log.warn(`failed to parse Slack interaction payload`),r}if(i.kind===`view_submission`)return handleViewSubmission(i,t,n);if(i.kind===`slash_command`)return dispatchSlashCommand(i,t,n.config),new Response(null,{status:200});if(i.kind===`unsupported`){let e=parseShortcutPayload(i.raw);return e===null?(log.warn(`unsupported Slack interaction payload ignored`,{type:i.type}),r):(dispatchShortcut(e,readInstallationTeamId(i.raw),t,n),new Response(null,{status:200}))}if(i.kind!==`block_actions`)return log.warn(`unsupported Slack interaction payload ignored`,{type:i.kind}),r;let a=parseBlockActionsPayload(i);if(!a)return r;let o=a.actions.find(e=>isFreeformAction(e.actionId));if(o)return await openFreeformModal({payload:i.raw,interaction:a,freeformAction:o,deps:n}),r;let s=a.actions.flatMap(e=>{let t=deriveHitlResponse(e);return t===null?[]:[{action:e,derived:t}]});if(s.length>0){let e=s[0].action.user,r=s[0].derived.route;t.waitUntil(dispatchBlockInputResponses({ctx:t,deps:n,interaction:a,route:r,submission:{type:`block_actions`,actions:s.map(({action:e})=>e),inputResponses:s.map(({derived:e})=>e.response),messageTs:s[0].action.messageTs,user:e}}))}let c=n.config.onInteraction;if(c){let e=a.actions.filter(e=>!isHitlAction(e.actionId));if(e.length>0){let r=e[0].user,{thread:i,slack:o}=buildSlackBinding({botToken:n.config.credentials?.botToken,channelId:a.channelId,threadTs:a.threadTs,installationTeamId:a.installationTeamId,teamId:a.teamId}),s={...bindSlackSessionOperations({address:slackContinuationToken(a.channelId,a.threadTs),defaultAuth:buildSlackAuthContext({channelId:a.channelId,teamId:a.teamId,threadTs:a.threadTs,userId:r.id,userName:r.username??r.name}),from:t.from,resolveSession:t.resolveSession,state:{channelId:a.channelId,installationTeamId:a.installationTeamId??null,teamId:a.teamId??null,threadTs:a.threadTs,triggeringUserId:r.id}}),thread:i,slack:o};for(let n of e)t.waitUntil(Promise.resolve(c(n,s)).catch(e=>{log.error(`custom interaction handler failed`,{error:e})}))}}return r}function parseShortcutPayload(e){if(!isObjectRecord(e))return null;let t=e.type;if(t!==`message_action`&&t!==`shortcut`)return null;let n=readRequiredString(e.callback_id),r=readRequiredString(e.trigger_id),i=isObjectRecord(e.user)?e.user:void 0,a=readRequiredString(i?.id);if(n===null||r===null||a===null)return null;let o=isObjectRecord(e.team)?e.team:void 0,s=readOptionalString(i?.team_id)??readOptionalString(o?.id),c={id:a,username:readOptionalString(i?.username),name:readOptionalString(i?.name)};if(t===`shortcut`)return{type:t,callbackId:n,triggerId:r,user:c,teamId:s};let l=isObjectRecord(e.channel)?e.channel:void 0,u=isObjectRecord(e.message)?e.message:void 0,d=readRequiredString(l?.id),f=readRequiredString(u?.ts);return d===null||f===null||u===void 0?null:{type:t,callbackId:n,triggerId:r,user:c,teamId:s,channelId:d,message:{text:typeof u.text==`string`?u.text:``,ts:f,threadTs:readOptionalString(u.thread_ts),userId:readOptionalString(u.user)},responseUrl:readOptionalString(e.response_url)}}function readRequiredString(e){return typeof e==`string`&&e.length>0?e:null}function readOptionalString(e){return typeof e==`string`&&e.length>0?e:void 0}function dispatchShortcut(e,t,n,r){let i=r.config.onShortcut;if(i===void 0){log.warn(`Slack shortcut ignored because onShortcut is not configured`,{type:e.type});return}let a=buildShortcutContext({config:r.config,installationTeamId:t,teamId:e.teamId});dispatchInteractionHook(()=>i(e,a),n,`shortcut handler failed`)}function buildShortcutContext(e){return{slack:buildSlackWorkspaceHandle({botToken:e.config.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:e.teamId})}}function dispatchInteractionHook(e,t,n){t.waitUntil(Promise.resolve().then(e).catch(e=>{log.error(n,{error:e})}))}async function dispatchBlockInputResponses(e){let t=e.route?.channelId??e.interaction.channelId,n=e.route?.threadTs??e.interaction.threadTs,r=await authorizeInputResponse({channelId:t,deps:e.deps,installationTeamId:e.interaction.installationTeamId,submission:e.submission,teamId:e.interaction.teamId,threadTs:n});if(r!==null){try{await e.ctx.from(slackContinuationToken(t,n)).respond(e.submission.inputResponses,{auth:r.auth,state:approvalResponderStatePatch(e.submission,r.auth)})}catch(e){log.error(`HITL interaction delivery failed`,{error:e});return}if(!e.submission.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`))try{await updateAnsweredHitlCard(e.interaction,e.deps)}catch(e){log.error(`HITL answered-card update failed`,{error:e})}}}async function openFreeformModal(e){let t=e.payload.trigger_id;if(typeof t!=`string`||t.length===0){log.warn(`freeform button click missing trigger_id — cannot open modal`);return}let n=decodeFreeformHitlActionId(e.freeformAction.actionId);if(n===null){log.warn(`freeform button click carries invalid metadata`);return}let r=n.requestId;if(!r){log.warn(`freeform button click missing requestId`);return}let i=e.freeformAction.messageTs;if(!i){log.warn(`freeform button click missing messageTs`);return}let a=n?.route?.channelId??e.interaction.channelId,o=n?.route?.threadTs??e.interaction.threadTs,s={continuationToken:slackContinuationToken(a,o),channelId:a,installationTeamId:e.interaction.installationTeamId,messageChannelId:e.interaction.channelId,threadTs:o,messageTs:i,requestId:r},c=readPromptTextFromBlocks(e.interaction.messageBlocks),l=buildFreeformModalView({metadata:s,prompt:c}),u=await resolveSlackBotToken(e.deps.config.credentials?.botToken,{teamId:e.interaction.installationTeamId}),d=await fetch(`https://slack.com/api/views.open`,{method:`POST`,headers:{authorization:`Bearer ${u}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({trigger_id:t,view:l})});d.ok||log.error(`Slack views.open returned non-2xx`,{status:d.status})}async function handleViewSubmission(e,t,n){let r=new Response(null,{status:200});if(e.callbackId!==HITL_FREEFORM_MODAL_CALLBACK_ID)return r;let i;try{i=JSON.parse(e.privateMetadata??``)}catch{return log.warn(`freeform view_submission carries invalid private_metadata`),r}if(!i.continuationToken||!i.requestId||!i.messageTs||!i.channelId||!i.threadTs)return r;let a=e.values?.find(e=>e.blockId===HITL_FREEFORM_MODAL_BLOCK_ID&&e.actionId===HITL_FREEFORM_MODAL_ACTION_ID)?.value??``;if(a.length===0)return r;let o=e.user,s=e.userId,c=o?.teamId??e.teamId??null,l=i.installationTeamId??readInstallationTeamId(e.raw),u={type:`view_submission`,inputResponses:[parseInputResponse({requestId:i.requestId,text:a})],messageTs:i.messageTs,user:{id:s,username:o?.username,name:o?.name}};return t.waitUntil(dispatchViewInputResponse({ctx:t,deps:n,installationTeamId:l,metadata:i,submission:u,teamId:c,text:a})),r}async function dispatchViewInputResponse(e){let t=await authorizeInputResponse({channelId:e.metadata.channelId,deps:e.deps,installationTeamId:e.installationTeamId,submission:e.submission,teamId:e.teamId,threadTs:e.metadata.threadTs});if(t!==null){try{await e.ctx.from(e.metadata.continuationToken).respond(e.submission.inputResponses,{auth:t.auth})}catch(e){log.error(`freeform answer delivery failed`,{error:e});return}try{await updateAnsweredFreeformCard({channelId:e.metadata.messageChannelId??e.metadata.channelId,messageTs:e.metadata.messageTs,answerLabel:e.text,userId:e.submission.user.id,installationTeamId:e.installationTeamId,deps:e.deps})}catch(e){log.error(`freeform answered-card update failed`,{error:e})}}}export{handleInteractionPost,parseBlockActionsPayload,parseShortcutPayload};
@@ -0,0 +1,11 @@
1
+ import type { SlackHandle } from "#public/channels/slack/api.js";
2
+ import type { SlackPendingApprovalCard } from "#public/channels/slack/slackChannel.js";
3
+ import type { InputRequest } from "#shared/input.js";
4
+ type PrivateApprovalSlack = Pick<SlackHandle, "channelId" | "request" | "threadTs">;
5
+ export declare function deliverPrivateInputRequest(input: {
6
+ readonly previewMessageTs: string;
7
+ readonly request: InputRequest;
8
+ readonly reviewer: string;
9
+ readonly slack: PrivateApprovalSlack;
10
+ }): Promise<SlackPendingApprovalCard>;
11
+ export {};
@@ -0,0 +1 @@
1
+ import{createLogger,logError}from"#internal/logging.js";import{renderInputRequestPostParts}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.private-approval-delivery`);async function deliverPrivateInputRequest(e){let t=await e.slack.request(`conversations.open`,{users:e.reviewer}),n=t.ok===!0?t.channel?.id:void 0;if(typeof n!=`string`||n.length===0)throw Error(`Slack conversations.open failed: ${t.error??`unknown_error`}`);let r={channelId:e.slack.channelId,threadTs:e.slack.threadTs},i=renderInputRequestPostParts(e.request,r),a=[];try{let t=await resolveMessagePermalink(e.slack,e.previewMessageTs);t!==void 0&&a.push(await postMessage(e.slack,{channel:n,markdown_text:t,unfurl_links:!0,unfurl_media:!1})),i.details!==void 0&&a.push(await postMessage(e.slack,{blocks:i.details.blocks,channel:n,text:i.details.text,unfurl_links:!1,unfurl_media:!1}));let r=await postMessage(e.slack,{blocks:i.controls.blocks,channel:n,text:i.controls.text,unfurl_links:!1,unfurl_media:!1});return a.push(r),{messageBlocks:i.controls.blocks,messageChannelId:n,messageTs:r}}catch(t){throw await Promise.allSettled(a.map(async t=>{try{await e.slack.request(`chat.delete`,{channel:n,ts:t})}catch(e){logError(log,`failed to roll back partial private approval delivery`,e,{channelId:n,messageTs:t})}})),t}}async function resolveMessagePermalink(e,t){if(!e.channelId||!t)return;let n=await e.request(`chat.getPermalink`,{channel:e.channelId,message_ts:t});return n.ok===!0&&typeof n.permalink==`string`?n.permalink:void 0}async function postMessage(e,t){let n=await e.request(`chat.postMessage`,t);if(n.ok!==!0||typeof n.ts!=`string`||n.ts.length===0)throw Error(`Slack chat.postMessage failed: ${n.error??`unknown_error`}`);return n.ts}export{deliverPrivateInputRequest};
@@ -6,7 +6,7 @@ import type { CardElement } from "#compiled/chat/index.js";
6
6
  import type { SessionContext } from "#public/definitions/callback-context.js";
7
7
  import type { ChannelContinuationOps } from "#public/definitions/channel.js";
8
8
  import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
9
- import type { InputResponse, StrictInputResponses, ValidatedInputResponse } from "#shared/input.js";
9
+ import type { InputRequest, InputResponse, StrictInputResponses, ValidatedInputResponse } from "#shared/input.js";
10
10
  import { type SlackBotToken, type SlackHandle, type SlackThread, type SlackWorkspaceHandle } from "#public/channels/slack/api.js";
11
11
  import { type SlackEvent, type SlackEventEnvelope, type SlackMessage } from "#public/channels/slack/inbound.js";
12
12
  import { type LoadThreadContextMessagesOptions } from "#public/channels/slack/thread.js";
@@ -94,6 +94,8 @@ type SlackSessionFailedHandler = (data: EventData<"session.failed">, channel: Sl
94
94
  * `JSON.stringify` / `JSON.parse`.
95
95
  */
96
96
  export interface SlackPendingApprovalCard {
97
+ /** Channel containing the approval card; differs from the session channel for DM delivery. */
98
+ readonly messageChannelId?: string;
97
99
  readonly messageBlocks: readonly unknown[];
98
100
  readonly messageTs: string;
99
101
  }
@@ -106,6 +108,8 @@ export interface SlackChannelState {
106
108
  threadTs: string | null;
107
109
  /** Slack team id, when the inbound event carried one. */
108
110
  teamId: string | null;
111
+ /** Slack message ts that triggered the active turn. */
112
+ triggeringMessageTs?: string | null;
109
113
  /** Slack workspace whose app installation supplies this session's credentials. */
110
114
  installationTeamId?: string | null;
111
115
  /**
@@ -470,9 +474,20 @@ export interface SlackChannelEvents {
470
474
  export interface SlackChannelInternalEvents extends Omit<SlackChannelEvents, "authorization.required"> {
471
475
  readonly "authorization.required"?: SlackEventHandler<"authorization.required">;
472
476
  }
477
+ export type SlackApprovalChannel = "direct-message" | "thread";
478
+ /** Input request passed to a {@link SlackApprovalChannelResolver}. */
479
+ export type SlackApprovalRequest = InputRequest;
480
+ /** Chooses the Slack destination for one input request. */
481
+ export type SlackApprovalChannelResolver = (request: SlackApprovalRequest, ctx: SessionContext) => SlackApprovalChannel | Promise<SlackApprovalChannel>;
473
482
  export interface SlackChannelConfig {
474
483
  readonly credentials?: SlackChannelCredentials;
475
484
  readonly botName?: string;
485
+ /**
486
+ * Chooses where each input request is delivered. Direct-message requests go to the
487
+ * Slack user who triggered the active turn; the session thread names that reviewer
488
+ * without exposing the request. Defaults to `"thread"` when omitted.
489
+ */
490
+ readonly approvalChannel?: SlackApprovalChannelResolver;
476
491
  /** Optional presentation-only activity rendered without starting parent turns. */
477
492
  readonly activity?: {
478
493
  readonly renderers: readonly SlackActivityRenderer[];
@@ -1 +1 @@
1
- import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{setChannelActivityRenderers}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{normalizeChannelAudience}from"#shared/channel-audience.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,postCompletedSlackReply}from"#public/channels/slack/defaults.js";import{buildSlackActivityRenderers,hasSlackActivityStatus}from"#public/channels/slack/activity.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,slackEventReceivingBotUserId,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`),ignoreSlackActivityEvent=async()=>void 0,activityOwnedMessageCompleted=async(e,t)=>{t.state.pendingToolCallMessage=null,e.finishReason!==`tool-calls`&&e.message&&await postCompletedSlackReply(t,e.message)},clearSlackTurnState=async(e,t)=>{t.state.pendingToolCallMessage=null,t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null};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}),r=buildSlackActivityRenderers({botToken:e.credentials?.botToken,renderers:e.activity?.renderers??[]}),i=e.onInputResponse??defaultOnInputResponse,a=e.events?.[`authorization.required`],o=e.events?.[`approval.candidate`]??defaultEvents[`approval.candidate`],s=hasSlackActivityStatus(e.activity?.renderers),c=e.events?.[`turn.started`]??(s?clearSlackTurnState:defaultEvents[`turn.started`]),l=e.events?.[`reasoning.appended`]??(s?ignoreSlackActivityEvent:defaultEvents[`reasoning.appended`]),u=e.events?.[`actions.requested`]??(s?ignoreSlackActivityEvent:defaultEvents[`actions.requested`]),d=e.events?.[`message.completed`]??(s?activityOwnedMessageCompleted:defaultEvents[`message.completed`]),f={...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 o(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 c(e,t,n)},"reasoning.appended":l,"actions.requested":u,"message.completed":d,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":a===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(a)},p=new Set,m=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 r=e.state?.approvalResponderUsers;return typeof r==`object`&&r&&(t.state.approvalResponderUsers={...t.state.approvalResponderUsers,...r}),defaultDeliverResult(e)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,r)=>{let{waitUntil:a}=r,{from:o,resolveSession:s}=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:o,resolveSession:s,waitUntil:a},{config:e,onInputResponse:i}):handleEventPost({body:c,from:o,resolveSession:s,waitUntil:a,config:e,uploadPolicy:t,handledEvents:p,headers:n.headers})})],receive(t,{from:n}){return receiveOnSlack(t,{from:n,credentials:e.credentials})},events:f});return setChannelActivityRenderers(m,{destination(e){let t=e;return{channelId:t?.channelId??null,installationTeamId:t?.installationTeamId??null,teamId:t?.teamId??null,threadTs:t?.threadTs??null,triggeringUserId:t?.triggeringUserId??null}},renderers:r}),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 s=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),s=(await e.post(n)).id}let c=s||crypto.randomUUID(),l=n.audience===void 0?void 0:normalizeChannelAudience(n.audience),u={channelId:r,installationTeamId:a??null,threadTs:s||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null};return l!==void 0&&(u.audience=l),t.from(slackContinuationToken(r,c)).send(e.message,{auth:e.auth,state:u,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(e){let{config:t}=e,n,r;try{n=parseSlackWebhookBody(e.body,{headers:e.headers}),r=parseSlackEventEnvelope(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(n.kind===`url_verification`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(r===null)return new Response(`ok`);let i=typeof r.api_app_id==`string`?r.api_app_id:void 0,a=slackEventBotUserId(r),o=slackEventReceivingBotUserId(r),s=slackEventInstallationTeamId(r),dispatch=null,c=null;if(n.kind===`app_mention`||n.kind===`direct_message`){let r=n.kind,l=slackMessageFromWebhookPayload(n);if(l!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},l)){let dispatchMessageWith=n=>()=>dispatchSlackMessage({appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:n,installationTeamId:s,kind:r,message:l,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy}),n=(r===`app_mention`?t.onAppMention:t.onDirectMessage)??t.onMessage;n===void 0?c=dispatchMessageWith(r===`app_mention`?defaultOnAppMention:defaultOnDirectMessage):dispatch=()=>dispatchSlackMessage({appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:n,installationTeamId:s,kind:r,message:l,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy})}}if(dispatch===null&&t.onMessage!==void 0){let n=parseMessageEvent(r);n!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},n)&&(a===void 0||!n.text.includes(`<@${a}`))&&(dispatch=()=>dispatchSlackMessage({appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:t.onMessage,installationTeamId:s,kind:`channel_message`,message:n,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy}))}let l=t.onEvent;if(dispatch===null&&l!==void 0&&(dispatch=()=>dispatchSlackEvent({from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,envelope:r,handler:l,installationTeamId:s})),dispatch??=c,dispatch===null)return new Response(`ok`);let u=r.event_id;if(u){if(e.handledEvents.has(u))return log.warn(`received a duplicate event`,{event_id:u,event_time:r.event_time,retry_num:n.retry?.num??`(null)`,retry_reason:n.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(u,e.handledEvents)}return e.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:n,slack:r}=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}),i=e.message.author,a={audience:`unknown`,channelId:e.message.channelId,installationTeamId:e.installationTeamId??null,teamId:e.message.teamId??null,threadTs:e.message.threadTs,triggeringUserId:i?.userId??null},o=bindSlackSessionOperations({address:t,defaultAuth:i===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:i.fullName,isBot:i.isBot,teamId:e.message.teamId,threadTs:e.message.threadTs,userId:i.userId,userName:i.userName}),from:e.from,resolveSession:e.resolveSession,state:a}),s,isDMOrPrivateChannel=()=>s??=isPrivateSlackConversation({channelId:e.message.channelId,raw:e.message.raw,request:r.request}),c=e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),l={...o,isBotMentioned:()=>c,isDMOrPrivateChannel,isSubscribed:async()=>await o.resolveSession()!==void 0,slack:r,thread:n},u;try{u=await e.handler(l,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId});return}if(u==null)return;let d=await isDMOrPrivateChannel();a.audience=e.kind===`direct_message`||d?`private`:`public`,await deliverSlackMessage({botUserId:e.receivingBotUserId,credentials:e.credentials,kind:e.kind,isPrivateConversation:d,isMentioned:c,message:e.message,result:u,sessionOperations:o,thread:n,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,n=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,r=[],sourceFor=t=>e.from(slackContinuationToken(t.channelId,t.threadTs)),i={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:r,target:i,title:a})=>receiveOnSlack({auth:r,message:t,target:i,title:a},{from:e.from,credentials:e.credentials,installationTeamId:e.installationTeamId,teamId:n,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({botToken:e.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:n}),waitUntil(e){r.push(e)}};try{await e.handler(i,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(r)}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:n}=e;try{let r=e.threadContext===void 0?[]:await loadThreadContextMessages(n,t,e.threadContext),i=formatSlackThreadContext(r),a=await collectInboundFileParts({mention:t,thread:n,policy:e.uploadPolicy}),o={botUserId:e.botUserId,channelId:t.channelId,fullName:t.author?.fullName,isMentioned:e.isMentioned,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},s=formatSlackInboundMessage(o,t),c=buildSlackTurnMessage(i===void 0?s:`${i}\n\n${s}`,a),l=e.result.context??[],u=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,d=l.length===0?{auth:e.result.auth,title:u}:{auth:e.result.auth,context:l,title:u};await e.sessionOperations.send(c,d)}catch(n){logError(log,`${e.kind} delivery failed`,n,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
1
+ import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{setChannelActivityRenderers}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{normalizeChannelAudience}from"#shared/channel-audience.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,postCompletedSlackReply}from"#public/channels/slack/defaults.js";import{buildSlackActivityRenderers,hasSlackActivityStatus}from"#public/channels/slack/activity.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,slackEventReceivingBotUserId,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`),ignoreSlackActivityEvent=async()=>void 0,activityOwnedMessageCompleted=async(e,t)=>{t.state.pendingToolCallMessage=null,e.finishReason!==`tool-calls`&&e.message&&await postCompletedSlackReply(t,e.message)},clearSlackTurnState=async(e,t)=>{t.state.pendingToolCallMessage=null,t.state.lastReasoningTypingAtMs=null,t.state.lastReasoningTypingStatus=null};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}),r=buildSlackActivityRenderers({botToken:e.credentials?.botToken,renderers:e.activity?.renderers??[]}),i=e.onInputResponse??defaultOnInputResponse,a=e.events?.[`authorization.required`],o=e.events?.[`approval.candidate`]??defaultEvents[`approval.candidate`],s=hasSlackActivityStatus(e.activity?.renderers),c=e.events?.[`turn.started`]??(s?clearSlackTurnState:defaultEvents[`turn.started`]),l=e.events?.[`reasoning.appended`]??(s?ignoreSlackActivityEvent:defaultEvents[`reasoning.appended`]),u=e.events?.[`actions.requested`]??(s?ignoreSlackActivityEvent:defaultEvents[`actions.requested`]),d=e.events?.[`message.completed`]??(s?activityOwnedMessageCompleted:defaultEvents[`message.completed`]),f={...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 o(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 c(e,t,n)},"reasoning.appended":l,"actions.requested":u,"message.completed":d,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(e.approvalChannel),"authorization.required":a===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(a)},p=new Set,m=defineChannel({kindHint:`slack`,turnPolicy:e.turnPolicy,state:{audience:`unknown`,channelId:null,threadTs:null,teamId:null,installationTeamId:null,triggeringUserId:null,triggeringMessageTs: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 r=e.state?.approvalResponderUsers;return typeof r==`object`&&r&&(t.state.approvalResponderUsers={...t.state.approvalResponderUsers,...r}),defaultDeliverResult(e)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,r)=>{let{waitUntil:a}=r,{from:o,resolveSession:s}=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:o,resolveSession:s,waitUntil:a},{config:e,onInputResponse:i}):handleEventPost({body:c,from:o,resolveSession:s,waitUntil:a,config:e,uploadPolicy:t,handledEvents:p,headers:n.headers})})],receive(t,{from:n}){return receiveOnSlack(t,{from:n,credentials:e.credentials})},events:f});return setChannelActivityRenderers(m,{destination(e){let t=e;return{channelId:t?.channelId??null,installationTeamId:t?.installationTeamId??null,teamId:t?.teamId??null,threadTs:t?.threadTs??null,triggeringUserId:t?.triggeringUserId??null}},renderers:r}),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 s=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),s=(await e.post(n)).id}let c=s||crypto.randomUUID(),l=n.audience===void 0?void 0:normalizeChannelAudience(n.audience),u={channelId:r,installationTeamId:a??null,threadTs:s||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null};return l!==void 0&&(u.audience=l),t.from(slackContinuationToken(r,c)).send(e.message,{auth:e.auth,state:u,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(e){let{config:t}=e,n,r;try{n=parseSlackWebhookBody(e.body,{headers:e.headers}),r=parseSlackEventEnvelope(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(n.kind===`url_verification`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(r===null)return new Response(`ok`);let i=typeof r.api_app_id==`string`?r.api_app_id:void 0,a=slackEventBotUserId(r),o=slackEventReceivingBotUserId(r),s=slackEventInstallationTeamId(r),dispatch=null,c=null;if(n.kind===`app_mention`||n.kind===`direct_message`){let r=n.kind,l=slackMessageFromWebhookPayload(n);if(l!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},l)){let dispatchMessageWith=n=>()=>dispatchSlackMessage({appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:n,installationTeamId:s,kind:r,message:l,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy}),n=(r===`app_mention`?t.onAppMention:t.onDirectMessage)??t.onMessage;n===void 0?c=dispatchMessageWith(r===`app_mention`?defaultOnAppMention:defaultOnDirectMessage):dispatch=()=>dispatchSlackMessage({appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:n,installationTeamId:s,kind:r,message:l,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy})}}if(dispatch===null&&t.onMessage!==void 0){let n=parseMessageEvent(r);n!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},n)&&(a===void 0||!n.text.includes(`<@${a}`))&&(dispatch=()=>dispatchSlackMessage({appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:t.onMessage,installationTeamId:s,kind:`channel_message`,message:n,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy}))}let l=t.onEvent;if(dispatch===null&&l!==void 0&&(dispatch=()=>dispatchSlackEvent({from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,envelope:r,handler:l,installationTeamId:s})),dispatch??=c,dispatch===null)return new Response(`ok`);let u=r.event_id;if(u){if(e.handledEvents.has(u))return log.warn(`received a duplicate event`,{event_id:u,event_time:r.event_time,retry_num:n.retry?.num??`(null)`,retry_reason:n.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(u,e.handledEvents)}return e.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:n,slack:r}=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}),i=e.message.author,a={audience:`unknown`,channelId:e.message.channelId,installationTeamId:e.installationTeamId??null,teamId:e.message.teamId??null,threadTs:e.message.threadTs,triggeringMessageTs:e.message.ts,triggeringUserId:i?.userId??null},o=bindSlackSessionOperations({address:t,defaultAuth:i===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:i.fullName,isBot:i.isBot,teamId:e.message.teamId,threadTs:e.message.threadTs,userId:i.userId,userName:i.userName}),from:e.from,resolveSession:e.resolveSession,state:a}),s,isDMOrPrivateChannel=()=>s??=isPrivateSlackConversation({channelId:e.message.channelId,raw:e.message.raw,request:r.request}),c=e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),l={...o,isBotMentioned:()=>c,isDMOrPrivateChannel,isSubscribed:async()=>await o.resolveSession()!==void 0,slack:r,thread:n},u;try{u=await e.handler(l,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId});return}if(u==null)return;let d=await isDMOrPrivateChannel();a.audience=e.kind===`direct_message`||d?`private`:`public`,await deliverSlackMessage({botUserId:e.receivingBotUserId,credentials:e.credentials,kind:e.kind,isPrivateConversation:d,isMentioned:c,message:e.message,result:u,sessionOperations:o,thread:n,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,n=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,r=[],sourceFor=t=>e.from(slackContinuationToken(t.channelId,t.threadTs)),i={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:r,target:i,title:a})=>receiveOnSlack({auth:r,message:t,target:i,title:a},{from:e.from,credentials:e.credentials,installationTeamId:e.installationTeamId,teamId:n,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({botToken:e.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:n}),waitUntil(e){r.push(e)}};try{await e.handler(i,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(r)}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:n}=e;try{let r=e.threadContext===void 0?[]:await loadThreadContextMessages(n,t,e.threadContext),i=formatSlackThreadContext(r),a=await collectInboundFileParts({mention:t,thread:n,policy:e.uploadPolicy}),o={botUserId:e.botUserId,channelId:t.channelId,fullName:t.author?.fullName,isMentioned:e.isMentioned,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},s=formatSlackInboundMessage(o,t),c=buildSlackTurnMessage(i===void 0?s:`${i}\n\n${s}`,a),l=e.result.context??[],u=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,d=l.length===0?{auth:e.result.auth,title:u}:{auth:e.result.auth,context:l,title:u};await e.sessionOperations.send(c,d)}catch(n){logError(log,`${e.kind} delivery failed`,n,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,editTelegramMessageText,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_AUTHORIZATION_CALLBACK_PREFIX}from"#public/channels/telegram/authorization.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage,isTelegramBotMentioned}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";import{dispatchTelegramAuthorizationCallback}from"#public/channels/telegram/authorization-callback.js";import{initialTelegramState,stateFromTelegramCallbackQuery,stateFromTelegramMessage,telegramContinuationTokenFromState}from"#public/channels/telegram/state.js";import{telegramInstrumentationMetadata}from"#public/channels/telegram/audience.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},i=defineChannel({kindHint:`telegram`,turnPolicy:e.turnPolicy,state:initialTelegramState(e.botUsername),metadata:telegramInstrumentationMetadata,fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/eve/v1/telegram`,async(r,{from:i,resolveSession:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,uploadPolicy:t,from:i})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,from:i,resolveSession:a})),new Response(`ok`))})],async receive(t,{from:n}){let r=t.target,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires target.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readChatId(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c={...initialTelegramState(e.botUsername),chatId:i,conversationId:o??null,messageThreadId:a??null};return s!==void 0&&await buildTelegramHandle({config:e,state:c}).sendMessage(s),n(telegramContinuationTokenFromState(c)).send(t.message,{auth:t.auth,state:c})},events:r});return attachTelegramDeliver(i),i}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let t=e.config.api,n=e.state,r=e.config.credentials;function anchor(t){let r=n.chatType??t.chatType??null;n.chatType===null&&t.chatType!==void 0&&(n.chatType=t.chatType),t.id&&shouldAnchorTelegramConversation(r)&&(n.conversationId=t.id,n.chatId&&e.session?.continuation?.rekey(telegramContinuationToken({chatId:n.chatId,conversationId:t.id,messageThreadId:n.messageThreadId??void 0})))}async function sendOne(e){let t=await sendMessage(e);return anchor(t),t}async function sendMessage(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for outbound message.`);return sendTelegramMessage({apiBaseUrl:t?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??n.messageThreadId??void 0},credentials:r,fetch:t?.fetch,fileBaseUrl:t?.fileBaseUrl,chatId:i})}return{botUsername:n.botUsername??e.config.botUsername,chatId:n.chatId??``,chatType:n.chatType??void 0,conversationId:n.conversationId??void 0,messageThreadId:n.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:t?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:r,fetch:t?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},editMessageText(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for text edit.`);return editTelegramMessageText({apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup,text:e.text})},post(e){return postTelegramMessage(e,sendOne)},postEphemeral(e,t,n){return postTelegramMessage({...typeof t==`string`?{text:t}:t,ephemeral_message_parameters:{...n?.callbackQueryId===void 0?{}:{callback_query_id:n.callbackQueryId},receiver_user_id:e}},sendMessage)},request(e,n){return callTelegramApi({apiBaseUrl:t?.apiBaseUrl,body:n,botToken:r?.botToken,fetch:t?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let i=n.chatId??``;if(i)try{await sendTelegramChatAction({action:e,apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageThreadId:n.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:i})}}}}function shouldAnchorTelegramConversation(e){return e===`group`||e===`supergroup`}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{...n.ephemeral_message_parameters===void 0?{}:{ephemeral_message_parameters:n.ephemeral_message_parameters},text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromTelegramMessage(e.message,e.config.botUsername),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=buildTelegramTurnMessage(e.message,i),o=formatTelegramContextBlock({botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,isMentioned:isTelegramBotMentioned(e.message,e.config.botUsername),messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[],c=e.message.text||e.message.caption,l=e.message.replyToMessage?.from?.isBot===!0&&c.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:c})]:void 0;try{let n=e.from(telegramContinuationTokenFromState(t));l===void 0?await n.send(a,{auth:r.auth,context:[o,...s],state:t,title:r.title}):await n.respond(l,{auth:r.auth,context:[o,...s]})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromTelegramCallbackQuery(e.query,e.config.botUsername),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_AUTHORIZATION_CALLBACK_PREFIX)===!0){await dispatchTelegramAuthorizationCallback({continuationToken:telegramContinuationTokenFromState(t),query:e.query,resolveSession:e.resolveSession,state:t,telegram:n});return}if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.from(telegramContinuationTokenFromState(t)).respond([telegramCallbackInputResponse(e.query.data)],{auth:null})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{INTERNAL_CHANNEL_DELIVER}from"#channel/channel-operations.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,editTelegramMessageText,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_AUTHORIZATION_CALLBACK_PREFIX}from"#public/channels/telegram/authorization.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage,isTelegramBotMentioned}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";import{dispatchTelegramAuthorizationCallback}from"#public/channels/telegram/authorization-callback.js";import{initialTelegramState,stateFromTelegramCallbackQuery,stateFromTelegramMessage,telegramContinuationTokenFromState}from"#public/channels/telegram/state.js";import{telegramInstrumentationMetadata}from"#public/channels/telegram/audience.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},i=defineChannel({kindHint:`telegram`,turnPolicy:e.turnPolicy,state:initialTelegramState(e.botUsername),metadata:telegramInstrumentationMetadata,fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/eve/v1/telegram`,async(r,{from:i,resolveSession:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,uploadPolicy:t,from:i})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,from:i,resolveSession:a})),new Response(`ok`))})],async receive(t,{from:n}){let r=t.target,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires target.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readChatId(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c={...initialTelegramState(e.botUsername),chatId:i,conversationId:o??null,messageThreadId:a??null};return s!==void 0&&await buildTelegramHandle({config:e,state:c}).sendMessage(s),n(telegramContinuationTokenFromState(c)).send(t.message,{auth:t.auth,state:c})},events:r});return attachTelegramDeliver(i),i}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let t=e.config.api,n=e.state,r=e.config.credentials;function anchor(t){let r=n.chatType??t.chatType??null;n.chatType===null&&t.chatType!==void 0&&(n.chatType=t.chatType),t.id&&shouldAnchorTelegramConversation(r)&&(n.conversationId=t.id,n.chatId&&e.session?.continuation?.rekey(telegramContinuationToken({chatId:n.chatId,conversationId:t.id,messageThreadId:n.messageThreadId??void 0})))}async function sendOne(e){let t=await sendMessage(e);return anchor(t),t}async function sendMessage(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for outbound message.`);return sendTelegramMessage({apiBaseUrl:t?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??n.messageThreadId??void 0},credentials:r,fetch:t?.fetch,fileBaseUrl:t?.fileBaseUrl,chatId:i})}return{botUsername:n.botUsername??e.config.botUsername,chatId:n.chatId??``,chatType:n.chatType??void 0,conversationId:n.conversationId??void 0,messageThreadId:n.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:t?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:r,fetch:t?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},editMessageText(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for text edit.`);return editTelegramMessageText({apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup,text:e.text})},post(e){return postTelegramMessage(e,sendOne)},postEphemeral(e,t,n){return postTelegramMessage({...typeof t==`string`?{text:t}:t,ephemeral_message_parameters:{...n?.callbackQueryId===void 0?{}:{callback_query_id:n.callbackQueryId},receiver_user_id:e}},sendMessage)},request(e,n){return callTelegramApi({apiBaseUrl:t?.apiBaseUrl,body:n,botToken:r?.botToken,fetch:t?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let i=n.chatId??``;if(i)try{await sendTelegramChatAction({action:e,apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageThreadId:n.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:i})}}}}function shouldAnchorTelegramConversation(e){return e===`group`||e===`supergroup`}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{...n.ephemeral_message_parameters===void 0?{}:{ephemeral_message_parameters:n.ephemeral_message_parameters},text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromTelegramMessage(e.message,e.config.botUsername),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=buildTelegramTurnMessage(e.message,i),o=formatTelegramContextBlock({botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,isMentioned:isTelegramBotMentioned(e.message,e.config.botUsername),messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[],c=e.message.text||e.message.caption,l=e.message.replyToMessage?.from?.isBot===!0&&c.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:c})]:void 0;try{await e.from(telegramContinuationTokenFromState(t))[INTERNAL_CHANNEL_DELIVER]({context:[o,...s],inputResponses:l,message:a},{auth:r.auth,state:t,title:r.title})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromTelegramCallbackQuery(e.query,e.config.botUsername),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_AUTHORIZATION_CALLBACK_PREFIX)===!0){await dispatchTelegramAuthorizationCallback({continuationToken:telegramContinuationTokenFromState(t),query:e.query,resolveSession:e.resolveSession,state:t,telegram:n});return}if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.from(telegramContinuationTokenFromState(t)).respond([telegramCallbackInputResponse(e.query.data)],{auth:null})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
@@ -1 +1 @@
1
- import{resolveSelfModificationConfig}from"./config.js";import{hasGitHubCredential}from"./credentials.js";import{resolveSelfModificationMode}from"./mode.js";import{defineAgent,defineDynamic}from"#public/index.js";const DEFAULT_SELF_MODIFICATION_MODEL=`anthropic/claude-sonnet-5`;function renderDescription(e){return e.filter(e=>e.length>0).join(` `)}function defineSelfModificationAgent(e={}){let t=e.model??`anthropic/claude-sonnet-5`,n=resolveSelfModificationConfig(e.config),resolve=async(e,r)=>{let i=resolveSelfModificationMode(n),a=renderDescription([`Delegate here when the user asks to change this eve agent or its authored source.`,`If the requested change references a tool or skill, include the exact identifier. Do not infer source paths; let the child resolve the appropriate file under /source. Delegate the requested change and existing constraints without adding unrequested features, implementation steps, or reporting requirements.`,`Treat requests for persistent changes to future behavior or capabilities as source-modification requests, even when the requester does not mention files or source code. Infer persistence from the request and conversation rather than waiting for phrases such as “modify your source.” For example, asking the agent to stop always doing something, add a capability, or change future responses calls for inspecting and editing the authored source instead of providing a one-turn workaround.`,i===`local`?`Delegate questions about which integrations, channels, connections, or capabilities are available to add: the subagent searches the eve registry and reports exact item addresses instead of guessing them.`:`Delegate requests to add integrations, channels, connections, or capabilities: the subagent searches the official eve registry and can install source changes into the draft proposal. Setup may require non-secret answers or an external action, and secret binding remains separate.`,i===`local`?`Delegate requests to investigate, diagnose, or optimize the agent's behavior from local traces to this subagent: it can inspect the invoking session's trace and make persistent source changes when warranted.`:``,`Resolve short follow-ups such as “yes” or “do it” against the preceding conversation. If whether the requested change should persist is genuinely ambiguous, ask one concise clarifying question.`,i===`local`?`Source edits do not affect the caller’s current turn. After this subagent reports changes, do not invoke edited tools or attempt runtime verification until a new user turn.`:`Source edits do not affect the caller’s current turn. The subagent can publish only a draft pull request, and changes become effective only after review, merge, and redeployment.`]);if(i===`local`)return defineAgent({description:a,model:t});if(i!==`deployed`||n.deployed===void 0||n.deployed.credentials.kind===`pat`&&!hasGitHubCredential())return null;try{if(!await n.deployed.authorize({channel:r.channel,principal:r.session.auth.current}))return null}catch{return null}return defineAgent({description:a,model:t})};return defineDynamic({events:{"session.started":resolve,"turn.started":resolve}})}var agent_default=defineSelfModificationAgent();export{DEFAULT_SELF_MODIFICATION_MODEL,agent_default as default,defineSelfModificationAgent};
1
+ import{resolveSelfModificationConfig}from"./config.js";import{hasGitHubCredential}from"./credentials.js";import{resolveSelfModificationMode}from"./mode.js";import{defineAgent,defineDynamic}from"#public/index.js";const DEFAULT_SELF_MODIFICATION_MODEL=`anthropic/claude-sonnet-5`;function renderDescription(e){return e.filter(e=>e.length>0).join(` `)}function defineSelfModificationAgent(e={}){let t=e.model??`anthropic/claude-sonnet-5`,n=resolveSelfModificationConfig(e.config),resolve=async(e,r)=>{let i=resolveSelfModificationMode(n),a=renderDescription([`Delegate here when the user asks to change this eve agent or its authored source.`,`If the requested change references a tool or skill, include the exact identifier. Do not infer source paths; let the child resolve the appropriate file under /source. Delegate the requested change and existing constraints without adding unrequested features, implementation steps, or reporting requirements.`,`Treat requests for persistent changes to future behavior or capabilities as source-modification requests, even when the requester does not mention files or source code. Infer persistence from the request and conversation rather than waiting for phrases such as “modify your source.” For example, asking the agent to stop always doing something, add a capability, or change future responses calls for inspecting and editing the authored source instead of providing a one-turn workaround.`,`Treat questions phrased as whether you can install, add, enable, or connect to a named product or service as requests to extend this eve agent and delegate immediately. Do not assume they refer to device software, ask what kind of installation they mean, or deny them because you lack access to the user's device. The subagent determines whether the request maps to an integration, channel, connection, or other capability, then checks registry availability and any required setup.`,i===`local`?`Delegate questions about which integrations, channels, connections, or capabilities are available to add: the subagent searches the eve registry and reports exact item addresses instead of guessing them.`:`Delegate requests to add integrations, channels, connections, or capabilities: the subagent searches the official eve registry and can install source changes into the draft proposal. Setup may require non-secret answers or an external action, and secret binding remains separate.`,i===`local`?`Delegate requests to investigate, diagnose, or optimize the agent's behavior from local traces to this subagent: it can inspect the invoking session's trace and make persistent source changes when warranted.`:``,`Resolve short follow-ups such as “yes” or “do it” against the preceding conversation. If whether the requested change should persist is genuinely ambiguous, ask one concise clarifying question.`,i===`local`?`Source edits do not affect the caller’s current turn. After this subagent reports changes, do not invoke edited tools or attempt runtime verification until a new user turn.`:`Source edits do not affect the caller’s current turn. The subagent can publish only a draft pull request, and changes become effective only after review, merge, and redeployment.`]);if(i===`local`)return defineAgent({description:a,model:t});if(i!==`deployed`||n.deployed===void 0||n.deployed.credentials.kind===`pat`&&!hasGitHubCredential())return null;try{if(!await n.deployed.authorize({channel:r.channel,principal:r.session.auth.current}))return null}catch{return null}return defineAgent({description:a,model:t})};return defineDynamic({events:{"session.started":resolve,"turn.started":resolve}})}var agent_default=defineSelfModificationAgent();export{DEFAULT_SELF_MODIFICATION_MODEL,agent_default as default,defineSelfModificationAgent};
@@ -10,7 +10,11 @@ Resolve named tools by filename under /source/tools before searching contents. T
10
10
 
11
11
  Use selfmod__edit_file for localized changes; include only the unique matching text. For a complete rewrite, use read_file then write_file; do not encode the whole old file as one replacement. If tool arguments fail argument parsing, retry the same tool with corrected encoding.
12
12
 
13
- Once paths are known, batch independent file reads and edits to different files. Never edit the same file concurrently.`,`## Add integrations
13
+ Once paths are known, batch independent file reads and edits to different files. Never edit the same file concurrently.`,`## Add model-callable actions
14
+
15
+ When the requester wants this agent to gain a reusable action or capability that it can invoke in future turns, implement it as an authored eve tool under /source/tools. This applies even when the request describes the action without using the word “tool.” Follow the path-derived naming and defineTool conventions in the mounted eve tools documentation and existing source.
16
+
17
+ Do not substitute a loose Python, shell, or JavaScript file for a model-callable tool. A support script is acceptable only when an authored eve tool invokes it as an implementation detail, or when the requester explicitly asks for a standalone script instead of an agent capability.`,`## Add integrations
14
18
 
15
19
  Before adding a new eve-managed channel, connection, extension, instrumentation, or memory integration, call selfmod__search_registry alongside source discovery. If an item fulfills the requirement, install it with selfmod__registry_add using its exact address. If no matching item fulfills the requirement, or if the developer asks for a custom implementation, write the integration yourself.
16
20
 
@@ -18,14 +22,7 @@ Modifying the behavior of an existing skill or tool does not require a registry
18
22
 
19
23
  Registry installation is outside the source sandbox.`,`## Consult documentation
20
24
 
21
- The eve framework documentation is mounted read-only at /eve-docs. Prefer source and existing local patterns. Before reading docs, identify the specific unresolved API question. Search the relevant file for its heading or term, then read only the bounded surrounding section. Do not concatenate wildcard files or read a full page when a section answers the question. Stop when the question is resolved.`,t===`local`?`## Local environment
22
-
23
- The selfmod__registry_add tool will complete installation for items that need no setup. In the local dev TUI, a \`needs-terminal\` result from the tool call automatically opens the existing setup panel for the user to complete setup there. In headless development, relay the terminal command the tool names.
24
-
25
- Local eve dev logs are available read-only at /logs.
26
- Local trace segments are mounted read-only at /traces when available. Inspect other traces only when the user asks about another session or broader behavior.
27
-
28
- The application package.json is not mounted. Do not search outside /source for application files. You cannot run host binaries such as git, node, pnpm, or tsc. Use existing imports and selfmod__registry_add for supported registry installations.`:"## Deployed environment\n\nThe selfmod__registry_add tool may return `completed`, `input-required`, `external-action-required`, `cancelled`, or `failed`. Supply only non-secret structured answers when continuing an `input-required` setup; set `installed: true` so the continuation does not reinstall source. Never request, accept, or repeat secret values. External authorization and secret binding are incomplete follow-up boundaries, not evidence that an integration is active.\n\nThe configured target branch is checked out as a disposable workspace under /workspace/repository. Make ordinary changes through /source, which is the writable view of the configured application's agent/ directory. Publication validates the final repository snapshot, including registry, manifest, and lockfile changes. Never modify Git refs, access GitHub directly, or use shell commands to write files. The sandbox has no reusable GitHub credential after checkout.\n\nComplete all edits and registry installations before publication, and call publish by itself. Before publication, review and summarize the complete intended scope. Call publish once with a concise title and summary. A successful result is only a draft pull request. Return its URL and changed paths, and state that merge and deployment have not occurred.",t===`local`?localTraceGuidance(e):``,`## Work efficiently
25
+ The eve framework documentation is mounted read-only at /eve-docs. Prefer source and existing local patterns. Before reading docs, identify the specific unresolved API question. Search the relevant file for its heading or term, then read only the bounded surrounding section. Do not concatenate wildcard files or read a full page when a section answers the question. Stop when the question is resolved.`,t===`local`?"## Local environment\n\nThe selfmod__registry_add tool will complete installation for items that need no setup. In the local dev TUI, a `needs-terminal` result from the tool call automatically opens the existing setup panel for the user to complete setup there. In headless development, if a `needs-terminal` result includes `nextCommand`, present that exact value as the only shell command in your response. Never infer, construct, or rewrite a command: installing an item uses `eve add <item>`; `eve registry add` configures registry namespace mappings and does not install items.\n\nLocal eve dev logs are available read-only at /logs.\nLocal trace segments are mounted read-only at /traces when available. Inspect other traces only when the user asks about another session or broader behavior.\n\nThe application package.json is not mounted. Do not search outside /source for application files. You cannot run host binaries such as git, node, pnpm, or tsc. Use existing imports and selfmod__registry_add for supported registry installations.":"## Deployed environment\n\nThe selfmod__registry_add tool may return `completed`, `input-required`, `external-action-required`, `cancelled`, or `failed`. Supply only non-secret structured answers when continuing an `input-required` setup; set `installed: true` so the continuation does not reinstall source. Never request, accept, or repeat secret values. External authorization and secret binding are incomplete follow-up boundaries, not evidence that an integration is active.\n\nThe configured target branch is checked out as a disposable workspace under /workspace/repository. Make ordinary changes through /source, which is the writable view of the configured application's agent/ directory. Publication validates the final repository snapshot, including registry, manifest, and lockfile changes. Never modify Git refs, access GitHub directly, or use shell commands to write files. The sandbox has no reusable GitHub credential after checkout.\n\nComplete all edits and registry installations before publication, and call publish by itself. Before publication, review and summarize the complete intended scope. Call publish once with a concise title and summary. A successful result is only a draft pull request. Return its URL and changed paths, and state that merge and deployment have not occurred.",t===`local`?localTraceGuidance(e):``,`## Work efficiently
29
26
 
30
27
  Skip task lists for simple work - only use them for complex actions. Never spend a turn only reporting status or updating the task list - batch them with other calls.
31
28
 
@@ -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.93`,DEFAULT_CONNECT_PACKAGE_VERSION=`1.0.0`,DEFAULT_ZOD_PACKAGE_VERSION=`4.5.4`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.54.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,t){let n=parseChatGptModelSelection(e);return n===void 0?BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(t)):`import { defineAgent } from "eve";\nimport { chatgpt } from "eve/models/openai";\n\nexport default defineAgent({\n model: chatgpt(${JSON.stringify(n)}),\n${reasoningTemplateLine(t)}});\n`}function reasoningTemplateLine(e){return e===void 0||e===`provider-default`?``:` reasoning: "${e}",\n`}function renderTemplate(e,t){return e===BASE_AGENT_TEMPLATE&&parseChatGptModelSelection(t.model)!==void 0?renderAgentTemplate(t.model,t.reasoning):e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(t.reasoning)).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(t.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(t.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(t.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,t.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,t.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,t.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,t.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,t.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,t.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.93`,DEFAULT_CONNECT_PACKAGE_VERSION=`1.0.0`,DEFAULT_ZOD_PACKAGE_VERSION=`4.5.4`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.54.5`,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,t){let n=parseChatGptModelSelection(e);return n===void 0?BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(t)):`import { defineAgent } from "eve";\nimport { chatgpt } from "eve/models/openai";\n\nexport default defineAgent({\n model: chatgpt(${JSON.stringify(n)}),\n${reasoningTemplateLine(t)}});\n`}function reasoningTemplateLine(e){return e===void 0||e===`provider-default`?``:` reasoning: "${e}",\n`}function renderTemplate(e,t){return e===BASE_AGENT_TEMPLATE&&parseChatGptModelSelection(t.model)!==void 0?renderAgentTemplate(t.model,t.reasoning):e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(t.reasoning)).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(t.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(t.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(t.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,t.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,t.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,t.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,t.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,t.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,t.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__",
@@ -438,6 +438,21 @@ export default slackChannel({
438
438
 
439
439
  HITL renders as Slack buttons and selects. Fixed-choice actions and freeform modal submissions pass through `onInputResponse` before the parked session resumes. The initial **Type your answer** action only opens eve's modal; the hook runs when the user submits an answer.
440
440
 
441
+ Set `approvalChannel` to choose where each input request appears, including tool approvals and questions from `ctx.ask()`. Return `"direct-message"` when the request must be visible only to the Slack user who triggered the turn, or `"thread"` for the normal shared thread.
442
+
443
+ ```ts title="agent/channels/slack.ts"
444
+ import { slackChannel } from "eve/channels/slack";
445
+
446
+ export default slackChannel({
447
+ approvalChannel: (request) =>
448
+ request.kind === "question" || request.action.toolName === "review_answer"
449
+ ? "direct-message"
450
+ : "thread",
451
+ });
452
+ ```
453
+
454
+ The callback receives the `InputRequest` and current `SessionContext`, so it can switch on `kind`, `action`, or presentation properties. Omit it to keep every request in the thread, or return `"direct-message"` unconditionally to make every request private. If eve cannot resolve the triggering Slack user for a direct message, it logs the undelivered request and posts no fallback, so private delivery fails closed. The existing `onInputResponse` or tool approval response policy still decides whether the person who clicks may respond.
455
+
441
456
  Authorization prompts split public status from private credentials. A sign-in challenge (OAuth URL, device code) is a credential. Anyone who completes it binds their identity to the session's connection. The default `authorization.required` handler posts a public, link-free status in the thread, delivers the actual challenge ephemerally to the triggering user, device code included, and then updates that public status when `authorization.completed` fires. The handler receives a private-delivery context with `postEphemeral`, `postDirectMessage` (needs the `im:write` scope), and `state`. There is, intentionally, no public `post` and no raw API access.
442
457
 
443
458
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve",
3
- "version": "0.54.4",
3
+ "version": "0.54.5",
4
4
  "private": false,
5
5
  "description": "Filesystem-first framework for durable backend AI agents that run anywhere.",
6
6
  "keywords": [