@pinet/slack-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +570 -0
  3. package/dist/activity-log.d.ts +62 -0
  4. package/dist/activity-log.js +293 -0
  5. package/dist/agent-completion-runtime.d.ts +17 -0
  6. package/dist/agent-completion-runtime.js +26 -0
  7. package/dist/agent-event-runtime.d.ts +13 -0
  8. package/dist/agent-event-runtime.js +27 -0
  9. package/dist/agent-prompt-guidance.d.ts +22 -0
  10. package/dist/agent-prompt-guidance.js +46 -0
  11. package/dist/broker/adapters/slack.d.ts +74 -0
  12. package/dist/broker/adapters/slack.js +563 -0
  13. package/dist/broker/adapters/types.d.ts +1 -0
  14. package/dist/broker/adapters/types.js +1 -0
  15. package/dist/broker/agent-messaging.d.ts +1 -0
  16. package/dist/broker/agent-messaging.js +1 -0
  17. package/dist/broker/auth.d.ts +1 -0
  18. package/dist/broker/auth.js +1 -0
  19. package/dist/broker/client.d.ts +167 -0
  20. package/dist/broker/client.js +624 -0
  21. package/dist/broker/control-plane-dashboard.d.ts +98 -0
  22. package/dist/broker/control-plane-dashboard.js +213 -0
  23. package/dist/broker/ghost-reaper.d.ts +49 -0
  24. package/dist/broker/ghost-reaper.js +209 -0
  25. package/dist/broker/index.d.ts +36 -0
  26. package/dist/broker/index.js +90 -0
  27. package/dist/broker/leader.d.ts +1 -0
  28. package/dist/broker/leader.js +1 -0
  29. package/dist/broker/maintenance.d.ts +1 -0
  30. package/dist/broker/maintenance.js +1 -0
  31. package/dist/broker/message-send.d.ts +1 -0
  32. package/dist/broker/message-send.js +1 -0
  33. package/dist/broker/paths.d.ts +1 -0
  34. package/dist/broker/paths.js +1 -0
  35. package/dist/broker/raw-tcp-loopback.d.ts +1 -0
  36. package/dist/broker/raw-tcp-loopback.js +1 -0
  37. package/dist/broker/router.d.ts +1 -0
  38. package/dist/broker/router.js +1 -0
  39. package/dist/broker/schema.d.ts +9 -0
  40. package/dist/broker/schema.js +63 -0
  41. package/dist/broker/socket-server.d.ts +120 -0
  42. package/dist/broker/socket-server.js +1087 -0
  43. package/dist/broker/types.d.ts +1 -0
  44. package/dist/broker/types.js +1 -0
  45. package/dist/broker-delivery.d.ts +10 -0
  46. package/dist/broker-delivery.js +26 -0
  47. package/dist/broker-inbound-persistence.d.ts +11 -0
  48. package/dist/broker-inbound-persistence.js +22 -0
  49. package/dist/broker-prompt-loader.d.ts +34 -0
  50. package/dist/broker-prompt-loader.js +185 -0
  51. package/dist/broker-runtime-access.d.ts +14 -0
  52. package/dist/broker-runtime-access.js +20 -0
  53. package/dist/broker-runtime.d.ts +100 -0
  54. package/dist/broker-runtime.js +533 -0
  55. package/dist/broker-thread-owner-hints.d.ts +10 -0
  56. package/dist/broker-thread-owner-hints.js +14 -0
  57. package/dist/canvases.d.ts +80 -0
  58. package/dist/canvases.js +221 -0
  59. package/dist/command-registration-runtime.d.ts +9 -0
  60. package/dist/command-registration-runtime.js +9 -0
  61. package/dist/core-tool-guardrails.d.ts +24 -0
  62. package/dist/core-tool-guardrails.js +66 -0
  63. package/dist/deploy-manifest.d.ts +27 -0
  64. package/dist/deploy-manifest.js +197 -0
  65. package/dist/follower-delivery.d.ts +16 -0
  66. package/dist/follower-delivery.js +70 -0
  67. package/dist/follower-runtime.d.ts +61 -0
  68. package/dist/follower-runtime.js +350 -0
  69. package/dist/git-metadata.d.ts +35 -0
  70. package/dist/git-metadata.js +88 -0
  71. package/dist/guardrails.d.ts +69 -0
  72. package/dist/guardrails.js +283 -0
  73. package/dist/helpers.d.ts +550 -0
  74. package/dist/helpers.js +2711 -0
  75. package/dist/home-tab.d.ts +27 -0
  76. package/dist/home-tab.js +242 -0
  77. package/dist/imessage-tools.d.ts +33 -0
  78. package/dist/imessage-tools.js +96 -0
  79. package/dist/inbox-drain-runtime.d.ts +30 -0
  80. package/dist/inbox-drain-runtime.js +71 -0
  81. package/dist/index.d.ts +2 -0
  82. package/dist/index.js +1330 -0
  83. package/dist/persisted-runtime-state.d.ts +52 -0
  84. package/dist/persisted-runtime-state.js +98 -0
  85. package/dist/pinet-activity-formatting.d.ts +20 -0
  86. package/dist/pinet-activity-formatting.js +43 -0
  87. package/dist/pinet-agent-status.d.ts +36 -0
  88. package/dist/pinet-agent-status.js +48 -0
  89. package/dist/pinet-commands.d.ts +70 -0
  90. package/dist/pinet-commands.js +402 -0
  91. package/dist/pinet-confirmation-replies.d.ts +7 -0
  92. package/dist/pinet-confirmation-replies.js +30 -0
  93. package/dist/pinet-control-plane-dashboard.d.ts +34 -0
  94. package/dist/pinet-control-plane-dashboard.js +86 -0
  95. package/dist/pinet-home-tabs.d.ts +36 -0
  96. package/dist/pinet-home-tabs.js +101 -0
  97. package/dist/pinet-maintenance-delivery.d.ts +22 -0
  98. package/dist/pinet-maintenance-delivery.js +45 -0
  99. package/dist/pinet-mesh-ops.d.ts +97 -0
  100. package/dist/pinet-mesh-ops.js +266 -0
  101. package/dist/pinet-registration-gate.d.ts +14 -0
  102. package/dist/pinet-registration-gate.js +42 -0
  103. package/dist/pinet-remote-control-acks.d.ts +16 -0
  104. package/dist/pinet-remote-control-acks.js +46 -0
  105. package/dist/pinet-remote-control.d.ts +17 -0
  106. package/dist/pinet-remote-control.js +74 -0
  107. package/dist/pinet-runtime-composition.d.ts +27 -0
  108. package/dist/pinet-runtime-composition.js +20 -0
  109. package/dist/pinet-tools.d.ts +67 -0
  110. package/dist/pinet-tools.js +1211 -0
  111. package/dist/prompts/broker/default.md +41 -0
  112. package/dist/prompts/broker/tmux.md +49 -0
  113. package/dist/ralph-loop.d.ts +76 -0
  114. package/dist/ralph-loop.js +524 -0
  115. package/dist/reaction-triggers.d.ts +25 -0
  116. package/dist/reaction-triggers.js +171 -0
  117. package/dist/repo-tool-guardrails.d.ts +20 -0
  118. package/dist/repo-tool-guardrails.js +81 -0
  119. package/dist/runtime-agent-context.d.ts +93 -0
  120. package/dist/runtime-agent-context.js +302 -0
  121. package/dist/runtime-mode.d.ts +10 -0
  122. package/dist/runtime-mode.js +39 -0
  123. package/dist/scheduled-wakeups.d.ts +1 -0
  124. package/dist/scheduled-wakeups.js +1 -0
  125. package/dist/session-ui-runtime.d.ts +20 -0
  126. package/dist/session-ui-runtime.js +163 -0
  127. package/dist/single-player-runtime.d.ts +71 -0
  128. package/dist/single-player-runtime.js +436 -0
  129. package/dist/skins/cosmere.json +1571 -0
  130. package/dist/skins/foundation.json +1304 -0
  131. package/dist/slack-access.d.ts +186 -0
  132. package/dist/slack-access.js +462 -0
  133. package/dist/slack-api.d.ts +2 -0
  134. package/dist/slack-api.js +1 -0
  135. package/dist/slack-block-kit.d.ts +36 -0
  136. package/dist/slack-block-kit.js +242 -0
  137. package/dist/slack-export.d.ts +31 -0
  138. package/dist/slack-export.js +206 -0
  139. package/dist/slack-message-context.d.ts +15 -0
  140. package/dist/slack-message-context.js +207 -0
  141. package/dist/slack-modals.d.ts +13 -0
  142. package/dist/slack-modals.js +79 -0
  143. package/dist/slack-pinet-runtime-adapter.d.ts +16 -0
  144. package/dist/slack-pinet-runtime-adapter.js +72 -0
  145. package/dist/slack-presence.d.ts +36 -0
  146. package/dist/slack-presence.js +89 -0
  147. package/dist/slack-request-runtime.d.ts +8 -0
  148. package/dist/slack-request-runtime.js +19 -0
  149. package/dist/slack-runtime-access.d.ts +28 -0
  150. package/dist/slack-runtime-access.js +107 -0
  151. package/dist/slack-scope-diagnostics.d.ts +31 -0
  152. package/dist/slack-scope-diagnostics.js +235 -0
  153. package/dist/slack-socket-dedup.d.ts +7 -0
  154. package/dist/slack-socket-dedup.js +135 -0
  155. package/dist/slack-thread-status.d.ts +41 -0
  156. package/dist/slack-thread-status.js +156 -0
  157. package/dist/slack-tool-policy-runtime.d.ts +36 -0
  158. package/dist/slack-tool-policy-runtime.js +96 -0
  159. package/dist/slack-tools.d.ts +52 -0
  160. package/dist/slack-tools.js +2688 -0
  161. package/dist/slack-turn-guardrails.d.ts +19 -0
  162. package/dist/slack-turn-guardrails.js +36 -0
  163. package/dist/slack-upload.d.ts +49 -0
  164. package/dist/slack-upload.js +224 -0
  165. package/dist/task-assignments.d.ts +45 -0
  166. package/dist/task-assignments.js +527 -0
  167. package/dist/thread-confirmations.d.ts +22 -0
  168. package/dist/thread-confirmations.js +139 -0
  169. package/dist/tool-registration-runtime.d.ts +13 -0
  170. package/dist/tool-registration-runtime.js +13 -0
  171. package/dist/ttl-cache.d.ts +47 -0
  172. package/dist/ttl-cache.js +105 -0
  173. package/manifest.yaml +57 -0
  174. package/package.json +60 -0
  175. package/skills/pinet-skin-creator/SKILL.md +109 -0
  176. package/skills/pinet-skin-creator/references/descriptor-format.md +99 -0
  177. package/skills/pinet-skin-creator/references/safety-checklist.md +56 -0
  178. package/skills/pinet-skin-creator/templates/pinet-skin-descriptor.json +77 -0
  179. package/skills/slack-bridge/SKILL.md +360 -0
