@cuylabs/channel-slack 0.8.0 → 0.9.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.
package/README.md CHANGED
@@ -4,8 +4,9 @@ Agent-runtime-agnostic Slack channel primitives.
4
4
 
5
5
  This package owns reusable Slack mechanics: event parsing, message admission,
6
6
  history loading, formatting, setup inspection, auth helpers, entrypoint
7
- normalization, artifact publishing, and HTTP or Socket Mode transport helpers.
8
- It does not create or run an agent, and it does not depend on an agent SDK.
7
+ normalization, artifact publishing, interactive request rendering/storage,
8
+ response sink contracts, and HTTP or Socket Mode transport helpers. It does not
9
+ create or run an agent, and it does not depend on an agent SDK.
9
10
 
10
11
  Runtime-specific adapters should compose these primitives with their own turn
11
12
  types, event streams, tools, prompts, and deployment policy.
@@ -1,112 +1,5 @@
1
- type SlackArtifactPublishMethod = "message" | "file" | "canvas";
2
- type SlackArtifactBinaryData = string | Uint8Array | ArrayBuffer;
3
- interface SlackArtifactBase {
4
- kind: string;
5
- title: string;
6
- summary?: string;
7
- metadata?: Record<string, unknown>;
8
- }
9
- interface SlackTextArtifact extends SlackArtifactBase {
10
- kind: "text";
11
- text: string;
12
- filename?: string;
13
- mimeType?: string;
14
- publishAs?: "message" | "file";
15
- }
16
- interface SlackFileArtifact extends SlackArtifactBase {
17
- kind: "file";
18
- filename: string;
19
- data?: SlackArtifactBinaryData;
20
- filePath?: string;
21
- mimeType?: string;
22
- initialComment?: string;
23
- }
24
- interface SlackImageArtifact extends SlackArtifactBase {
25
- kind: "image";
26
- filename: string;
27
- altText: string;
28
- data?: SlackArtifactBinaryData;
29
- filePath?: string;
30
- initialComment?: string;
31
- }
32
- interface SlackLinkArtifact extends SlackArtifactBase {
33
- kind: "link";
34
- url: string;
35
- }
36
- interface SlackCanvasArtifact extends SlackArtifactBase {
37
- kind: "canvas";
38
- markdown: string;
39
- /**
40
- * When set, updates an existing canvas instead of creating one.
41
- */
42
- canvasId?: string;
43
- /**
44
- * Create a channel canvas when a channel is available.
45
- *
46
- * @default true
47
- */
48
- channelCanvas?: boolean;
49
- }
50
- type SlackArtifact = SlackTextArtifact | SlackFileArtifact | SlackImageArtifact | SlackLinkArtifact | SlackCanvasArtifact;
51
- interface SlackArtifactPostMessageResponse {
52
- ok?: boolean;
53
- channel?: string;
54
- ts?: string;
55
- [key: string]: unknown;
56
- }
57
- interface SlackArtifactFileUploadResponse {
58
- ok?: boolean;
59
- file?: {
60
- id?: string;
61
- [key: string]: unknown;
62
- };
63
- files?: Array<{
64
- id?: string;
65
- [key: string]: unknown;
66
- }>;
67
- [key: string]: unknown;
68
- }
69
- interface SlackArtifactCanvasResponse {
70
- ok?: boolean;
71
- canvas_id?: string;
72
- [key: string]: unknown;
73
- }
74
- interface SlackArtifactClient {
75
- chat?: {
76
- postMessage(args: Record<string, unknown>): Promise<SlackArtifactPostMessageResponse>;
77
- };
78
- files?: {
79
- uploadV2(args: Record<string, unknown>): Promise<SlackArtifactFileUploadResponse>;
80
- };
81
- canvases?: {
82
- create(args?: Record<string, unknown>): Promise<SlackArtifactCanvasResponse>;
83
- edit?(args: Record<string, unknown>): Promise<SlackArtifactCanvasResponse>;
84
- };
85
- conversations?: {
86
- canvases?: {
87
- create(args: Record<string, unknown>): Promise<SlackArtifactCanvasResponse>;
88
- };
89
- };
90
- }
91
- interface PublishSlackArtifactOptions {
92
- client: SlackArtifactClient;
93
- artifact: SlackArtifact;
94
- channelId?: string;
95
- threadTs?: string;
96
- token?: string;
97
- unfurlLinks?: boolean;
98
- unfurlMedia?: boolean;
99
- }
100
- interface SlackArtifactPublication {
101
- artifact: SlackArtifact;
102
- method: SlackArtifactPublishMethod;
103
- response: unknown;
104
- channelId?: string;
105
- threadTs?: string;
106
- messageTs?: string;
107
- fileId?: string;
108
- canvasId?: string;
109
- }
1
+ import { S as SlackArtifact, l as SlackLinkArtifact, P as PublishSlackArtifactOptions, g as SlackArtifactPublication, i as SlackCanvasArtifact, j as SlackFileArtifact, k as SlackImageArtifact, m as SlackTextArtifact } from '../types-C8nkPuD4.js';
2
+ export { a as SlackArtifactBase, b as SlackArtifactBinaryData, c as SlackArtifactCanvasResponse, d as SlackArtifactClient, e as SlackArtifactFileUploadResponse, f as SlackArtifactPostMessageResponse, h as SlackArtifactPublishMethod } from '../types-C8nkPuD4.js';
110
3
 
111
4
  interface SlackArtifactBlockKitMessage {
112
5
  text: string;
@@ -132,4 +25,4 @@ declare function publishSlackCanvasArtifact(options: PublishSlackArtifactOptions
132
25
  artifact: SlackCanvasArtifact;
133
26
  }): Promise<SlackArtifactPublication>;
134
27
 
135
- export { type PublishSlackArtifactOptions, type SlackArtifact, type SlackArtifactBase, type SlackArtifactBinaryData, type SlackArtifactBlockKitMessage, type SlackArtifactCanvasResponse, type SlackArtifactClient, type SlackArtifactFileUploadResponse, type SlackArtifactPostMessageResponse, type SlackArtifactPublication, type SlackArtifactPublishMethod, type SlackCanvasArtifact, type SlackFileArtifact, type SlackImageArtifact, type SlackLinkArtifact, type SlackTextArtifact, createSlackArtifactBlocks, createSlackLinkArtifactBlocks, publishSlackArtifact, publishSlackCanvasArtifact, publishSlackFileArtifact, publishSlackImageArtifact, publishSlackLinkArtifact, publishSlackTextArtifact };
28
+ export { PublishSlackArtifactOptions, SlackArtifact, type SlackArtifactBlockKitMessage, SlackArtifactPublication, SlackCanvasArtifact, SlackFileArtifact, SlackImageArtifact, SlackLinkArtifact, SlackTextArtifact, createSlackArtifactBlocks, createSlackLinkArtifactBlocks, publishSlackArtifact, publishSlackCanvasArtifact, publishSlackFileArtifact, publishSlackImageArtifact, publishSlackLinkArtifact, publishSlackTextArtifact };
package/dist/core.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { S as SlackActivityInfo, b as SlackUserIdentity, a as SlackChannelType } from './activity-ByrD9Ftr.js';
2
+ export { S as SlackApprovalRequest, a as SlackEventInteractiveRequestHandler, b as SlackHumanInputRequest, c as SlackInteractiveMessage, d as SlackInteractiveMessageRef, e as SlackInteractiveRequest, f as SlackInteractiveRequestBaseContext, g as SlackInteractiveRequestContext, h as SlackInteractiveRequestHandler, i as SlackInteractiveRequestKind, j as SlackInteractiveResponder } from './interactive-CbKYkkc_.js';
2
3
  export { RegisterSlackTurnCancelActionOptions, SLACK_TURN_CANCEL_ACTION_ID, SlackTurnCancelActionContext, SlackTurnCancelActionHandler, SlackTurnCancelButtonValue, SlackTurnCancelMessage, SlackTurnCancelMessageOptions, createSlackTurnCancelMessage, decodeSlackTurnCancelButtonValue, encodeSlackTurnCancelButtonValue, registerSlackTurnCancelAction, resolveSlackTurnCancelActionId } from './turn-controls/index.js';
3
4
  import '@slack/types';
4
5
  import '@slack/bolt';
@@ -178,45 +179,6 @@ interface SlackTurnPreparation {
178
179
  context?: Record<string, unknown>;
179
180
  }
180
181
 
