eve 0.66.1 → 0.66.3
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 +18 -0
- package/dist/src/cli/commands/deploy.d.ts +3 -1
- package/dist/src/cli/commands/deploy.js +1 -1
- package/dist/src/cli/commands/integration-connect.js +1 -1
- package/dist/src/cli/commands/register-project-commands.js +1 -1
- package/dist/src/cli/commands/registry-declared-setups.d.ts +0 -1
- package/dist/src/cli/commands/registry-declared-setups.js +1 -1
- package/dist/src/cli/commands/registry.d.ts +5 -1
- package/dist/src/cli/commands/registry.js +1 -1
- package/dist/src/cli/commands/vercel-non-interactive.d.ts +7 -0
- package/dist/src/cli/commands/vercel-non-interactive.js +1 -1
- package/dist/src/cli/dev/tui/argument-typeahead.d.ts +1 -2
- package/dist/src/cli/dev/tui/argument-typeahead.js +2 -2
- package/dist/src/cli/dev/tui/blocks.d.ts +5 -1
- package/dist/src/cli/dev/tui/blocks.js +3 -3
- package/dist/src/cli/dev/tui/flow-drawer.d.ts +11 -0
- package/dist/src/cli/dev/tui/flow-drawer.js +1 -0
- package/dist/src/cli/dev/tui/prompt-command-handler.js +1 -1
- package/dist/src/cli/dev/tui/prompt-commands.d.ts +199 -0
- package/dist/src/cli/dev/tui/prompt-commands.js +2 -2
- package/dist/src/cli/dev/tui/registry-result-message.d.ts +15 -4
- package/dist/src/cli/dev/tui/registry-result-message.js +4 -4
- package/dist/src/cli/dev/tui/remote-auth.js +1 -1
- package/dist/src/cli/dev/tui/runner.d.ts +18 -6
- package/dist/src/cli/dev/tui/runner.js +3 -2
- package/dist/src/cli/dev/tui/setup-commands.d.ts +5 -22
- package/dist/src/cli/dev/tui/setup-commands.js +2 -1
- package/dist/src/cli/dev/tui/setup-flow.d.ts +2 -10
- package/dist/src/cli/dev/tui/setup-panel.d.ts +4 -23
- package/dist/src/cli/dev/tui/setup-panel.js +3 -3
- package/dist/src/cli/dev/tui/terminal-renderer.d.ts +11 -7
- package/dist/src/cli/dev/tui/terminal-renderer.js +11 -10
- package/dist/src/cli/dev/tui/tui-prompter.js +1 -1
- package/dist/src/cli/dev/tui/tui.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +6 -6
- package/dist/src/compiled/@workflow/core/create-hook.d.ts +38 -1
- package/dist/src/compiled/@workflow/core/global.d.ts +2 -0
- package/dist/src/compiled/@workflow/core/index.js +2 -2
- package/dist/src/compiled/@workflow/core/private.js +1 -1
- package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +9 -0
- package/dist/src/compiled/@workflow/core/runtime/helpers.js +1 -1
- package/dist/src/compiled/@workflow/core/runtime/hook-wake.d.ts +89 -0
- package/dist/src/compiled/@workflow/core/runtime/open-hook-wait-state.d.ts +55 -0
- package/dist/src/compiled/@workflow/core/runtime/quickjs-runtime.d.ts +2 -0
- package/dist/src/compiled/@workflow/core/runtime/suspension-handler.d.ts +8 -1
- package/dist/src/compiled/@workflow/core/runtime.js +4 -4
- package/dist/src/compiled/@workflow/core/serialization/types.d.ts +8 -0
- package/dist/src/compiled/@workflow/core/serialization.d.ts +8 -0
- package/dist/src/compiled/@workflow/core/serialization.js +1 -1
- package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/workflow.js +1 -1
- package/dist/src/compiled/@workflow/errors/index.d.ts +23 -0
- package/dist/src/compiled/@workflow/errors/index.js +1 -1
- package/dist/src/compiled/@workflow/world/events.d.ts +34 -0
- package/dist/src/compiled/@workflow/world/hooks.d.ts +27 -0
- package/dist/src/compiled/@workflow/world/index.d.ts +2 -2
- package/dist/src/compiled/@workflow/world/interfaces.d.ts +24 -0
- package/dist/src/compiled/@workflow/world/spec-version.d.ts +32 -11
- package/dist/src/compiled/@workflow/world-local/index.js +3 -3
- package/dist/src/compiled/@workflow/world-vercel/index.js +17 -17
- package/dist/src/compiled/_chunks/workflow/{attribute-changes-89Nwz7XW.js → attribute-changes-DzJovO_f.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/dist-BQSf8Hax.js +1 -0
- package/dist/src/compiled/_chunks/workflow/dist-BfMqXLK3.js +3 -0
- package/dist/src/compiled/_chunks/workflow/frames-qKc30rjg.js +3 -0
- package/dist/src/compiled/_chunks/workflow/{global-DIlFoH2w.js → global-BQnKzRXo.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{helpers-k3zcudWr.js → helpers-CNMO7zB1.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{logger-Bzc8jWWj.js → logger-BCEj9caG.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/resume-hook-QS_cMwHn.js +1 -0
- package/dist/src/compiled/_chunks/workflow/run-CA5wWV2S.js +1 -0
- package/dist/src/compiled/_chunks/workflow/serialization-CJ265vSe.js +3 -0
- package/dist/src/compiled/_chunks/workflow/sleep-CBn94Ayv.js +1 -0
- package/dist/src/compiled/_chunks/workflow/{wait-until-DXLZx3tU.js → wait-until-Bo2U6ON9.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{ws-transport-BSM_Cl7a.js → ws-transport-BrsMYqJg.js} +1 -1
- package/dist/src/discover/filesystem.d.ts +2 -0
- package/dist/src/discover/filesystem.js +1 -1
- package/dist/src/discover/grammar.d.ts +4 -2
- package/dist/src/discover/grammar.js +1 -1
- package/dist/src/discover/sandbox.js +1 -1
- package/dist/src/execution/dynamic-workflow/tool.js +1 -1
- package/dist/src/execution/session/entry-input.d.ts +4 -0
- package/dist/src/execution/session/entry-input.js +1 -1
- package/dist/src/execution/tools/agent-router-workflow.js +1 -1
- package/dist/src/execution/tools/ask-question-workflow.js +1 -1
- package/dist/src/execution/tools/sleep-workflow.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/extensions/code/extension/lib/connect-authentication.js +1 -1
- package/dist/src/extensions/code/extension/lib/github-shell.js +1 -1
- package/dist/src/extensions/code/extension/lib/prwatch-github.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/instrumentation-layout.js +1 -1
- package/dist/src/internal/model-auth/vercel-cli.d.ts +2 -0
- package/dist/src/internal/model-auth/vercel-cli.js +1 -1
- package/dist/src/internal/nitro/host/build-extension.js +1 -1
- package/dist/src/internal/nitro/host/extension-capability-requirements.d.ts +1 -0
- package/dist/src/internal/nitro/host/extension-capability-requirements.js +1 -1
- package/dist/src/internal/nitro/host/extension-declarations.js +1 -1
- package/dist/src/internal/nitro/host/extension-distribution.js +1 -1
- package/dist/src/internal/nitro/host/extension-state-usage.d.ts +2 -2
- package/dist/src/internal/nitro/host/extension-state-usage.js +1 -1
- package/dist/src/internal/workflow/development-world-client.js +1 -1
- package/dist/src/public/channels/slack/activity-plan.d.ts +2 -1
- package/dist/src/public/channels/slack/activity-plan.js +1 -1
- package/dist/src/public/channels/slack/activity.d.ts +2 -0
- package/dist/src/public/channels/slack/activity.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +7 -0
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/attachments.d.ts +5 -2
- package/dist/src/public/channels/slack/attachments.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +1 -0
- package/dist/src/public/channels/slack/input-response.d.ts +2 -0
- package/dist/src/public/channels/slack/input-response.js +1 -1
- package/dist/src/public/channels/slack/interaction-cards.js +1 -1
- package/dist/src/public/channels/slack/interactions.d.ts +2 -0
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +3 -0
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/slack/slash-command.d.ts +5 -1
- package/dist/src/public/channels/slack/slash-command.js +1 -1
- package/dist/src/public/channels/slack/transport.d.ts +76 -0
- package/dist/src/public/channels/slack/transport.js +1 -0
- package/dist/src/setup/boxes/link-project.d.ts +2 -0
- package/dist/src/setup/boxes/link-project.js +1 -1
- package/dist/src/setup/cli/channel-setup-prompter.d.ts +2 -9
- package/dist/src/setup/cli/channel-setup-prompter.js +1 -1
- package/dist/src/setup/cli/index.d.ts +1 -1
- package/dist/src/setup/connection-connector.js +1 -1
- package/dist/src/setup/flows/deploy.d.ts +1 -0
- package/dist/src/setup/flows/deploy.js +1 -1
- package/dist/src/setup/flows/install-vercel-cli.js +1 -1
- package/dist/src/setup/flows/login.js +1 -1
- package/dist/src/setup/flows/model-login-connection.d.ts +0 -16
- package/dist/src/setup/flows/model-login-connection.js +1 -1
- package/dist/src/setup/flows/model-login-options.d.ts +59 -0
- package/dist/src/setup/flows/model-login-options.js +1 -0
- package/dist/src/setup/flows/model-login.js +1 -1
- package/dist/src/setup/flows/model-source-change.d.ts +0 -2
- package/dist/src/setup/flows/model-source-change.js +1 -1
- package/dist/src/setup/flows/registry-session.d.ts +15 -13
- package/dist/src/setup/flows/registry-session.js +1 -1
- package/dist/src/setup/flows/registry.js +1 -1
- package/dist/src/setup/flows/vercel-model-login.js +1 -1
- package/dist/src/setup/integrations/photon/setup-flow.js +1 -1
- package/dist/src/setup/integrations/runner.js +1 -1
- package/dist/src/setup/prompter.d.ts +2 -2
- package/dist/src/setup/registry-setup-client.js +1 -1
- package/dist/src/setup/registry-setup-protocol.d.ts +0 -4
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/slack-connect-lifecycle.js +1 -1
- package/dist/src/setup/slackbot.js +1 -1
- package/dist/src/setup/vercel-project.d.ts +2 -0
- package/dist/src/setup/vercel-project.js +0 -0
- package/dist/src/setup/vercel-trace-sampling.d.ts +3 -0
- package/dist/src/setup/vercel-trace-sampling.js +1 -0
- package/docs/channels/slack.mdx +31 -2
- package/docs/guides/deployment/vercel.mdx +2 -0
- package/docs/guides/dev-tui.md +1 -1
- package/docs/guides/instrumentation/agent-runs.mdx +4 -4
- package/docs/reference/agent-files.md +16 -0
- package/package.json +6 -6
- package/dist/src/compiled/_chunks/workflow/dist-CNT2vjZ4.js +0 -1
- package/dist/src/compiled/_chunks/workflow/dist-DtXwQbZr.js +0 -3
- package/dist/src/compiled/_chunks/workflow/frames-DnrfSd1P.js +0 -3
- package/dist/src/compiled/_chunks/workflow/resume-hook-BmJ263ZR.js +0 -1
- package/dist/src/compiled/_chunks/workflow/run-NLvREued.js +0 -1
- package/dist/src/compiled/_chunks/workflow/serialization-HCDeQWcR.js +0 -3
- package/dist/src/compiled/_chunks/workflow/sleep-Bi508qmg.js +0 -1
- package/dist/src/public/channels/slack/api-errors.d.ts +0 -33
- package/dist/src/public/channels/slack/api-errors.js +0 -1
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/authorization.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/connector.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/index.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/installation.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/internal/base-url.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/internal/default-scopes.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/internal/env.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/internal/url-validation.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_37d5e4f23f0f03cba2e0819e67e81b38 → @vercel_connect@2.2.0_@ai-sdk_mcp@2.0.52_zod@4.5.4__@auth_core@0.41.2_@chat-adapter_sla_3fe44d656b2dcd93a04011e80966afff}/node_modules/@vercel/connect/dist/token.js +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChannelActivityRenderer } from "#channel/activity-renderer.js";
|
|
2
2
|
import type { ActivitySnapshotV1 } from "#protocol/activity.js";
|
|
3
3
|
import { type SlackBotToken } from "#public/channels/slack/api.js";
|
|
4
|
+
import type { SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
4
5
|
declare const SLACK_ACTIVITY_RENDERER: unique symbol;
|
|
5
6
|
export interface SlackActivityRenderer {
|
|
6
7
|
readonly id: string;
|
|
@@ -53,6 +54,7 @@ export declare function experimental_slackActivityPlan(): SlackActivityRenderer;
|
|
|
53
54
|
export declare function experimental_slackActivityRenderer<State>(renderer: ExperimentalSlackActivityRenderer<State>): SlackActivityRenderer;
|
|
54
55
|
export declare function hasSlackActivityStatus(renderers: readonly SlackActivityRenderer[] | undefined): boolean;
|
|
55
56
|
export declare function buildSlackActivityRenderers(input: {
|
|
57
|
+
readonly api?: SlackTransportOptions;
|
|
56
58
|
readonly botToken: SlackBotToken | undefined;
|
|
57
59
|
readonly renderers: readonly SlackActivityRenderer[];
|
|
58
60
|
}): readonly ChannelActivityRenderer[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{callSlackApi}from"#public/channels/slack/api.js";import{SLACK_ACTIVITY_PLAN_RENDERER_ID,createSlackPlanRenderer}from"#public/channels/slack/activity-plan.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";const SLACK_ACTIVITY_STATUS_RENDERER_ID=`slack.status.v1`,SLACK_ACTIVITY_MESSAGE_RENDERER_ID=`slack.experimental.tree.v1`,SLACK_ACTIVITY_RENDERER=Symbol(`eve.slack.activity-renderer`);function experimental_slackActivityStatus(){return{[SLACK_ACTIVITY_RENDERER]:!0,id:SLACK_ACTIVITY_STATUS_RENDERER_ID}}function experimental_slackActivityTree(){return{[SLACK_ACTIVITY_RENDERER]:!0,id:SLACK_ACTIVITY_MESSAGE_RENDERER_ID}}function experimental_slackActivityPlan(){return{[SLACK_ACTIVITY_RENDERER]:!0,id:SLACK_ACTIVITY_PLAN_RENDERER_ID}}function experimental_slackActivityRenderer(e){return validateCustomSlackActivityRenderer(e),{[SLACK_ACTIVITY_RENDERER]:!0,id:e.id,render:e.render,dispose:e.dispose}}function hasSlackActivityStatus(e){return e?.some(e=>e.id===SLACK_ACTIVITY_STATUS_RENDERER_ID)===!0}function buildSlackActivityRenderers(e){let t=new Set;return e.renderers.map(n=>{if(n[SLACK_ACTIVITY_RENDERER]!==!0)throw TypeError(`Slack activity renderers must be created by an eve renderer factory.`);if(t.has(n.id))throw TypeError(`Duplicate Slack activity renderer "${n.id}".`);return t.add(n.id),n.id===SLACK_ACTIVITY_MESSAGE_RENDERER_ID?createSlackActivityRenderer(e.botToken):n.id===SLACK_ACTIVITY_PLAN_RENDERER_ID?createSlackPlanRenderer(e.botToken):n.id===SLACK_ACTIVITY_STATUS_RENDERER_ID?createSlackStatusRenderer(e.botToken):(assertCustomSlackActivityRenderer(n),createCustomSlackActivityRenderer(n))})}function assertCustomSlackActivityRenderer(e){validateCustomSlackActivityRenderer(e)}function validateCustomSlackActivityRenderer(e){if(typeof e!=`object`||!e||!(`id`in e)||typeof e.id!=`string`||e.id.trim()===``)throw TypeError(`Slack activity renderer ids must be non-empty strings.`);if(e.id===SLACK_ACTIVITY_STATUS_RENDERER_ID||e.id===SLACK_ACTIVITY_MESSAGE_RENDERER_ID||e.id===SLACK_ACTIVITY_PLAN_RENDERER_ID)throw TypeError(`Slack activity renderer id "${e.id}" is reserved by eve.`);if(!(`render`in e)||typeof e.render!=`function`)throw TypeError(`Custom Slack activity renderers must define a render function.`);if(`dispose`in e&&e.dispose!==void 0&&typeof e.dispose!=`function`)throw TypeError(`Custom Slack activity renderer dispose must be a function.`)}function createCustomSlackActivityRenderer(e){return{id:e.id,async render({destination:t,snapshot:n,state:r}){return e.render({destination:experimentalSlackActivityDestination(t),snapshot:n,state:r})},async dispose({destination:t,state:n}){await e.dispose?.({destination:experimentalSlackActivityDestination(t),state:n})}}}function experimentalSlackActivityDestination(e){return{channelId:typeof e.channelId==`string`?e.channelId:null,installationTeamId:typeof e.installationTeamId==`string`?e.installationTeamId:null,teamId:typeof e.teamId==`string`?e.teamId:null,threadTs:typeof e.threadTs==`string`?e.threadTs:null,triggeringUserId:typeof e.triggeringUserId==`string`?e.triggeringUserId:null}}function createSlackStatusRenderer(e){return{id:SLACK_ACTIVITY_STATUS_RENDERER_ID,async dispose({destination:
|
|
1
|
+
import{callSlackApi}from"#public/channels/slack/api.js";import{SLACK_ACTIVITY_PLAN_RENDERER_ID,createSlackPlanRenderer}from"#public/channels/slack/activity-plan.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";const SLACK_ACTIVITY_STATUS_RENDERER_ID=`slack.status.v1`,SLACK_ACTIVITY_MESSAGE_RENDERER_ID=`slack.experimental.tree.v1`,SLACK_ACTIVITY_RENDERER=Symbol(`eve.slack.activity-renderer`);function experimental_slackActivityStatus(){return{[SLACK_ACTIVITY_RENDERER]:!0,id:SLACK_ACTIVITY_STATUS_RENDERER_ID}}function experimental_slackActivityTree(){return{[SLACK_ACTIVITY_RENDERER]:!0,id:SLACK_ACTIVITY_MESSAGE_RENDERER_ID}}function experimental_slackActivityPlan(){return{[SLACK_ACTIVITY_RENDERER]:!0,id:SLACK_ACTIVITY_PLAN_RENDERER_ID}}function experimental_slackActivityRenderer(e){return validateCustomSlackActivityRenderer(e),{[SLACK_ACTIVITY_RENDERER]:!0,id:e.id,render:e.render,dispose:e.dispose}}function hasSlackActivityStatus(e){return e?.some(e=>e.id===SLACK_ACTIVITY_STATUS_RENDERER_ID)===!0}function buildSlackActivityRenderers(e){let t=new Set;return e.renderers.map(n=>{if(n[SLACK_ACTIVITY_RENDERER]!==!0)throw TypeError(`Slack activity renderers must be created by an eve renderer factory.`);if(t.has(n.id))throw TypeError(`Duplicate Slack activity renderer "${n.id}".`);return t.add(n.id),n.id===SLACK_ACTIVITY_MESSAGE_RENDERER_ID?createSlackActivityRenderer(e.api,e.botToken):n.id===SLACK_ACTIVITY_PLAN_RENDERER_ID?createSlackPlanRenderer(e.api,e.botToken):n.id===SLACK_ACTIVITY_STATUS_RENDERER_ID?createSlackStatusRenderer(e.api,e.botToken):(assertCustomSlackActivityRenderer(n),createCustomSlackActivityRenderer(n))})}function assertCustomSlackActivityRenderer(e){validateCustomSlackActivityRenderer(e)}function validateCustomSlackActivityRenderer(e){if(typeof e!=`object`||!e||!(`id`in e)||typeof e.id!=`string`||e.id.trim()===``)throw TypeError(`Slack activity renderer ids must be non-empty strings.`);if(e.id===SLACK_ACTIVITY_STATUS_RENDERER_ID||e.id===SLACK_ACTIVITY_MESSAGE_RENDERER_ID||e.id===SLACK_ACTIVITY_PLAN_RENDERER_ID)throw TypeError(`Slack activity renderer id "${e.id}" is reserved by eve.`);if(!(`render`in e)||typeof e.render!=`function`)throw TypeError(`Custom Slack activity renderers must define a render function.`);if(`dispose`in e&&e.dispose!==void 0&&typeof e.dispose!=`function`)throw TypeError(`Custom Slack activity renderer dispose must be a function.`)}function createCustomSlackActivityRenderer(e){return{id:e.id,async render({destination:t,snapshot:n,state:r}){return e.render({destination:experimentalSlackActivityDestination(t),snapshot:n,state:r})},async dispose({destination:t,state:n}){await e.dispose?.({destination:experimentalSlackActivityDestination(t),state:n})}}}function experimentalSlackActivityDestination(e){return{channelId:typeof e.channelId==`string`?e.channelId:null,installationTeamId:typeof e.installationTeamId==`string`?e.installationTeamId:null,teamId:typeof e.teamId==`string`?e.teamId:null,threadTs:typeof e.threadTs==`string`?e.threadTs:null,triggeringUserId:typeof e.triggeringUserId==`string`?e.triggeringUserId:null}}function createSlackStatusRenderer(e,t){return{id:SLACK_ACTIVITY_STATUS_RENDERER_ID,async dispose({destination:n,state:r}){if(!isSlackStatusState(r)||r.status===``)return;let i=n.channelId,a=n.installationTeamId,o=n.threadTs;if(typeof i!=`string`||typeof o!=`string`||o===``)return;let s=await callSlackApi({api:e,body:{channel_id:i,status:``,thread_ts:o},botToken:t,context:{teamId:typeof a==`string`?a:void 0},operation:`assistant.threads.setStatus`});if(s.ok!==!0)throw Error(`Slack status disposal failed: ${s.error??`unknown_error`}`)},async render({destination:n,snapshot:r,state:i}){let a=n.channelId,o=n.installationTeamId,s=n.threadTs;if(typeof a!=`string`||typeof s!=`string`||s===``)return i;let c=selectSlackActivityStatus(r);if(isSlackStatusState(i)&&i.status===c)return i;let l={channel_id:a,status:c,thread_ts:s};c!==``&&(l.loading_messages=[c]);let u=await callSlackApi({api:e,body:l,botToken:t,context:{teamId:typeof o==`string`?o:void 0},operation:`assistant.threads.setStatus`});if(u.ok!==!0)throw Error(`Slack assistant.threads.setStatus failed: ${u.error??`unknown_error`}`);return{status:c}}}}function createSlackActivityRenderer(e,t){return{id:SLACK_ACTIVITY_MESSAGE_RENDERER_ID,async dispose(){},async render({destination:n,snapshot:r,state:i}){let a=n.channelId,o=n.installationTeamId,s=n.threadTs;if(typeof a!=`string`||typeof s!=`string`||s===``)return i;let c=isActivityState(i)?i.messages:{},l=activityMessages(r),u=Object.fromEntries(Object.entries(c).filter(([e])=>l.has(e)));for(let[n,r]of l){let i=c[n]??await recoverActivityMessage({api:e,botToken:t,channelId:a,installationTeamId:o,rootTurnId:n,threadTs:s});if(i?.text===r){u[n]=i;continue}let l=await writeActivityMessage({api:e,botToken:t,channelId:a,current:i,installationTeamId:o,rootTurnId:n,text:r,threadTs:s});if(l.ok!==!0&&i!==void 0&&l.error===`message_not_found`&&(l=await writeActivityMessage({api:e,botToken:t,channelId:a,installationTeamId:o,rootTurnId:n,text:r,threadTs:s})),l.ok!==!0)throw Error(`Slack activity message failed: ${l.error??`unknown_error`}`);let d=l.ts??i?.ts;if(typeof d!=`string`||d===``)throw Error(`Slack activity message response did not include ts.`);u[n]={text:r,ts:d}}return{messages:u}}}}async function writeActivityMessage(e){return await callSlackApi({body:e.current===void 0?{channel:e.channelId,metadata:{event_payload:{root_turn_id:e.rootTurnId},event_type:`eve_progress`},text:e.text,thread_ts:e.threadTs}:{channel:e.channelId,text:e.text,ts:e.current.ts},api:e.api,botToken:e.botToken,context:{teamId:typeof e.installationTeamId==`string`?e.installationTeamId:void 0},operation:e.current===void 0?`chat.postMessage`:`chat.update`})}async function recoverActivityMessage(e){let t,n=new Set;for(;;){let r={channel:e.channelId,inclusive:!0,limit:100,ts:e.threadTs};t!==void 0&&(r.cursor=t);let i=await callSlackApi({api:e.api,body:r,botToken:e.botToken,context:{teamId:typeof e.installationTeamId==`string`?e.installationTeamId:void 0},operation:`conversations.replies`});if(i.ok!==!0||!Array.isArray(i.messages))return;for(let t of i.messages){if(typeof t!=`object`||!t)continue;let n=Reflect.get(t,`metadata`),r=typeof n==`object`&&n?Reflect.get(n,`event_payload`):void 0;if(typeof n==`object`&&n&&Reflect.get(n,`event_type`)===`eve_progress`&&typeof r==`object`&&r&&Reflect.get(r,`root_turn_id`)===e.rootTurnId){let e=Reflect.get(t,`ts`),n=Reflect.get(t,`text`);if(typeof e==`string`)return{text:typeof n==`string`?n:``,ts:e}}}let a=Reflect.get(i,`response_metadata`),o=typeof a==`object`&&a?Reflect.get(a,`next_cursor`):void 0;if(typeof o!=`string`||o===``||n.has(o))return;n.add(o),t=o}}function activityMessages(e){let t=new Map;for(let n of Object.values(e.work)){let e=t.get(n.rootTurnId)??[];e.push(n),t.set(n.rootTurnId,e)}return new Map([...t].map(([t,n])=>[t,renderWorkTree(n,Object.values(e.actions).filter(e=>e.rootTurnId===t),Object.values(e.blockers).filter(e=>e.rootTurnId===t))]))}function renderWorkTree(e,t,n){let r=new Map,i=new Set(e.map(e=>e.id));for(let t of e){let e=t.parentId!==void 0&&i.has(t.parentId)?t.parentId:void 0,n=r.get(e)??[];n.push(t),r.set(e,n)}let a=new Set(e.flatMap(e=>e.parentId!==void 0&&e.callId!==void 0?[`action:${e.parentId}:${e.callId}`]:[])),o=new Map;for(let e of t){if(e.kind===`tool`&&a.has(e.id))continue;let t=o.get(e.parentWorkId)??[];t.push(e),o.set(e.parentWorkId,t)}let s=new Map;for(let e of n){let t=s.get(e.parentWorkId)??[];t.push(e),s.set(e.parentWorkId,t)}let c=[],append=e=>{c.length<20&&c.push(e)},visit=(e,t,n)=>{let i=e.kind===`root-turn`?`Working`:e.name??`Agent work`;append(`${t}${n}${phaseIcon(e.phase)} ${escapeSlackText(i)}`);let a=[...(s.get(e.id)??[]).map(e=>({blocker:e})),...(o.get(e.id)??[]).map(e=>({action:e})),...(r.get(e.id)??[]).map(e=>({child:e}))],c=`${t}${n===`├── `?`│ `:n===`└── `?` `:``}`;a.forEach((e,t)=>{let n=t===a.length-1?`└── `:`├── `;`blocker`in e?append(`${c}${n}${blockerIcon(e.blocker.phase)} ${escapeSlackText(e.blocker.label??blockerLabel(e.blocker.kind))}`):`action`in e?append(`${c}${n}${phaseIcon(e.action.phase)} ${escapeSlackText(e.action.label??e.action.name)}`):visit(e.child,c,n)})};for(let e of r.get(void 0)??[])visit(e,``,``);return`\`\`\`\n${c.join(`
|
|
2
2
|
`)}\n\`\`\``}function phaseIcon(e){switch(e){case`completed`:return`✓`;case`failed`:case`rejected`:return`✗`;case`cancelled`:return`⊘`;case`running`:return`•`}}function escapeSlackText(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`)}function isActivityState(e){return typeof e==`object`&&!!e&&typeof Reflect.get(e,`messages`)==`object`}function selectSlackActivityStatus(e){let t=Object.values(e.work).filter(e=>e.phase===`running`);if(t.length===0)return``;let n=newestBlocker(Object.values(e.blockers).filter(e=>e.phase===`blocked`));if(n!==void 0)return blockerLabel(n.kind);let r=newestAction(Object.values(e.actions).filter(e=>e.phase===`running`));if(r!==void 0)return truncateTypingStatus(r.label??r.name);let i=newestWork(t.filter(e=>e.kind!==`root-turn`));return i===void 0?`Working…`:truncateTypingStatus(i.name??`Working with agent`)}function blockerLabel(e){switch(e){case`approval`:return`Waiting for approval…`;case`authorization`:return`Waiting for sign-in…`;case`input`:return`Waiting for input…`}}function blockerIcon(e){return e===`blocked`?`◌`:e===`completed`?`✓`:e===`failed`?`✗`:`–`}function newestBlocker(e){return e.reduce((e,t)=>e===void 0||t.startedAt>=e.startedAt?t:e,void 0)}function newestAction(e){return e.reduce((e,t)=>e===void 0||t.startedAt>=e.startedAt?t:e,void 0)}function newestWork(e){return e.reduce((e,t)=>e===void 0||t.startedAt>=e.startedAt?t:e,void 0)}function isSlackStatusState(e){return typeof e==`object`&&!!e&&typeof Reflect.get(e,`status`)==`string`}export{activityMessages,buildSlackActivityRenderers,experimental_slackActivityPlan,experimental_slackActivityRenderer,experimental_slackActivityStatus,experimental_slackActivityTree,hasSlackActivityStatus,selectSlackActivityStatus};
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type SlackApiResponse as SlackPrimitiveApiResponse } from "#compiled/@chat-adapter/slack/api.js";
|
|
18
18
|
import { type CardElement, type FileUpload } from "#compiled/chat/index.js";
|
|
19
|
+
import { type SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
19
20
|
/** Slack app installation workspace available when eve resolves a bot token. */
|
|
20
21
|
export interface SlackBotTokenContext {
|
|
21
22
|
readonly teamId?: string;
|
|
@@ -49,8 +50,12 @@ export type SlackApiResponse = SlackPrimitiveApiResponse;
|
|
|
49
50
|
* and form-encoded. Form is the only safe default: Slack's JSON support
|
|
50
51
|
* is partial (e.g. `conversations.replies` rejects JSON). Returns the
|
|
51
52
|
* raw JSON response; callers inspect `response.ok` themselves.
|
|
53
|
+
*
|
|
54
|
+
* `api` takes the same value {@link slackChannel} does and is checked and
|
|
55
|
+
* confined here, so a call made without a channel carries the same guarantees.
|
|
52
56
|
*/
|
|
53
57
|
export declare function callSlackApi(input: {
|
|
58
|
+
readonly api?: SlackTransportOptions;
|
|
54
59
|
readonly botToken: SlackBotToken | undefined;
|
|
55
60
|
readonly context?: SlackBotTokenContext;
|
|
56
61
|
readonly operation: string;
|
|
@@ -280,6 +285,7 @@ export interface SlackWorkspaceHandle {
|
|
|
280
285
|
}
|
|
281
286
|
/** Builds the workspace-scoped API handle used by generic event callbacks. */
|
|
282
287
|
export declare function buildSlackWorkspaceHandle(input: {
|
|
288
|
+
readonly api?: SlackTransportOptions;
|
|
283
289
|
readonly botToken: SlackBotToken | undefined;
|
|
284
290
|
/** Workspace whose app installation supplies the bot token. */
|
|
285
291
|
readonly installationTeamId?: string;
|
|
@@ -306,6 +312,7 @@ interface SlackBinding {
|
|
|
306
312
|
*/
|
|
307
313
|
export declare function buildSlackBinding(input: {
|
|
308
314
|
/** Slack app id used to identify this app's fetched thread replies. */
|
|
315
|
+
readonly api?: SlackTransportOptions;
|
|
309
316
|
readonly appId?: string;
|
|
310
317
|
readonly botToken: SlackBotToken | undefined;
|
|
311
318
|
/** Slack bot user id used to identify this app's fetched thread replies. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{isCardElement}from"#compiled/chat/index.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{isCardElement}from"#compiled/chat/index.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{resolveSlackInboundMrkdwn}from"#public/channels/slack/inbound-content.js";import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";import{callSlackApiTrackingResponse,slackApiOptions}from"#public/channels/slack/transport.js";const log=createLogger(`slack.api`);function slackContinuationToken(e,t){return`${e}:${t}`}async function resolveSlackBotToken(e,t={}){let n=e??process.env.SLACK_BOT_TOKEN;if(!n)throw Error(`SLACK_BOT_TOKEN is required.`);return typeof n==`function`?n(t):resolveSlackBotToken$1(n)}async function callSlackApi(e){return callSlackApiTrackingResponse(e.operation,normalizeSlackApiBody(e.body),slackApiOptions(e.api,()=>resolveSlackBotToken(e.botToken,e.context)))}function createSlackRequester(e,t,n){return(r,i)=>callSlackApi({api:e,botToken:t,context:n,operation:r,body:i})}function buildSlackWorkspaceHandle(e){return{teamId:e.teamId,request:createSlackRequester(e.api,e.botToken,{teamId:e.installationTeamId})}}function buildSlackBinding(e){let t={teamId:e.installationTeamId},n=slackApiOptions(e.api,()=>resolveSlackBotToken(e.botToken,t)),r=createSlackRequester(e.api,e.botToken,t),i=[],a=e.threadTs,o;function handleMessageTs(t){a||t===a||(a=t,e.onThreadTsChanged?.(t))}async function uploadFiles(t,r){let i=r?.channelId??e.channelId,o=r?.threadTs??a,s=t.map(e=>toSlackFileUpload(e,r?.snippetType));return uploadSlackFiles(s,{...n,channelId:i||void 0,initialComment:r?.initialComment,threadTs:o||void 0})}function refreshMessages(){if(o!==void 0)return o;async function fetchAndReplace(){if(!e.channelId||!a){i=[];return}try{i=(await fetchSlackThreadReplies({...n,channel:e.channelId,limit:50,ts:a})).messages.map(t=>parseThreadMessage(t,a,{appId:e.appId,botUserId:e.botUserId}))}catch(t){logError(log,`refresh threw — swallowed`,t,{channelId:e.channelId})}}let t=fetchAndReplace().finally(()=>{o===t&&(o=void 0)});return o=t,t}let s={get recentMessages(){return i},async listParticipants(){await s.refresh();let e=new Set;for(let t of i)t.user!==void 0&&t.botId===void 0&&e.add(t.user);return[...e]},async post(t){let r=normalizePostInput(t),i=r.files??[],o=`blocks`in r||`card`in r||`markdown`in r;if(i.length>0&&!o){let e=await uploadFiles(i,{initialComment:`text`in r?r.text:void 0});return{id:Array.isArray(e.raw.files)&&e.raw.files.length>0?String(e.raw.files[0].id??``):``,raw:e.raw}}let s=await postSlackMessage(buildPostMessageOptions(r,e.channelId,a,n)),c=s.id;if(handleMessageTs(c),i.length>0&&o)try{await uploadFiles(i)}catch(e){log.warn(`file upload after message post failed`,{error:e})}return{id:c,raw:s.raw}},async postEphemeral(t,r){let i=normalizePostInput(r),o=await postSlackEphemeral({...buildPostMessageOptions(i,e.channelId,a,n),user:t});return{id:o.id,raw:o.raw}},async postDirectMessage(e,t){let i=await r(`conversations.open`,{users:e}),a=i.ok===!0?i.channel?.id:void 0;if(typeof a!=`string`||a.length===0)throw Error(`Slack conversations.open failed: ${i.error??`unknown_error`}`);let o=normalizePostInput(t),s=await postSlackMessage(buildPostMessageOptions(o,a,``,n));return{id:s.id,raw:s.raw}},async startTyping(t){if(e.channelId&&a)try{let n=t===void 0?``:truncateTypingStatus(t),i={channel_id:e.channelId,thread_ts:a,status:n};n.length>0&&(i.loading_messages=[n]);let o=await r(`assistant.threads.setStatus`,i);o.ok!==!0&&log.warn(`assistant.threads.setStatus returned not-ok`,{error:o.error})}catch(t){logError(log,`startTyping threw — swallowed`,t,{channelId:e.channelId})}},refresh(){return refreshMessages()},mentionUser(e){return`<@${e}>`}};return{thread:s,slack:{channelId:e.channelId,get threadTs(){return a},teamId:e.teamId,request:r,uploadFiles}}}function normalizePostInput(e){return typeof e==`string`?{markdown:e}:isCardElement(e)?{card:e}:e}function buildPostMessageOptions(e,t,n,r){let i={...r,channel:t,threadTs:n||void 0,unfurlLinks:!1,unfurlMedia:!1};return`card`in e?(i.blocks=cardToBlocks(e.card),i.text=e.fallbackText??cardToFallbackText(e.card),i):`blocks`in e?(i.blocks=[...e.blocks],e.text!==void 0&&(i.text=e.text),i):`markdown`in e?(i.markdownText=e.markdown,i):(i.text=e.text,i)}function normalizeSlackApiBody(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}function toSlackFileUpload(e,t){return{data:normalizeFileData(e.data),filename:e.filename,snippetType:t}}function normalizeFileData(e){if(e instanceof ArrayBuffer||typeof Blob<`u`&&e instanceof Blob)return e;if(ArrayBuffer.isView(e)){let t=e;return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}return e}function parseThreadMessage(e,t,n){let r=typeof e.text==`string`?e.text:``,i=resolveSlackInboundMrkdwn(r,e),a=typeof e.ts==`string`?e.ts:``,o=typeof e.thread_ts==`string`?e.thread_ts:t,s=typeof e.user==`string`?e.user:void 0,c=typeof e.bot_id==`string`?e.bot_id:void 0,l=typeof e.app_id==`string`?e.app_id:void 0,u=n.botUserId!==void 0&&s===n.botUserId||n.appId!==void 0&&l===n.appId;return{text:i,markdown:slackMrkdwnToGfm(i),user:s,botId:c,ts:a,threadTs:o,isMe:u,raw:e}}export{buildSlackBinding,buildSlackWorkspaceHandle,callSlackApi,resolveSlackBotToken,slackContinuationToken};
|
|
@@ -2,6 +2,7 @@ import type { FilePart, UserContent } from "ai";
|
|
|
2
2
|
import type { FetchFileContext, FetchFileResult } from "#channel/adapter.js";
|
|
3
3
|
import { type SlackBotToken, type SlackThread } from "#public/channels/slack/api.js";
|
|
4
4
|
import type { SlackAttachment, SlackMessage } from "#public/channels/slack/inbound.js";
|
|
5
|
+
import { type SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
5
6
|
import type { UploadPolicy } from "#public/channels/upload-policy.js";
|
|
6
7
|
/**
|
|
7
8
|
* Emits one {@link FilePart} per supported attachment in the inbound
|
|
@@ -44,9 +45,11 @@ export declare function buildSlackTurnMessage(text: string, fileParts: readonly
|
|
|
44
45
|
* Creates a `fetchFile` function for the Slack channel.
|
|
45
46
|
*
|
|
46
47
|
* Returns `null` for URLs that don't belong to Slack so they pass
|
|
47
|
-
* through to the model provider unchanged. Fetches Slack file URLs
|
|
48
|
-
* with the bot token
|
|
48
|
+
* through to the model provider unchanged. Fetches Slack file URLs, and
|
|
49
|
+
* URLs under a configured `api.fileBaseUrl`, with the bot token, on
|
|
50
|
+
* `api.fetch` when one is configured.
|
|
49
51
|
*/
|
|
50
52
|
export declare function createSlackFetchFile(input: {
|
|
53
|
+
readonly api?: SlackTransportOptions;
|
|
51
54
|
readonly botToken?: SlackBotToken;
|
|
52
55
|
}): (url: string, context?: FetchFileContext) => Promise<FetchFileResult | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{evaluateFilePart,formatUploadPolicyViolation,isUploadsDisabled}from"#public/channels/upload-policy.js";import{EveAttachmentError}from"#internal/attachments/errors.js";import{resolveSlackBotToken}from"#public/channels/slack/api.js";const log=createLogger(`slack.attachments`);function collectSlackFileParts(e,t){let n=[];for(let r of e??[]){let e=toSlackFilePart(r,n.length);if(e===null)continue;let i=evaluateFilePart(e,t);if(i!==null){log.warn(`dropped attachment — ${formatUploadPolicyViolation(i)}`,{name:r.name});continue}n.push(e)}return n}function toSlackFilePart(e,t){return e.type===`audio`||e.type===`video`?null:e.url?{type:`file`,mediaType:e.mimeType??`application/octet-stream`,filename:e.name??`attachment-${t}`,data:new URL(e.url)}:(log.warn(`dropped attachment — no url available`,{name:e.name}),null)}async function collectInboundFileParts(e){let t=collectSlackFileParts(e.mention.attachments,e.policy);if(t.length>0)return t;if(isUploadsDisabled(e.policy))return[];if(e.thread.recentMessages.length===0)try{await e.thread.refresh()}catch(e){return log.warn(`slack thread refresh failed for attachment collection`,{error:e}),[]}let n=e.thread.recentMessages;for(let t=n.length-1;t>=0;--t){let r=n[t];if(!r||r.isMe)continue;let i=r.raw,a=collectSlackFileParts(extractAttachmentsFromRaw(i?.files),e.policy);return a.length>0?a:[]}return[]}function extractAttachmentsFromRaw(e){return Array.isArray(e)?e.map(e=>{let t=typeof e.mimetype==`string`?e.mimetype:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:typeof e.url_private==`string`?e.url_private:void 0,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}):[]}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}function buildSlackTurnMessage(e,t){return t.length===0?e:e.trim().length===0?[...t]:[{type:`text`,text:e},...t]}function createSlackFetchFile(e){return async(
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{evaluateFilePart,formatUploadPolicyViolation,isUploadsDisabled}from"#public/channels/upload-policy.js";import{EveAttachmentError}from"#internal/attachments/errors.js";import{resolveSlackBotToken}from"#public/channels/slack/api.js";import{isConfiguredSlackFileUrl,resolveSlackTransportOptions}from"#public/channels/slack/transport.js";const log=createLogger(`slack.attachments`);function collectSlackFileParts(e,t){let n=[];for(let r of e??[]){let e=toSlackFilePart(r,n.length);if(e===null)continue;let i=evaluateFilePart(e,t);if(i!==null){log.warn(`dropped attachment — ${formatUploadPolicyViolation(i)}`,{name:r.name});continue}n.push(e)}return n}function toSlackFilePart(e,t){return e.type===`audio`||e.type===`video`?null:e.url?{type:`file`,mediaType:e.mimeType??`application/octet-stream`,filename:e.name??`attachment-${t}`,data:new URL(e.url)}:(log.warn(`dropped attachment — no url available`,{name:e.name}),null)}async function collectInboundFileParts(e){let t=collectSlackFileParts(e.mention.attachments,e.policy);if(t.length>0)return t;if(isUploadsDisabled(e.policy))return[];if(e.thread.recentMessages.length===0)try{await e.thread.refresh()}catch(e){return log.warn(`slack thread refresh failed for attachment collection`,{error:e}),[]}let n=e.thread.recentMessages;for(let t=n.length-1;t>=0;--t){let r=n[t];if(!r||r.isMe)continue;let i=r.raw,a=collectSlackFileParts(extractAttachmentsFromRaw(i?.files),e.policy);return a.length>0?a:[]}return[]}function extractAttachmentsFromRaw(e){return Array.isArray(e)?e.map(e=>{let t=typeof e.mimetype==`string`?e.mimetype:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:typeof e.url_private==`string`?e.url_private:void 0,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}):[]}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}function buildSlackTurnMessage(e,t){return t.length===0?e:e.trim().length===0?[...t]:[{type:`text`,text:e},...t]}function createSlackFetchFile(e){let t=resolveSlackTransportOptions(e.api);return async(n,r)=>{if(!isConfiguredSlackFileUrl(t,n)&&!isSlackFileUrl(n))return null;let i=r?.state.installationTeamId,a=await resolveSlackBotToken(e.botToken,{teamId:typeof i==`string`?i:void 0}),o=await(t?.fetch??fetch)(n,{headers:{authorization:`Bearer ${a}`}});if(!o.ok)throw new EveAttachmentError({adapterKind:`slack`,kind:`resolver-threw`,message:`Slack file fetch returned HTTP ${o.status}.`});let s=o.headers.get(`content-type`)??void 0;if(s?.split(`;`,1)[0]?.trim().toLowerCase()===`text/html`)throw new EveAttachmentError({adapterKind:`slack`,kind:`resolver-threw`,message:`Slack returned an HTML sign-in page instead of file bytes. The bot token may be missing the files:read scope. Add the scope, reinstall the Slack app, and retry.`});return{bytes:Buffer.from(await o.arrayBuffer()),mediaType:s}}}function isSlackFileUrl(e){let t=URL.parse(e);return t?.protocol===`https:`?t.hostname===`files.slack.com`||(t.hostname===`enterprise.slack.com`||t.hostname.endsWith(`.enterprise.slack.com`))&&t.pathname.startsWith(`/files/`):!1}export{buildSlackTurnMessage,collectInboundFileParts,collectSlackFileParts,createSlackFetchFile};
|
|
@@ -8,6 +8,7 @@ export type { ModelMessage } from "ai";
|
|
|
8
8
|
export { slackChannel, type SlackApiResponse, type SlackAuthorizationEventContext, type SlackAuthorizationRequiredHandler, type SlackBotToken, type SlackChannel, type SlackChannelConfig, type SlackChannelCredentials, type SlackChannelEvents, type SlackChannelState, type SlackContext, type SlackEventContext, type SlackEventRespondFn, type SlackEventRespondOptions, type SlackEventSendFn, type SlackEventSendOptions, type SlackHandle, type SlackInboundEventContext, type SlackInboundMessageContext, type SlackInboundResult, type SlackInboundResultOrPromise, type SlackInitialMessage, type SlackInputRequestedDefaultDeliver, type SlackInputRequestedEvent, type SlackInputRequestedHandler, type SlackInputResponseContext, type SlackInputResponseResult, type SlackInputResponseSubmission, type SlackInstrumentationMetadata, type SlackInteractionAction, type SlackInteractionContext, type SlackSlashCommand, type SlackSlashCommandContext, type SlackShortcut, type SlackShortcutContext, type SlackShortcutMessage, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveTarget, type SlackSessionTarget, type SlackRespondOptions, type SlackSendOptions, type SlackSessionOperations, type SlackThread, type SlackWebhookVerifier, type SlackWorkspaceHandle, } from "#public/channels/slack/slackChannel.js";
|
|
9
9
|
export type { SlackAttachment, SlackAuthor, SlackEvent, SlackEventEnvelope, SlackInboundContext, SlackMessage, } from "#public/channels/slack/inbound.js";
|
|
10
10
|
export { callSlackApi, resolveSlackBotToken, slackContinuationToken, type SlackPostInput, type SlackPostedMessage, type SlackBotTokenContext, type SlackThreadMessage, type SlackUploadFilesOptions, type SlackUploadFilesResult, } from "#public/channels/slack/api.js";
|
|
11
|
+
export { type SlackFetch, type SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
11
12
|
export { defaultSlackAuth } from "#public/channels/slack/defaults.js";
|
|
12
13
|
export { experimental_slackActivityPlan, experimental_slackActivityRenderer, experimental_slackActivityTree, experimental_slackActivityStatus, type ExperimentalSlackActivityDestination, type ExperimentalSlackActivityRenderer, type ExperimentalSlackActivitySnapshot, type SlackActivityRenderer, } from "#public/channels/slack/activity.js";
|
|
13
14
|
export { describeActionRequest, describeActionRequests, } from "#public/channels/slack/action-status.js";
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
2
2
|
import type { SlackChannelConfig, SlackInputResponseResult, SlackInputResponseSubmission, SlackChannelState } from "#public/channels/slack/slackChannel.js";
|
|
3
|
+
import type { SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
3
4
|
export declare function approvalResponderStatePatch(submission: Extract<SlackInputResponseSubmission, {
|
|
4
5
|
type: "block_actions";
|
|
5
6
|
}>, auth: SessionAuthContext | null): Partial<SlackChannelState> | undefined;
|
|
6
7
|
export declare function authorizeInputResponse(input: {
|
|
7
8
|
readonly channelId: string;
|
|
8
9
|
readonly deps: {
|
|
10
|
+
readonly api: SlackTransportOptions | undefined;
|
|
9
11
|
readonly config: SlackChannelConfig;
|
|
10
12
|
readonly onInputResponse: NonNullable<SlackChannelConfig["onInputResponse"]>;
|
|
11
13
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{buildSlackBinding}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{deriveHitlResponse}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.interactions`);function approvalResponderStatePatch(e,t){if(t?.principalId!==void 0&&e.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`))return{approvalResponderUsers:{[t.principalId]:e.user.id}}}async function authorizeInputResponse(e){let t=buildSlackAuthContext({channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,userId:e.submission.user.id,userName:e.submission.user.username??e.submission.user.name}),{thread:n,slack:r}=buildSlackBinding({botToken:e.deps.config.credentials?.botToken,channelId:e.channelId,threadTs:e.threadTs,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0}),i={defaultAuth:t,slack:r,thread:n};try{return await e.deps.onInputResponse(i,e.submission)}catch(e){return log.error(`HITL input response authorization failed`,{error:e}),null}}export{approvalResponderStatePatch,authorizeInputResponse};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{buildSlackBinding}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{deriveHitlResponse}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.interactions`);function approvalResponderStatePatch(e,t){if(t?.principalId!==void 0&&e.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`))return{approvalResponderUsers:{[t.principalId]:e.user.id}}}async function authorizeInputResponse(e){let t=buildSlackAuthContext({channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,userId:e.submission.user.id,userName:e.submission.user.username??e.submission.user.name}),{thread:n,slack:r}=buildSlackBinding({api:e.deps.api,botToken:e.deps.config.credentials?.botToken,channelId:e.channelId,threadTs:e.threadTs,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0}),i={defaultAuth:t,slack:r,thread:n};try{return await e.deps.onInputResponse(i,e.submission)}catch(e){return log.error(`HITL input response authorization failed`,{error:e}),null}}export{approvalResponderStatePatch,authorizeInputResponse};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{callSlackApi}from"#public/channels/slack/api.js";import{SLACK_CARD_SUBTEXT_MAX_LENGTH,truncateCardSubtext}from"#public/channels/slack/limits.js";import{buildAnsweredBlocks,isHitlAction}from"#public/channels/slack/hitl.js";function buildAnsweredHitlMessageBlocks(e){let t=e.messageBlocks.findIndex(t=>blockContainsActionId(t,e.actionId));if(t===-1)return buildAnsweredBlocks({promptBlocks:findPromptBlocks(e.messageBlocks),answerLabel:e.answerLabel,userId:e.userId});let n=e.messageBlocks[t],r=answeredBlocksFromActionBlock({answerLabel:e.answerLabel,block:n,userId:e.userId})??buildAnsweredBlocks({promptBlocks:promptBlocksFromActionBlock(n),answerLabel:e.answerLabel,userId:e.userId});return[...e.messageBlocks.slice(0,t),...r,...e.messageBlocks.slice(t+1)]}function findPromptBlocks(e){let t=[];for(let n of e){if(!isObjectRecord(n))continue;let e=n.type;if(e===`actions`)break;(e===`section`||e===`context`||e===`divider`||e===`image`)&&t.push(n)}return t}function blockContainsActionId(e,t){return isObjectRecord(e)?actionsContainActionId(e.elements,t)||actionsContainActionId(e.actions,t):!1}function actionsContainActionId(e,t){return Array.isArray(e)?e.some(e=>isObjectRecord(e)&&e.action_id===t):!1}function answeredBlocksFromActionBlock(e){if(!isObjectRecord(e.block)||e.block.type!==`card`)return;let{actions:t,subtext:n,...r}=e.block,i={...r,subtext:{type:`mrkdwn`,text:formatAnsweredCardSubtext(e),verbatim:!1}};return hasCardContent(i)?[i]:void 0}function formatAnsweredCardSubtext(e){let t=e.userId.length>0?` by <@${e.userId}>`:``,n=SLACK_CARD_SUBTEXT_MAX_LENGTH-20-1-t.length,r=truncateWithEllipsis(e.answerLabel,n);return truncateCardSubtext(`:white_check_mark: *${r}*${t}`)}function truncateWithEllipsis(e,t){if(t<=0)return``;if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}function promptBlocksFromActionBlock(e){if(!isObjectRecord(e)||e.type!==`card`)return[];let{actions:t,...n}=e;return hasCardContent(n)?[n]:[]}function hasCardContent(e){return e.body!==void 0||e.title!==void 0||e.hero_image!==void 0}function isObjectRecord(e){return typeof e==`object`&&!!e}async function updateAnsweredHitlCard(e,t){let n=e.actions.find(e=>isHitlAction(e.actionId));if(!n?.messageTs)return;let r=n.label??n.selectedOptionValue??n.value;r&&await updateAnsweredCard({answerLabel:r,blocks:buildAnsweredHitlMessageBlocks({actionId:n.actionId,answerLabel:r,messageBlocks:e.messageBlocks,userId:n.user.id}),channelId:e.channelId,deps:t,installationTeamId:e.installationTeamId,messageTs:n.messageTs})}async function updateAnsweredFreeformCard(e){await updateAnsweredCard({...e,blocks:buildAnsweredBlocks({promptBlocks:[],answerLabel:e.answerLabel,userId:e.userId})})}async function updateAnsweredCard(e){await callSlackApi({botToken:e.deps.config.credentials?.botToken,context:{teamId:e.installationTeamId},operation:`chat.update`,body:{channel:e.channelId,ts:e.messageTs,blocks:e.blocks,text:`Answered: ${e.answerLabel}`}})}export{updateAnsweredFreeformCard,updateAnsweredHitlCard};
|
|
1
|
+
import{callSlackApi}from"#public/channels/slack/api.js";import{SLACK_CARD_SUBTEXT_MAX_LENGTH,truncateCardSubtext}from"#public/channels/slack/limits.js";import{buildAnsweredBlocks,isHitlAction}from"#public/channels/slack/hitl.js";function buildAnsweredHitlMessageBlocks(e){let t=e.messageBlocks.findIndex(t=>blockContainsActionId(t,e.actionId));if(t===-1)return buildAnsweredBlocks({promptBlocks:findPromptBlocks(e.messageBlocks),answerLabel:e.answerLabel,userId:e.userId});let n=e.messageBlocks[t],r=answeredBlocksFromActionBlock({answerLabel:e.answerLabel,block:n,userId:e.userId})??buildAnsweredBlocks({promptBlocks:promptBlocksFromActionBlock(n),answerLabel:e.answerLabel,userId:e.userId});return[...e.messageBlocks.slice(0,t),...r,...e.messageBlocks.slice(t+1)]}function findPromptBlocks(e){let t=[];for(let n of e){if(!isObjectRecord(n))continue;let e=n.type;if(e===`actions`)break;(e===`section`||e===`context`||e===`divider`||e===`image`)&&t.push(n)}return t}function blockContainsActionId(e,t){return isObjectRecord(e)?actionsContainActionId(e.elements,t)||actionsContainActionId(e.actions,t):!1}function actionsContainActionId(e,t){return Array.isArray(e)?e.some(e=>isObjectRecord(e)&&e.action_id===t):!1}function answeredBlocksFromActionBlock(e){if(!isObjectRecord(e.block)||e.block.type!==`card`)return;let{actions:t,subtext:n,...r}=e.block,i={...r,subtext:{type:`mrkdwn`,text:formatAnsweredCardSubtext(e),verbatim:!1}};return hasCardContent(i)?[i]:void 0}function formatAnsweredCardSubtext(e){let t=e.userId.length>0?` by <@${e.userId}>`:``,n=SLACK_CARD_SUBTEXT_MAX_LENGTH-20-1-t.length,r=truncateWithEllipsis(e.answerLabel,n);return truncateCardSubtext(`:white_check_mark: *${r}*${t}`)}function truncateWithEllipsis(e,t){if(t<=0)return``;if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}function promptBlocksFromActionBlock(e){if(!isObjectRecord(e)||e.type!==`card`)return[];let{actions:t,...n}=e;return hasCardContent(n)?[n]:[]}function hasCardContent(e){return e.body!==void 0||e.title!==void 0||e.hero_image!==void 0}function isObjectRecord(e){return typeof e==`object`&&!!e}async function updateAnsweredHitlCard(e,t){let n=e.actions.find(e=>isHitlAction(e.actionId));if(!n?.messageTs)return;let r=n.label??n.selectedOptionValue??n.value;r&&await updateAnsweredCard({answerLabel:r,blocks:buildAnsweredHitlMessageBlocks({actionId:n.actionId,answerLabel:r,messageBlocks:e.messageBlocks,userId:n.user.id}),channelId:e.channelId,deps:t,installationTeamId:e.installationTeamId,messageTs:n.messageTs})}async function updateAnsweredFreeformCard(e){await updateAnsweredCard({...e,blocks:buildAnsweredBlocks({promptBlocks:[],answerLabel:e.answerLabel,userId:e.userId})})}async function updateAnsweredCard(e){await callSlackApi({api:e.deps.api,botToken:e.deps.config.credentials?.botToken,context:{teamId:e.installationTeamId},operation:`chat.update`,body:{channel:e.channelId,ts:e.messageTs,blocks:e.blocks,text:`Answered: ${e.answerLabel}`}})}export{updateAnsweredFreeformCard,updateAnsweredHitlCard};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* forwards user-owned actions, shortcuts, and slash commands to their authored hooks.
|
|
5
5
|
*/
|
|
6
6
|
import { type SlackBlockActionsPayload } from "#compiled/@chat-adapter/slack/webhook.js";
|
|
7
|
+
import { type SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
7
8
|
import type { SlackChannelConfig, SlackChannelState, SlackInteractionAction, SlackShortcut } from "#public/channels/slack/slackChannel.js";
|
|
8
9
|
import type { ChannelFrom, ChannelResolveSession } from "#channel/channel-operations.js";
|
|
9
10
|
/**
|
|
@@ -31,6 +32,7 @@ interface ParsedBlockActionsPayload {
|
|
|
31
32
|
export declare function parseBlockActionsPayload(body: Record<string, unknown> | SlackBlockActionsPayload): ParsedBlockActionsPayload | null;
|
|
32
33
|
/** Channel-supplied dependencies for {@link handleInteractionPost}. */
|
|
33
34
|
export interface InteractionHandlerDeps {
|
|
35
|
+
readonly api: SlackTransportOptions | undefined;
|
|
34
36
|
readonly config: SlackChannelConfig;
|
|
35
37
|
readonly onInputResponse: NonNullable<SlackChannelConfig["onInputResponse"]>;
|
|
36
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{parseInputResponse}from"#shared/input.js";import{buildSlackBinding,buildSlackWorkspaceHandle,callSlackApi,resolveSlackBotToken,slackContinuationToken}from"#public/channels/slack/api.js";import{isSlackResponseError}from"#public/channels/slack/api-errors.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 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=findPromptBlocks(e)[0],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});try{await callSlackApi({botToken:u,operation:`views.open`,body:{trigger_id:t,view:l}})}catch(e){if(!isSlackResponseError(e))throw e;log.error(`Slack views.open failed`,{error:e})}}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};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{parseInputResponse}from"#shared/input.js";import{buildSlackBinding,buildSlackWorkspaceHandle,callSlackApi,resolveSlackBotToken,slackContinuationToken}from"#public/channels/slack/api.js";import{readSlackTextObject}from"#public/channels/slack/inbound-content.js";import{isSlackResponseError}from"#public/channels/slack/transport.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 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=findPromptBlocks(e)[0],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),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({api:n.api,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={slack:buildSlackWorkspaceHandle({api:r.api,botToken:r.config.credentials?.botToken,installationTeamId:t,teamId:e.teamId})};dispatchInteractionHook(()=>i(e,a),n,`shortcut handler failed`)}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});try{await callSlackApi({api:e.deps.api,botToken:u,operation:`views.open`,body:{trigger_id:t,view:l}})}catch(e){if(!isSlackResponseError(e))throw e;log.error(`Slack views.open failed`,{error:e})}}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};
|
|
@@ -8,6 +8,7 @@ import type { ChannelContinuationOps } from "#public/definitions/channel.js";
|
|
|
8
8
|
import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
|
|
9
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
|
+
import { type SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
11
12
|
import { type SlackEvent, type SlackEventEnvelope, type SlackMessage } from "#public/channels/slack/inbound.js";
|
|
12
13
|
import { type LoadThreadContextMessagesOptions } from "#public/channels/slack/thread.js";
|
|
13
14
|
import { type SlackSessionOperations } from "#public/channels/slack/session-operations.js";
|
|
@@ -490,6 +491,8 @@ export type SlackApprovalRequest = InputRequest;
|
|
|
490
491
|
/** Chooses the Slack destination for one input request. */
|
|
491
492
|
export type SlackApprovalChannelResolver = (request: SlackApprovalRequest, ctx: SessionContext) => SlackApprovalChannel | Promise<SlackApprovalChannel>;
|
|
492
493
|
export interface SlackChannelConfig {
|
|
494
|
+
/** Slack API transport overrides: base URLs and a custom fetch. */
|
|
495
|
+
readonly api?: SlackTransportOptions;
|
|
493
496
|
readonly credentials?: SlackChannelCredentials;
|
|
494
497
|
readonly botName?: string;
|
|
495
498
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{setChannelActivityRenderers}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,buildSlackWorkspaceHandle,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage,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?.alias(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=defaultInputRequestedHandler(e.approvalChannel),p=e.events?.[`input.requested`],m=p===void 0?f:async(e,t,n)=>p(e,t,n,async e=>{await f(e,t,n)}),h={...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":m,"authorization.required":a===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(a)},g=new Set,_=defineChannel({kindHint:`slack`,turnPolicy:e.turnPolicy,state:{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{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},audience:({state:e})=>e.audience??`unknown`,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:g,headers:n.headers})})],receive(t,{from:n}){return receiveOnSlack(t,{from:n,credentials:e.credentials})},events:h});return setChannelActivityRenderers(_,{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}),_}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,taskDeliveryPolicy:e.taskDeliveryPolicy})}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={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,taskDeliveryPolicy:o})=>receiveOnSlack({auth:r,message:t,target:i,title:a,taskDeliveryPolicy:o},{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{normalizeChannelAudience}from"#shared/channel-audience.js";import{setChannelActivityRenderers}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,buildSlackWorkspaceHandle,slackContinuationToken}from"#public/channels/slack/api.js";import{resolveSlackTransportOptions}from"#public/channels/slack/transport.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,r){let{thread:i,slack:a}=buildSlackBinding({api:n,botToken:r?.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?.alias(slackContinuationToken(e.channelId,n))}});return{thread:i,slack:a,state:e}}function slackChannel(e={}){let t=resolveSlackTransportOptions(e.api),n=mergeUploadPolicy(e.uploadPolicy),r=createSlackFetchFile({api:t,botToken:e.credentials?.botToken}),i=buildSlackActivityRenderers({api:t,botToken:e.credentials?.botToken,renderers:e.activity?.renderers??[]}),a=e.onInputResponse??defaultOnInputResponse,o=e.events?.[`authorization.required`],s=e.events?.[`approval.candidate`]??defaultEvents[`approval.candidate`],c=hasSlackActivityStatus(e.activity?.renderers),l=e.events?.[`turn.started`]??(c?clearSlackTurnState:defaultEvents[`turn.started`]),u=e.events?.[`reasoning.appended`]??(c?ignoreSlackActivityEvent:defaultEvents[`reasoning.appended`]),d=e.events?.[`actions.requested`]??(c?ignoreSlackActivityEvent:defaultEvents[`actions.requested`]),f=e.events?.[`message.completed`]??(c?activityOwnedMessageCompleted:defaultEvents[`message.completed`]),p=defaultInputRequestedHandler(e.approvalChannel),m=e.events?.[`input.requested`],h=m===void 0?p:async(e,t,n)=>m(e,t,n,async e=>{await p(e,t,n)}),g={...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 s(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 l(e,t,n)},"reasoning.appended":u,"actions.requested":d,"message.completed":f,"input.requested":h,"authorization.required":o===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(o)},_=new Set,v=defineChannel({kindHint:`slack`,turnPolicy:e.turnPolicy,state:{channelId:null,threadTs:null,teamId:null,installationTeamId:null,triggeringUserId:null,triggeringMessageTs:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{},pendingApprovalCards:{},pendingApprovalCandidateUsers:{},approvalResponderUsers:{}},fetchFile:r,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},audience:({state:e})=>e.audience??`unknown`,context(n,r){return rebuildSlackContext(n,r,t,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(r,i)=>{let{waitUntil:o}=i,{from:s,resolveSession:c}=i,l=await verifyInbound(r,e.credentials);return l===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(r.headers)?new Response(`ok`):(r.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(l,{from:s,resolveSession:c,waitUntil:o},{api:t,config:e,onInputResponse:a}):handleEventPost({api:t,body:l,from:s,resolveSession:c,waitUntil:o,config:e,uploadPolicy:n,handledEvents:_,headers:r.headers})})],receive(n,{from:r}){return receiveOnSlack(n,{from:r,api:t,credentials:e.credentials})},events:g});return setChannelActivityRenderers(v,{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:i}),v}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({api:t.api,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,taskDeliveryPolicy:e.taskDeliveryPolicy})}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({api:e.api,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({api:e.api,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({api:e.api,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({api:e.api,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({api:e.api,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={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,taskDeliveryPolicy:o})=>receiveOnSlack({auth:r,message:t,target:i,title:a,taskDeliveryPolicy:o},{from:e.from,api:e.api,credentials:e.credentials,installationTeamId:e.installationTeamId,teamId:n,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({api:e.api,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,9 +1,13 @@
|
|
|
1
1
|
import { type parseSlackWebhookBody } from "#compiled/@chat-adapter/slack/webhook.js";
|
|
2
2
|
import type { SlackChannelConfig } from "#public/channels/slack/slackChannel.js";
|
|
3
|
+
import type { SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
3
4
|
type SlashCommandPayload = Extract<ReturnType<typeof parseSlackWebhookBody>, {
|
|
4
5
|
kind: "slash_command";
|
|
5
6
|
}>;
|
|
6
7
|
export declare function dispatchSlashCommand(payload: SlashCommandPayload, ctx: {
|
|
7
8
|
readonly waitUntil: (task: Promise<unknown>) => void;
|
|
8
|
-
},
|
|
9
|
+
}, deps: {
|
|
10
|
+
readonly api: SlackTransportOptions | undefined;
|
|
11
|
+
readonly config: SlackChannelConfig;
|
|
12
|
+
}): void;
|
|
9
13
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{buildSlackWorkspaceHandle}from"#public/channels/slack/api.js";import"#compiled/@chat-adapter/slack/webhook.js";const log=createLogger(`slack.slash-command`);function dispatchSlashCommand(e,t,n){let r=parseSlashCommandPayload(e),i=n.onSlashCommand;if(i===void 0){log.warn(`Slack slash command ignored because onSlashCommand is not configured`,{command:r.command});return}let a={slack:buildSlackWorkspaceHandle({botToken:n.credentials?.botToken,installationTeamId:r.teamId,teamId:r.teamId})};t.waitUntil(Promise.resolve().then(()=>i(r,a)).catch(e=>{log.error(`slash command handler failed`,{error:e})}))}function parseSlashCommandPayload(e){return{command:e.command,text:e.text,user:{id:e.userId,username:e.userName},teamId:e.teamId,channelId:e.channelId,channelName:e.channelName,enterpriseId:e.enterpriseId,isEnterpriseInstall:e.isEnterpriseInstall,triggerId:e.triggerId,responseUrl:e.responseUrl}}export{dispatchSlashCommand};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{buildSlackWorkspaceHandle}from"#public/channels/slack/api.js";import"#compiled/@chat-adapter/slack/webhook.js";const log=createLogger(`slack.slash-command`);function dispatchSlashCommand(e,t,n){let r=parseSlashCommandPayload(e),i=n.config.onSlashCommand;if(i===void 0){log.warn(`Slack slash command ignored because onSlashCommand is not configured`,{command:r.command});return}let a={slack:buildSlackWorkspaceHandle({api:n.api,botToken:n.config.credentials?.botToken,installationTeamId:r.teamId,teamId:r.teamId})};t.waitUntil(Promise.resolve().then(()=>i(r,a)).catch(e=>{log.error(`slash command handler failed`,{error:e})}))}function parseSlashCommandPayload(e){return{command:e.command,text:e.text,user:{id:e.userId,username:e.userName},teamId:e.teamId,channelId:e.channelId,channelName:e.channelName,enterpriseId:e.enterpriseId,isEnterpriseInstall:e.isEnterpriseInstall,triggerId:e.triggerId,responseUrl:e.responseUrl}}export{dispatchSlashCommand};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between eve and the vendored Slack transport: the host and fetch
|
|
3
|
+
* every outbound call travels on, and the record of whether a thrown error
|
|
4
|
+
* means Slack answered.
|
|
5
|
+
*/
|
|
6
|
+
import { type SlackApiOptions, type SlackApiResponse } from "#compiled/@chat-adapter/slack/api.js";
|
|
7
|
+
/** Fetch implementation used for Slack traffic. */
|
|
8
|
+
export type SlackFetch = typeof globalThis.fetch;
|
|
9
|
+
/**
|
|
10
|
+
* Slack API transport overrides. The base URLs say where a call may go:
|
|
11
|
+
* `apiBaseUrl` defaults to `https://slack.com/api/`, and `fileBaseUrl` falls back to
|
|
12
|
+
* `apiBaseUrl` for attachment downloads. Both are absolute http or https URLs
|
|
13
|
+
* carrying no query string or fragment.
|
|
14
|
+
*
|
|
15
|
+
* `fetch` says how the traffic travels: it replaces the global fetch, which attaches
|
|
16
|
+
* the per-call header a host behind an authenticating proxy requires, and observes or
|
|
17
|
+
* stubs the calls. It is confined to the configured bases' origins, so a wrapper may
|
|
18
|
+
* attach a credential without checking each destination itself. With no base
|
|
19
|
+
* configured every call goes to Slack's own hosts and it sees all of them.
|
|
20
|
+
*/
|
|
21
|
+
export interface SlackTransportOptions {
|
|
22
|
+
readonly apiBaseUrl?: string;
|
|
23
|
+
readonly fetch?: SlackFetch;
|
|
24
|
+
readonly fileBaseUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks the configured bases and resolves each to the directory href that method
|
|
28
|
+
* names and file paths resolve against, throwing on one eve cannot reach.
|
|
29
|
+
* `fileBaseUrl` absorbs its fallback here, `fetch` is confined here, and every
|
|
30
|
+
* outbound call then reads a resolved string.
|
|
31
|
+
*
|
|
32
|
+
* Resolving an already-resolved value returns it unchanged, so `slackChannel()`
|
|
33
|
+
* can resolve once at construction — where a bad base belongs — while an entry
|
|
34
|
+
* point reached without a channel resolves for itself.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveSlackTransportOptions(api: SlackTransportOptions | undefined): SlackTransportOptions | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The options an outbound Slack Web API call is made with. Resolves `api`, so a
|
|
39
|
+
* caller cannot reach the transport with a base that was never checked or a fetch
|
|
40
|
+
* that was never confined.
|
|
41
|
+
*/
|
|
42
|
+
export declare function slackApiOptions(api: SlackTransportOptions | undefined, token: SlackApiOptions["token"]): SlackApiOptions;
|
|
43
|
+
/**
|
|
44
|
+
* True when a configured file base serves `url`, which makes it downloadable with
|
|
45
|
+
* the bot token. The match is a path prefix, so an `apiBaseUrl` of
|
|
46
|
+
* `https://slack.com/api/` authorizes downloads under that path alone.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isConfiguredSlackFileUrl(api: SlackTransportOptions | undefined, url: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* POSTs to a Slack Web API method and records the response arriving.
|
|
51
|
+
*
|
|
52
|
+
* Slack retries an interaction whose acknowledgement is not a 2xx, so a
|
|
53
|
+
* handler answering a click has to swallow a call Slack answered badly
|
|
54
|
+
* and rethrow a call that never arrived. Those two are indistinguishable
|
|
55
|
+
* once thrown: the transport consumes the response body before it reads
|
|
56
|
+
* the status, so a body cut off partway raises the same `TypeError` as a
|
|
57
|
+
* connection that was never made. Nothing on the error says which it
|
|
58
|
+
* was, so the answer is recorded here while the call runs. `callSlackApi`
|
|
59
|
+
* posts through here, which covers `ctx.slack.request` and the
|
|
60
|
+
* `views.open` that opens the freeform modal; the message, upload and
|
|
61
|
+
* thread-fetch helpers in `api.ts` post through the vendored transport.
|
|
62
|
+
*
|
|
63
|
+
* The transport is handed a `fetch` that observes the response, so
|
|
64
|
+
* everything thrown from that moment on — a non-2xx, a body that is not
|
|
65
|
+
* the JSON envelope, a body cut off mid-read — is marked for
|
|
66
|
+
* {@link isSlackResponseError}. A failure before then stays unmarked and
|
|
67
|
+
* reaches the caller, which is what fails the interaction request.
|
|
68
|
+
*/
|
|
69
|
+
export declare function callSlackApiTrackingResponse(operation: string, body: Record<string, unknown>, options: SlackApiOptions): Promise<SlackApiResponse>;
|
|
70
|
+
/**
|
|
71
|
+
* True when Slack answered the call, false when the call never got a
|
|
72
|
+
* response. A caller that must acknowledge Slack swallows the first and
|
|
73
|
+
* rethrows the second; see {@link callSlackApiTrackingResponse} for why
|
|
74
|
+
* the error itself cannot be asked.
|
|
75
|
+
*/
|
|
76
|
+
export declare function isSlackResponseError(error: unknown): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{callSlackApi}from"#compiled/@chat-adapter/slack/api.js";const resolvedTransports=new WeakSet;function resolveSlackTransportOptions(e){if(e===void 0)return;if(resolvedTransports.has(e))return e;let t=slackBaseHref(e.apiBaseUrl,`api.apiBaseUrl`),n=e.fileBaseUrl===void 0?t:slackBaseHref(e.fileBaseUrl,`api.fileBaseUrl`),r={apiBaseUrl:t,fetch:confineFetch(e.fetch,t,n),fileBaseUrl:n};return resolvedTransports.add(r),r}function confineFetch(e,t,n){if(e===void 0)return;let r=[t,n].filter(e=>e!==void 0);if(r.length===0)return e;let i=new Set(r.map(e=>new URL(e).origin));return(t,n)=>{let r=URL.parse(t instanceof Request?t.url:String(t))?.origin;return r!==void 0&&i.has(r)?e(t,n):globalThis.fetch(t,n)}}function slackApiOptions(e,t){let n=resolveSlackTransportOptions(e);return{apiUrl:n?.apiBaseUrl,fetch:n?.fetch,token:t}}function isConfiguredSlackFileUrl(e,t){let n=e?.fileBaseUrl;return n!==void 0&&URL.parse(t)?.href.startsWith(n)===!0}function slackBaseHref(e,t){if(e===void 0)return;let n=URL.parse(e);if(n===null||n.protocol!==`http:`&&n.protocol!==`https:`)throw TypeError(`${t} must be an absolute http or https URL. Received: ${e}`);if(n.search!==``||n.hash!==``)throw TypeError(`${t} must carry no query string or fragment. Received: ${e}`);return n.href.endsWith(`/`)?n.href:`${n.href}/`}const slackResponseErrors=new WeakSet;async function callSlackApiTrackingResponse(e,t,n){let r=!1,observe=async(e,t)=>{let i=await(n.fetch??globalThis.fetch)(e,t);return r=!0,i};try{return await callSlackApi(e,t,{...n,fetch:observe})}catch(e){throw r&&typeof e==`object`&&e&&slackResponseErrors.add(e),e}}function isSlackResponseError(e){return typeof e==`object`&&!!e&&slackResponseErrors.has(e)}export{callSlackApiTrackingResponse,isConfiguredSlackFileUrl,isSlackResponseError,resolveSlackTransportOptions,slackApiOptions};
|
|
@@ -14,6 +14,8 @@ export interface LinkProjectOptions {
|
|
|
14
14
|
prompter: Prompter;
|
|
15
15
|
/** Headless runs must not ask follow-up questions after the plan is fixed. */
|
|
16
16
|
headless?: boolean;
|
|
17
|
+
/** Configure 100% trace sampling for a newly created project. */
|
|
18
|
+
traceSampling?: boolean;
|
|
17
19
|
deps?: LinkProjectDeps;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{detectProjectResolution,mergeProjectResolution}from"../project-resolution.js";import{requireProjectPath}from"../state.js";import{linkProject,unresolvedProject}from"../vercel-project.js";import{createPromptCommandOutput}from"#setup/cli/index.js";function linkVercelProject(e){let t=e.deps??{linkProject,detectProjectResolution,unresolvedProject};return{id:`link-project`,shouldRun(e){return e.vercelProject.kind!==`none`},async gather(){return null},async perform({state:n,signal:r}){let i=n.vercelProject;if(i.kind===`none`)return t.unresolvedProject();let a=requireProjectPath(n),o=createPromptCommandOutput(e.prompter.log),s=await t.linkProject(e.prompter,a,i,o,
|
|
1
|
+
import{detectProjectResolution,mergeProjectResolution}from"../project-resolution.js";import{requireProjectPath}from"../state.js";import{linkProject,unresolvedProject}from"../vercel-project.js";import{createPromptCommandOutput}from"#setup/cli/index.js";function linkVercelProject(e){let t=e.deps??{linkProject,detectProjectResolution,unresolvedProject};return{id:`link-project`,shouldRun(e){return e.vercelProject.kind!==`none`},async gather(){return null},async perform({state:n,signal:r}){let i=n.vercelProject;if(i.kind===`none`)return t.unresolvedProject();let a=requireProjectPath(n),o=createPromptCommandOutput(e.prompter.log),s={signal:r};e.headless&&(s.headless=!0),e.traceSampling!==void 0&&(s.traceSampling=e.traceSampling);let c=await t.linkProject(e.prompter,a,i,o,s);if(r?.throwIfAborted(),!c)throw Error("Vercel project provisioning did not complete. Run `vercel link` manually, or re-run and choose not to deploy to Vercel.");let l=await t.detectProjectResolution(a,{signal:r});if(l.kind===`unresolved`)throw Error(`Linked the directory, but could not resolve the Vercel project from its link metadata.`);if(l.projectId!==c.projectId)throw Error(`The linked project does not match the selected project: expected ${c.projectId}, found ${l.projectId}.`);return l},apply(e,t){return{...e,project:mergeProjectResolution(e.project,t)}}}}export{linkVercelProject};
|
|
@@ -22,13 +22,6 @@ export interface ChannelSetupChoice {
|
|
|
22
22
|
}
|
|
23
23
|
/** Optional interaction capability for a long-running setup operation. */
|
|
24
24
|
export type ChannelSetupAwaitChoice = (options: ChannelSetupChoiceOptions) => ChannelSetupChoice;
|
|
25
|
-
/** Why a live setup indicator is waiting. */
|
|
26
|
-
export type SetupSpinnerIntent = {
|
|
27
|
-
kind: "progress";
|
|
28
|
-
} | {
|
|
29
|
-
kind: "external-action";
|
|
30
|
-
emphasis: string;
|
|
31
|
-
};
|
|
32
25
|
/** Status and subprocess output operations used by shared setup flows. */
|
|
33
26
|
export interface ChannelSetupLog {
|
|
34
27
|
message(text: string): void;
|
|
@@ -45,7 +38,7 @@ export interface ChannelSetupLog {
|
|
|
45
38
|
* (which have no live status surface) can omit it and callers fall back to
|
|
46
39
|
* a persisted message.
|
|
47
40
|
*/
|
|
48
|
-
spinner?(message: string
|
|
41
|
+
spinner?(message: string): {
|
|
49
42
|
stop(): void;
|
|
50
43
|
};
|
|
51
44
|
}
|
|
@@ -55,4 +48,4 @@ export interface ChannelSetupLog {
|
|
|
55
48
|
* phase message instead, keeping their progress trail; the spinner stops
|
|
56
49
|
* whether the work resolves or throws.
|
|
57
50
|
*/
|
|
58
|
-
export declare function withPhase<T>(log: ChannelSetupLog, message: string, task: () => Promise<T
|
|
51
|
+
export declare function withPhase<T>(log: ChannelSetupLog, message: string, task: () => Promise<T>): Promise<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
async function withPhase(e,t,n
|
|
1
|
+
async function withPhase(e,t,n){let r=e.spinner?.(t);r||e.message(t);try{return await n()}finally{r?.stop()}}export{withPhase};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type ConnectionSelectOption } from "./connection-add-prompter.js";
|
|
2
|
-
export { type ChannelSetupAction, type ChannelSetupAwaitChoice, type ChannelSetupChoice, type ChannelSetupChoiceOptions, type ChannelSetupLog, type DisabledChannelReasons,
|
|
2
|
+
export { type ChannelSetupAction, type ChannelSetupAwaitChoice, type ChannelSetupChoice, type ChannelSetupChoiceOptions, type ChannelSetupLog, type DisabledChannelReasons, withPhase, } from "./channel-setup-prompter.js";
|
|
3
3
|
export { createPromptCommandOutput, type PromptCommandLog } from "./command-output.js";
|
|
4
4
|
export { runSelectComponent, SelectComponent, type SelectGuard } from "./select-component.js";
|
|
5
5
|
export { createSelectOptionCodec, type SelectOptionCodec } from "./select-option-codec.js";
|