@@ -0,0 +1,186 @@
1
+ import type { RuntimeScopeCarrier } from "@pinet/transport-core";
2
+ import { type SlackInteractiveInboxEvent } from "./slack-block-kit.js";
3
+ import { type ThreadOwnerHint } from "./broker/router.js";
4
+ export { SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE, SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS, } from "./slack-socket-dedup.js";
5
+ export type SlackCall = (method: string, token: string, body?: Record<string, unknown>) => Promise<Record<string, unknown>>;
6
+ export interface SlackAccessCache<K, V> {
7
+ get(key: K): V | undefined;
8
+ set(key: K, value: V): unknown;
9
+ has?(key: K): boolean;
10
+ }
11
+ export interface SlackAccessSet<K> {
12
+ has(key: K): boolean;
13
+ add(key: K): unknown;
14
+ delete?(key: K): unknown;
15
+ }
16
+ export interface SlackThreadContext {
17
+ channelId: string;
18
+ teamId?: string;
19
+ scope: RuntimeScopeCarrier;
20
+ }
21
+ export declare function buildSlackThreadRuntimeScope(input: {
22
+ channelId?: string | null;
23
+ context?: SlackThreadContext | null;
24
+ }): RuntimeScopeCarrier;
25
+ export interface ParsedEnvelope {
26
+ envelopeId?: string;
27
+ type: string;
28
+ dedupKey?: string;
29
+ event?: Record<string, unknown>;
30
+ interactivePayload?: Record<string, unknown>;
31
+ }
32
+ export declare function isSlackUserAllowed(allowlist: Set<string> | null, userId: string): boolean;
33
+ /**
34
+ * Parse a raw Socket Mode WebSocket frame into a structured envelope.
35
+ * Returns null if the frame is malformed JSON.
36
+ */
37
+ export declare function parseSocketFrame(raw: string): ParsedEnvelope | null;
38
+ export interface ParsedThreadStarted {
39
+ channelId: string;
40
+ threadTs: string;
41
+ userId: string;
42
+ context?: SlackThreadContext;
43
+ }
44
+ /**
45
+ * Extract thread info from an assistant_thread_started event.
46
+ */
47
+ export declare function extractThreadStarted(evt: Record<string, unknown>): ParsedThreadStarted | null;
48
+ export interface ParsedThreadContextChanged {
49
+ threadTs: string;
50
+ context?: SlackThreadContext;
51
+ }
52
+ export declare function extractThreadContextChanged(evt: Record<string, unknown>): ParsedThreadContextChanged | null;
53
+ export interface ParsedAppHomeOpened {
54
+ userId: string;
55
+ tab: string;
56
+ eventTs: string | null;
57
+ }
58
+ export declare function extractAppHomeOpened(evt: Record<string, unknown>): ParsedAppHomeOpened | null;
59
+ /**
60
+ * Classification result for an incoming message event.
61
+ * Uses a discriminated union so TypeScript narrows fields when relevant is true.
62
+ */
63
+ export type MessageClassification = {
64
+ relevant: false;
65
+ } | {
66
+ relevant: true;
67
+ threadTs: string;
68
+ channel: string;
69
+ userId: string;
70
+ text: string;
71
+ isDM: boolean;
72
+ isChannelMention: boolean;
73
+ messageTs: string;
74
+ metadata?: Record<string, unknown>;
75
+ };
76
+ /**
77
+ * Classify an incoming Slack message event. Determines whether the
78
+ * message is relevant (DM, known thread, or bot mention) and
79
+ * extracts the cleaned fields.
80
+ */
81
+ export declare function classifyMessage(evt: Record<string, unknown>, botUserId: string | null, trackedThreadIds: Set<string>, isKnownThread?: (threadTs: string) => boolean): MessageClassification;
82
+ /**
83
+ * Parse a member_joined_channel event. Returns null if required fields
84
+ * are missing.
85
+ */
86
+ export declare function parseMemberJoinedChannel(evt: Record<string, unknown>, botUserId: string | null): {
87
+ channel: string;
88
+ isSelf: boolean;
89
+ } | null;
90
+ export interface FetchSlackMessageByTsInput {
91
+ slack: SlackCall;
92
+ token: string;
93
+ channel: string;
94
+ messageTs: string;
95
+ }
96
+ export declare function fetchSlackMessageByTs(input: FetchSlackMessageByTsInput): Promise<Record<string, unknown> | null>;
97
+ export interface SlackReactionInput {
98
+ slack: SlackCall;
99
+ token: string;
100
+ channel: string;
101
+ timestamp: string;
102
+ emoji: string;
103
+ }
104
+ export declare function addSlackReaction(input: SlackReactionInput): Promise<void>;
105
+ export declare function removeSlackReaction(input: SlackReactionInput): Promise<void>;
106
+ export interface ResolveSlackUserNameInput {
107
+ slack: SlackCall;
108
+ token: string;
109
+ userId: string;
110
+ cache?: SlackAccessCache<string, string>;
111
+ shouldUseResult?: () => boolean;
112
+ }
113
+ export declare function resolveSlackUserName(input: ResolveSlackUserNameInput): Promise<string>;
114
+ export interface ResolveSlackChannelIdInput {
115
+ slack: SlackCall;
116
+ token: string;
117
+ nameOrId: string;
118
+ cache?: SlackAccessCache<string, string>;
119
+ }
120
+ export declare function resolveSlackChannelId(input: ResolveSlackChannelIdInput): Promise<string>;
121
+ export interface ClearSlackThreadStatusInput {
122
+ slack: SlackCall;
123
+ token: string;
124
+ channelId: string;
125
+ threadTs: string;
126
+ }
127
+ export declare function clearSlackThreadStatus(input: ClearSlackThreadStatusInput): Promise<void>;
128
+ export interface SlackSuggestedPrompt {
129
+ title: string;
130
+ message: string;
131
+ }
132
+ export interface SetSlackSuggestedPromptsInput {
133
+ slack: SlackCall;
134
+ token: string;
135
+ channelId: string;
136
+ threadTs: string;
137
+ prompts: SlackSuggestedPrompt[];
138
+ }
139
+ export declare function setSlackSuggestedPrompts(input: SetSlackSuggestedPromptsInput): Promise<void>;
140
+ export interface ResolveSlackThreadOwnerHintInput {
141
+ slack: SlackCall;
142
+ token: string;
143
+ channel: string;
144
+ threadTs: string;
145
+ cache?: SlackAccessCache<string, ThreadOwnerHint>;
146
+ limit?: number;
147
+ }
148
+ export declare function resolveSlackThreadOwnerHint(input: ResolveSlackThreadOwnerHintInput): Promise<ThreadOwnerHint | null>;
149
+ export declare const RECONNECT_DELAY_MS = 5000;
150
+ export interface SlackSocketModeClientConfig {
151
+ slack: SlackCall;
152
+ botToken: string;
153
+ appToken: string;
154
+ resolveBotUserIdOnConnect?: boolean;
155
+ reconnectDelayMs?: number;
156
+ dedup?: SlackAccessSet<string>;
157
+ abortAndWait?: () => Promise<void>;
158
+ onOpen?: () => void;
159
+ onReconnectScheduled?: () => void;
160
+ onError?: (error: unknown) => void;
161
+ onThreadStarted?: (event: ParsedThreadStarted) => Promise<void> | void;
162
+ onThreadContextChanged?: (event: ParsedThreadContextChanged) => Promise<void> | void;
163
+ onMessage?: (event: Record<string, unknown>) => Promise<void> | void;
164
+ onReactionAdded?: (event: Record<string, unknown>) => Promise<void> | void;
165
+ onMemberJoinedChannel?: (event: {
166
+ channel: string;
167
+ isSelf: boolean;
168
+ }) => Promise<void> | void;
169
+ onAppHomeOpened?: (event: ParsedAppHomeOpened) => Promise<void> | void;
170
+ onInteractive?: (event: SlackInteractiveInboxEvent) => Promise<void> | void;
171
+ }
172
+ export declare class SlackSocketModeClient {
173
+ private readonly config;
174
+ private botUserId;
175
+ private ws;
176
+ private reconnectTimer;
177
+ private shuttingDown;
178
+ constructor(config: SlackSocketModeClientConfig);
179
+ getBotUserId(): string | null;
180
+ isConnected(): boolean;
181
+ connect(): Promise<void>;
182
+ disconnect(): Promise<void>;
183
+ private connectSocketMode;
184
+ private handleFrame;
185
+ private scheduleReconnect;
186
+ }
@@ -0,0 +1,462 @@
1
+ import { buildSlackCompatibilityScope, isUserAllowed, isChannelId, stripBotMention, isAbortError, } from "./helpers.js";
2
+ import { buildSlackInboundMessageText, extractSlackMessageFileMetadata, } from "./slack-message-context.js";
3
+ import { extractSlackInteractivePayloadFromEnvelope, normalizeSlackBlockActionPayload, normalizeSlackViewSubmissionPayload, } from "./slack-block-kit.js";
4
+ import { extractSlackSocketDedupKey } from "./slack-socket-dedup.js";
5
+ import { extractPiAgentThreadOwnerHint } from "./broker/router.js";
6
+ export { SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE, SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS, } from "./slack-socket-dedup.js";
7
+ function buildSlackThreadContext(channelId, teamId) {
8
+ const normalizedTeamId = typeof teamId === "string" && teamId.trim().length > 0 ? teamId.trim() : undefined;
9
+ return {
10
+ channelId,
11
+ ...(normalizedTeamId ? { teamId: normalizedTeamId } : {}),
12
+ scope: buildSlackCompatibilityScope({
13
+ teamId: normalizedTeamId,
14
+ channelId,
15
+ }),
16
+ };
17
+ }
18
+ export function buildSlackThreadRuntimeScope(input) {
19
+ return (input.context?.scope ??
20
+ buildSlackCompatibilityScope({
21
+ teamId: input.context?.teamId,
22
+ channelId: input.context?.channelId ?? input.channelId,
23
+ }));
24
+ }
25
+ export function isSlackUserAllowed(allowlist, userId) {
26
+ return isUserAllowed(allowlist, userId);
27
+ }
28
+ /**
29
+ * Parse a raw Socket Mode WebSocket frame into a structured envelope.
30
+ * Returns null if the frame is malformed JSON.
31
+ */
32
+ export function parseSocketFrame(raw) {
33
+ try {
34
+ const data = JSON.parse(raw);
35
+ const result = {
36
+ type: data.type ?? "",
37
+ };
38
+ if (data.envelope_id) {
39
+ result.envelopeId = data.envelope_id;
40
+ }
41
+ const dedupKey = extractSlackSocketDedupKey(data);
42
+ if (dedupKey) {
43
+ result.dedupKey = dedupKey;
44
+ }
45
+ if (data.type === "events_api") {
46
+ const payload = data.payload;
47
+ result.event = payload?.event;
48
+ }
49
+ const interactivePayload = extractSlackInteractivePayloadFromEnvelope(data);
50
+ if (interactivePayload) {
51
+ result.interactivePayload = interactivePayload;
52
+ }
53
+ return result;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * Extract thread info from an assistant_thread_started event.
61
+ */
62
+ export function extractThreadStarted(evt) {
63
+ const t = evt.assistant_thread;
64
+ if (!t)
65
+ return null;
66
+ const channelId = typeof t.channel_id === "string" && t.channel_id.length > 0 ? t.channel_id : null;
67
+ const threadTs = typeof t.thread_ts === "string" && t.thread_ts.length > 0 ? t.thread_ts : null;
68
+ const userId = typeof t.user_id === "string" && t.user_id.length > 0 ? t.user_id : null;
69
+ if (!channelId || !threadTs || !userId) {
70
+ return null;
71
+ }
72
+ const result = {
73
+ channelId,
74
+ threadTs,
75
+ userId,
76
+ };
77
+ const ctx = t.context;
78
+ if (ctx?.channel_id) {
79
+ result.context = buildSlackThreadContext(ctx.channel_id, ctx.team_id);
80
+ }
81
+ return result;
82
+ }
83
+ export function extractThreadContextChanged(evt) {
84
+ const t = evt.assistant_thread;
85
+ if (!t)
86
+ return null;
87
+ const threadTs = typeof t.thread_ts === "string" && t.thread_ts.length > 0 ? t.thread_ts : null;
88
+ if (!threadTs) {
89
+ return null;
90
+ }
91
+ const result = { threadTs };
92
+ const ctx = t.context;
93
+ if (ctx?.channel_id) {
94
+ result.context = buildSlackThreadContext(ctx.channel_id, ctx.team_id);
95
+ }
96
+ return result;
97
+ }
98
+ export function extractAppHomeOpened(evt) {
99
+ const userId = typeof evt.user === "string" && evt.user.length > 0 ? evt.user : null;
100
+ if (!userId)
101
+ return null;
102
+ const tab = typeof evt.tab === "string" && evt.tab.length > 0 ? evt.tab : "home";
103
+ return {
104
+ userId,
105
+ tab,
106
+ eventTs: typeof evt.event_ts === "string" && evt.event_ts.length > 0 ? evt.event_ts : null,
107
+ };
108
+ }
109
+ /**
110
+ * Classify an incoming Slack message event. Determines whether the
111
+ * message is relevant (DM, known thread, or bot mention) and
112
+ * extracts the cleaned fields.
113
+ */
114
+ export function classifyMessage(evt, botUserId, trackedThreadIds, isKnownThread) {
115
+ const subtype = typeof evt.subtype === "string" ? evt.subtype : undefined;
116
+ const allowsSubtype = subtype === undefined || subtype === "file_share";
117
+ if (!allowsSubtype || evt.bot_id)
118
+ return { relevant: false };
119
+ const text = evt.text ?? "";
120
+ const user = evt.user;
121
+ const threadTs = evt.thread_ts;
122
+ const channel = evt.channel;
123
+ const channelType = evt.channel_type;
124
+ const isKnown = !!threadTs && (isKnownThread?.(threadTs) ?? trackedThreadIds.has(threadTs));
125
+ const isDM = channelType === "im";
126
+ const isMention = botUserId != null && text.includes(`<@${botUserId}>`);
127
+ if (!isKnown && !isDM && !isMention)
128
+ return { relevant: false };
129
+ const effectiveTs = threadTs ?? evt.ts;
130
+ const isChannelMention = isMention && !isDM && !isKnown;
131
+ const cleanText = isChannelMention && botUserId ? stripBotMention(text, botUserId) : text;
132
+ const slackFiles = extractSlackMessageFileMetadata(evt.files);
133
+ const metadata = {
134
+ ...(subtype ? { slackSubtype: subtype } : {}),
135
+ ...(slackFiles.length > 0 ? { slackFiles } : {}),
136
+ };
137
+ return {
138
+ relevant: true,
139
+ threadTs: effectiveTs,
140
+ channel,
141
+ userId: user,
142
+ text: buildSlackInboundMessageText(cleanText, evt),
143
+ isDM,
144
+ isChannelMention,
145
+ messageTs: evt.ts ?? effectiveTs,
146
+ ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
147
+ };
148
+ }
149
+ /**
150
+ * Parse a member_joined_channel event. Returns null if required fields
151
+ * are missing.
152
+ */
153
+ export function parseMemberJoinedChannel(evt, botUserId) {
154
+ const user = evt.user;
155
+ const channel = evt.channel;
156
+ if (!user || !channel)
157
+ return null;
158
+ return { channel, isSelf: user === botUserId };
159
+ }
160
+ export async function fetchSlackMessageByTs(input) {
161
+ try {
162
+ const response = await input.slack("conversations.history", input.token, {
163
+ channel: input.channel,
164
+ oldest: input.messageTs,
165
+ latest: input.messageTs,
166
+ inclusive: true,
167
+ limit: 1,
168
+ });
169
+ const messages = response.messages ?? [];
170
+ return messages.find((message) => message.ts === input.messageTs) ?? messages[0] ?? null;
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ export async function addSlackReaction(input) {
177
+ try {
178
+ await input.slack("reactions.add", input.token, {
179
+ channel: input.channel,
180
+ timestamp: input.timestamp,
181
+ name: input.emoji,
182
+ });
183
+ }
184
+ catch {
185
+ /* already_reacted or non-critical */
186
+ }
187
+ }
188
+ export async function removeSlackReaction(input) {
189
+ try {
190
+ await input.slack("reactions.remove", input.token, {
191
+ channel: input.channel,
192
+ timestamp: input.timestamp,
193
+ name: input.emoji,
194
+ });
195
+ }
196
+ catch {
197
+ /* not_reacted or non-critical */
198
+ }
199
+ }
200
+ export async function resolveSlackUserName(input) {
201
+ const cached = input.cache?.get(input.userId);
202
+ if (cached)
203
+ return cached;
204
+ try {
205
+ const response = await input.slack("users.info", input.token, {
206
+ user: input.userId,
207
+ });
208
+ if (input.shouldUseResult && !input.shouldUseResult()) {
209
+ return input.userId;
210
+ }
211
+ const user = response.user;
212
+ const name = user.real_name ?? user.name ?? input.userId;
213
+ input.cache?.set(input.userId, name);
214
+ return name;
215
+ }
216
+ catch {
217
+ return input.userId;
218
+ }
219
+ }
220
+ export async function resolveSlackChannelId(input) {
221
+ if (isChannelId(input.nameOrId))
222
+ return input.nameOrId;
223
+ const name = input.nameOrId.replace(/^#/, "");
224
+ const cached = input.cache?.get(name);
225
+ if (cached)
226
+ return cached;
227
+ let cursor;
228
+ do {
229
+ const body = {
230
+ types: "public_channel,private_channel",
231
+ limit: 200,
232
+ };
233
+ if (cursor) {
234
+ body.cursor = cursor;
235
+ }
236
+ const response = await input.slack("conversations.list", input.token, body);
237
+ const channels = response.channels ?? [];
238
+ for (const channel of channels) {
239
+ input.cache?.set(channel.name, channel.id);
240
+ }
241
+ const resolved = input.cache?.get(name) ?? channels.find((channel) => channel.name === name)?.id;
242
+ if (resolved) {
243
+ return resolved;
244
+ }
245
+ cursor =
246
+ response.response_metadata?.next_cursor ||
247
+ undefined;
248
+ } while (cursor);
249
+ throw new Error(`Channel "${name}" not found.`);
250
+ }
251
+ export async function clearSlackThreadStatus(input) {
252
+ try {
253
+ await input.slack("assistant.threads.setStatus", input.token, {
254
+ channel_id: input.channelId,
255
+ thread_ts: input.threadTs,
256
+ status: "",
257
+ });
258
+ }
259
+ catch {
260
+ /* non-critical */
261
+ }
262
+ }
263
+ export async function setSlackSuggestedPrompts(input) {
264
+ try {
265
+ await input.slack("assistant.threads.setSuggestedPrompts", input.token, {
266
+ channel_id: input.channelId,
267
+ thread_ts: input.threadTs,
268
+ prompts: input.prompts,
269
+ });
270
+ }
271
+ catch {
272
+ /* non-critical */
273
+ }
274
+ }
275
+ export async function resolveSlackThreadOwnerHint(input) {
276
+ if (!input.channel || !input.threadTs) {
277
+ return null;
278
+ }
279
+ const cacheKey = `${input.channel}:${input.threadTs}`;
280
+ const cached = input.cache?.get(cacheKey);
281
+ if (cached) {
282
+ return cached;
283
+ }
284
+ try {
285
+ const response = await input.slack("conversations.replies", input.token, {
286
+ channel: input.channel,
287
+ ts: input.threadTs,
288
+ limit: input.limit ?? 200,
289
+ include_all_metadata: true,
290
+ });
291
+ const replies = response.messages ?? [];
292
+ const hint = extractPiAgentThreadOwnerHint(replies);
293
+ if (hint) {
294
+ input.cache?.set(cacheKey, hint);
295
+ }
296
+ return hint;
297
+ }
298
+ catch {
299
+ return null;
300
+ }
301
+ }
302
+ export const RECONNECT_DELAY_MS = 5000;
303
+ export class SlackSocketModeClient {
304
+ config;
305
+ botUserId = null;
306
+ ws = null;
307
+ reconnectTimer = null;
308
+ shuttingDown = false;
309
+ constructor(config) {
310
+ this.config = config;
311
+ }
312
+ getBotUserId() {
313
+ return this.botUserId;
314
+ }
315
+ isConnected() {
316
+ return this.ws?.readyState === WebSocket.OPEN;
317
+ }
318
+ async connect() {
319
+ this.shuttingDown = false;
320
+ if (this.config.resolveBotUserIdOnConnect ?? true) {
321
+ const auth = await this.config.slack("auth.test", this.config.botToken);
322
+ this.botUserId = typeof auth.user_id === "string" ? auth.user_id : null;
323
+ }
324
+ await this.connectSocketMode();
325
+ }
326
+ async disconnect() {
327
+ this.shuttingDown = true;
328
+ if (this.reconnectTimer) {
329
+ clearTimeout(this.reconnectTimer);
330
+ this.reconnectTimer = null;
331
+ }
332
+ try {
333
+ this.ws?.close();
334
+ }
335
+ catch {
336
+ /* ignore close errors */
337
+ }
338
+ this.ws = null;
339
+ await this.config.abortAndWait?.();
340
+ }
341
+ async connectSocketMode() {
342
+ if (this.shuttingDown)
343
+ return;
344
+ try {
345
+ const response = await this.config.slack("apps.connections.open", this.config.appToken);
346
+ this.ws = new WebSocket(response.url);
347
+ this.ws.addEventListener("open", () => {
348
+ this.config.onOpen?.();
349
+ });
350
+ this.ws.addEventListener("message", (event) => {
351
+ void this.handleFrame(String(event.data)).catch((error) => {
352
+ this.config.onError?.(error);
353
+ });
354
+ });
355
+ this.ws.addEventListener("close", () => {
356
+ if (!this.shuttingDown) {
357
+ this.scheduleReconnect();
358
+ }
359
+ });
360
+ this.ws.addEventListener("error", () => {
361
+ /* close fires after error — handled there */
362
+ });
363
+ }
364
+ catch (error) {
365
+ if (!isAbortError(error)) {
366
+ this.config.onError?.(error);
367
+ }
368
+ this.scheduleReconnect();
369
+ }
370
+ }
371
+ async handleFrame(raw) {
372
+ if (this.shuttingDown)
373
+ return;
374
+ const envelope = parseSocketFrame(raw);
375
+ if (!envelope)
376
+ return;
377
+ if (envelope.envelopeId) {
378
+ this.ws?.send(JSON.stringify({ envelope_id: envelope.envelopeId }));
379
+ }
380
+ const dedupKey = envelope.dedupKey ?? null;
381
+ try {
382
+ if (dedupKey) {
383
+ if (this.config.dedup?.has(dedupKey)) {
384
+ return;
385
+ }
386
+ this.config.dedup?.add(dedupKey);
387
+ }
388
+ if (envelope.type === "disconnect") {
389
+ this.scheduleReconnect();
390
+ return;
391
+ }
392
+ if (envelope.interactivePayload) {
393
+ let normalized = null;
394
+ if (envelope.interactivePayload.type === "block_actions") {
395
+ normalized = normalizeSlackBlockActionPayload(envelope.interactivePayload);
396
+ }
397
+ else if (envelope.interactivePayload.type === "view_submission") {
398
+ normalized = normalizeSlackViewSubmissionPayload(envelope.interactivePayload);
399
+ }
400
+ if (normalized) {
401
+ await this.config.onInteractive?.(normalized);
402
+ }
403
+ return;
404
+ }
405
+ if (!envelope.event)
406
+ return;
407
+ const evt = envelope.event;
408
+ switch (evt.type) {
409
+ case "assistant_thread_started": {
410
+ const parsed = extractThreadStarted(evt);
411
+ if (parsed) {
412
+ await this.config.onThreadStarted?.(parsed);
413
+ }
414
+ break;
415
+ }
416
+ case "assistant_thread_context_changed": {
417
+ const parsed = extractThreadContextChanged(evt);
418
+ if (parsed) {
419
+ await this.config.onThreadContextChanged?.(parsed);
420
+ }
421
+ break;
422
+ }
423
+ case "message":
424
+ await this.config.onMessage?.(evt);
425
+ break;
426
+ case "reaction_added":
427
+ await this.config.onReactionAdded?.(evt);
428
+ break;
429
+ case "member_joined_channel": {
430
+ const parsed = parseMemberJoinedChannel(evt, this.botUserId);
431
+ if (parsed) {
432
+ await this.config.onMemberJoinedChannel?.(parsed);
433
+ }
434
+ break;
435
+ }
436
+ case "app_home_opened": {
437
+ const parsed = extractAppHomeOpened(evt);
438
+ if (parsed && parsed.tab === "home") {
439
+ await this.config.onAppHomeOpened?.(parsed);
440
+ }
441
+ break;
442
+ }
443
+ }
444
+ }
445
+ catch (error) {
446
+ if (dedupKey) {
447
+ this.config.dedup?.delete?.(dedupKey);
448
+ }
449
+ throw error;
450
+ }
451
+ }
452
+ scheduleReconnect() {
453
+ if (this.shuttingDown || this.reconnectTimer)
454
+ return;
455
+ this.config.onReconnectScheduled?.();
456
+ this.reconnectTimer = setTimeout(() => {
457
+ this.reconnectTimer = null;
458
+ void this.connectSocketMode();
459
+ }, this.config.reconnectDelayMs ?? RECONNECT_DELAY_MS);
460
+ this.reconnectTimer.unref?.();
461
+ }
462
+ }
@@ -0,0 +1,2 @@
1
+ export { callSlackAPI as callSlackApi } from "./helpers.js";
2
+ export type { SlackResult } from "./helpers.js";
@@ -0,0 +1 @@
1
+ export { callSlackAPI as callSlackApi } from "./helpers.js";
@@ -0,0 +1,36 @@
1
+ export type SlackBlock = Record<string, unknown>;
2
+ export interface SlackNormalizedBlockAction {
3
+ actionId: string;
4
+ blockId?: string;
5
+ text?: string;
6
+ type?: string;
7
+ style?: string;
8
+ url?: string;
9
+ value?: string;
10
+ parsedValue?: unknown;
11
+ actionTs?: string;
12
+ }
13
+ export interface SlackBlockActionInboxEvent {
14
+ channel: string;
15
+ threadTs: string;
16
+ userId: string;
17
+ text: string;
18
+ timestamp: string;
19
+ metadata: Record<string, unknown>;
20
+ }
21
+ export interface SlackViewSubmissionInboxEvent {
22
+ channel: string;
23
+ threadTs: string;
24
+ userId: string;
25
+ text: string;
26
+ timestamp: string;
27
+ metadata: Record<string, unknown>;
28
+ }
29
+ export type SlackInteractiveInboxEvent = SlackBlockActionInboxEvent | SlackViewSubmissionInboxEvent;
30
+ export declare function normalizeSlackBlocksInput(blocks: unknown): SlackBlock[];
31
+ export declare function summarizeSlackBlocksForPolicy(blocks: unknown): string;
32
+ export declare function encodeSlackBlockActionValue(value: unknown): string;
33
+ export declare function extractSlackInteractivePayloadFromEnvelope(envelope: Record<string, unknown>): Record<string, unknown> | null;
34
+ export declare function extractSlackBlockActionsPayloadFromEnvelope(envelope: Record<string, unknown>): Record<string, unknown> | null;
35
+ export declare function normalizeSlackBlockActionPayload(payload: Record<string, unknown>): SlackBlockActionInboxEvent | null;
36
+ export declare function normalizeSlackViewSubmissionPayload(payload: Record<string, unknown>): SlackViewSubmissionInboxEvent | null;