181
- /**
182
- * Transport-neutral interactive request contracts.
183
- *
184
- * These types let callers delegate approval and human-input requests to a
185
- * Slack-specific renderer without importing Bolt or Web API types into
186
- * `shared/`.
187
- */
188
-
189
- type SlackInteractiveRequestKind = "approval" | "human-input";
190
- type SlackApprovalRequest = Record<string, unknown>;
191
- type SlackHumanInputRequest = Record<string, unknown>;
192
- type SlackInteractiveRequest = SlackApprovalRequest | SlackHumanInputRequest;
193
- interface SlackInteractiveMessageRef {
194
- channel: string;
195
- ts: string;
196
- }
197
- interface SlackInteractiveMessage {
198
- text: string;
199
- blocks?: unknown[];
200
- }
201
- interface SlackInteractiveResponder {
202
- postMessage(message: SlackInteractiveMessage): Promise<SlackInteractiveMessageRef>;
203
- updateMessage(ref: SlackInteractiveMessageRef, message: SlackInteractiveMessage): Promise<void>;
204
- }
205
- interface SlackInteractiveRequestBaseContext {
206
- kind: SlackInteractiveRequestKind;
207
- request: SlackInteractiveRequest;
208
- fallbackText: string;
209
- }
210
- interface SlackInteractiveRequestContext extends SlackInteractiveRequestBaseContext {
211
- slackActivity: SlackActivityInfo;
212
- user: SlackUserIdentity;
213
- sessionId: string;
214
- message: string;
215
- responder: SlackInteractiveResponder;
216
- }
217
- type SlackInteractiveRequestHandler = (context: SlackInteractiveRequestContext) => boolean | void | Promise<boolean | void>;
218
- type SlackEventInteractiveRequestHandler = (context: SlackInteractiveRequestBaseContext) => boolean | void | Promise<boolean | void>;
219
-
220
182
  /**
221
183
  * Raw Slack event payload parsers — produce `SlackActivityInfo` from the
222
184
  * shapes Bolt delivers for `message` and `app_mention` events.
@@ -425,4 +387,4 @@ interface SlackAmbientTurnContext extends SlackTurnRequestContext {
425
387
  declare function currentSlackTurnContext(): Readonly<SlackAmbientTurnContext> | undefined;
426
388
  declare function runWithSlackTurnContext<T>(value: SlackAmbientTurnContext, fn: () => T | Promise<T>): Promise<T>;
427
389
 
428
- export { type ExtractSlackMessageTextOptions, type RawSlackActionTokenPayload, type RawSlackAppMentionPayload, type RawSlackAssistantThreadPayload, type RawSlackMessagePayload, SlackActivityInfo, type SlackAmbientTurnContext, type SlackApprovalRequest, type SlackAssistantStatusUpdate, type SlackAssistantSuggestedPrompt, type SlackAssistantSuggestedPrompts, type SlackAssistantTaskDisplayMode, type SlackAssistantThreadContext, type SlackAssistantUtilities, type SlackAuthContext, SlackChannelType, type SlackChatStreamStartArgs, type SlackEventInteractiveRequestHandler, type SlackHumanInputRequest, type SlackInteractiveMessage, type SlackInteractiveMessageRef, type SlackInteractiveRequest, type SlackInteractiveRequestBaseContext, type SlackInteractiveRequestContext, type SlackInteractiveRequestHandler, type SlackInteractiveRequestKind, type SlackInteractiveResponder, type SlackMessageAuthorshipOptions, type SlackMessageFormatter, type SlackMessageFormattingOptions, type SlackMessageTextPayload, type SlackThreadStatusSetter, type SlackTurnPreparation, type SlackTurnRequestContext, SlackUserIdentity, currentSlackTurnContext, extractSlackActionToken, extractSlackAttachmentsText, extractSlackAuthContext, extractSlackBlocksText, extractSlackMessageText, extractSlackUserIdentity, formatSlackAttributedFollowUp, isProcessableMessage, markdownToSlackMrkdwn, parseSlackMentionActivity, parseSlackMessageActivity, resolveSlackChannelType, resolveSlackMessageFormatter, resolveThreadAwareSlackSessionId, runWithSlackTurnContext, stripLeadingMentions };
390
+ export { type ExtractSlackMessageTextOptions, type RawSlackActionTokenPayload, type RawSlackAppMentionPayload, type RawSlackAssistantThreadPayload, type RawSlackMessagePayload, SlackActivityInfo, type SlackAmbientTurnContext, type SlackAssistantStatusUpdate, type SlackAssistantSuggestedPrompt, type SlackAssistantSuggestedPrompts, type SlackAssistantTaskDisplayMode, type SlackAssistantThreadContext, type SlackAssistantUtilities, type SlackAuthContext, SlackChannelType, type SlackChatStreamStartArgs, type SlackMessageAuthorshipOptions, type SlackMessageFormatter, type SlackMessageFormattingOptions, type SlackMessageTextPayload, type SlackThreadStatusSetter, type SlackTurnPreparation, type SlackTurnRequestContext, SlackUserIdentity, currentSlackTurnContext, extractSlackActionToken, extractSlackAttachmentsText, extractSlackAuthContext, extractSlackBlocksText, extractSlackMessageText, extractSlackUserIdentity, formatSlackAttributedFollowUp, isProcessableMessage, markdownToSlackMrkdwn, parseSlackMentionActivity, parseSlackMessageActivity, resolveSlackChannelType, resolveSlackMessageFormatter, resolveThreadAwareSlackSessionId, runWithSlackTurnContext, stripLeadingMentions };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { S as SlackActivityInfo, a as SlackChannelType, b as SlackUserIdentity } from './activity-ByrD9Ftr.js';
2
- export { ExtractSlackMessageTextOptions, RawSlackActionTokenPayload, RawSlackAppMentionPayload, RawSlackAssistantThreadPayload, RawSlackMessagePayload, SlackAmbientTurnContext, SlackApprovalRequest, SlackAssistantStatusUpdate, SlackAssistantSuggestedPrompt, SlackAssistantSuggestedPrompts, SlackAssistantTaskDisplayMode, SlackAssistantThreadContext, SlackAssistantUtilities, SlackAuthContext, SlackChatStreamStartArgs, SlackEventInteractiveRequestHandler, SlackHumanInputRequest, SlackInteractiveMessage, SlackInteractiveMessageRef, SlackInteractiveRequest, SlackInteractiveRequestBaseContext, SlackInteractiveRequestContext, SlackInteractiveRequestHandler, SlackInteractiveRequestKind, SlackInteractiveResponder, SlackMessageAuthorshipOptions, SlackMessageFormatter, SlackMessageFormattingOptions, SlackMessageTextPayload, SlackThreadStatusSetter, SlackTurnPreparation, SlackTurnRequestContext, currentSlackTurnContext, extractSlackActionToken, extractSlackAttachmentsText, extractSlackAuthContext, extractSlackBlocksText, extractSlackMessageText, extractSlackUserIdentity, formatSlackAttributedFollowUp, isProcessableMessage, markdownToSlackMrkdwn, parseSlackMentionActivity, parseSlackMessageActivity, resolveSlackChannelType, resolveSlackMessageFormatter, resolveThreadAwareSlackSessionId, runWithSlackTurnContext, stripLeadingMentions } from './core.js';
2
+ export { ExtractSlackMessageTextOptions, RawSlackActionTokenPayload, RawSlackAppMentionPayload, RawSlackAssistantThreadPayload, RawSlackMessagePayload, SlackAmbientTurnContext, SlackAssistantStatusUpdate, SlackAssistantSuggestedPrompt, SlackAssistantSuggestedPrompts, SlackAssistantTaskDisplayMode, SlackAssistantThreadContext, SlackAssistantUtilities, SlackAuthContext, SlackChatStreamStartArgs, SlackMessageAuthorshipOptions, SlackMessageFormatter, SlackMessageFormattingOptions, SlackMessageTextPayload, SlackThreadStatusSetter, SlackTurnPreparation, SlackTurnRequestContext, currentSlackTurnContext, extractSlackActionToken, extractSlackAttachmentsText, extractSlackAuthContext, extractSlackBlocksText, extractSlackMessageText, extractSlackUserIdentity, formatSlackAttributedFollowUp, isProcessableMessage, markdownToSlackMrkdwn, parseSlackMentionActivity, parseSlackMessageActivity, resolveSlackChannelType, resolveSlackMessageFormatter, resolveThreadAwareSlackSessionId, runWithSlackTurnContext, stripLeadingMentions } from './core.js';
3
+ export { S as SlackApprovalRequest, a as SlackEventInteractiveRequestHandler, b as SlackHumanInputRequest, c as SlackInteractiveMessage, d as SlackInteractiveMessageRef, e as SlackInteractiveRequest, f as SlackInteractiveRequestBaseContext, g as SlackInteractiveRequestContext, h as SlackInteractiveRequestHandler, i as SlackInteractiveRequestKind, j as SlackInteractiveResponder } from './interactive-CbKYkkc_.js';
3
4
  export { RegisterSlackTurnCancelActionOptions, SLACK_TURN_CANCEL_ACTION_ID, SlackTurnCancelActionContext, SlackTurnCancelActionHandler, SlackTurnCancelButtonValue, SlackTurnCancelMessage, SlackTurnCancelMessageOptions, createSlackTurnCancelMessage, decodeSlackTurnCancelButtonValue, encodeSlackTurnCancelButtonValue, registerSlackTurnCancelAction, resolveSlackTurnCancelActionId } from './turn-controls/index.js';
4
5
  export { InMemorySlackThreadParticipationStateStoreOptions, PostgresSlackMessagePolicyPruneResult, PostgresSlackMessagePolicyStateStore, PostgresSlackMessagePolicyStateStoreOptions, PostgresSlackThreadParticipationStateStore, PostgresSlackThreadParticipationStateStoreOptions, SlackAsyncMessagePolicyConfig, SlackAsyncMessagePolicyResolver, SlackChannelMessagePolicy, SlackMentionedThreadState, SlackMessagePolicyAcceptReason, SlackMessagePolicyAcceptedDecision, SlackMessagePolicyConfig, SlackMessagePolicyDecision, SlackMessagePolicyPostgresClient, SlackMessagePolicyRejectReason, SlackMessagePolicyRejectedDecision, SlackMessagePolicyResolver, SlackMessagePolicyStateContext, SlackMessagePolicyStateStore, SlackQuietThreadRejectedDecision, SlackSyncMessagePolicyStateStore, SlackThreadAwareMessagePolicyConfig, SlackThreadAwareMessagePolicyDecision, SlackThreadAwareMessagePolicyResolver, SlackThreadParticipationEligibility, SlackThreadParticipationEligibilityOptions, SlackThreadParticipationMode, SlackThreadParticipationPostgresClient, SlackThreadParticipationPruneResult, SlackThreadParticipationReactivation, SlackThreadParticipationState, SlackThreadParticipationStateContext, SlackThreadParticipationStateStore, SlackThreadReplyPolicy, createAsyncSlackMessagePolicyResolver, createAsyncSlackThreadAwareMessagePolicyResolver, createInMemorySlackMessagePolicyStateStore, createInMemorySlackThreadParticipationStateStore, createPostgresSlackMessagePolicyStateStore, createPostgresSlackThreadParticipationStateStore, createSlackMessagePolicyMessageKey, createSlackMessagePolicyResolver, createSlackMessagePolicyThreadKey, initializePostgresSlackMessagePolicyState, initializePostgresSlackThreadParticipationState, prunePostgresSlackMessagePolicyState, prunePostgresSlackThreadParticipationState, resolveSlackThreadParticipationEligibility, shouldRegisterSlackPassiveChannelMessages } from './policy/index.js';
5
6
  export { L as Logger } from './logging-Bl3HfcC8.js';
@@ -0,0 +1,154 @@
1
+ import { g as SlackInteractiveRequestContext } from '../interactive-CbKYkkc_.js';
2
+ export { c as SlackInteractiveMessage, d as SlackInteractiveMessageRef, f as SlackInteractiveRequestBaseContext, i as SlackInteractiveRequestKind, j as SlackInteractiveResponder } from '../interactive-CbKYkkc_.js';
3
+ import { View } from '@slack/types';
4
+ import '../activity-ByrD9Ftr.js';
5
+
6
+ type SlackInteractiveRequestStatus = "pending" | "resolved";
7
+ type SlackInteractiveApprovalAction = "allow" | "deny" | "remember";
8
+ type SlackInteractiveHumanInputResponse = {
9
+ kind: "text";
10
+ text: string;
11
+ } | {
12
+ kind: "confirm";
13
+ confirmed: boolean;
14
+ text: string;
15
+ } | {
16
+ kind: "choice";
17
+ selected: string[];
18
+ text: string;
19
+ };
20
+ interface SlackInteractiveApprovalRequest {
21
+ id: string;
22
+ tool: string;
23
+ args: unknown;
24
+ description: string;
25
+ risk: string;
26
+ rememberScopes?: string[];
27
+ defaultRememberScope?: string;
28
+ }
29
+ interface SlackInteractiveHumanInputOption {
30
+ label: string;
31
+ value?: string;
32
+ description?: string;
33
+ }
34
+ interface SlackInteractiveHumanInputRequest {
35
+ id: string;
36
+ kind: "text" | "confirm" | "choice";
37
+ title: string;
38
+ question: string;
39
+ options?: SlackInteractiveHumanInputOption[];
40
+ allowMultiple?: boolean;
41
+ placeholder?: string;
42
+ confirmLabel?: string;
43
+ denyLabel?: string;
44
+ }
45
+ type SlackInteractiveStoredRequest = SlackInteractiveApprovalRequest | SlackInteractiveHumanInputRequest;
46
+ type SlackInteractiveResolution = {
47
+ kind: "approval";
48
+ action: SlackInteractiveApprovalAction;
49
+ feedback?: string;
50
+ rememberScope?: string;
51
+ } | {
52
+ kind: "human-input";
53
+ response: SlackInteractiveHumanInputResponse;
54
+ };
55
+ interface SlackInteractiveMessageTarget {
56
+ channel: string;
57
+ ts: string;
58
+ threadTs?: string;
59
+ userId: string;
60
+ teamId?: string;
61
+ }
62
+ interface SlackInteractiveRequestRecord {
63
+ id: string;
64
+ kind: SlackInteractiveResolution["kind"];
65
+ request: SlackInteractiveStoredRequest;
66
+ status: SlackInteractiveRequestStatus;
67
+ createdAt: string;
68
+ updatedAt: string;
69
+ target?: SlackInteractiveMessageTarget;
70
+ resolution?: SlackInteractiveResolution;
71
+ }
72
+ interface SlackInteractiveRequestStore {
73
+ get(requestId: string): Promise<SlackInteractiveRequestRecord | undefined>;
74
+ upsert(record: SlackInteractiveRequestRecord): Promise<SlackInteractiveRequestRecord>;
75
+ attachTarget(requestId: string, target: SlackInteractiveMessageTarget): Promise<SlackInteractiveRequestRecord | undefined>;
76
+ resolve(requestId: string, resolution: SlackInteractiveResolution): Promise<SlackInteractiveRequestRecord | undefined>;
77
+ delete(requestId: string): Promise<void>;
78
+ }
79
+ interface SlackInteractiveActionIds {
80
+ approvalAllow: string;
81
+ approvalDeny: string;
82
+ approvalRemember: string;
83
+ humanConfirm: string;
84
+ humanDeny: string;
85
+ humanOpen: string;
86
+ humanSubmit: string;
87
+ }
88
+ interface SlackInteractiveActor {
89
+ userId: string;
90
+ teamId?: string;
91
+ }
92
+ type SlackInteractiveRequestHandler = (context: SlackInteractiveRequestContext) => boolean | void | Promise<boolean | void>;
93
+
94
+ declare function buildApprovalRequestMessage(request: SlackInteractiveApprovalRequest, actionIds: SlackInteractiveActionIds): {
95
+ text: string;
96
+ blocks: unknown[];
97
+ };
98
+ declare function buildHumanInputRequestMessage(request: SlackInteractiveHumanInputRequest, actionIds: SlackInteractiveActionIds): {
99
+ text: string;
100
+ blocks: unknown[];
101
+ };
102
+ declare function buildResolvedMessage(label: string, resolution: SlackInteractiveResolution): {
103
+ text: string;
104
+ blocks: unknown[];
105
+ };
106
+ declare function buildHumanInputModal(request: SlackInteractiveHumanInputRequest, actionIds: SlackInteractiveActionIds): View;
107
+ declare function encodeActionValue(payload: Record<string, unknown>): string;
108
+ declare function decodeActionValue(value: unknown): Record<string, unknown>;
109
+
110
+ declare function createInMemorySlackInteractiveRequestStore(): SlackInteractiveRequestStore;
111
+ declare function nowIso(): string;
112
+ declare function cloneRecord(record: SlackInteractiveRequestRecord): SlackInteractiveRequestRecord;
113
+
114
+ interface SlackInteractivePostgresClient {
115
+ end?: () => Promise<void>;
116
+ query<T = unknown>(sql: string, values?: readonly unknown[]): Promise<{
117
+ rows: T[];
118
+ rowCount?: number | null;
119
+ }>;
120
+ }
121
+ interface PostgresSlackInteractiveRequestStoreOptions {
122
+ client?: SlackInteractivePostgresClient;
123
+ connectionString?: string;
124
+ ensureSchema?: boolean;
125
+ onPruneError?: (error: unknown) => void;
126
+ pruneBatchSize?: number;
127
+ pruneIntervalMs?: number;
128
+ retentionMs?: number;
129
+ schema?: string;
130
+ tableName?: string;
131
+ }
132
+ interface PostgresSlackInteractiveRequestStore extends SlackInteractiveRequestStore {
133
+ close(): Promise<void>;
134
+ prune(): Promise<PostgresSlackInteractiveRequestPruneResult>;
135
+ }
136
+ interface PostgresSlackInteractiveRequestPruneResult {
137
+ deleted: number;
138
+ }
139
+ declare function createPostgresSlackInteractiveRequestStore({ client, connectionString, ensureSchema, onPruneError, pruneBatchSize, pruneIntervalMs, retentionMs, schema, tableName, }: PostgresSlackInteractiveRequestStoreOptions): PostgresSlackInteractiveRequestStore;
140
+ declare function initializePostgresSlackInteractiveRequestStore({ client, ensureSchema, schema, tableName, }: {
141
+ client: SlackInteractivePostgresClient;
142
+ ensureSchema?: boolean;
143
+ schema?: string;
144
+ tableName?: string;
145
+ }): Promise<void>;
146
+ declare function prunePostgresSlackInteractiveRequestStore({ client, pruneBatchSize, retentionMs, schema, tableName, }: {
147
+ client: SlackInteractivePostgresClient;
148
+ pruneBatchSize?: number;
149
+ retentionMs?: number;
150
+ schema?: string;
151
+ tableName?: string;
152
+ }): Promise<PostgresSlackInteractiveRequestPruneResult>;
153
+
154
+ export { type PostgresSlackInteractiveRequestPruneResult, type PostgresSlackInteractiveRequestStore, type PostgresSlackInteractiveRequestStoreOptions, type SlackInteractiveActionIds, type SlackInteractiveActor, type SlackInteractiveApprovalAction, type SlackInteractiveApprovalRequest, type SlackInteractiveHumanInputOption, type SlackInteractiveHumanInputRequest, type SlackInteractiveHumanInputResponse, type SlackInteractiveMessageTarget, type SlackInteractivePostgresClient, SlackInteractiveRequestContext, type SlackInteractiveRequestHandler, type SlackInteractiveRequestRecord, type SlackInteractiveRequestStatus, type SlackInteractiveRequestStore, type SlackInteractiveResolution, type SlackInteractiveStoredRequest, buildApprovalRequestMessage, buildHumanInputModal, buildHumanInputRequestMessage, buildResolvedMessage, cloneRecord, createInMemorySlackInteractiveRequestStore, createPostgresSlackInteractiveRequestStore, decodeActionValue, encodeActionValue, initializePostgresSlackInteractiveRequestStore, nowIso, prunePostgresSlackInteractiveRequestStore };
@@ -0,0 +1,650 @@
1
+ // src/interactive/blocks.ts
2
+ var MAX_BLOCK_TEXT = 2800;
3
+ function buildApprovalRequestMessage(request, actionIds) {
4
+ const rememberScopes = request.rememberScopes ?? [];
5
+ const canRemember = rememberScopes.length > 0 || Boolean(request.defaultRememberScope);
6
+ const text = `Approval required for ${request.tool}`;
7
+ return {
8
+ text,
9
+ blocks: [
10
+ section(
11
+ `*Approval required*
12
+ ${escapeMrkdwn(request.description || request.tool)}`
13
+ ),
14
+ fields([
15
+ `*Tool*
16
+ ${escapeMrkdwn(request.tool)}`,
17
+ `*Risk*
18
+ ${escapeMrkdwn(request.risk)}`
19
+ ]),
20
+ section(`*Arguments*
21
+ \`\`\`${truncate(formatArgs(request.args))}\`\`\``),
22
+ {
23
+ type: "actions",
24
+ elements: [
25
+ button("Allow", "primary", actionIds.approvalAllow, {
26
+ requestId: request.id
27
+ }),
28
+ button("Deny", "danger", actionIds.approvalDeny, {
29
+ requestId: request.id
30
+ }),
31
+ ...canRemember ? [
32
+ button("Remember", void 0, actionIds.approvalRemember, {
33
+ requestId: request.id,
34
+ rememberScope: request.defaultRememberScope ?? rememberScopes[0]
35
+ })
36
+ ] : []
37
+ ]
38
+ }
39
+ ]
40
+ };
41
+ }
42
+ function buildHumanInputRequestMessage(request, actionIds) {
43
+ const text = request.title || "Input required";
44
+ const confirmLabel = request.confirmLabel ?? "Submit";
45
+ const denyLabel = request.denyLabel ?? "Cancel";
46
+ if (request.kind === "confirm") {
47
+ return {
48
+ text,
49
+ blocks: [
50
+ section(`*${escapeMrkdwn(text)}*
51
+ ${escapeMrkdwn(request.question)}`),
52
+ {
53
+ type: "actions",
54
+ elements: [
55
+ button(confirmLabel, "primary", actionIds.humanConfirm, {
56
+ requestId: request.id
57
+ }),
58
+ button(denyLabel, "danger", actionIds.humanDeny, {
59
+ requestId: request.id
60
+ })
61
+ ]
62
+ }
63
+ ]
64
+ };
65
+ }
66
+ return {
67
+ text,
68
+ blocks: [
69
+ section(`*${escapeMrkdwn(text)}*
70
+ ${escapeMrkdwn(request.question)}`),
71
+ {
72
+ type: "actions",
73
+ elements: [
74
+ button(confirmLabel, "primary", actionIds.humanOpen, {
75
+ requestId: request.id
76
+ }),
77
+ button(denyLabel, "danger", actionIds.humanDeny, {
78
+ requestId: request.id
79
+ })
80
+ ]
81
+ }
82
+ ]
83
+ };
84
+ }
85
+ function buildResolvedMessage(label, resolution) {
86
+ const text = resolution.kind === "approval" ? resolution.action === "deny" ? "Approval denied" : "Approval granted" : "Input submitted";
87
+ return {
88
+ text,
89
+ blocks: [section(`*${escapeMrkdwn(text)}*
90
+ ${escapeMrkdwn(label)}`)]
91
+ };
92
+ }
93
+ function buildHumanInputModal(request, actionIds) {
94
+ const title = truncatePlain(request.title || "Input required", 24);
95
+ const submit = truncatePlain(request.confirmLabel ?? "Submit", 24);
96
+ const close = truncatePlain(request.denyLabel ?? "Cancel", 24);
97
+ if (request.kind === "choice") {
98
+ const options = (request.options ?? []).slice(0, 100).map((option) => ({
99
+ text: {
100
+ type: "plain_text",
101
+ text: truncatePlain(option.label, 75)
102
+ },
103
+ value: option.value ?? option.label,
104
+ ...option.description ? {
105
+ description: {
106
+ type: "plain_text",
107
+ text: truncatePlain(option.description, 75)
108
+ }
109
+ } : {}
110
+ }));
111
+ return {
112
+ type: "modal",
113
+ callback_id: actionIds.humanSubmit,
114
+ private_metadata: JSON.stringify({ requestId: request.id }),
115
+ title: { type: "plain_text", text: title },
116
+ submit: { type: "plain_text", text: submit },
117
+ close: { type: "plain_text", text: close },
118
+ blocks: [
119
+ {
120
+ type: "input",
121
+ block_id: "input",
122
+ label: {
123
+ type: "plain_text",
124
+ text: truncatePlain(request.question, 200)
125
+ },
126
+ element: {
127
+ type: request.allowMultiple ? "checkboxes" : "radio_buttons",
128
+ action_id: "value",
129
+ options
130
+ }
131
+ }
132
+ ]
133
+ };
134
+ }
135
+ return {
136
+ type: "modal",
137
+ callback_id: actionIds.humanSubmit,
138
+ private_metadata: JSON.stringify({ requestId: request.id }),
139
+ title: { type: "plain_text", text: title },
140
+ submit: { type: "plain_text", text: submit },
141
+ close: { type: "plain_text", text: close },
142
+ blocks: [
143
+ {
144
+ type: "input",
145
+ block_id: "input",
146
+ label: {
147
+ type: "plain_text",
148
+ text: truncatePlain(request.question, 200)
149
+ },
150
+ element: {
151
+ type: "plain_text_input",
152
+ action_id: "value",
153
+ multiline: true,
154
+ ...request.placeholder ? {
155
+ placeholder: {
156
+ type: "plain_text",
157
+ text: truncatePlain(request.placeholder, 150)
158
+ }
159
+ } : {}
160
+ }
161
+ }
162
+ ]
163
+ };
164
+ }
165
+ function encodeActionValue(payload) {
166
+ return JSON.stringify(payload);
167
+ }
168
+ function decodeActionValue(value) {
169
+ if (typeof value !== "string") return {};
170
+ try {
171
+ const parsed = JSON.parse(value);
172
+ return parsed && typeof parsed === "object" ? parsed : {};
173
+ } catch {
174
+ return {};
175
+ }
176
+ }
177
+ function button(text, style, actionId, value) {
178
+ return {
179
+ type: "button",
180
+ text: { type: "plain_text", text },
181
+ action_id: actionId,
182
+ value: encodeActionValue(value),
183
+ ...style ? { style } : {}
184
+ };
185
+ }
186
+ function section(text) {
187
+ return { type: "section", text: { type: "mrkdwn", text: truncate(text) } };
188
+ }
189
+ function fields(items) {
190
+ return {
191
+ type: "section",
192
+ fields: items.map((text) => ({ type: "mrkdwn", text: truncate(text) }))
193
+ };
194
+ }
195
+ function formatArgs(args) {
196
+ if (typeof args === "string") return args;
197
+ try {
198
+ return JSON.stringify(args, null, 2);
199
+ } catch {
200
+ return String(args);
201
+ }
202
+ }
203
+ function truncate(value, max = MAX_BLOCK_TEXT) {
204
+ return value.length <= max ? value : `${value.slice(0, max - 3)}...`;
205
+ }
206
+ function truncatePlain(value, max) {
207
+ const normalized = value.trim() || "Input";
208
+ return normalized.length <= max ? normalized : `${normalized.slice(0, max - 3)}...`;
209
+ }
210
+ function escapeMrkdwn(value) {
211
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
212
+ }
213
+
214
+ // src/interactive/store.ts
215
+ function createInMemorySlackInteractiveRequestStore() {
216
+ const records = /* @__PURE__ */ new Map();
217
+ return {
218
+ async get(requestId) {
219
+ const record = records.get(requestId);
220
+ return record ? cloneRecord(record) : void 0;
221
+ },
222
+ async upsert(record) {
223
+ const existing = records.get(record.id);
224
+ if (existing?.status === "resolved") {
225
+ return cloneRecord(existing);
226
+ }
227
+ const next = existing ? {
228
+ ...existing,
229
+ ...record,
230
+ target: record.target ?? existing.target,
231
+ resolution: record.resolution ?? existing.resolution,
232
+ updatedAt: nowIso()
233
+ } : { ...record };
234
+ records.set(record.id, cloneRecord(next));
235
+ return cloneRecord(next);
236
+ },
237
+ async attachTarget(requestId, target) {
238
+ const existing = records.get(requestId);
239
+ if (!existing) return void 0;
240
+ const next = {
241
+ ...existing,
242
+ target: cloneTarget(target),
243
+ updatedAt: nowIso()
244
+ };
245
+ records.set(requestId, cloneRecord(next));
246
+ return cloneRecord(next);
247
+ },
248
+ async resolve(requestId, resolution) {
249
+ const existing = records.get(requestId);
250
+ if (!existing) return void 0;
251
+ if (existing.status === "resolved") {
252
+ return cloneRecord(existing);
253
+ }
254
+ const next = {
255
+ ...existing,
256
+ status: "resolved",
257
+ resolution: cloneResolution(resolution),
258
+ updatedAt: nowIso()
259
+ };
260
+ records.set(requestId, cloneRecord(next));
261
+ return cloneRecord(next);
262
+ },
263
+ async delete(requestId) {
264
+ records.delete(requestId);
265
+ }
266
+ };
267
+ }
268
+ function nowIso() {
269
+ return (/* @__PURE__ */ new Date()).toISOString();
270
+ }
271
+ function cloneRecord(record) {
272
+ return {
273
+ ...record,
274
+ request: structuredClone(record.request),
275
+ ...record.target ? { target: cloneTarget(record.target) } : {},
276
+ ...record.resolution ? { resolution: cloneResolution(record.resolution) } : {}
277
+ };
278
+ }
279
+ function cloneTarget(target) {
280
+ return { ...target };
281
+ }
282
+ function cloneResolution(resolution) {
283
+ return structuredClone(resolution);
284
+ }
285
+
286
+ // src/interactive/postgres-store.ts
287
+ var DEFAULT_TABLE = "channel_slack_interactive_requests";
288
+ var DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
289
+ var DEFAULT_PRUNE_BATCH_SIZE = 1e3;
290
+ var DEFAULT_PRUNE_INTERVAL_MS = 6 * 60 * 60 * 1e3;
291
+ function createPostgresSlackInteractiveRequestStore({
292
+ client,
293
+ connectionString,
294
+ ensureSchema = true,
295
+ onPruneError,
296
+ pruneBatchSize = DEFAULT_PRUNE_BATCH_SIZE,
297
+ pruneIntervalMs = DEFAULT_PRUNE_INTERVAL_MS,
298
+ retentionMs = DEFAULT_RETENTION_MS,
299
+ schema,
300
+ tableName = DEFAULT_TABLE
301
+ }) {
302
+ let activeClient = client;
303
+ let ownsClient = false;
304
+ let initialized;
305
+ let pruneTimer;
306
+ async function getClient() {
307
+ if (activeClient) {
308
+ return activeClient;
309
+ }
310
+ if (!connectionString) {
311
+ throw new Error(
312
+ "connectionString is required when a Postgres Slack interactive request client is not provided"
313
+ );
314
+ }
315
+ const Pool = await importPostgresPoolConstructor();
316
+ activeClient = new Pool({ connectionString });
317
+ ownsClient = true;
318
+ return activeClient;
319
+ }
320
+ async function ensureInitialized() {
321
+ const currentClient = await getClient();
322
+ initialized ??= initializePostgresSlackInteractiveRequestStore({
323
+ client: currentClient,
324
+ ensureSchema,
325
+ schema,
326
+ tableName
327
+ });
328
+ await initialized;
329
+ startPruneTimer();
330
+ return currentClient;
331
+ }
332
+ function startPruneTimer() {
333
+ if (pruneTimer || pruneIntervalMs <= 0) {
334
+ return;
335
+ }
336
+ pruneTimer = setInterval(() => {
337
+ void prune().catch((error) => {
338
+ onPruneError?.(error);
339
+ });
340
+ }, pruneIntervalMs);
341
+ pruneTimer.unref?.();
342
+ }
343
+ async function prune() {
344
+ const currentClient = await ensureInitialized();
345
+ return prunePostgresSlackInteractiveRequestStore({
346
+ client: currentClient,
347
+ pruneBatchSize,
348
+ retentionMs,
349
+ schema,
350
+ tableName
351
+ });
352
+ }
353
+ return {
354
+ async get(requestId) {
355
+ const currentClient = await ensureInitialized();
356
+ const result = await currentClient.query(
357
+ `SELECT * FROM ${qualifiedTableName({
358
+ schema,
359
+ tableName
360
+ })} WHERE id = $1::text LIMIT 1`,
361
+ [requestId]
362
+ );
363
+ return rowToRecord(result.rows[0]);
364
+ },
365
+ async upsert(record) {
366
+ const currentClient = await ensureInitialized();
367
+ const result = await currentClient.query(
368
+ `INSERT INTO ${qualifiedTableName({
369
+ schema,
370
+ tableName
371
+ })} (
372
+ id,
373
+ kind,
374
+ request,
375
+ status,
376
+ created_at,
377
+ updated_at,
378
+ target,
379
+ resolution
380
+ )
381
+ VALUES (
382
+ $1::text,
383
+ $2::text,
384
+ $3::jsonb,
385
+ $4::text,
386
+ $5::timestamptz,
387
+ $6::timestamptz,
388
+ $7::jsonb,
389
+ $8::jsonb
390
+ )
391
+ ON CONFLICT (id) DO UPDATE SET
392
+ kind = CASE
393
+ WHEN ${qualifiedTableName({ schema, tableName })}.status = 'resolved'
394
+ THEN ${qualifiedTableName({ schema, tableName })}.kind
395
+ ELSE EXCLUDED.kind
396
+ END,
397
+ request = CASE
398
+ WHEN ${qualifiedTableName({ schema, tableName })}.status = 'resolved'
399
+ THEN ${qualifiedTableName({ schema, tableName })}.request
400
+ ELSE EXCLUDED.request
401
+ END,
402
+ status = CASE
403
+ WHEN ${qualifiedTableName({ schema, tableName })}.status = 'resolved'
404
+ THEN ${qualifiedTableName({ schema, tableName })}.status
405
+ ELSE EXCLUDED.status
406
+ END,
407
+ target = CASE
408
+ WHEN ${qualifiedTableName({ schema, tableName })}.status = 'resolved'
409
+ THEN ${qualifiedTableName({ schema, tableName })}.target
410
+ ELSE COALESCE(EXCLUDED.target, ${qualifiedTableName({
411
+ schema,
412
+ tableName
413
+ })}.target)
414
+ END,
415
+ resolution = CASE
416
+ WHEN ${qualifiedTableName({ schema, tableName })}.status = 'resolved'
417
+ THEN ${qualifiedTableName({ schema, tableName })}.resolution
418
+ ELSE COALESCE(EXCLUDED.resolution, ${qualifiedTableName({
419
+ schema,
420
+ tableName
421
+ })}.resolution)
422
+ END,
423
+ updated_at = CASE
424
+ WHEN ${qualifiedTableName({ schema, tableName })}.status = 'resolved'
425
+ THEN ${qualifiedTableName({ schema, tableName })}.updated_at
426
+ ELSE now()
427
+ END
428
+ RETURNING *`,
429
+ [
430
+ record.id,
431
+ record.kind,
432
+ JSON.stringify(record.request),
433
+ record.status,
434
+ record.createdAt,
435
+ record.updatedAt,
436
+ record.target ? JSON.stringify(record.target) : null,
437
+ record.resolution ? JSON.stringify(record.resolution) : null
438
+ ]
439
+ );
440
+ return requireReturnedRecord(result.rows[0], record.id);
441
+ },
442
+ async attachTarget(requestId, target) {
443
+ const currentClient = await ensureInitialized();
444
+ const result = await currentClient.query(
445
+ `UPDATE ${qualifiedTableName({ schema, tableName })}
446
+ SET target = $2::jsonb, updated_at = now()
447
+ WHERE id = $1::text
448
+ RETURNING *`,
449
+ [requestId, JSON.stringify(target)]
450
+ );
451
+ return rowToRecord(result.rows[0]);
452
+ },
453
+ async resolve(requestId, resolution) {
454
+ const currentClient = await ensureInitialized();
455
+ const result = await currentClient.query(
456
+ `UPDATE ${qualifiedTableName({ schema, tableName })}
457
+ SET status = 'resolved',
458
+ resolution = $2::jsonb,
459
+ updated_at = now()
460
+ WHERE id = $1::text AND status <> 'resolved'
461
+ RETURNING *`,
462
+ [requestId, JSON.stringify(resolution)]
463
+ );
464
+ const resolved = rowToRecord(result.rows[0]);
465
+ if (resolved) {
466
+ return resolved;
467
+ }
468
+ const existing = await currentClient.query(
469
+ `SELECT * FROM ${qualifiedTableName({
470
+ schema,
471
+ tableName
472
+ })} WHERE id = $1::text LIMIT 1`,
473
+ [requestId]
474
+ );
475
+ return rowToRecord(existing.rows[0]);
476
+ },
477
+ async delete(requestId) {
478
+ const currentClient = await ensureInitialized();
479
+ await currentClient.query(
480
+ `DELETE FROM ${qualifiedTableName({
481
+ schema,
482
+ tableName
483
+ })} WHERE id = $1::text`,
484
+ [requestId]
485
+ );
486
+ },
487
+ prune,
488
+ async close() {
489
+ if (pruneTimer) {
490
+ clearInterval(pruneTimer);
491
+ pruneTimer = void 0;
492
+ }
493
+ if (ownsClient) {
494
+ await activeClient?.end?.();
495
+ }
496
+ }
497
+ };
498
+ }
499
+ async function initializePostgresSlackInteractiveRequestStore({
500
+ client,
501
+ ensureSchema = true,
502
+ schema,
503
+ tableName = DEFAULT_TABLE
504
+ }) {
505
+ if (schema && ensureSchema) {
506
+ await client.query(
507
+ `CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(schema)}`
508
+ );
509
+ }
510
+ const table = qualifiedTableName({ schema, tableName });
511
+ await client.query(`
512
+ CREATE TABLE IF NOT EXISTS ${table} (
513
+ id text PRIMARY KEY,
514
+ kind text NOT NULL CHECK (kind IN ('approval', 'human-input')),
515
+ request jsonb NOT NULL,
516
+ status text NOT NULL CHECK (status IN ('pending', 'resolved')),
517
+ target jsonb,
518
+ resolution jsonb,
519
+ created_at timestamptz NOT NULL DEFAULT now(),
520
+ updated_at timestamptz NOT NULL DEFAULT now()
521
+ )
522
+ `);
523
+ const indexPrefix = interactiveIndexPrefix({ schema, tableName });
524
+ await client.query(
525
+ `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
526
+ `${indexPrefix}_status_updated_idx`
527
+ )} ON ${table} (status, updated_at DESC)`
528
+ );
529
+ await client.query(
530
+ `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
531
+ `${indexPrefix}_updated_idx`
532
+ )} ON ${table} (updated_at DESC)`
533
+ );
534
+ }
535
+ async function prunePostgresSlackInteractiveRequestStore({
536
+ client,
537
+ pruneBatchSize = DEFAULT_PRUNE_BATCH_SIZE,
538
+ retentionMs = DEFAULT_RETENTION_MS,
539
+ schema,
540
+ tableName = DEFAULT_TABLE
541
+ }) {
542
+ if (retentionMs <= 0) {
543
+ return { deleted: 0 };
544
+ }
545
+ const batchSize = Math.max(1, Math.floor(pruneBatchSize));
546
+ const result = await client.query(
547
+ `WITH expired AS (
548
+ SELECT id
549
+ FROM ${qualifiedTableName({ schema, tableName })}
550
+ WHERE updated_at < now() - ($1::bigint * interval '1 millisecond')
551
+ ORDER BY updated_at ASC
552
+ LIMIT $2::integer
553
+ )
554
+ DELETE FROM ${qualifiedTableName({ schema, tableName })} target
555
+ USING expired
556
+ WHERE target.id = expired.id`,
557
+ [Math.max(1, Math.floor(retentionMs)), batchSize]
558
+ );
559
+ return { deleted: result.rowCount ?? 0 };
560
+ }
561
+ function rowToRecord(row) {
562
+ if (!row) return void 0;
563
+ return cloneRecord({
564
+ id: row.id,
565
+ kind: row.kind,
566
+ request: readJsonValue(
567
+ row.request
568
+ ),
569
+ status: row.status,
570
+ createdAt: toIsoString(row.created_at),
571
+ updatedAt: toIsoString(row.updated_at),
572
+ ...row.target ? {
573
+ target: readJsonValue(
574
+ row.target
575
+ )
576
+ } : {},
577
+ ...row.resolution ? {
578
+ resolution: readJsonValue(
579
+ row.resolution
580
+ )
581
+ } : {}
582
+ });
583
+ }
584
+ function requireReturnedRecord(row, requestId) {
585
+ const record = rowToRecord(row);
586
+ if (!record) {
587
+ throw new Error(
588
+ `Postgres Slack interactive request store did not return request ${requestId}.`
589
+ );
590
+ }
591
+ return record;
592
+ }
593
+ function readJsonValue(value) {
594
+ if (typeof value === "string") {
595
+ return JSON.parse(value);
596
+ }
597
+ return value;
598
+ }
599
+ function toIsoString(value) {
600
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
601
+ }
602
+ function qualifiedTableName({
603
+ schema,
604
+ tableName
605
+ }) {
606
+ return schema ? `${quoteIdentifier(schema)}.${quoteIdentifier(tableName)}` : quoteIdentifier(tableName);
607
+ }
608
+ function quoteIdentifier(value) {
609
+ return `"${value.replace(/"/g, '""')}"`;
610
+ }
611
+ function interactiveIndexPrefix({
612
+ schema,
613
+ tableName
614
+ }) {
615
+ const raw = [schema, tableName].filter(Boolean).join("_");
616
+ return raw.replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40) || "channel_slack_interactive";
617
+ }
618
+ async function importPostgresPoolConstructor() {
619
+ const dynamicImport = new Function(
620
+ "specifier",
621
+ "return import(specifier)"
622
+ );
623
+ try {
624
+ const pg = await dynamicImport("pg");
625
+ return pg.Pool;
626
+ } catch (error) {
627
+ throw new Error(
628
+ `The "pg" package is required when using connectionString with createPostgresSlackInteractiveRequestStore. Install pg or pass a client. ${formatImportError(
629
+ error
630
+ )}`
631
+ );
632
+ }
633
+ }
634
+ function formatImportError(error) {
635
+ return error instanceof Error ? error.message : String(error);
636
+ }
637
+ export {
638
+ buildApprovalRequestMessage,
639
+ buildHumanInputModal,
640
+ buildHumanInputRequestMessage,
641
+ buildResolvedMessage,
642
+ cloneRecord,
643
+ createInMemorySlackInteractiveRequestStore,
644
+ createPostgresSlackInteractiveRequestStore,
645
+ decodeActionValue,
646
+ encodeActionValue,
647
+ initializePostgresSlackInteractiveRequestStore,
648
+ nowIso,
649
+ prunePostgresSlackInteractiveRequestStore
650
+ };
@@ -0,0 +1,46 @@
1
+ import { S as SlackActivityInfo, b as SlackUserIdentity } from './activity-ByrD9Ftr.js';
2
+
3
+ /**
4
+ * Transport-neutral interactive request contracts.
5
+ *
6
+ * These types let callers delegate approval and human-input requests to a
7
+ * Slack-specific renderer without importing Bolt or Web API types into
8
+ * `shared/`.
9
+ */
10
+
11
+ type SlackInteractiveRequestKind = "approval" | "human-input";
12
+ interface SlackApprovalRequest {
13
+ id: string;
14
+ }
15
+ interface SlackHumanInputRequest {
16
+ id: string;
17
+ }
18
+ type SlackInteractiveRequest = SlackApprovalRequest | SlackHumanInputRequest;
19
+ interface SlackInteractiveMessageRef {
20
+ channel: string;
21
+ ts: string;
22
+ }
23
+ interface SlackInteractiveMessage {
24
+ text: string;
25
+ blocks?: unknown[];
26
+ }
27
+ interface SlackInteractiveResponder {
28
+ postMessage(message: SlackInteractiveMessage): Promise<SlackInteractiveMessageRef>;
29
+ updateMessage(ref: SlackInteractiveMessageRef, message: SlackInteractiveMessage): Promise<void>;
30
+ }
31
+ interface SlackInteractiveRequestBaseContext {
32
+ kind: SlackInteractiveRequestKind;
33
+ request: SlackInteractiveRequest;
34
+ fallbackText: string;
35
+ }
36
+ interface SlackInteractiveRequestContext extends SlackInteractiveRequestBaseContext {
37
+ slackActivity: SlackActivityInfo;
38
+ user: SlackUserIdentity;
39
+ sessionId: string;
40
+ message: string;
41
+ responder: SlackInteractiveResponder;
42
+ }
43
+ type SlackInteractiveRequestHandler = (context: SlackInteractiveRequestContext) => boolean | void | Promise<boolean | void>;
44
+ type SlackEventInteractiveRequestHandler = (context: SlackInteractiveRequestBaseContext) => boolean | void | Promise<boolean | void>;
45
+
46
+ export type { SlackApprovalRequest as S, SlackEventInteractiveRequestHandler as a, SlackHumanInputRequest as b, SlackInteractiveMessage as c, SlackInteractiveMessageRef as d, SlackInteractiveRequest as e, SlackInteractiveRequestBaseContext as f, SlackInteractiveRequestContext as g, SlackInteractiveRequestHandler as h, SlackInteractiveRequestKind as i, SlackInteractiveResponder as j };
@@ -0,0 +1,74 @@
1
+ import { d as SlackArtifactClient } from '../types-C8nkPuD4.js';
2
+
3
+ /**
4
+ * Slack response-sink contracts consumed by the event bridge.
5
+ *
6
+ * The adapter constructs a `SlackResponseSink` from the Bolt `say` function
7
+ * and `WebClient` instance available in each handler. Keeping the sink
8
+ * interface separate makes the bridge testable without a live Slack
9
+ * connection.
10
+ */
11
+
12
+ type SlackStreamTaskStatus = "pending" | "in_progress" | "complete" | "error";
13
+ type SlackStreamChunk = {
14
+ type: "markdown_text";
15
+ text: string;
16
+ } | {
17
+ type: "plan_update";
18
+ title: string;
19
+ } | {
20
+ type: "task_update";
21
+ id: string;
22
+ title: string;
23
+ status: SlackStreamTaskStatus;
24
+ details?: string;
25
+ output?: string;
26
+ };
27
+ interface SlackChatStream {
28
+ append(args: {
29
+ markdown_text?: string;
30
+ chunks?: SlackStreamChunk[];
31
+ }): Promise<unknown>;
32
+ stop(args?: {
33
+ markdown_text?: string;
34
+ chunks?: SlackStreamChunk[];
35
+ }): Promise<unknown>;
36
+ }
37
+ /**
38
+ * Minimal Slack posting interface consumed by the event bridge.
39
+ */
40
+ interface SlackArtifactPublicationTarget {
41
+ channelId: string;
42
+ threadTs?: string;
43
+ }
44
+ interface SlackResponseSink {
45
+ /**
46
+ * Slack Web API surface used by optional artifact publishers.
47
+ */
48
+ artifactClient?: SlackArtifactClient;
49
+ /**
50
+ * Channel/thread target used by optional artifact publishers.
51
+ */
52
+ artifactTarget?: SlackArtifactPublicationTarget;
53
+ /**
54
+ * Post a new message to the channel / thread.
55
+ * Returns the channel ID and message timestamp needed for updates.
56
+ */
57
+ postMessage(text: string): Promise<{
58
+ channel: string;
59
+ ts: string;
60
+ }>;
61
+ /**
62
+ * Update an existing message by channel + ts.
63
+ */
64
+ updateMessage(channel: string, ts: string, text: string): Promise<void>;
65
+ /**
66
+ * Create a native Slack chat stream. Required when `streamingMode` is
67
+ * `"chat-stream"`.
68
+ */
69
+ createChatStream?(options: {
70
+ bufferSize: number;
71
+ }): SlackChatStream;
72
+ }
73
+
74
+ export type { SlackArtifactPublicationTarget, SlackChatStream, SlackResponseSink, SlackStreamChunk, SlackStreamTaskStatus };
File without changes
@@ -0,0 +1,111 @@
1
+ type SlackArtifactPublishMethod = "message" | "file" | "canvas";
2
+ type SlackArtifactBinaryData = string | Uint8Array | ArrayBuffer;
3
+ interface SlackArtifactBase {
4
+ kind: string;
5
+ title: string;
6
+ summary?: string;
7
+ metadata?: Record<string, unknown>;
8
+ }
9
+ interface SlackTextArtifact extends SlackArtifactBase {
10
+ kind: "text";
11
+ text: string;
12
+ filename?: string;
13
+ mimeType?: string;
14
+ publishAs?: "message" | "file";
15
+ }
16
+ interface SlackFileArtifact extends SlackArtifactBase {
17
+ kind: "file";
18
+ filename: string;
19
+ data?: SlackArtifactBinaryData;
20
+ filePath?: string;
21
+ mimeType?: string;
22
+ initialComment?: string;
23
+ }
24
+ interface SlackImageArtifact extends SlackArtifactBase {
25
+ kind: "image";
26
+ filename: string;
27
+ altText: string;
28
+ data?: SlackArtifactBinaryData;
29
+ filePath?: string;
30
+ initialComment?: string;
31
+ }
32
+ interface SlackLinkArtifact extends SlackArtifactBase {
33
+ kind: "link";
34
+ url: string;
35
+ }
36
+ interface SlackCanvasArtifact extends SlackArtifactBase {
37
+ kind: "canvas";
38
+ markdown: string;
39
+ /**
40
+ * When set, updates an existing canvas instead of creating one.
41
+ */
42
+ canvasId?: string;
43
+ /**
44
+ * Create a channel canvas when a channel is available.
45
+ *
46
+ * @default true
47
+ */
48
+ channelCanvas?: boolean;
49
+ }
50
+ type SlackArtifact = SlackTextArtifact | SlackFileArtifact | SlackImageArtifact | SlackLinkArtifact | SlackCanvasArtifact;
51
+ interface SlackArtifactPostMessageResponse {
52
+ ok?: boolean;
53
+ channel?: string;
54
+ ts?: string;
55
+ [key: string]: unknown;
56
+ }
57
+ interface SlackArtifactFileUploadResponse {
58
+ ok?: boolean;
59
+ file?: {
60
+ id?: string;
61
+ [key: string]: unknown;
62
+ };
63
+ files?: Array<{
64
+ id?: string;
65
+ [key: string]: unknown;
66
+ }>;
67
+ [key: string]: unknown;
68
+ }
69
+ interface SlackArtifactCanvasResponse {
70
+ ok?: boolean;
71
+ canvas_id?: string;
72
+ [key: string]: unknown;
73
+ }
74
+ interface SlackArtifactClient {
75
+ chat?: {
76
+ postMessage(args: Record<string, unknown>): Promise<SlackArtifactPostMessageResponse>;
77
+ };
78
+ files?: {
79
+ uploadV2(args: Record<string, unknown>): Promise<SlackArtifactFileUploadResponse>;
80
+ };
81
+ canvases?: {
82
+ create(args?: Record<string, unknown>): Promise<SlackArtifactCanvasResponse>;
83
+ edit?(args: Record<string, unknown>): Promise<SlackArtifactCanvasResponse>;
84
+ };
85
+ conversations?: {
86
+ canvases?: {
87
+ create(args: Record<string, unknown>): Promise<SlackArtifactCanvasResponse>;
88
+ };
89
+ };
90
+ }
91
+ interface PublishSlackArtifactOptions {
92
+ client: SlackArtifactClient;
93
+ artifact: SlackArtifact;
94
+ channelId?: string;
95
+ threadTs?: string;
96
+ token?: string;
97
+ unfurlLinks?: boolean;
98
+ unfurlMedia?: boolean;
99
+ }
100
+ interface SlackArtifactPublication {
101
+ artifact: SlackArtifact;
102
+ method: SlackArtifactPublishMethod;
103
+ response: unknown;
104
+ channelId?: string;
105
+ threadTs?: string;
106
+ messageTs?: string;
107
+ fileId?: string;
108
+ canvasId?: string;
109
+ }
110
+
111
+ export type { PublishSlackArtifactOptions as P, SlackArtifact as S, SlackArtifactBase as a, SlackArtifactBinaryData as b, SlackArtifactCanvasResponse as c, SlackArtifactClient as d, SlackArtifactFileUploadResponse as e, SlackArtifactPostMessageResponse as f, SlackArtifactPublication as g, SlackArtifactPublishMethod as h, SlackCanvasArtifact as i, SlackFileArtifact as j, SlackImageArtifact as k, SlackLinkArtifact as l, SlackTextArtifact as m };
@@ -24,6 +24,9 @@ contracts.
24
24
  - User profile and mention helpers.
