eve 0.84.1 → 0.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/src/channel/channel-operations.d.ts +2 -18
  3. package/dist/src/channel/compiled-channel.d.ts +3 -7
  4. package/dist/src/channel/cross-channel-receive.d.ts +9 -10
  5. package/dist/src/channel/cross-channel-receive.js +1 -1
  6. package/dist/src/channel/schedule.js +1 -1
  7. package/dist/src/channel/session.d.ts +26 -4
  8. package/dist/src/channel/session.js +1 -1
  9. package/dist/src/chunks/{use-eve-agent-DMeP0JOF.js → use-eve-agent-B2jCfcnG.js} +3 -2
  10. package/dist/src/chunks/{use-eve-agent-DP5Rso9C.js → use-eve-agent-DCLFi26P.js} +3 -2
  11. package/dist/src/client/client.js +1 -1
  12. package/dist/src/client/open-stream.js +1 -1
  13. package/dist/src/client/types.d.ts +5 -4
  14. package/dist/src/compiled/.vendor-stamp.json +1 -1
  15. package/dist/src/context/keys.d.ts +7 -0
  16. package/dist/src/context/keys.js +1 -1
  17. package/dist/src/execution/agent-sessions/context.d.ts +5 -1
  18. package/dist/src/execution/agent-sessions/context.js +1 -1
  19. package/dist/src/execution/dynamic-workflow/tool.js +1 -1
  20. package/dist/src/execution/legacy-session/snapshot.d.ts +1 -2
  21. package/dist/src/execution/legacy-session/snapshot.js +1 -1
  22. package/dist/src/execution/proxied-deliver-step.js +1 -1
  23. package/dist/src/execution/public-destination.d.ts +3 -0
  24. package/dist/src/execution/public-destination.js +1 -1
  25. package/dist/src/execution/publish-session-events.js +1 -1
  26. package/dist/src/execution/session/checkpoint-migrations.d.ts +16 -5
  27. package/dist/src/execution/session/checkpoint-migrations.js +1 -1
  28. package/dist/src/execution/session/checkpoint-refusal.d.ts +4 -0
  29. package/dist/src/execution/session/checkpoint-refusal.js +1 -0
  30. package/dist/src/execution/session/entry.js +1 -1
  31. package/dist/src/execution/session/handoff-steps.d.ts +7 -1
  32. package/dist/src/execution/session/handoff-steps.js +1 -1
  33. package/dist/src/execution/session/imported-conversation.d.ts +2 -0
  34. package/dist/src/execution/session/imported-conversation.js +1 -0
  35. package/dist/src/execution/session/interrupted-step.js +1 -1
  36. package/dist/src/execution/session/pre-v27-checkpoint.d.ts +36 -0
  37. package/dist/src/execution/session/pre-v27-checkpoint.js +1 -0
  38. package/dist/src/execution/session/program.d.ts +2 -0
  39. package/dist/src/execution/session/program.js +1 -1
  40. package/dist/src/execution/session/turn-step.js +1 -1
  41. package/dist/src/execution/session-callback-request.js +1 -1
  42. package/dist/src/execution/tool-auth.js +1 -1
  43. package/dist/src/execution/tools/agent-router-workflow.js +1 -1
  44. package/dist/src/execution/tools/channel-to.d.ts +12 -0
  45. package/dist/src/execution/tools/channel-to.js +1 -0
  46. package/dist/src/execution/tools/workflow/body.d.ts +3 -0
  47. package/dist/src/execution/tools/workflow/body.js +1 -1
  48. package/dist/src/execution/tools/workflow/step-context.d.ts +5 -0
  49. package/dist/src/execution/tools/workflow/step-execution.js +1 -1
  50. package/dist/src/execution/tools/workflow/step.js +1 -1
  51. package/dist/src/execution/workflow-callback-url.d.ts +2 -3
  52. package/dist/src/execution/workflow-callback-url.js +1 -1
  53. package/dist/src/execution/workflow-runtime.js +1 -1
  54. package/dist/src/harness/compaction/step.js +1 -1
  55. package/dist/src/harness/model-call/call.js +1 -1
  56. package/dist/src/harness/model-call/run.js +1 -1
  57. package/dist/src/harness/session-machine/transitions.js +1 -1
  58. package/dist/src/internal/application/stamped-package-version.js +1 -1
  59. package/dist/src/internal/attachments/limited-read.d.ts +2 -0
  60. package/dist/src/internal/attachments/limited-read.js +1 -1
  61. package/dist/src/internal/attachments/url-refs.js +1 -1
  62. package/dist/src/internal/capability-error.d.ts +3 -0
  63. package/dist/src/internal/capability-error.js +1 -0
  64. package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +1 -1
  65. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  66. package/dist/src/public/channels/github/githubChannel.js +1 -1
  67. package/dist/src/public/channels/linear/linearChannel.js +1 -1
  68. package/dist/src/public/channels/linq/inboundContent.js +1 -1
  69. package/dist/src/public/channels/slack/answers.d.ts +2 -3
  70. package/dist/src/public/channels/slack/answers.js +1 -1
  71. package/dist/src/public/channels/slack/api.d.ts +23 -3
  72. package/dist/src/public/channels/slack/api.js +1 -1
  73. package/dist/src/public/channels/slack/app-manifest.d.ts +2 -0
  74. package/dist/src/public/channels/slack/app-manifest.js +1 -1
  75. package/dist/src/public/channels/slack/auth.d.ts +2 -1
  76. package/dist/src/public/channels/slack/auth.js +1 -1
  77. package/dist/src/public/channels/slack/code-channel.d.ts +42 -0
  78. package/dist/src/public/channels/slack/code-channel.js +1 -0
  79. package/dist/src/public/channels/slack/defaults.d.ts +7 -3
  80. package/dist/src/public/channels/slack/defaults.js +3 -3
  81. package/dist/src/public/channels/slack/hitl.d.ts +7 -17
  82. package/dist/src/public/channels/slack/hitl.js +1 -1
  83. package/dist/src/public/channels/slack/index.d.ts +4 -3
  84. package/dist/src/public/channels/slack/index.js +1 -1
  85. package/dist/src/public/channels/slack/interaction-cards.d.ts +0 -2
  86. package/dist/src/public/channels/slack/interaction-cards.js +1 -1
  87. package/dist/src/public/channels/slack/interactions.js +1 -1
  88. package/dist/src/public/channels/slack/open-code-channel.d.ts +30 -0
  89. package/dist/src/public/channels/slack/open-code-channel.js +1 -0
  90. package/dist/src/public/channels/slack/private-approval-delivery.d.ts +16 -4
  91. package/dist/src/public/channels/slack/private-approval-delivery.js +1 -1
  92. package/dist/src/public/channels/slack/renderers.d.ts +58 -6
  93. package/dist/src/public/channels/slack/renderers.js +1 -1
  94. package/dist/src/public/channels/slack/reply-delivery.d.ts +4 -0
  95. package/dist/src/public/channels/slack/reply-delivery.js +1 -1
  96. package/dist/src/public/channels/slack/request-card-render.d.ts +45 -0
  97. package/dist/src/public/channels/slack/request-card-render.js +1 -0
  98. package/dist/src/public/channels/slack/request-cards.d.ts +35 -0
  99. package/dist/src/public/channels/slack/request-cards.js +3 -0
  100. package/dist/src/public/channels/slack/sign-in-cancel.d.ts +3 -5
  101. package/dist/src/public/channels/slack/sign-in-cancel.js +1 -1
  102. package/dist/src/public/channels/slack/slackChannel.d.ts +109 -22
  103. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  104. package/dist/src/public/channels/slack/task-card.d.ts +3 -3
  105. package/dist/src/public/channels/slack/task-card.js +1 -1
  106. package/dist/src/public/channels/slack/thread-status.js +1 -1
  107. package/dist/src/public/channels/teams/api.js +1 -1
  108. package/dist/src/public/channels/teams/defaults.js +1 -1
  109. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  110. package/dist/src/public/channels/teams/verify.d.ts +9 -5
  111. package/dist/src/public/channels/teams/verify.js +1 -1
  112. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  113. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  114. package/dist/src/public/schedules/subscription.d.ts +10 -1
  115. package/dist/src/runtime/subagents/workflow.js +1 -1
  116. package/dist/src/setup/index.d.ts +1 -1
  117. package/dist/src/setup/primitives/index.d.ts +1 -1
  118. package/dist/src/setup/primitives/pm/run.d.ts +0 -2
  119. package/dist/src/setup/scaffold/create/project.js +1 -1
  120. package/dist/src/shared/channel-definition.d.ts +4 -4
  121. package/dist/src/subagents/adapter.js +1 -1
  122. package/dist/src/subagents/parent-notification.js +1 -1
  123. package/dist/src/svelte/index.js +1 -1
  124. package/dist/src/svelte/use-eve-agent.js +1 -1
  125. package/dist/src/tools/definition.d.ts +21 -0
  126. package/dist/src/tools/provided/ask-question-workflow.js +1 -1
  127. package/dist/src/tools/provided/sleep-workflow.js +1 -1
  128. package/dist/src/tools/workflow-definition.d.ts +4 -4
  129. package/dist/src/vue/index.js +1 -1
  130. package/dist/src/vue/use-eve-agent.js +1 -1
  131. package/docs/channels/custom.mdx +16 -3
  132. package/docs/channels/eve.mdx +2 -0
  133. package/docs/channels/linq.mdx +1 -1
  134. package/docs/channels/slack.mdx +185 -18
  135. package/docs/channels/teams.mdx +6 -2
  136. package/docs/concepts/upgrade-to-v27-events.md +2 -2
  137. package/docs/experimental/dynamic-schedules.mdx +2 -2
  138. package/docs/guides/client/overview.mdx +1 -1
  139. package/docs/guides/remote-agents.md +8 -0
  140. package/docs/patterns/fork-conversations-into-new-threads.md +159 -0
  141. package/docs/patterns/meta.json +1 -0
  142. package/docs/schedules.mdx +1 -1
  143. package/docs/tools/overview.mdx +34 -0
  144. package/docs/tools/workflows.mdx +47 -5
  145. package/package.json +1 -1
  146. package/dist/src/public/channels/slack/approval-cards.d.ts +0 -25
  147. package/dist/src/public/channels/slack/approval-cards.js +0 -2
@@ -1,10 +1,13 @@
1
+ import type { Interaction } from "#channel/interaction-prompts.js";
1
2
  import type { TaskCard } from "#channel/task-card.js";
3
+ import type { SessionView } from "#protocol/session-projection/session-view.js";
4
+ import type { ChannelAudience } from "#shared/channel-audience.js";
2
5
  import type { ChannelEventContext, ChannelEventOf } from "#public/definitions/channel.js";
3
6
  import type { BlockKitBlock } from "#public/channels/slack/blocks.js";
4
7
  import type { SlackMessage } from "#public/channels/slack/inbound.js";
5
- import type { SlackAuthorizationEventContext, SlackChannelInternalEvents, SlackContext, SlackEventContext } from "#public/channels/slack/slackChannel.js";
8
+ import type { SlackApprovalChannel, SlackAuthorizationEventContext, SlackChannelInternalEvents, SlackContext, SlackEventContext } from "#public/channels/slack/slackChannel.js";
6
9
  /** The session events a Slack renderer can handle, in no particular order. */
