@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 +3 -2
- package/dist/artifacts/index.d.ts +3 -110
- package/dist/core.d.ts +2 -40
- package/dist/index.d.ts +2 -1
- package/dist/interactive/index.d.ts +154 -0
- package/dist/interactive/index.js +650 -0
- package/dist/interactive-CbKYkkc_.d.ts +46 -0
- package/dist/responses/index.d.ts +74 -0
- package/dist/responses/index.js +0 -0
- package/dist/types-C8nkPuD4.d.ts +111 -0
- package/docs/reference/channel-slack-boundary.md +5 -2
- package/docs/reference/exports.md +2 -0
- package/package.json +13 -3
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,
|
|
8
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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 {
|
|
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
|
|
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,
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
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.
|
|
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",
|