25
25
  - Target parsing and resolution.
26
26
  - Feedback blocks and action handler.
27
+ - Slack response sink and chat stream contracts for runtime adapters.
28
+ - Slack interactive request Block Kit/modal builders and in-memory/Postgres
29
+ stores.
27
30
  - Slack message policy resolver and in-memory/Postgres state stores.
28
31
  - Supplemental history reader, context loader, and visibility policy.
29
32
  - Assistant message parser and thread-context store.
@@ -33,8 +36,8 @@ contracts.
33
36
  - Agent runtime execution.
34
37
  - Agent-runtime scopes and context-fragment middleware.
35
38
  - Runtime-specific mapping from Slack entrypoints into an agent turn.
36
- - Agent event stream rendering.
37
- - Agent-specific approval and human-input request contracts.
39
+ - Agent event stream rendering and runtime-specific response orchestration.
40
+ - Agent-specific approval and human-input controllers.
38
41
  - Product prompts, tools, audit policy, and deployment policy.
39
42
 
40
43
  Those pieces belong in runtime-specific adapters or product applications.
@@ -8,9 +8,11 @@ keep application code close to the package boundary it uses.
8
8
  | `@cuylabs/channel-slack/core` | no Slack SDK runtime imports | Transport-neutral parsing, formatting, types, sessions, turn context |