7
- declare const SLACK_RENDERED_EVENTS: readonly ["call.progress", "call.requested", "call.started", "call.settled", "content.completed", "content.delta", "delivery.settled", "interaction.opened", "interaction.settled", "model.settled", "model.started", "response.settled", "session.ended", "task.ended", "task.started", "turn.paused", "turn.settled", "turn.started"];
10
+ declare const SLACK_RENDERED_EVENTS: readonly ["call.progress", "call.requested", "call.started", "call.settled", "content.completed", "content.delta", "delivery.settled", "interaction.opened", "interaction.settled", "model.settled", "model.started", "response.admitted", "response.settled", "session.ended", "task.ended", "task.started", "turn.paused", "turn.settled", "turn.started"];
8
11
  export type SlackRenderedEvent = (typeof SLACK_RENDERED_EVENTS)[number];
9
12
  type SlackSessionEvent = Exclude<SlackRenderedEvent, "interaction.opened">;
10
13
  /**
@@ -26,11 +29,47 @@ export type SlackRendererEvents = {
26
29
  } & {
27
30
  readonly "interaction.opened"?: (event: ChannelEventOf<"interaction.opened">, ctx: ChannelEventContext<SlackEventContext | SlackAuthorizationEventContext>, next: SlackRenderNext<"interaction.opened">) => void | Promise<void>;
28
31
  };
29
- /** The message eve posts for a turn's tasks: Block Kit blocks and notification text. */
30
- export interface SlackTaskCard {
32
+ /**
33
+ * One Slack message a renderer hook returns, such as a task card or an input request's controls:
34
+ * Block Kit blocks and notification text.
35
+ */
36
+ export interface SlackCardMessage {
31
37
  readonly blocks: readonly BlockKitBlock[];
32
38
  readonly text: string;
33
39
  }
40
+ /** One input request as eve is about to post or update it. */
41
+ export interface SlackInteractionCard {
42
+ /**
43
+ * The request, read from the session: `status`, `outcome`, `call` (with `input`), and `answer`.
44
+ * Once settled, `answer` is the answer that decided it. While open, it is the latest admitted
45
+ * answer, if any; an answer still being checked, or one that was refused, doesn't count.
46
+ */
47
+ readonly interaction: Interaction;
48
+ /**
49
+ * Where eve posts it, as `approvalChannel` decided. In a code channel, `"thread"` means the code
50
+ * channel itself.
51
+ */
52
+ readonly destination: SlackApprovalChannel;
53
+ /**
54
+ * Who reads the message. A direct message is always `private`; a thread request has the
55
+ * thread's audience. Same meaning as `TaskCard.audience`.
56
+ */
57
+ readonly audience: ChannelAudience;
58
+ /** The Slack user whose answer stands or decided the request, when known. */
59
+ readonly answeredBy?: string;
60
+ /** Session snapshot, for the `eve/events` selectors. */
61
+ readonly view: SessionView;
62
+ }
63
+ /** The messages of one input request. */
64
+ export interface SlackInteractionCardParts {
65
+ /** The message holding eve's controls. Slack echoes it back on every click, so keep it small. */
66
+ readonly controls: SlackCardMessage;
67
+ /**
68
+ * Posted as a separate message before the controls, for any kind of request. Requests asked
69
+ * together share one details message, which eve deletes once its last open request settles.
70
+ */
71
+ readonly details?: SlackCardMessage;
72
+ }
34
73
  /**
35
74
  * One link in a Slack channel's rendering chain. Every part is optional and
36
75
  * wraps the renderers after it, ending with eve's default renderer.
@@ -53,7 +92,17 @@ export interface SlackRenderer {
53
92
  * turn changes, and handles Slack's rate limits. `next` returns eve's default
54
93
  * card, which is `null` for a card without tasks.
55
94
  */
56
- readonly taskCard?: (card: TaskCard, next: (card: TaskCard) => SlackTaskCard | null) => SlackTaskCard | null;
95
+ readonly taskCard?: (card: TaskCard, next: (card: TaskCard) => SlackCardMessage | null) => SlackCardMessage | null;
96
+ /**
97
+ * Returns the messages for one approval, question, or budget prompt. Runs when the request
98
+ * opens, and again when an answer is admitted or refused and when it settles. Pure and
99
+ * synchronous: eve keeps routing, batching, action IDs, and posting. While the request is open
100
+ * and unanswered, `controls` must hold exactly the `eve_input:` controls `next` renders, each
101
+ * once; once it is answered or settled, it must hold none. `details` never holds them, and each
102
+ * message holds at most 50 blocks. A result that breaks these rules, or a hook that throws,
103
+ * falls back to eve's default for that request.
104
+ */
105
+ readonly interactionCard?: (card: SlackInteractionCard, next: (card: SlackInteractionCard) => SlackInteractionCardParts) => SlackInteractionCardParts;
57
106
  }
58
107
  /** Types a Slack renderer. */
59
108
  export declare function defineSlackRenderer(renderer: SlackRenderer): SlackRenderer;
@@ -64,7 +113,10 @@ export declare function defineSlackRenderer(renderer: SlackRenderer): SlackRende
64
113
  export interface SlackRenderChain {
65
114
  readonly events: SlackChannelInternalEvents;
66
115
  readonly received: (message: SlackMessage, channel: SlackContext) => Promise<void>;
67
- readonly taskCard: (card: TaskCard) => SlackTaskCard | null;
116
+ readonly taskCard: (card: TaskCard) => SlackCardMessage | null;
68
117
  }
69
118
  export declare function composeSlackRenderers(renderers: readonly SlackRenderer[], defaults: SlackRenderChain): SlackRenderChain;
119
+ /** Renderers' `interactionCard` hooks, first outermost, around `last`: eve's default. */
120
+ export type SlackInteractionCardChain = (card: SlackInteractionCard, last: (card: SlackInteractionCard) => SlackInteractionCardParts) => SlackInteractionCardParts;
121
+ export declare function composeInteractionCards(renderers: readonly SlackRenderer[]): SlackInteractionCardChain;
70
122
  export {};