9
9
  | `@cuylabs/channel-slack/policy` | `pg` only when using connection-string Postgres state | Message admission and in-memory/Postgres policy state |
10
10
  | `@cuylabs/channel-slack/history` | `@slack/web-api` types | History reader accepts a Slack WebClient or minimal conversations client |
11
+ | `@cuylabs/channel-slack/interactive` | `@slack/types`; `pg` only when using connection-string Postgres storage | Slack interactive Block Kit/modal builders and request stores |
11
12
  | `@cuylabs/channel-slack/app-home` | `@slack/bolt`, `@slack/types` | Slack App Home registration helper |
12
13
  | `@cuylabs/channel-slack/artifacts` | no Slack SDK runtime imports; requires a Web API-like client at call time | Text, file, image, link, and Canvas artifact publishing |
13
14
  | `@cuylabs/channel-slack/auth` | no Slack SDK runtime imports; `pg` is not required | Auth option types, auth resolution, OAuth installation stores |
15
+ | `@cuylabs/channel-slack/responses` | no Slack SDK runtime imports | Slack response sink and chat stream contracts for runtime adapters |
14
16
  | `@cuylabs/channel-slack/transports` | `@slack/bolt`, `express`; `pg` only when using connection-string Postgres locks | HTTP and Socket Mode transport helpers |
15
17
  | `@cuylabs/channel-slack/transports/http` | `@slack/bolt`, `express` | HTTP Events API Bolt app factory |
16
18
  | `@cuylabs/channel-slack/transports/socket` | `@slack/bolt`; `pg` only when using connection-string Postgres locks | Socket Mode app factory, runtime guard, process/Postgres locks |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuylabs/channel-slack",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Agent-runtime-agnostic Slack channel primitives for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,11 +56,21 @@
56
56
  "import": "./dist/history/index.js",
57
57
  "default": "./dist/history/index.js"
58
58
  },
59
+ "./interactive": {
60
+ "types": "./dist/interactive/index.d.ts",
61
+ "import": "./dist/interactive/index.js",
62
+ "default": "./dist/interactive/index.js"
63
+ },
59
64
  "./policy": {
60
65
  "types": "./dist/policy/index.d.ts",
61
66
  "import": "./dist/policy/index.js",
62
67
  "default": "./dist/policy/index.js"
63
68
  },
69
+ "./responses": {
70
+ "types": "./dist/responses/index.d.ts",
71
+ "import": "./dist/responses/index.js",
72
+ "default": "./dist/responses/index.js"
73
+ },
64
74
  "./setup": {
65
75
  "types": "./dist/setup/index.d.ts",
66
76
  "import": "./dist/setup/index.js",
@@ -153,9 +163,9 @@
153
163
  "node": ">=20"
154
164
  },