@@ -1 +1 @@
1
- import{removedEventKeyMessage}from"#public/definitions/removed-event-keys.js";const SLACK_RENDERED_EVENTS=[`call.progress`,`call.requested`,`call.started`,`call.settled`,`content.completed`,`content.delta`,`delivery.settled`,`interaction.opened`,`interaction.settled`,`model.settled`,`model.started`,`response.settled`,`session.ended`,`task.ended`,`task.started`,`turn.paused`,`turn.settled`,`turn.started`];function defineSlackRenderer(e){for(let t of Object.keys(e.events??{})){let e=removedEventKeyMessage(t);if(e!==void 0)throw Error(`A Slack renderer handles ${e}`)}return e}function composeSlackRenderers(e,t){for(let t of e)defineSlackRenderer(t);return{events:composeEvents(e,t.events),received:(n,r)=>composeReceived(e,t,n,r,0),taskCard:n=>composeTaskCard(e,t,n,0)}}function composeEvents(e,t){let n={};for(let r of SLACK_RENDERED_EVENTS){let i=t[r],a=e.flatMap(e=>{let t=e.events?.[r];return t===void 0?[]:[t]});(a.length!==0||i!==void 0)&&(n[r]=(e,t)=>runChain(a,e,(e,n,i)=>e(n,userContext(r,t,n),i),e=>i?.(e,t)))}return n}async function runChain(e,t,n,r){let run=async(t,i)=>{if(t===e.length){await r(i);return}let a;await n(e[t],i,e=>a??=run(t+1,e===void 0?i:e))};await run(0,t)}function userContext(e,t,n){if(e!==`interaction.opened`||!isSignIn(n))return t;let{channel:r}=t,i={postDirectMessage:(e,t)=>r.thread.postDirectMessage(e,t),postEphemeral:(e,t)=>r.thread.postEphemeral(e,t),state:r.state};return{...t,channel:i}}function isSignIn(e){return e?.data?.request?.kind===`sign-in`}async function composeReceived(e,t,n,r,i){if(i===e.length){await t.received(n,r);return}let a=e[i].received;if(a===void 0){await composeReceived(e,t,n,r,i+1);return}let o;await a(n,r,()=>(o??=composeReceived(e,t,n,r,i+1),o))}function composeTaskCard(e,t,n,r){if(r===e.length)return t.taskCard(n);let i=e[r].taskCard;return i===void 0?composeTaskCard(e,t,n,r+1):i(n,n=>composeTaskCard(e,t,n,r+1))}export{composeSlackRenderers,defineSlackRenderer};
1
+ import{removedEventKeyMessage}from"#public/definitions/removed-event-keys.js";const SLACK_RENDERED_EVENTS=[`call.progress`,`call.requested`,`call.started`,`call.settled`,`content.completed`,`content.delta`,`delivery.settled`,`interaction.opened`,`interaction.settled`,`model.settled`,`model.started`,`response.admitted`,`response.settled`,`session.ended`,`task.ended`,`task.started`,`turn.paused`,`turn.settled`,`turn.started`];function defineSlackRenderer(e){for(let t of Object.keys(e.events??{})){let e=removedEventKeyMessage(t);if(e!==void 0)throw Error(`A Slack renderer handles ${e}`)}return e}function composeSlackRenderers(e,t){for(let t of e)defineSlackRenderer(t);return{events:composeEvents(e,t.events),received:(n,r)=>composeReceived(e,t,n,r,0),taskCard:n=>composeTaskCard(e,t,n,0)}}function composeEvents(e,t){let n={};for(let r of SLACK_RENDERED_EVENTS){let i=t[r],a=e.flatMap(e=>{let t=e.events?.[r];return t===void 0?[]:[t]});(a.length!==0||i!==void 0)&&(n[r]=(e,t)=>runChain(a,e,(e,n,i)=>e(n,userContext(r,t,n),i),e=>i?.(e,t)))}return n}async function runChain(e,t,n,r){let run=async(t,i)=>{if(t===e.length){await r(i);return}let a;await n(e[t],i,e=>a??=run(t+1,e===void 0?i:e))};await run(0,t)}function userContext(e,t,n){if(e!==`interaction.opened`||!isSignIn(n))return t;let{channel:r}=t,i={postDirectMessage:(e,t)=>r.thread.postDirectMessage(e,t),postEphemeral:(e,t)=>r.thread.postEphemeral(e,t),state:r.state};return{...t,channel:i}}function isSignIn(e){return e?.data?.request?.kind===`sign-in`}async function composeReceived(e,t,n,r,i){if(i===e.length){await t.received(n,r);return}let a=e[i].received;if(a===void 0){await composeReceived(e,t,n,r,i+1);return}let o;await a(n,r,()=>(o??=composeReceived(e,t,n,r,i+1),o))}function composeInteractionCards(e){let t=e.flatMap(e=>e.interactionCard??[]);return(e,n)=>{let run=(e,r)=>e===t.length?n(r):t[e](r,t=>run(e+1,t));return run(0,e)}}function composeTaskCard(e,t,n,r){if(r===e.length)return t.taskCard(n);let i=e[r].taskCard;return i===void 0?composeTaskCard(e,t,n,r+1):i(n,n=>composeTaskCard(e,t,n,r+1))}export{composeInteractionCards,composeSlackRenderers,defineSlackRenderer};
@@ -56,6 +56,10 @@ export declare function postCompletedSlackReply(channel: SlackContext, reply: Sl
56
56
  export declare function deliverCompletedSlackReply(channel: SlackContext, reply: SlackCompletedReply, logFields: {
57
57
  readonly turnId?: string;
58
58
  }): Promise<void>;
59
+ /** Whether Slack's error `code` means it refused the message's content and posted nothing. */
60
+ export declare function isRefusedPayloadCode(code: unknown): code is string;
61
+ /** Slack's error code when `error` is a refused-payload error, checking its causes. */
62
+ export declare function refusedPayloadError(error: unknown): string | undefined;
59
63
  /**
60
64
  * Wraps a channel's composed `content.completed` chain, authored renderers
61
65
  * included, so a final reply that fails is never lost without a trace.
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{replyTextOf}from"#public/channels/reply.js";import{reportUndeliveredReply}from"#public/channels/undelivered-reply.js";import{SLACK_MARKDOWN_TEXT_MAX_LENGTH,truncateMessageText}from"#public/channels/slack/limits.js";const log=createLogger(`slack.reply`),LONG_RESPONSE_NOTICE=`Here's a snippet with the full response.`,FALLBACK_LOG=`Slack refused the final reply; delivering it with a fallback`,REFUSED_PAYLOAD_ERRORS=new Set([`invalid_blocks`,`invalid_blocks_format`,`msg_blocks_too_long`,`msg_blocks_too_many`,`msg_too_long`]);async function postCompletedSlackReply(e,t){await deliverCompletedSlackReply(e,t,{})}async function deliverCompletedSlackReply(e,t,n){let{blocks:r,markdown:i,text:a}=typeof t==`string`?{markdown:t}:t;if(await threadRootIsGone(e)){log.warn(`Slack thread root was deleted; dropping the final reply`,n);return}let o;if(r!==void 0)try{await e.thread.post({blocks:r,text:a??truncateMessageText(i)});return}catch(e){if(o=refusedPayloadError(e),o===void 0)throw e}if(i.length<=SLACK_MARKDOWN_TEXT_MAX_LENGTH){o!==void 0&&log.warn(FALLBACK_LOG,{fallback:`inline`,slackError:o,...n});try{await e.thread.post(i);return}catch(e){if(o=refusedPayloadError(e),o===void 0)throw e}}o!==void 0&&log.warn(FALLBACK_LOG,{fallback:`snippet`,slackError:o,...n}),await uploadReplySnippet(e,i)}async function threadRootIsGone(e){let{channelId:t,threadTs:n}=e.slack;if(!t||!n||e.state?.postedInThread===!0)return!1;try{let r=await e.slack.request(`chat.getPermalink`,{channel:t,message_ts:n});return r.ok!==!0&&r.error===`message_not_found`}catch{return!1}}async function uploadReplySnippet(e,t){let n={data:new Blob([t],{type:`text/markdown`}),filename:`eve-response.md`,mimeType:`text/markdown`},r=e.slack.threadTs.length>0;if(!r&&(!(await e.thread.post(LONG_RESPONSE_NOTICE)).id||e.slack.threadTs.length===0))throw Error(`Slack did not return a thread timestamp for the long response notice.`);await e.slack.uploadFiles([n],{initialComment:r?LONG_RESPONSE_NOTICE:void 0,snippetType:`markdown`})}function refusedPayloadError(e){let t=e;for(let e=0;e<5&&typeof t==`object`&&t;e++){let e=t,n=e.response?.error??e.data?.error;if(typeof n==`string`&&REFUSED_PAYLOAD_ERRORS.has(n))return n;t=e.cause}}function withFinalReplyDelivery(e){return async(t,n)=>{if(replyTextOf(t.data)===void 0){await e?.(t,n);return}try{await e?.(t,n)}catch(e){let{channel:t}=n;await reportUndeliveredReply({log,name:`Slack`,post:e=>t.thread.post(e),state:t.state},n.session.turn.id,e)}}}export{deliverCompletedSlackReply,postCompletedSlackReply,withFinalReplyDelivery};
1
+ import{createLogger}from"#internal/logging.js";import{replyTextOf}from"#public/channels/reply.js";import{reportUndeliveredReply}from"#public/channels/undelivered-reply.js";import{SLACK_MARKDOWN_TEXT_MAX_LENGTH,truncateMessageText}from"#public/channels/slack/limits.js";const log=createLogger(`slack.reply`),LONG_RESPONSE_NOTICE=`Here's a snippet with the full response.`,FALLBACK_LOG=`Slack refused the final reply; delivering it with a fallback`,REFUSED_PAYLOAD_ERRORS=new Set([`invalid_blocks`,`invalid_blocks_format`,`msg_blocks_too_long`,`msg_blocks_too_many`,`msg_too_long`]);async function postCompletedSlackReply(e,t){await deliverCompletedSlackReply(e,t,{})}async function deliverCompletedSlackReply(e,t,n){let{blocks:r,markdown:i,text:a}=typeof t==`string`?{markdown:t}:t;if(await threadRootIsGone(e)){log.warn(`Slack thread root was deleted; dropping the final reply`,n);return}let o;if(r!==void 0)try{await e.thread.post({blocks:r,text:a??truncateMessageText(i)});return}catch(e){if(o=refusedPayloadError(e),o===void 0)throw e}if(i.length<=SLACK_MARKDOWN_TEXT_MAX_LENGTH){o!==void 0&&log.warn(FALLBACK_LOG,{fallback:`inline`,slackError:o,...n});try{await e.thread.post(i);return}catch(e){if(o=refusedPayloadError(e),o===void 0)throw e}}o!==void 0&&log.warn(FALLBACK_LOG,{fallback:`snippet`,slackError:o,...n}),await uploadReplySnippet(e,i)}async function threadRootIsGone(e){let{channelId:t,codeChannel:n,threadTs:r}=e.slack;if(!t||!r||n||e.state?.postedInThread===!0)return!1;try{let n=await e.slack.request(`chat.getPermalink`,{channel:t,message_ts:r});return n.ok!==!0&&n.error===`message_not_found`}catch{return!1}}async function uploadReplySnippet(e,t){let n={data:new Blob([t],{type:`text/markdown`}),filename:`eve-response.md`,mimeType:`text/markdown`},r=e.slack.codeChannel||e.slack.threadTs.length>0;if(!r&&(!(await e.thread.post(LONG_RESPONSE_NOTICE)).id||e.slack.threadTs.length===0))throw Error(`Slack did not return a thread timestamp for the long response notice.`);await e.slack.uploadFiles([n],{initialComment:r?LONG_RESPONSE_NOTICE:void 0,snippetType:`markdown`})}function isRefusedPayloadCode(e){return typeof e==`string`&&REFUSED_PAYLOAD_ERRORS.has(e)}function refusedPayloadError(e){let t=e;for(let e=0;e<5&&typeof t==`object`&&t;e++){let e=t,n=e.response?.error??e.data?.error;if(isRefusedPayloadCode(n))return n;t=e.cause}}function withFinalReplyDelivery(e){return async(t,n)=>{if(replyTextOf(t.data)===void 0){await e?.(t,n);return}try{await e?.(t,n)}catch(e){let{channel:t}=n;await reportUndeliveredReply({log,name:`Slack`,post:e=>t.thread.post(e),state:t.state},n.session.turn.id,e)}}}export{deliverCompletedSlackReply,isRefusedPayloadCode,postCompletedSlackReply,refusedPayloadError,withFinalReplyDelivery};
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Renders one approval, question, or budget prompt through the renderers' `interactionCard`
3
+ * chain, and checks the result against eve's own controls so a renderer can restyle a card but
4
+ * never break how its clicks route.
5
+ */
6
+ import type { Interaction } from "#channel/interaction-prompts.js";
7
+ import { type SlackHitlRoute } from "#public/channels/slack/hitl.js";
8
+ import type { SlackCardMessage, SlackInteractionCard, SlackInteractionCardChain, SlackInteractionCardParts } from "#public/channels/slack/renderers.js";
9
+ /** One request's messages, and eve's default for it, sent when Slack rejects the rendered ones. */
10
+ export interface RenderedRequestCard {
11
+ readonly parts: SlackInteractionCardParts;
12
+ readonly fallback: SlackInteractionCardParts;
13
+ }
14
+ /**
15
+ * eve's default card: an open, unanswered request's controls, or once answered or settled, one
16
+ * block naming the answer and, when known, who gave it.
17
+ */
18
+ export declare function defaultInteractionCard(card: SlackInteractionCard, route?: SlackHitlRoute): SlackInteractionCardParts;
19
+ /**
20
+ * How an answered or settled request reads: the answer's label, or how it ended without one,
21
+ * and the notification text. The card and a direct-message request's thread note share it.
22
+ */
23
+ export declare function endingOf(interaction: Interaction): {
24
+ readonly label: string;
25
+ readonly text: string;
26
+ };
27
+ /**
28
+ * Runs `chain` for one request, falling back to eve's default when the renderer throws or its
29
+ * result fails {@link invalidCardReason}. The default renders from the original interaction, so
30
+ * a changed card handed to `next` can't change which controls are expected.
31
+ */
32
+ export declare function renderRequestCard(chain: SlackInteractionCardChain, card: SlackInteractionCard, route?: SlackHitlRoute): RenderedRequestCard;
33
+ /**
34
+ * eve's default at its smallest: only the block holding its controls, which every open default
35
+ * has exactly one of, or the single block of an answered or settled default.
36
+ */
37
+ export declare function compactDefault(fallback: SlackCardMessage): SlackCardMessage;
38
+ /**
39
+ * Sends `message`, and once more with eve's default when Slack refuses its content, unless the
40
+ * default is the same message. Any other failure is thrown, since resending after a timeout could
41
+ * duplicate a message that landed. A request without a default for this message, such as details
42
+ * a renderer added, sends nothing the second time.
43
+ */
44
+ export declare function sendWithFallback<T>(send: (message: SlackCardMessage) => Promise<T>, message: SlackCardMessage, fallback: SlackCardMessage | undefined): Promise<T | undefined>;
45
+ export declare function sameJson(a: unknown, b: unknown): boolean;
@@ -0,0 +1 @@
1
+ import{createLogger,logError}from"#internal/logging.js";import{truncate}from"#public/channels/text.js";import{SLACK_MAX_BLOCKS_PER_MESSAGE,SLACK_SECTION_TEXT_MAX_LENGTH}from"#public/channels/slack/limits.js";import{refusedPayloadError}from"#public/channels/slack/reply-delivery.js";import{isFreeformAction,isHitlAction,renderInteractionPostParts}from"#public/channels/slack/hitl.js";import{answeredBlocksFromActionBlock}from"#public/channels/slack/interaction-cards.js";const log=createLogger(`slack.interaction-card`);function defaultInteractionCard(e,t){let{interaction:n}=e,r=renderInteractionPostParts(n,t);if(n.status===`open`&&n.answer===void 0)return r;let{label:i,text:a}=endingOf(n);return{controls:{blocks:[answeredBlocksFromActionBlock({answerLabel:i,block:r.controls.blocks[0],userId:e.answeredBy})?.[0]??answeredSection(n.request.prompt,i,e.answeredBy)],text:a}}}function endingOf(e){let{answer:t,outcome:n,request:r}=e;if(t!==void 0){let e=r.options?.find(e=>e.id===t.optionId)?.label??t.text??t.optionId??`Answered`;return{label:e,text:`Answered: ${e}`}}let i=n===`accepted`?`Accepted`:n===`declined`?`Declined`:`No longer needed`;return{label:i,text:i}}function answeredSection(e,t,n){let r=n===void 0?``:` by <@${n}>`,i=`:white_check_mark: *${truncate(t,500)}*${r}`;return{text:{text:`${truncate(e,SLACK_SECTION_TEXT_MAX_LENGTH-i.length-1)}\n${i}`,type:`mrkdwn`},type:`section`}}function renderRequestCard(e,t,n){let r=defaultInteractionCard(t,n),{interactionId:i}=t.interaction;try{let a=JSON.parse(JSON.stringify(e(t,e=>defaultInteractionCard(e,n)))),o=invalidCardReason(a,r);if(o===void 0)return{fallback:r,parts:a};log.warn(`interactionCard result rejected; using eve's default`,{interactionId:i,problem:o})}catch(e){logError(log,`interactionCard renderer failed; using eve's default`,e,{interactionId:i})}return{fallback:r,parts:r}}function invalidCardReason(e,t){for(let[t,n]of[[`controls`,e.controls],[`details`,e.details]])if(n!==void 0||t!==`details`){if(!isMessage(n))return`${t} is not a message with blocks and text`;if(n.blocks.length>SLACK_MAX_BLOCKS_PER_MESSAGE)return`${t} has more than ${SLACK_MAX_BLOCKS_PER_MESSAGE} blocks`}if(e.details!==void 0&&eveControls(e.details.blocks).length>0)return`details hold eve controls`;let n=eveControls(t.controls.blocks),r=eveControls(e.controls.blocks);if(n.length!==r.length||n.some((e,t)=>e!==r[t]))return n.length===0?`controls of an answered or settled request hold eve controls`:`controls don't hold exactly eve's controls`}function isMessage(e){if(typeof e!=`object`||!e)return!1;let{blocks:t,text:n}=e;return Array.isArray(t)&&typeof n==`string`}function compactDefault(e){let t=e.blocks.filter(e=>eveControls(e).length>0);return t.length===0?e:{...e,blocks:t}}function eveControls(e){let t=[],walk=e=>{if(Array.isArray(e)){for(let t of e)walk(t);return}if(typeof e!=`object`||!e)return;let n=e,r=n.action_id;if(typeof r==`string`&&(isHitlAction(r)||isFreeformAction(r))){let e=Array.isArray(n.options)?n.options.map(e=>e.value).sort():null;t.push(JSON.stringify([r,n.value??null,e]))}for(let t of Object.values(e))walk(t)};return walk(e),t.sort()}async function sendWithFallback(e,t,n){try{return await e(t)}catch(r){if(refusedPayloadError(r)===void 0||sameJson(t,n))throw r;return logError(log,`Slack rejected a rendered input request; sending eve's default`,r),n===void 0?void 0:await e(n)}}function sameJson(e,t){return JSON.stringify(e)===JSON.stringify(t)}export{compactDefault,defaultInteractionCard,endingOf,renderRequestCard,sameJson,sendWithFallback};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * eve's Slack cards for approvals, questions, and budget prompts: a commit's requests post as one
3
+ * batch, and each card updates as its request is answered and settles. Renderers choose the
4
+ * blocks through `interactionCard`; this module owns routing, batching, and the messages.
5
+ */
6
+ import { type RefusedAnswer, type RequestBatch } from "#channel/interaction-prompts.js";
7
+ import type { ChannelEventContext } from "#public/definitions/channel.js";
8
+ import type { SlackInteractionCardChain } from "#public/channels/slack/renderers.js";
9
+ import type { SlackApprovalChannelResolver, SlackEventContext } from "#public/channels/slack/slackChannel.js";
10
+ type RequestCardContext = ChannelEventContext<SlackEventContext>;
11
+ /**
12
+ * Posts a commit's requests: each `approvalChannel` sends to the thread joins one batch, details
13
+ * first, then controls, grouped under Slack's 50-block cap; each sent to a DM posts there alone.
14
+ */
15
+ export declare function postRequestCards(batch: RequestBatch, ctx: RequestCardContext, chain: SlackInteractionCardChain, approvalChannel: SlackApprovalChannelResolver | undefined): Promise<void>;
16
+ /**
17
+ * Shows an open request's changed answer: an admitted one, or the controls again once it was
18
+ * refused. A request already settled in the commit is left to `interaction.settled`.
19
+ */
20
+ export declare function showAnswerChange(interactionId: string, ctx: RequestCardContext, chain: SlackInteractionCardChain): Promise<void>;
21
+ /**
22
+ * Writes a settled request's card. Every request of its message that settled in the same commit
23
+ * without its settled rendering written joins the one update, since the commit's view already
24
+ * shows them settled. Then deletes each details message whose requests have all written their
25
+ * settled rendering, reports the outcome in a DM request's thread note, and forgets the message
26
+ * once every request in it has.
27
+ */
28
+ export declare function settleRequestCard(interactionId: string, ctx: RequestCardContext, chain: SlackInteractionCardChain): Promise<void>;
29
+ /**
30
+ * A responder an approval's policy or check refused hears why privately. A pending answer gets
31
+ * no notice: an ephemeral cannot be removed once the approval settles, and the card itself
32
+ * reports the outcome moments later.
33
+ */
34
+ export declare function notifyRefusedResponder(refused: RefusedAnswer, channel: SlackEventContext): Promise<void>;
35
+ export {};
@@ -0,0 +1,3 @@
1
+ import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{interaction,responderOf}from"#channel/interaction-prompts.js";import{sessionTables}from"#protocol/session-projection/session-view.js";import{slackSessionTargetOf}from"#public/channels/slack/api.js";import{slackUserIdForPrincipal,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{SLACK_MAX_BLOCKS_PER_MESSAGE,truncateMessageText}from"#public/channels/slack/limits.js";import{isRefusedPayloadCode,refusedPayloadError}from"#public/channels/slack/reply-delivery.js";import{compactDefault,endingOf,renderRequestCard,sameJson}from"#public/channels/slack/request-card-render.js";import{deliverPrivateInteraction}from"#public/channels/slack/private-approval-delivery.js";const log=createLogger(`slack.request-cards`);async function postRequestCards(e,t,n,r){forgetSettledCards(t);let i=[],a=[];for(let n of e.requests)((r===void 0?`thread`:await r(n,t))===`direct-message`?i:a).push(n);await postThreadCards(a,t,n);for(let e of i)await postDirectMessageCard(e,t,n)}async function postThreadCards(e,t,n){let{channel:r}=t,i=r.state.channelId;if(e.length===0)return;if(i===null){log.warn(`thread input requests not delivered because the session has no Slack channel`,{requestIds:e.map(e=>e.interactionId),sessionId:t.session.id});return}let a=r.slack.codeChannel?slackSessionTargetOf(r.slack):void 0,o=e.map(e=>({interactionId:e.interactionId,...renderRequestCard(n,cardOf(e,`thread`,t),a)})),post=async e=>(await r.thread.post({blocks:[...e.blocks],text:e.text})).id,s=new Map;await postGroups(o,`details`,post,(e,t)=>{for(let n of e)s.set(n.interactionId,t)}),await postGroups(o,`controls`,post,(e,t)=>{let n={channelId:i,destination:`thread`,requests:e.map(({interactionId:e,message:t})=>cardRequest(e,t.blocks,s.get(e))),ts:t};a!==void 0&&(n.route=a),remember(r.state,n)})}async function postGroups(e,t,n,r){let send=async e=>{let t=await n(joinParts(e));t&&r(e,t)};for(let n of groupParts(e,e=>e.parts[t]))try{await send(n)}catch(r){let i=e.filter(e=>n.some(t=>t.interactionId===e.interactionId)),a=i.every(e=>sameJson(e.parts[t],e.fallback[t]));if(refusedPayloadError(r)===void 0||a)throw r;logError(log,`Slack refused rendered input requests; posting eve's defaults`,r);for(let e of groupParts(i,e=>e.fallback[t]))await send(e)}}function groupParts(e,t){let n=[],r=0;for(let i of e){let e=t(i);if(e===void 0)continue;let a=n.at(-1);a&&r+e.blocks.length<=SLACK_MAX_BLOCKS_PER_MESSAGE?(a.push({interactionId:i.interactionId,message:e}),r+=e.blocks.length):(n.push([{interactionId:i.interactionId,message:e}]),r=e.blocks.length)}return n}function joinParts(e){return{blocks:e.flatMap(e=>e.message.blocks),text:truncateMessageText(e.map(e=>e.message.text).join(`
2
+ `))}}async function postDirectMessageCard(e,t,n){let{channel:r}=t,i=slackUserIdFromAuthContext(t.session.auth.current)??r.state.triggeringUserId;if(!i){log.warn(`direct-message input request not delivered because no reviewer was resolved`,{requestId:e.interactionId,sessionId:t.session.id});return}let a=slackSessionTargetOf(r.slack),o=await deliverPrivateInteraction({card:renderRequestCard(n,cardOf(e,`direct-message`,t),a),previewMessageTs:r.state.triggeringMessageTs??r.slack.threadTs,reviewer:i,slack:r.slack}),s={channelId:o.channelId,destination:`direct-message`,requests:[cardRequest(e.interactionId,o.blocks,o.detailsTs)],route:a,ts:o.ts};remember(r.state,s);try{let t=await r.thread.post(`Waiting on ${e.request.kind===`approval`?`approval`:`a response`} from <@${i}>…`);t.id&&replace(r.state,s,{...s,announcementTs:t.id})}catch(n){logError(log,`failed to announce private input request`,n,{requestId:e.interactionId,sessionId:t.session.id})}}async function showAnswerChange(e,t,n){forgetSettledCards(t);let r=recordOf(t.channel.state,e);r!==void 0&&sessionTables(t.view).interactions[e]?.status===`open`&&replace(t.channel.state,r,await writeRequests(t,n,r,[e]))}async function settleRequestCard(e,t,n){let{channel:r}=t;forgetSettledCards(t);let i=recordOf(r.state,e),a=sessionTables(t.view),o=(i?.requests??[]).filter(e=>e.settled!==!0&&a.interactions[e.interactionId]?.status===`settled`);if(i===void 0||o.length===0)return;let s=o.map(e=>e.interactionId),c=await writeRequests(t,n,i,s),l={...c,requests:c.requests.map(e=>s.includes(e.interactionId)?{...e,settled:!0}:e)};if(replace(r.state,i,l),l.announcementTs!==void 0&&r.state.channelId!==null)for(let e of s){let n=interaction(a,e);if(n===void 0)continue;let i=cardOf(n,l.destination,t),o=i.answeredBy===void 0?``:` by <@${i.answeredBy}>`;await callSlack(r,`chat.update`,e,{channel:r.state.channelId,text:`${endingOf(i.interaction).text}${o}.`,ts:l.announcementTs})}for(let e of o)e.detailsTs!==void 0&&await retireDetails(r,l,e.detailsTs);l.requests.every(e=>e.settled===!0)&&replace(r.state,l,void 0)}async function writeRequests(e,t,n,r){let i=sessionTables(e.view),a=r.flatMap(r=>{let a=interaction(i,r);if(a===void 0)return[];let o=renderRequestCard(t,cardOf(a,n.destination,e),n.route),s=o.fallback.controls;return[{compact:compactDefault(s),fallback:s,interactionId:r,rendered:o.parts.controls}]}),blocksOf=e=>e.flatMap(e=>e.blocks),[o,...s]=[`rendered`,`fallback`,`compact`].map(e=>{let t=[];return{kind:e,requests:n.requests.map(n=>{let r=a.find(e=>e.interactionId===n.interactionId);return r===void 0?n:(t.push(r[e].text),{...n,blocks:r[e].blocks})}),text:truncateMessageText(t.join(`
3
+ `))}}).filter(e=>blocksOf(e.requests).length<=SLACK_MAX_BLOCKS_PER_MESSAGE);if(o===void 0||(o.kind!==`rendered`&&log.warn(`input request card would pass Slack's 50 blocks; using eve's default`,{fallback:o.kind,requestIds:r}),sameJson(blocksOf(o.requests),blocksOf(n.requests))))return n;let update=async t=>await callSlack(e.channel,`chat.update`,r.join(`,`),{blocks:blocksOf(t.requests),channel:n.channelId,text:t.text,ts:n.ts}),c=await update(o);if(c===`ok`)return{...n,requests:o.requests};let l=s.find(e=>!sameJson(blocksOf(e.requests),blocksOf(o.requests)));return c!==`refused`||l===void 0||sameJson(blocksOf(l.requests),blocksOf(n.requests))?n:await update(l)===`ok`?{...n,requests:l.requests}:n}async function retireDetails(e,t,n){let r=e.state.interactionCards??[],i=r.flatMap(e=>e.requests.filter(e=>e.detailsTs===n));i.length===0||i.some(e=>e.settled!==!0)||(await callSlack(e,`chat.delete`,n,{channel:t.channelId,ts:n}),e.state.interactionCards=r.map(e=>({...e,requests:e.requests.map(e=>{if(e.detailsTs!==n)return e;let{detailsTs:t,...r}=e;return r})})))}async function callSlack(e,t,n,r){try{let i=await e.slack.request(t,r);return i.ok===!0?`ok`:(log.warn(`failed to update input request message`,{error:i.error,operation:t,requestId:n}),isRefusedPayloadCode(i.error)?`refused`:`failed`)}catch(e){return logError(log,`failed to update input request message`,e,{operation:t,requestId:n}),`failed`}}function cardOf(e,t,n){let r=sessionTables(n.view),i=standingOf(r,e),a={audience:t===`direct-message`?`private`:normalizeChannelAudience(n.channel.state.audience),destination:t,interaction:i.interaction,view:n.view},o=i.responseId===void 0?void 0:slackUserIdForPrincipal(n.channel.state,responderOf(r,i.responseId)?.id);return o!==void 0&&(a.answeredBy=o),a}function cardRequest(e,t,n){let r={blocks:t,interactionId:e};return n!==void 0&&(r.detailsTs=n),r}function standingOf(e,t){let{interactionId:n}=t;if(t.status===`settled`){let r=e.interactions[n]?.cause;return{interaction:t,responseId:t.answer!==void 0&&r!==void 0&&`responseId`in r?r.responseId:void 0}}let{answer:r,...i}=t,a=Object.values(e.responses).findLast(e=>e.interactionId===n&&e.status===`admitted`&&e.value!==void 0);return a?.value===void 0?{interaction:i,responseId:void 0}:{interaction:{...i,answer:{...a.value,interactionId:n}},responseId:a.responseId}}function forgetSettledCards(e){let{state:t}=e.channel,n=t.interactionCards;if(n===void 0||n.length===0)return;let r=sessionTables(e.view),i=e.position.line,a=n.filter(e=>e.requests.some(e=>{let t=r.interactions[e.interactionId];return t!==void 0&&(t.status===`open`||(t.closedAt??i)>=i)}));a.length!==n.length&&(t.interactionCards=a)}function recordOf(e,t){return e.interactionCards?.find(e=>e.requests.some(e=>e.interactionId===t))}function remember(e,t){e.interactionCards=[...e.interactionCards??[],t]}function replace(e,t,n){e.interactionCards=(e.interactionCards??[]).flatMap(e=>e.ts!==t.ts||e.channelId!==t.channelId?[e]:n===void 0?[]:[n])}async function notifyRefusedResponder(e,t){let n=slackUserIdForPrincipal(t.state,e.responder?.id);n!==void 0&&await t.thread.postEphemeral(n,e.reason??`We couldn’t verify your response. Please try again.`)}export{notifyRefusedResponder,postRequestCards,settleRequestCard,showAnswerChange};
@@ -1,13 +1,11 @@
1
1
  import type { ChannelFrom } from "#channel/channel-operations.js";
2
- import type { SlackChannelState } from "#public/channels/slack/slackChannel.js";
2
+ import type { SlackChannelState, SlackSessionTarget } from "#public/channels/slack/slackChannel.js";
3
3
  /** `action_id` of the sign-in prompt's Cancel button; outside eve's `eve_input:` HITL prefix. */
4
4
  export declare const SIGN_IN_CANCEL_ACTION_ID = "eve_sign_in:cancel";
5
5
  /** The held turn a sign-in prompt's Cancel button cancels. */
6
- export interface SignInCancelTarget {
7
- readonly channelId: string;
8
- readonly threadTs: string;
6
+ export type SignInCancelTarget = SlackSessionTarget & {
9
7
  readonly turnId: string;
10
- }
8
+ };
11
9
  /** Reads a Cancel click's target from a sign-in prompt's button value. */
12
10
  export declare function parseSignInCancelTarget(value: unknown): SignInCancelTarget | undefined;
13
11
  /**
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{slackContinuationToken}from"#public/channels/slack/api.js";const log=createLogger(`slack.interactions`),SIGN_IN_CANCEL_ACTION_ID=`eve_sign_in:cancel`;function parseSignInCancelTarget(e){if(typeof e!=`string`)return;let t;try{t=JSON.parse(e)}catch{return}if(typeof t!=`object`||!t)return;let{channelId:n,threadTs:r,turnId:i}=t;if(typeof n==`string`&&typeof r==`string`&&typeof i==`string`)return{channelId:n,threadTs:r,turnId:i}}function dispatchSignInCancel(e,t){let n=readSignInCancel(e);return n!==void 0&&(t.waitUntil(cancelHeldSignIn(n,t)),!0)}function readSignInCancel(e){let t=parseSignInCancelTarget((Array.isArray(e.actions)?e.actions:[]).find(e=>isObjectRecord(e)&&e.action_id===`eve_sign_in:cancel`)?.value);if(t!==void 0)return{responseUrl:typeof e.response_url==`string`?e.response_url:void 0,target:t}}async function cancelHeldSignIn(e,t){let{channelId:n,threadTs:r,turnId:i}=e.target;try{await t.from(slackContinuationToken({channelId:n,threadTs:r})).cancel({turnId:i})}catch(e){log.error(`sign-in cancel delivery failed`,{error:e});return}if(e.responseUrl!==void 0)try{await fetch(e.responseUrl,{body:JSON.stringify({delete_original:!0}),headers:{"content-type":`application/json`},method:`POST`})}catch(e){log.warn(`failed to remove the cancelled sign-in prompt`,{error:e})}}function isObjectRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}export{SIGN_IN_CANCEL_ACTION_ID,dispatchSignInCancel,parseSignInCancelTarget};
1
+ import{createLogger}from"#internal/logging.js";import{slackSessionToken}from"#public/channels/slack/api.js";const log=createLogger(`slack.interactions`),SIGN_IN_CANCEL_ACTION_ID=`eve_sign_in:cancel`;function parseSignInCancelTarget(e){if(typeof e!=`string`)return;let t;try{t=JSON.parse(e)}catch{return}if(typeof t!=`object`||!t)return;let{channelId:n,codeChannel:r,threadTs:i,turnId:a}=t;if(typeof n==`string`&&typeof a==`string`)return r===!0?{channelId:n,codeChannel:r,turnId:a}:typeof i==`string`?{channelId:n,threadTs:i,turnId:a}:void 0}function dispatchSignInCancel(e,t){let n=readSignInCancel(e);return n!==void 0&&(t.waitUntil(cancelHeldSignIn(n,t)),!0)}function readSignInCancel(e){let t=parseSignInCancelTarget((Array.isArray(e.actions)?e.actions:[]).find(e=>isObjectRecord(e)&&e.action_id===`eve_sign_in:cancel`)?.value);if(t!==void 0)return{responseUrl:typeof e.response_url==`string`?e.response_url:void 0,target:t}}async function cancelHeldSignIn(e,t){let{turnId:n}=e.target;try{await t.from(slackSessionToken(e.target)).cancel({turnId:n})}catch(e){log.error(`sign-in cancel delivery failed`,{error:e});return}if(e.responseUrl!==void 0)try{await fetch(e.responseUrl,{body:JSON.stringify({delete_original:!0}),headers:{"content-type":`application/json`},method:`POST`})}catch(e){log.warn(`failed to remove the cancelled sign-in prompt`,{error:e})}}function isObjectRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}export{SIGN_IN_CANCEL_ACTION_ID,dispatchSignInCancel,parseSignInCancelTarget};
@@ -1,11 +1,14 @@
1
1
  import type { ChannelEventContext, ChannelEventOf, CommittedLine } from "#public/definitions/channel.js";
2
2
  import type { SessionEvent } from "#protocol/session-event.js";
3
3
  import type { MessageContent } from "#shared/message-content.js";
4
+ import { type SessionSendOptions } from "#channel/session.js";
4
5
  import type { ChannelResolveSession, ChannelSource } from "#channel/channel-operations.js";
5
6
  import type { Delivery } from "#shared/delivery.js";
6
7
  import type { SessionAuthContext, TurnPolicy } from "#channel/types.js";
7
8
  import type { VercelConnectMetadata } from "#shared/vercel-connect-metadata.js";
8
9
  import type { CardElement } from "#public/channels/slack/cards.js";
10
+ import type { BlockKitBlock } from "#public/channels/slack/blocks.js";
11
+ import type { SlackHitlRoute } from "#public/channels/slack/hitl.js";
9
12
  import type { SessionContext } from "#public/definitions/callback-context.js";
10
13
  import type { SessionHistoryMessage } from "#shared/session-history.js";
11
14
  import type { ChannelContinuationOps } from "#public/definitions/channel.js";
@@ -83,28 +86,57 @@ export interface SlackAuthorizationEventContext {
83
86
  */
84
87
  readonly state: SlackChannelState;
85
88
  }
89
+ /** One request's controls in a posted card message. */
90
+ export interface SlackInteractionCardRequest {
91
+ readonly interactionId: string;
92
+ /** The controls blocks the message shows for this request now. */
93
+ readonly blocks: readonly BlockKitBlock[];
94
+ /** The separate details message, deleted once every request it covers settled. */
95
+ readonly detailsTs?: string;
96
+ /**
97
+ * Set once eve sent the request's settled rendering, even if Slack refused it, so a failed
98
+ * update is never retried forever. The message is forgotten once every request has it.
99
+ */
100
+ readonly settled?: true;
101
+ }
102
+ /** A message eve posted holding the controls of one or more input requests. */
103
+ export interface SlackInteractionCardRecord {
104
+ /** The message's conversation: the session's channel, or the reviewer's DM. */
105
+ readonly channelId: string;
106
+ readonly ts: string;
107
+ readonly destination: SlackApprovalChannel;
108
+ /** The session a card's controls answer back to: a direct message's thread, or a code channel. */
109
+ readonly route?: SlackHitlRoute;
110
+ /** Thread post announcing a direct-message request; updated with the outcome. */
111
+ readonly announcementTs?: string;
112
+ readonly requests: readonly SlackInteractionCardRequest[];
113
+ }
86
114
  /**
87
115
  * JSON-serializable per-session state, stored verbatim across workflow
88
116
  * step boundaries. Anything written here must round-trip through
89
117
  * `JSON.stringify` / `JSON.parse`.
90
118
  */
91
- export interface SlackPendingApprovalCard {
92
- /** Thread post announcing a direct-message approval; updated with the outcome. */
93
- readonly announcementTs?: string;
94
- /** Separate tool-input details post; deleted once none of its approvals are pending. */
95
- readonly detailsMessageTs?: string;
96
- /** Channel containing the approval card; differs from the session channel for DM delivery. */
97
- readonly messageChannelId?: string;
98
- readonly messageBlocks: readonly unknown[];
99
- readonly messageTs: string;
100
- }
101
119
  export interface SlackChannelState extends UndeliveredReplyState {
102
120
  /** Audience classification captured before the session is dispatched. */
103
121
  audience?: ChannelAudience;
104
122
  /** Slack channel id seeded by the inbound mention. */
105
123
  channelId: string | null;
106
- /** Slack thread root ts. */
124
+ /** Slack thread root ts; `null` in a code channel, which is one session across its threads. */
107
125
  threadTs: string | null;
126
+ /** Whether this session serves a Slack code channel. */
127
+ codeChannel?: boolean;
128
+ /**
129
+ * In a code channel, the thread of the message the current turn answers;
130
+ * `null` posts top-level.
131
+ * @internal
132
+ */
133
+ replyThreadTs?: string | null;
134
+ /**
135
+ * In a code channel, the thread of the latest delivered message, which the
136
+ * next turn replies in.
137
+ * @internal
138
+ */
139
+ pendingReplyThreadTs?: string | null;
108
140
  /** Slack team id, when the inbound event carried one. */
109
141
  teamId: string | null;
110
142
  /** Slack message ts that triggered the active turn. */
@@ -137,7 +169,13 @@ export interface SlackChannelState extends UndeliveredReplyState {
137
169
  * resolution outcome.
138
170
  */
139
171
  pendingAuthMessageTs?: Record<string, string>;
140
- pendingApprovalCards?: Record<string, SlackPendingApprovalCard>;
172
+ /**
173
+ * The messages holding open or recently answered approvals, questions, and budget prompts.
174
+ * Each is updated as its requests change and forgotten once all of them settle. eve ignores
175
+ * the `pendingApprovalCards` field that earlier versions wrote, so cards posted before an
176
+ * upgrade keep their buttons.
177
+ */
178
+ interactionCards?: readonly SlackInteractionCardRecord[];
141
179
  /**
142
180
  * Each recent turn's tool calls and the task card eve last wrote for it, by
143
181
  * turn id. The default `turn.paused` handler names the working tasks.
@@ -177,6 +215,8 @@ export interface SlackInstrumentationMetadata extends Record<string, unknown> {
177
215
  readonly teamId: string | null;
178
216
  readonly threadTs: string | null;
179
217
  readonly triggeringUserId: string | null;
218
+ /** `true` in a code channel session; absent elsewhere. */
219
+ readonly codeChannel?: true;
180
220
  }
181
221
  /**
182
222
  * Slack channel credentials: outbound bot token plus inbound webhook
@@ -224,6 +264,24 @@ export interface SlackReceiveTarget {
224
264
  * exclusive with {@link threadTs}.
225
265
  */
226
266
  readonly initialMessage?: SlackInitialMessage;
267
+ /**
268
+ * Opens a Slack code channel from a message in {@link channelId} and starts
269
+ * the session there, as the code channel's one session. Slack links the
270
+ * channel and the origin message to each other. One requester gets one code
271
+ * channel per origin message, so a repeated send joins that channel's
272
+ * session. Requires `codeChannels: true`. Mutually exclusive with
273
+ * {@link threadTs} and {@link initialMessage}.
274
+ */
275
+ readonly codeChannel?: SlackCodeChannelTarget;
276
+ }
277
+ /** The code channel `slackChannel().receive` opens for `target.codeChannel`. */
278
+ export interface SlackCodeChannelTarget {
279
+ /** Channel name shown in Slack. Omit it to let Slack name the channel from the message. */
280
+ readonly name?: string;
281
+ /** ts of the message the code channel continues. */
282
+ readonly originTs: string;
283
+ /** Thread of the origin message: its own ts when top-level. */
284
+ readonly originThreadTs: string;
227
285
  }
228
286
  /**
229
287
  * Pre-agent post issued by `slackChannel().receive` when the caller
@@ -237,14 +295,9 @@ export interface SlackInitialMessage {
237
295
  /**
238
296
  * Options for one turn requested by a generic Slack event handler.
239
297
  */
240
- export interface SlackEventSendOptions {
241
- readonly auth: SessionAuthContext | null;
242
- /** Prior conversation added as user and assistant turns before the message. */
243
- readonly history?: readonly SessionHistoryMessage[];
298
+ export type SlackEventSendOptions = SessionSendOptions & {
244
299
  readonly target: SlackReceiveTarget;
245
- /** Overrides the workflow run title without changing the message sent to the model. */
246
- readonly title?: string;
247
- }
300
+ };
248
301
  /** Options for answering pending input requests from a generic Slack event handler. */
249
302
  export interface SlackEventRespondOptions {
250
303
  readonly auth: SessionAuthContext | null;
@@ -258,12 +311,18 @@ export type SlackEventSendFn = (message: MessageContent, options: SlackEventSend
258
311
  /** Answers pending input requests on one Slack thread. */
259
312
  export type SlackEventRespondFn = <const TResponses extends readonly Answer[]>(answers: StrictAnswers<TResponses>, options: SlackEventRespondOptions) => ReturnType<SlackSessionOperations["respond"]>;
260
313
  /**
261
- * Slack thread identity used by workspace-scoped inbound helpers.
314
+ * Slack session identity used by workspace-scoped inbound helpers: a thread,
315
+ * or a code channel, whose one session spans all of its threads.
262
316
  */
263
- export interface SlackSessionTarget {
317
+ export type SlackSessionTarget = {
264
318
  readonly channelId: string;
265
319
  readonly threadTs: string;
266
- }
320
+ readonly codeChannel?: never;
321
+ } | {
322
+ readonly channelId: string;
323
+ readonly codeChannel: true;
324
+ readonly threadTs?: never;
325
+ };
267
326
  /**
268
327
  * Imperative surface handed to `slackChannel({ onEvent })`. Generic Events API
269
328
  * payloads are not necessarily tied to one thread, so the context exposes a
@@ -318,6 +377,25 @@ export interface SlackInboundMessageContext extends SlackContext, SlackSessionOp
318
377
  isSubscribed(): Promise<boolean>;
319
378
  /** Returns whether the inbound event explicitly mentions this bot. */
320
379
  isBotMentioned(): boolean;
380
+ /**
381
+ * Opens a Slack code channel from this message and returns session
382
+ * operations bound to the channel's one session. Slack links the channel
383
+ * in this thread. Send the first turn with the returned `send`, then return
384
+ * `null` to give the thread no turn. One author gets one code channel per
385
+ * message, so calling it again returns the same channel. Requires
386
+ * `codeChannels: true`, and throws for a message already in a code channel.
387
+ */
388
+ openCodeChannel(options?: SlackOpenCodeChannelOptions): Promise<SlackOpenedCodeChannel>;
389
+ }
390
+ /** Options for `ctx.openCodeChannel()`. */
391
+ export interface SlackOpenCodeChannelOptions {
392
+ /** Channel name shown in Slack. Omit to let Slack name it from the message. */
393
+ readonly name?: string;
394
+ }
395
+ /** A code channel opened by `ctx.openCodeChannel()`, bound to its one session. */
396
+ export interface SlackOpenedCodeChannel extends SlackSessionOperations {
397
+ /** Slack channel id of the new code channel. */
398
+ readonly channelId: string;
321
399
  }
322
400
  /** Interaction-scoped context handed to `slackChannel({ onInteraction })`. */
323
401
  export interface SlackInteractionContext extends SlackContext, SlackSessionOperations {
@@ -488,6 +566,15 @@ export interface SlackChannelConfig {
488
566
  readonly route?: string;
489
567
  /** Policy for accepted messages that arrive while a turn is active. */
490
568
  readonly turnPolicy?: TurnPolicy;
569
+ /**
570
+ * Serves each Slack code channel as one session and answers every message in
571
+ * it, not just mentions. Un-mentioned messages go to `onMessage`, else
572
+ * `onAppMention` (where `ctx.isBotMentioned()` returns `false`), else the
573
+ * default handler. Adds the code channel scopes, events, and feature flag to
574
+ * the app manifest. Without it, eve treats a code channel like any other
575
+ * channel and never looks one up.
576
+ */
577
+ readonly codeChannels?: boolean;
491
578
  /**
492
579
  * Inbound upload policy applied to file attachments before they reach
493
580
  * the harness. Violating attachments are dropped with a warning so the
@@ -1 +1 @@
1
- import{markEventHandled}from"./utils.js";import{attachInputText}from"#internal/input-text.js";import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{setChannelBuildMetadata}from"#channel/compiled-channel.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{maxBytesOf}from"#internal/attachments/limited-read.js";import{withUndeliveredReplyNote}from"#public/channels/undelivered-reply.js";import{buildSlackBinding,buildSlackWorkspaceHandle,fetchSlackThreadWindow,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{resolveSlackTransportOptions}from"#public/channels/slack/transport.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackEventReceivingBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{withFinalReplyDelivery}from"#public/channels/slack/reply-delivery.js";import{defaultEvents,defaultInteractionOpenedHandler,defaultOnMessage,defaultReceived}from"#public/channels/slack/defaults.js";import{composeSlackRenderers}from"#public/channels/slack/renderers.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{bindSlackSessionOperations,withSlackResponder}from"#public/channels/slack/session-operations.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{renderDefaultSlackTaskCard,withTaskCards}from"#public/channels/slack/task-card.js";import{formatSlackInboundMessage,formatSlackThreadContext,slackThreadSessionHistory}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{defineSlackAppManifest}from"#public/channels/slack/app-manifest.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest as verifySlackRequest$1}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n,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({channelId:e.channelId,threadTs:n}))},onPosted(){e.postedInThread=!0}});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,maxBytes:maxBytesOf(n)}),i=e.onAnswers??defaultOnAnswers,a=composeSlackRenderers(e.renderers??[],{events:{...defaultEvents,"interaction.opened":defaultInteractionOpenedHandler(e.approvalChannel)},received:defaultReceived,taskCard:renderDefaultSlackTaskCard}),o=a.events[`turn.started`],s=withTaskCards({...a.events,"content.completed":withFinalReplyDelivery(a.events[`content.completed`]),async"turn.started"(e,t){let{channel:n}=t,r=slackUserIdFromAuthContext(t.session.auth.current);r!==void 0&&(n.state.triggeringUserId=r),await o?.(e,t)}},a.taskCard),c=new Set,l=defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.uploadPolicy,state:{channelId:null,threadTs:null,teamId:null,installationTeamId:null,triggeringUserId:null,triggeringMessageTs:null,pendingToolCallMessage:null,pendingAuthMessageTs:{},pendingApprovalCards:{},slackUsersByPrincipal:{}},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){return recordSlackPrincipal(t.state,t.session.auth.current,e),withUndeliveredReplyNote(t.state,e,`Slack`)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(r,o)=>{let{waitUntil:s}=o,{from:l,resolveSession:u}=o,d=await verifyInbound(r,e.credentials);return d===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(r.headers)?new Response(`ok`):(r.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(d,{from:l,resolveSession:u,waitUntil:s},{api:t,config:e,onAnswers:i}):handleEventPost({api:t,body:d,from:l,resolveSession:u,waitUntil:s,config:e,received:a.received,uploadPolicy:n,handledEvents:c,headers:r.headers})})],receive(n,{from:r}){return receiveOnSlack(n,{from:r,api:t,credentials:e.credentials})},events:s}),u=e.credentials,d=defineSlackAppManifest({botName:e.botName});return setChannelBuildMetadata(l,e=>({externalCredentials:u?.vercelConnect,manifest:d.build(e)})),l}function defaultOnAnswers(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({channelId:r,threadTs:c})).send(e.message,{auth:e.auth,history:e.history,state:u,title:e.title})}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,dispatchMessage=(n,r,c)=>()=>dispatchSlackMessage({api:e.api,appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:c,installationTeamId:s,kind:n,message:r,received:e.received,resolvesTypedInput:t.onAnswers===void 0&&t.approvalChannel===void 0,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy});if(n.kind===`app_mention`||n.kind===`direct_message`){let e=n.kind,r=slackMessageFromWebhookPayload(n,s);if(r!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},r)){let n=(e===`app_mention`?t.onAppMention:t.onDirectMessage)??t.onMessage;n===void 0?c=dispatchMessage(e,r,defaultOnMessage):dispatch=dispatchMessage(e,r,n)}}let l=t.onMessage;if(dispatch===null&&l!==void 0){let e=parseMessageEvent(r);e!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},e)&&(a===void 0||!e.text.includes(`<@${a}`))&&(dispatch=dispatchMessage(`channel_message`,e,l))}let u=t.onEvent;if(dispatch===null&&u!==void 0&&(dispatch=()=>dispatchSlackEvent({api:e.api,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,envelope:r,handler:u,installationTeamId:s})),dispatch??=c,dispatch===null)return new Response(`ok`);let d=r.event_id;if(d){if(e.handledEvents.has(d))return log.warn(`received a duplicate event`,{event_id:d,event_time:r.event_time,retry_num:n.retry?.num??`(null)`,retry_reason:n.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(d,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({channelId:e.message.channelId,threadTs: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,threadHasReplies:e.message.threadTs!==e.message.ts,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,installationTeamId:e.installationTeamId,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=e.kind===`channel_message`?void 0:acknowledgeMessage(e.received,e.message,{slack:r,thread:n}),d;try{d=await e.handler(l,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId})}if(d==null){u!==void 0&&(await u,await n.startTyping());return}await(u??acknowledgeMessage(e.received,e.message,{slack:r,thread:n}));let f=await isDMOrPrivateChannel();a.audience=e.kind===`direct_message`||f?`private`:`public`,await deliverSlackMessage({botUserId:e.receivingBotUserId,credentials:e.credentials,kind:e.kind,isPrivateConversation:f,isMentioned:c,message:e.message,resolvesTypedInput:e.resolvesTypedInput,result:d,sessionOperations:o,thread:n,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function acknowledgeMessage(e,t,n){try{await e(t,n)}catch(e){logError(log,`received renderer failed`,e,{channelId:t.channelId})}}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({channelId:t.channelId,threadTs: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({channelId:t.channelId,threadTs:t.threadTs})),respond:(t,{auth:n,target:r})=>sourceFor(r).respond(t,{auth:n,state:withSlackResponder(void 0,typeof e.envelope.event.user==`string`?e.envelope.event.user:void 0)}),send:(t,{auth:r,history:i,target:a,title:o})=>receiveOnSlack({auth:r,history:i,message:t,target:a,title: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=r.length>0&&e.result.history===void 0&&await e.sessionOperations.resolveSession()===void 0,a=i?void 0:formatSlackThreadContext(r),o=e.result.history??(i?slackThreadSessionHistory(r):void 0),s=await collectInboundFileParts({botUserId:e.botUserId,fetchThreadWindow:(e,t)=>fetchSlackThreadWindow(n,e,t),isMentioned:e.kind===`app_mention`,mention:t,thread:n,policy:e.uploadPolicy}),c={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},l=formatSlackInboundMessage(c,t),u=buildSlackTurnMessage(a===void 0?l:`${a}\n\n${l}`,s),d=e.result.context??[],f=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,p=e.resolvesTypedInput?slackTypedText(t.text,e.botUserId):``,m=attachInputText({auth:e.result.auth,title:f,...d.length>0&&{context:d},...o!==void 0&&{history:o}},s.length===0?p:void 0);await e.sessionOperations.send(u,m)}catch(n){logError(log,`${e.kind} delivery failed`,n,{channelId:t.channelId})}}function slackTypedText(e,t){return t===void 0?e:e.replace(RegExp(`<@${t}(?:\\|[^>]*)?>`,`gu`),``).trim()}function recordSlackPrincipal(e,t,n){if(t===null)return;let r=n.state?.triggeringUserId,i=slackUserIdFromAuthContext(t)??(typeof r==`string`?r:void 0);i!==void 0&&(e.slackUsersByPrincipal={...e.slackUsersByPrincipal,[t.principalId]:i})}export{slackChannel};
1
+ import{markEventHandled}from"./utils.js";import{createHash}from"node:crypto";import{toSessionSendOptions}from"#channel/session.js";import{attachInputText}from"#internal/input-text.js";import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{SessionNotActiveError}from"#channel/session-not-active-error.js";import{setChannelBuildMetadata}from"#channel/compiled-channel.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{maxBytesOf}from"#internal/attachments/limited-read.js";import{withUndeliveredReplyNote}from"#public/channels/undelivered-reply.js";import{buildSlackBinding,buildSlackWorkspaceHandle,fetchSlackThreadWindow,slackCodeChannelContinuationToken,slackContinuationToken,slackSessionToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{createSlackCodeChannel,isSlackCodeChannel,isSlackCodeChannelOriginContext,setSlackCodeChannelStatus}from"#public/channels/slack/code-channel.js";import{resolveSlackTransportOptions}from"#public/channels/slack/transport.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackEventReceivingBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{withFinalReplyDelivery}from"#public/channels/slack/reply-delivery.js";import{defaultEvents,defaultInteractionEvents,defaultOnMessage,defaultReceived}from"#public/channels/slack/defaults.js";import{composeInteractionCards,composeSlackRenderers}from"#public/channels/slack/renderers.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{bindSlackSessionOperations,withSlackResponder}from"#public/channels/slack/session-operations.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{renderDefaultSlackTaskCard,withTaskCards}from"#public/channels/slack/task-card.js";import{formatSlackInboundMessage,formatSlackThreadContext,slackThreadSessionHistory}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{defineSlackAppManifest}from"#public/channels/slack/app-manifest.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest as verifySlackRequest$1}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n,r){let{thread:i,slack:a}=buildSlackBinding({api:n,botToken:r?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,codeChannel:e.codeChannel===!0,replyThreadTs:()=>e.replyThreadTs??void 0,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.continuation?.alias(slackContinuationToken({channelId:e.channelId,threadTs:n}))},onPosted(){e.postedInThread=!0}});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,maxBytes:maxBytesOf(n)}),i=e.onAnswers??defaultOnAnswers,a=composeInteractionCards(e.renderers??[]),o=composeSlackRenderers(e.renderers??[],{events:{...defaultEvents,...defaultInteractionEvents(e.approvalChannel,a)},received:defaultReceived,taskCard:renderDefaultSlackTaskCard}),s=o.events[`turn.started`],c=withTaskCards({...o.events,"content.completed":withFinalReplyDelivery(o.events[`content.completed`]),async"turn.started"(e,t){let{channel:n}=t,r=slackUserIdFromAuthContext(t.session.auth.current);r!==void 0&&(n.state.triggeringUserId=r),n.state.pendingReplyThreadTs!==void 0&&(n.state.replyThreadTs=n.state.pendingReplyThreadTs,n.state.pendingReplyThreadTs=void 0),await s?.(e,t)}},o.taskCard),l=new Set,u=defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.uploadPolicy,state:{channelId:null,threadTs:null,teamId:null,installationTeamId:null,triggeringUserId:null,triggeringMessageTs:null,pendingToolCallMessage:null,pendingAuthMessageTs:{},interactionCards:[],slackUsersByPrincipal:{}},fetchFile:r,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null,installationTeamId:e.installationTeamId??null,triggeringMessageTs:e.triggeringMessageTs??null,...e.codeChannel===!0&&{codeChannel:!0}}},audience:({state:e})=>e.audience??`unknown`,context(n,r){return rebuildSlackContext(n,r,t,e.credentials)},deliver(e,t){return recordSlackPrincipal(t.state,t.session.auth.current,e),followReplyThread(t.state,e),followTriggeringMessage(t.state,e),withUndeliveredReplyNote(t.state,e,`Slack`)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(r,a)=>{let{waitUntil:s}=a,{from:c,resolveSession:u}=a,d=await verifyInbound(r,e.credentials);return d===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(r.headers)?new Response(`ok`):(r.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(d,{from:c,resolveSession:u,waitUntil:s},{api:t,config:e,onAnswers:i}):handleEventPost({api:t,body:d,from:c,resolveSession:u,waitUntil:s,config:e,received:o.received,uploadPolicy:n,handledEvents:l,headers:r.headers})})],receive(n,{from:r}){return receiveOnSlack(n,{from:r,api:t,codeChannels:e.codeChannels===!0,credentials:e.credentials})},events:c}),d=e.credentials,f=defineSlackAppManifest({botName:e.botName,codeChannels:e.codeChannels===!0});return setChannelBuildMetadata(u,e=>({externalCredentials:d?.vercelConnect,manifest:f.build(e)})),u}function defaultOnAnswers(e){return{auth:e.defaultAuth}}async function receiveOnSlack(e,t){let{message:n,target:r,...i}=e,a=r,o=a.channelId;if(!o||typeof o!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let s=typeof a.threadTs==`string`?a.threadTs:``,c=typeof a.installationTeamId==`string`?a.installationTeamId:t.installationTeamId,l=a.initialMessage;if(l&&s.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");if(a.codeChannel!==void 0){if(!t.codeChannels)throw Error("slackChannel().receive: set `codeChannels: true` on slackChannel() to open code channels.");if(l||s.length>0)throw Error("slackChannel().receive: `codeChannel` can't be combined with `threadTs` or `initialMessage`.");return await receiveInNewCodeChannel(e,t,{codeChannel:a.codeChannel,installationTeamId:c,originChannelId:o})}let u=s;if(l){let{thread:e}=buildSlackBinding({api:t.api,botToken:t.credentials?.botToken,channelId:o,threadTs:``,installationTeamId:c,teamId:t.teamId}),n={card:l.card};l.fallbackText!==void 0&&(n.fallbackText=l.fallbackText),u=(await e.post(n)).id}let d=u||(i.idempotencyKey===void 0?crypto.randomUUID():threadlessIdentityFromKey(i.idempotencyKey)),f=a.audience===void 0?void 0:normalizeChannelAudience(a.audience),p={channelId:o,installationTeamId:c??null,threadTs:u||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null};return f!==void 0&&(p.audience=f),t.from(slackContinuationToken({channelId:o,threadTs:d})).send(n,{...i,state:p})}function threadlessIdentityFromKey(e){return`key-${createHash(`sha256`).update(e).digest(`hex`).slice(0,32)}`}async function receiveInNewCodeChannel(e,t,n){let{codeChannel:r,installationTeamId:i,originChannelId:a}=n,{request:o}=buildSlackWorkspaceHandle({api:t.api,botToken:t.credentials?.botToken,installationTeamId:i,teamId:t.teamId}),s=await openSlackCodeChannel({auth:e.auth,installationTeamId:i,name:r.name,originChannelId:a,originTs:r.originTs,request:o,teamId:t.teamId,triggeringUserId:t.triggeringUserId??slackUserIdFromAuthContext(e.auth)}),{message:c,target:l,...u}=e;return await t.from(s.address).send(c,{...u,auth:s.auth,state:s.state})}async function openSlackCodeChannel(e){let{channelId:t,isPrivate:n}=await createSlackCodeChannel({name:e.name??``,originChannelId:e.originChannelId,originTs:e.originTs,requester:e.auth?.principalId??`anonymous`,request:e.request});return{address:slackCodeChannelContinuationToken(t),auth:codeChannelAuth(e.auth,t),channelId:t,state:{audience:n?`private`:`public`,channelId:t,codeChannel:!0,installationTeamId:e.installationTeamId??null,replyThreadTs:null,teamId:e.teamId??null,threadTs:null,triggeringUserId:e.triggeringUserId??null}}}function codeChannelAuth(e,t){if(e?.authenticator!==`slack-webhook`)return e;let{thread_ts:n,...r}=e.attributes;return{...e,attributes:{...r,channel_id:t}}}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),c=r.event,l=c.channel;if(t.codeChannels===!0&&c.type===`agent_session_stopped`&&typeof l==`string`){if(isDuplicateEvent(r,n.retry,e.handledEvents))return new Response(`ok`);let i=buildSlackWorkspaceHandle({api:e.api,botToken:t.credentials?.botToken,installationTeamId:s,teamId:typeof r.team_id==`string`?r.team_id:void 0}).request,a=t.onEvent;return e.waitUntil((async()=>{if(c.thread_ts!==void 0&&!await isSlackCodeChannel(l,i)){if(a===void 0)return;await dispatchSlackEvent({api:e.api,from:e.from,resolveSession:e.resolveSession,codeChannels:!0,credentials:t.credentials,envelope:r,handler:a,installationTeamId:s});return}await cancelledARunningTurn(e.from,l)||await setSlackCodeChannelStatus(i,l,`active`)})().catch(e=>{logError(log,`agent_session_stopped handling failed`,e,{channelId:l})})),new Response(`ok`)}let u=null,d=null,dispatchMessage=(n,r,c,l)=>()=>dispatchSlackMessage({api:e.api,appId:i,botUserId:a,codeChannelOnly:l,detectCodeChannels:t.codeChannels===!0,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:c,installationTeamId:s,kind:n,message:r,received:e.received,resolvesTypedInput:t.onAnswers===void 0&&t.approvalChannel===void 0,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy});if(n.kind===`app_mention`||n.kind===`direct_message`){let e=n.kind,r=slackMessageFromWebhookPayload(n,s);if(r!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},r)){let n=(e===`app_mention`?t.onAppMention:t.onDirectMessage)??t.onMessage;n===void 0?d=dispatchMessage(e,r,defaultOnMessage):u=dispatchMessage(e,r,n)}}let f=t.onMessage;if(u===null&&f!==void 0){let e=parseMessageEvent(r);e!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},e)&&(a===void 0||!e.text.includes(`<@${a}`))&&(u=dispatchMessage(`channel_message`,e,f))}let p=t.onEvent,m=p===void 0?void 0:()=>dispatchSlackEvent({api:e.api,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,codeChannels:t.codeChannels===!0,envelope:r,handler:p,installationTeamId:s});if(u===null&&d===null&&t.codeChannels===!0){let e=parseMessageEvent(r);if(e!==null&&e.author?.isBot===!1&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},e)&&!isSlackCodeChannelOriginContext(e.raw)&&(a===void 0||!e.text.includes(`<@${a}`))){let n=t.onAppMention;n===void 0?d=dispatchMessage(`channel_message`,e,defaultOnMessage,{}):u=dispatchMessage(`channel_message`,e,n,{otherwise:m})}}return u??=m??d,u===null||isDuplicateEvent(r,n.retry,e.handledEvents)||e.waitUntil(u()),new Response(`ok`)}function isDuplicateEvent(e,t,n){let r=e.event_id;return r?n.has(r)?(log.warn(`received a duplicate event`,{event_id:r,event_time:e.event_time,retry_num:t?.num??`(null)`,retry_reason:t?.reason??`(null)`}),!0):(markEventHandled(r,n),!1):!1}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=e.message.author,n=buildSlackWorkspaceHandle({api:e.api,botToken:e.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:e.message.teamId}).request,r,conversationRequest=(e,t)=>e===`conversations.info`?r??=n(e,t):n(e,t),i=e.detectCodeChannels&&e.kind!==`direct_message`&&await isSlackCodeChannel(e.message.channelId,conversationRequest);if(!i&&e.codeChannelOnly!==void 0){await e.codeChannelOnly.otherwise?.();return}let a=i?``:e.message.threadTs,o=e.message.threadTs!==e.message.ts,s=i&&o?e.message.threadTs:null,c=i?slackCodeChannelContinuationToken(e.message.channelId):slackContinuationToken({channelId:e.message.channelId,threadTs:a}),{thread:l,slack:u}=buildSlackBinding({api:e.api,appId:e.appId,botToken:e.credentials?.botToken,botUserId:e.botUserId,channelId:e.message.channelId,threadTs:a,codeChannel:i,replyThreadTs:()=>s??void 0,threadHasReplies:o,installationTeamId:e.installationTeamId,teamId:e.message.teamId}),d={channelId:e.message.channelId,installationTeamId:e.installationTeamId??null,teamId:e.message.teamId??null,threadTs:a||null,triggeringMessageTs:e.message.ts,triggeringUserId:t?.userId??null,...i&&{codeChannel:!0,replyThreadTs:s}},f=t===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:t.fullName,installationTeamId:e.installationTeamId,isBot:t.isBot,teamId:e.message.teamId,threadTs:a,userId:t.userId,userName:t.userName}),p=bindSlackSessionOperations({address:c,defaultAuth:f,from:e.from,resolveSession:e.resolveSession,state:d}),m,isDMOrPrivateChannel=()=>m??=isPrivateSlackConversation({channelId:e.message.channelId,raw:e.message.raw,request:conversationRequest}),openCodeChannel=async(t={})=>{if(!e.detectCodeChannels)throw Error("ctx.openCodeChannel(): set `codeChannels: true` on slackChannel() to open code channels.");if(i)throw Error(`ctx.openCodeChannel(): this message is already in a code channel.`);return await openCodeChannelFromMessage({defaultAuth:f,from:e.from,installationTeamId:e.installationTeamId,message:e.message,name:t.name,request:n,resolveSession:e.resolveSession})},h=e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),g={...p,isBotMentioned:()=>h,isDMOrPrivateChannel,isSubscribed:async()=>await p.resolveSession()!==void 0,openCodeChannel,slack:u,thread:l},_=e.kind!==`channel_message`&&!i?acknowledgeMessage(e.received,e.message,{slack:u,thread:l}):void 0,v;try{v=await e.handler(g,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId})}if(v==null){_!==void 0&&(await _,await l.startTyping());return}await(_??acknowledgeMessage(e.received,e.message,{slack:u,thread:l}));let y=await isDMOrPrivateChannel();d.audience=e.kind===`direct_message`||y?`private`:`public`,await deliverSlackMessage({botUserId:e.receivingBotUserId,credentials:e.credentials,kind:e.kind,isPrivateConversation:y,isMentioned:h,message:e.message,resolvesTypedInput:e.resolvesTypedInput,result:v,sessionOperations:p,thread:l,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function openCodeChannelFromMessage(e){let t=await openSlackCodeChannel({auth:e.defaultAuth,installationTeamId:e.installationTeamId,name:e.name,originChannelId:e.message.channelId,originTs:e.message.ts,request:e.request,teamId:e.message.teamId,triggeringUserId:e.message.author?.userId}),n=bindSlackSessionOperations({address:t.address,defaultAuth:t.auth,from:e.from,resolveSession:e.resolveSession,state:t.state}),moved=e=>e?.auth?{...e,auth:codeChannelAuth(e.auth,t.channelId)}:e;return{...n,channelId:t.channelId,send:(e,t)=>n.send(e,moved(t)),respond:(e,t)=>n.respond(e,moved(t))}}async function acknowledgeMessage(e,t,n){try{await e(t,n)}catch(e){logError(log,`received renderer failed`,e,{channelId:t.channelId})}}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(slackSessionToken(t)),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(slackSessionToken(t)),respond:(t,{auth:n,target:r})=>sourceFor(r).respond(t,{auth:n,state:withSlackResponder(void 0,typeof e.envelope.event.user==`string`?e.envelope.event.user:void 0)}),send:async(t,r)=>await receiveOnSlack({...toSessionSendOptions(r),target:r.target,message:t},{from:e.from,api:e.api,codeChannels:e.codeChannels,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=r.length>0&&e.result.history===void 0&&await e.sessionOperations.resolveSession()===void 0,a=i?void 0:formatSlackThreadContext(r),o=e.result.history??(i?slackThreadSessionHistory(r):void 0),s=await collectInboundFileParts({botUserId:e.botUserId,fetchThreadWindow:(e,t)=>fetchSlackThreadWindow(n,e,t),isMentioned:e.kind===`app_mention`,mention:t,thread:n,policy:e.uploadPolicy}),c={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},l=formatSlackInboundMessage(c,t),u=buildSlackTurnMessage(a===void 0?l:`${a}\n\n${l}`,s),d=e.result.context??[],f=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,p=e.resolvesTypedInput?slackTypedText(t.text,e.botUserId):``,m=attachInputText({auth:e.result.auth,title:f,...d.length>0&&{context:d},...o!==void 0&&{history:o}},s.length===0?p:void 0);await e.sessionOperations.send(u,m)}catch(n){logError(log,`${e.kind} delivery failed`,n,{channelId:t.channelId})}}function slackTypedText(e,t){return t===void 0?e:e.replace(RegExp(`<@${t}(?:\\|[^>]*)?>`,`gu`),``).trim()}async function cancelledARunningTurn(e,t){try{let{outcome:n}=await(await e(slackCodeChannelContinuationToken(t)).cancel()).result({signal:AbortSignal.timeout(1e4)});return n!==`ignored`}catch(e){if(e instanceof SessionNotActiveError)return!1;throw e}}function followReplyThread(e,t){t.message!==void 0&&e.codeChannel===!0&&(e.pendingReplyThreadTs=t.state?.replyThreadTs??null)}function followTriggeringMessage(e,t){if(t.message===void 0)return;let n=t.state?.triggeringMessageTs;typeof n==`string`&&(e.triggeringMessageTs=n)}function recordSlackPrincipal(e,t,n){if(t===null)return;let r=n.state?.triggeringUserId,i=slackUserIdFromAuthContext(t)??(typeof r==`string`?r:void 0);i!==void 0&&(e.slackUsersByPrincipal={...e.slackUsersByPrincipal,[t.principalId]:i})}export{slackChannel};
@@ -1,5 +1,5 @@
1
1
  import { type TaskCard } from "#channel/task-card.js";
2
- import type { SlackTaskCard } from "#public/channels/slack/renderers.js";
2
+ import type { SlackCardMessage } from "#public/channels/slack/renderers.js";
3
3
  import type { SlackChannelInternalEvents } from "#public/channels/slack/slackChannel.js";
4
4
  /**
5
5
  * eve's default task card: a `task_card` block for one task, or a `plan` block
@@ -8,7 +8,7 @@ import type { SlackChannelInternalEvents } from "#public/channels/slack/slackCha
8
8
  * with `eve__task_cancel` shows as done, since it no longer needed the work; a
9
9
  * failed task, or one stopped any other way, shows as an error.
10
10
  */
11
- export declare function renderDefaultSlackTaskCard(snapshot: TaskCard): SlackTaskCard | null;
11
+ export declare function renderDefaultSlackTaskCard(snapshot: TaskCard): SlackCardMessage | null;
12
12
  /** The card message eve last wrote for a turn: the card that begins at `since`. */
13
13
  export interface SlackTaskCardState {
14
14
  readonly ts?: string;
@@ -21,4 +21,4 @@ export interface SlackTaskCardState {
21
21
  * the renderer chain's own `committed`, so the card never depends on which handlers a renderer
22
22
  * keeps, and writes each card `taskCard` returns if it changed.
23
23
  */
24
- export declare function withTaskCards(events: SlackChannelInternalEvents, taskCard: (card: TaskCard) => SlackTaskCard | null): SlackChannelInternalEvents;
24
+ export declare function withTaskCards(events: SlackChannelInternalEvents, taskCard: (card: TaskCard) => SlackCardMessage | null): SlackChannelInternalEvents;