155
165
  "scripts": {
156
- "build": "tsup src/index.ts src/app-home.ts src/artifacts/index.ts src/core.ts src/assistant/index.ts src/auth/index.ts src/diagnostics/index.ts src/entrypoints/index.ts src/feedback/index.ts src/history/index.ts src/policy/index.ts src/setup/index.ts src/targets/index.ts src/turn-controls/index.ts src/transports/index.ts src/transports/http/index.ts src/transports/socket/index.ts src/users/index.ts src/views/index.ts --format esm --dts --clean",
166
+ "build": "tsup src/index.ts src/app-home.ts src/artifacts/index.ts src/core.ts src/assistant/index.ts src/auth/index.ts src/diagnostics/index.ts src/entrypoints/index.ts src/feedback/index.ts src/history/index.ts src/interactive/index.ts src/policy/index.ts src/responses/index.ts src/setup/index.ts src/targets/index.ts src/turn-controls/index.ts src/transports/index.ts src/transports/http/index.ts src/transports/socket/index.ts src/users/index.ts src/views/index.ts --format esm --dts --clean",
157
167
  "clean": "rm -rf dist",
158
- "dev": "tsup src/index.ts src/app-home.ts src/artifacts/index.ts src/core.ts src/assistant/index.ts src/auth/index.ts src/diagnostics/index.ts src/entrypoints/index.ts src/feedback/index.ts src/history/index.ts src/policy/index.ts src/setup/index.ts src/targets/index.ts src/turn-controls/index.ts src/transports/index.ts src/transports/http/index.ts src/transports/socket/index.ts src/users/index.ts src/views/index.ts --format esm --dts --watch",
168
+ "dev": "tsup src/index.ts src/app-home.ts src/artifacts/index.ts src/core.ts src/assistant/index.ts src/auth/index.ts src/diagnostics/index.ts src/entrypoints/index.ts src/feedback/index.ts src/history/index.ts src/interactive/index.ts src/policy/index.ts src/responses/index.ts src/setup/index.ts src/targets/index.ts src/turn-controls/index.ts src/transports/index.ts src/transports/http/index.ts src/transports/socket/index.ts src/users/index.ts src/views/index.ts --format esm --dts --watch",
159
169
  "lint": "eslint \"src/**/*.{ts,tsx}\" \"tests/**/*.{ts,tsx}\" --max-warnings=0",
160
170
  "test": "vitest run",
161
171
  "test:watch": "vitest",