@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.
- package/LICENSE +21 -0
- package/README.md +570 -0
- package/dist/activity-log.d.ts +62 -0
- package/dist/activity-log.js +293 -0
- package/dist/agent-completion-runtime.d.ts +17 -0
- package/dist/agent-completion-runtime.js +26 -0
- package/dist/agent-event-runtime.d.ts +13 -0
- package/dist/agent-event-runtime.js +27 -0
- package/dist/agent-prompt-guidance.d.ts +22 -0
- package/dist/agent-prompt-guidance.js +46 -0
- package/dist/broker/adapters/slack.d.ts +74 -0
- package/dist/broker/adapters/slack.js +563 -0
- package/dist/broker/adapters/types.d.ts +1 -0
- package/dist/broker/adapters/types.js +1 -0
- package/dist/broker/agent-messaging.d.ts +1 -0
- package/dist/broker/agent-messaging.js +1 -0
- package/dist/broker/auth.d.ts +1 -0
- package/dist/broker/auth.js +1 -0
- package/dist/broker/client.d.ts +167 -0
- package/dist/broker/client.js +624 -0
- package/dist/broker/control-plane-dashboard.d.ts +98 -0
- package/dist/broker/control-plane-dashboard.js +213 -0
- package/dist/broker/ghost-reaper.d.ts +49 -0
- package/dist/broker/ghost-reaper.js +209 -0
- package/dist/broker/index.d.ts +36 -0
- package/dist/broker/index.js +90 -0
- package/dist/broker/leader.d.ts +1 -0
- package/dist/broker/leader.js +1 -0
- package/dist/broker/maintenance.d.ts +1 -0
- package/dist/broker/maintenance.js +1 -0
- package/dist/broker/message-send.d.ts +1 -0
- package/dist/broker/message-send.js +1 -0
- package/dist/broker/paths.d.ts +1 -0
- package/dist/broker/paths.js +1 -0
- package/dist/broker/raw-tcp-loopback.d.ts +1 -0
- package/dist/broker/raw-tcp-loopback.js +1 -0
- package/dist/broker/router.d.ts +1 -0
- package/dist/broker/router.js +1 -0
- package/dist/broker/schema.d.ts +9 -0
- package/dist/broker/schema.js +63 -0
- package/dist/broker/socket-server.d.ts +120 -0
- package/dist/broker/socket-server.js +1087 -0
- package/dist/broker/types.d.ts +1 -0
- package/dist/broker/types.js +1 -0
- package/dist/broker-delivery.d.ts +10 -0
- package/dist/broker-delivery.js +26 -0
- package/dist/broker-inbound-persistence.d.ts +11 -0
- package/dist/broker-inbound-persistence.js +22 -0
- package/dist/broker-prompt-loader.d.ts +34 -0
- package/dist/broker-prompt-loader.js +185 -0
- package/dist/broker-runtime-access.d.ts +14 -0
- package/dist/broker-runtime-access.js +20 -0
- package/dist/broker-runtime.d.ts +100 -0
- package/dist/broker-runtime.js +533 -0
- package/dist/broker-thread-owner-hints.d.ts +10 -0
- package/dist/broker-thread-owner-hints.js +14 -0
- package/dist/canvases.d.ts +80 -0
- package/dist/canvases.js +221 -0
- package/dist/command-registration-runtime.d.ts +9 -0
- package/dist/command-registration-runtime.js +9 -0
- package/dist/core-tool-guardrails.d.ts +24 -0
- package/dist/core-tool-guardrails.js +66 -0
- package/dist/deploy-manifest.d.ts +27 -0
- package/dist/deploy-manifest.js +197 -0
- package/dist/follower-delivery.d.ts +16 -0
- package/dist/follower-delivery.js +70 -0
- package/dist/follower-runtime.d.ts +61 -0
- package/dist/follower-runtime.js +350 -0
- package/dist/git-metadata.d.ts +35 -0
- package/dist/git-metadata.js +88 -0
- package/dist/guardrails.d.ts +69 -0
- package/dist/guardrails.js +283 -0
- package/dist/helpers.d.ts +550 -0
- package/dist/helpers.js +2711 -0
- package/dist/home-tab.d.ts +27 -0
- package/dist/home-tab.js +242 -0
- package/dist/imessage-tools.d.ts +33 -0
- package/dist/imessage-tools.js +96 -0
- package/dist/inbox-drain-runtime.d.ts +30 -0
- package/dist/inbox-drain-runtime.js +71 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1330 -0
- package/dist/persisted-runtime-state.d.ts +52 -0
- package/dist/persisted-runtime-state.js +98 -0
- package/dist/pinet-activity-formatting.d.ts +20 -0
- package/dist/pinet-activity-formatting.js +43 -0
- package/dist/pinet-agent-status.d.ts +36 -0
- package/dist/pinet-agent-status.js +48 -0
- package/dist/pinet-commands.d.ts +70 -0
- package/dist/pinet-commands.js +402 -0
- package/dist/pinet-confirmation-replies.d.ts +7 -0
- package/dist/pinet-confirmation-replies.js +30 -0
- package/dist/pinet-control-plane-dashboard.d.ts +34 -0
- package/dist/pinet-control-plane-dashboard.js +86 -0
- package/dist/pinet-home-tabs.d.ts +36 -0
- package/dist/pinet-home-tabs.js +101 -0
- package/dist/pinet-maintenance-delivery.d.ts +22 -0
- package/dist/pinet-maintenance-delivery.js +45 -0
- package/dist/pinet-mesh-ops.d.ts +97 -0
- package/dist/pinet-mesh-ops.js +266 -0
- package/dist/pinet-registration-gate.d.ts +14 -0
- package/dist/pinet-registration-gate.js +42 -0
- package/dist/pinet-remote-control-acks.d.ts +16 -0
- package/dist/pinet-remote-control-acks.js +46 -0
- package/dist/pinet-remote-control.d.ts +17 -0
- package/dist/pinet-remote-control.js +74 -0
- package/dist/pinet-runtime-composition.d.ts +27 -0
- package/dist/pinet-runtime-composition.js +20 -0
- package/dist/pinet-tools.d.ts +67 -0
- package/dist/pinet-tools.js +1211 -0
- package/dist/prompts/broker/default.md +41 -0
- package/dist/prompts/broker/tmux.md +49 -0
- package/dist/ralph-loop.d.ts +76 -0
- package/dist/ralph-loop.js +524 -0
- package/dist/reaction-triggers.d.ts +25 -0
- package/dist/reaction-triggers.js +171 -0
- package/dist/repo-tool-guardrails.d.ts +20 -0
- package/dist/repo-tool-guardrails.js +81 -0
- package/dist/runtime-agent-context.d.ts +93 -0
- package/dist/runtime-agent-context.js +302 -0
- package/dist/runtime-mode.d.ts +10 -0
- package/dist/runtime-mode.js +39 -0
- package/dist/scheduled-wakeups.d.ts +1 -0
- package/dist/scheduled-wakeups.js +1 -0
- package/dist/session-ui-runtime.d.ts +20 -0
- package/dist/session-ui-runtime.js +163 -0
- package/dist/single-player-runtime.d.ts +71 -0
- package/dist/single-player-runtime.js +436 -0
- package/dist/skins/cosmere.json +1571 -0
- package/dist/skins/foundation.json +1304 -0
- package/dist/slack-access.d.ts +186 -0
- package/dist/slack-access.js +462 -0
- package/dist/slack-api.d.ts +2 -0
- package/dist/slack-api.js +1 -0
- package/dist/slack-block-kit.d.ts +36 -0
- package/dist/slack-block-kit.js +242 -0
- package/dist/slack-export.d.ts +31 -0
- package/dist/slack-export.js +206 -0
- package/dist/slack-message-context.d.ts +15 -0
- package/dist/slack-message-context.js +207 -0
- package/dist/slack-modals.d.ts +13 -0
- package/dist/slack-modals.js +79 -0
- package/dist/slack-pinet-runtime-adapter.d.ts +16 -0
- package/dist/slack-pinet-runtime-adapter.js +72 -0
- package/dist/slack-presence.d.ts +36 -0
- package/dist/slack-presence.js +89 -0
- package/dist/slack-request-runtime.d.ts +8 -0
- package/dist/slack-request-runtime.js +19 -0
- package/dist/slack-runtime-access.d.ts +28 -0
- package/dist/slack-runtime-access.js +107 -0
- package/dist/slack-scope-diagnostics.d.ts +31 -0
- package/dist/slack-scope-diagnostics.js +235 -0
- package/dist/slack-socket-dedup.d.ts +7 -0
- package/dist/slack-socket-dedup.js +135 -0
- package/dist/slack-thread-status.d.ts +41 -0
- package/dist/slack-thread-status.js +156 -0
- package/dist/slack-tool-policy-runtime.d.ts +36 -0
- package/dist/slack-tool-policy-runtime.js +96 -0
- package/dist/slack-tools.d.ts +52 -0
- package/dist/slack-tools.js +2688 -0
- package/dist/slack-turn-guardrails.d.ts +19 -0
- package/dist/slack-turn-guardrails.js +36 -0
- package/dist/slack-upload.d.ts +49 -0
- package/dist/slack-upload.js +224 -0
- package/dist/task-assignments.d.ts +45 -0
- package/dist/task-assignments.js +527 -0
- package/dist/thread-confirmations.d.ts +22 -0
- package/dist/thread-confirmations.js +139 -0
- package/dist/tool-registration-runtime.d.ts +13 -0
- package/dist/tool-registration-runtime.js +13 -0
- package/dist/ttl-cache.d.ts +47 -0
- package/dist/ttl-cache.js +105 -0
- package/manifest.yaml +57 -0
- package/package.json +60 -0
- package/skills/pinet-skin-creator/SKILL.md +109 -0
- package/skills/pinet-skin-creator/references/descriptor-format.md +99 -0
- package/skills/pinet-skin-creator/references/safety-checklist.md +56 -0
- package/skills/pinet-skin-creator/templates/pinet-skin-descriptor.json +77 -0
- package/skills/slack-bridge/SKILL.md +360 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { addSlackReaction, buildSlackThreadRuntimeScope, classifyMessage, extractAppHomeOpened, extractThreadContextChanged, extractThreadStarted, fetchSlackMessageByTs, isSlackUserAllowed, removeSlackReaction, resolveSlackUserName, setSlackSuggestedPrompts, SlackSocketModeClient, } from "../../slack-access.js";
|
|
2
|
+
import { createAbortableOperationTracker, callSlackAPI, isAbortError, buildAllowlist, buildPinetControlMessage, buildPinetControlMetadata, } from "../../helpers.js";
|
|
3
|
+
import { buildReactionTriggerMessage, normalizeReactionName, resolveReactionCommands, } from "../../reaction-triggers.js";
|
|
4
|
+
import { TtlCache, TtlSet } from "../../ttl-cache.js";
|
|
5
|
+
import { SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE, SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS, } from "../../slack-access.js";
|
|
6
|
+
import { DEFAULT_SLACK_THREAD_STATUS, SlackThreadStatusManager, } from "../../slack-thread-status.js";
|
|
7
|
+
export { classifyMessage, extractAppHomeOpened, extractThreadStarted, parseMemberJoinedChannel, parseSocketFrame, RECONNECT_DELAY_MS, } from "../../slack-access.js";
|
|
8
|
+
export const SLACK_THREAD_CACHE_MAX_SIZE = 5000;
|
|
9
|
+
export const SLACK_THREAD_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
10
|
+
export const SLACK_PENDING_ATTENTION_MAX_THREADS = 1000;
|
|
11
|
+
export const SLACK_PENDING_ATTENTION_TTL_MS = 2 * 60 * 60 * 1000;
|
|
12
|
+
export const SLACK_PENDING_ATTENTION_MAX_MESSAGES_PER_THREAD = 50;
|
|
13
|
+
export class SlackAdapter {
|
|
14
|
+
name = "slack";
|
|
15
|
+
config;
|
|
16
|
+
allowlist;
|
|
17
|
+
slackRequests = createAbortableOperationTracker();
|
|
18
|
+
botUserId = null;
|
|
19
|
+
socketMode = null;
|
|
20
|
+
shuttingDown = false;
|
|
21
|
+
inboundHandler = null;
|
|
22
|
+
reactionCommands;
|
|
23
|
+
threads;
|
|
24
|
+
userNames = new TtlCache({
|
|
25
|
+
maxSize: 2000,
|
|
26
|
+
ttlMs: 60 * 60 * 1000,
|
|
27
|
+
});
|
|
28
|
+
processedSocketDeliveries = new TtlSet({
|
|
29
|
+
maxSize: SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE,
|
|
30
|
+
ttlMs: SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS,
|
|
31
|
+
});
|
|
32
|
+
pendingEyes;
|
|
33
|
+
threadStatuses;
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.config = config;
|
|
36
|
+
this.threads = new TtlCache({
|
|
37
|
+
maxSize: SLACK_THREAD_CACHE_MAX_SIZE,
|
|
38
|
+
ttlMs: SLACK_THREAD_CACHE_TTL_MS,
|
|
39
|
+
});
|
|
40
|
+
this.pendingEyes = new TtlCache({
|
|
41
|
+
maxSize: SLACK_PENDING_ATTENTION_MAX_THREADS,
|
|
42
|
+
ttlMs: SLACK_PENDING_ATTENTION_TTL_MS,
|
|
43
|
+
});
|
|
44
|
+
this.threadStatuses = new SlackThreadStatusManager({
|
|
45
|
+
slack: this.callSlack.bind(this),
|
|
46
|
+
getBotToken: () => this.config.botToken,
|
|
47
|
+
formatError: errorMsg,
|
|
48
|
+
logger: console,
|
|
49
|
+
});
|
|
50
|
+
this.allowlist = buildAllowlist({
|
|
51
|
+
allowedUsers: config.allowedUsers,
|
|
52
|
+
allowAllWorkspaceUsers: config.allowAllWorkspaceUsers,
|
|
53
|
+
}, undefined, undefined);
|
|
54
|
+
this.reactionCommands = resolveReactionCommands(config.reactionCommands);
|
|
55
|
+
}
|
|
56
|
+
async callSlack(method, token, body) {
|
|
57
|
+
return this.slackRequests.run((signal) => callSlackAPI(method, token, body, { signal }));
|
|
58
|
+
}
|
|
59
|
+
async connect() {
|
|
60
|
+
this.shuttingDown = false;
|
|
61
|
+
this.slackRequests = createAbortableOperationTracker();
|
|
62
|
+
this.socketMode = new SlackSocketModeClient({
|
|
63
|
+
slack: this.callSlack.bind(this),
|
|
64
|
+
botToken: this.config.botToken,
|
|
65
|
+
appToken: this.config.appToken,
|
|
66
|
+
dedup: this.processedSocketDeliveries,
|
|
67
|
+
abortAndWait: () => this.slackRequests.abortAndWait(),
|
|
68
|
+
onThreadStarted: (event) => this.onThreadStarted(event),
|
|
69
|
+
onThreadContextChanged: (event) => this.onContextChanged(event),
|
|
70
|
+
onMessage: (event) => this.onMessage(event),
|
|
71
|
+
onReactionAdded: (event) => this.onReactionAdded(event),
|
|
72
|
+
onMemberJoinedChannel: (event) => this.onMemberJoined(event),
|
|
73
|
+
onAppHomeOpened: (event) => this.onAppHomeOpened(event),
|
|
74
|
+
onInteractive: (event) => this.emitInteractiveInbound(event),
|
|
75
|
+
onError: (error) => {
|
|
76
|
+
if (!isAbortError(error)) {
|
|
77
|
+
console.error(`[slack-adapter] Socket Mode: ${errorMsg(error)}`);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
await this.socketMode.connect();
|
|
82
|
+
this.botUserId = this.socketMode.getBotUserId();
|
|
83
|
+
}
|
|
84
|
+
async disconnect() {
|
|
85
|
+
this.shuttingDown = true;
|
|
86
|
+
await this.threadStatuses.clearAll();
|
|
87
|
+
const socketMode = this.socketMode;
|
|
88
|
+
this.socketMode = null;
|
|
89
|
+
if (socketMode) {
|
|
90
|
+
await socketMode.disconnect();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
await this.slackRequests.abortAndWait();
|
|
94
|
+
}
|
|
95
|
+
onInbound(handler) {
|
|
96
|
+
this.inboundHandler = handler;
|
|
97
|
+
}
|
|
98
|
+
async invokeCapability(request) {
|
|
99
|
+
if (request.capability !== "api.call") {
|
|
100
|
+
throw new Error(`Unsupported Slack adapter capability: ${request.capability}`);
|
|
101
|
+
}
|
|
102
|
+
const method = typeof request.params.method === "string" ? request.params.method.trim() : "";
|
|
103
|
+
if (!method) {
|
|
104
|
+
throw new Error("method is required for Slack api.call capability");
|
|
105
|
+
}
|
|
106
|
+
const body = request.params.params &&
|
|
107
|
+
typeof request.params.params === "object" &&
|
|
108
|
+
!Array.isArray(request.params.params)
|
|
109
|
+
? request.params.params
|
|
110
|
+
: {};
|
|
111
|
+
const result = await this.callSlack(method, this.config.botToken, body);
|
|
112
|
+
return {
|
|
113
|
+
result,
|
|
114
|
+
...this.buildSlackApiCapabilityEffects(method, body, result),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async send(msg) {
|
|
118
|
+
const contentSlackBlocks = msg.content?.slackBlocks;
|
|
119
|
+
const slackBlocks = contentSlackBlocks && contentSlackBlocks.length > 0
|
|
120
|
+
? contentSlackBlocks
|
|
121
|
+
: msg.blocks && msg.blocks.length > 0
|
|
122
|
+
? msg.blocks
|
|
123
|
+
: undefined;
|
|
124
|
+
const body = {
|
|
125
|
+
channel: msg.channel,
|
|
126
|
+
text: msg.content?.text ?? msg.text,
|
|
127
|
+
thread_ts: msg.threadId,
|
|
128
|
+
...(slackBlocks ? { blocks: slackBlocks } : {}),
|
|
129
|
+
};
|
|
130
|
+
const scope = msg.scope ?? this.resolveScopeForThread(msg.threadId, msg.channel);
|
|
131
|
+
if (msg.agentName ?? msg.agentOwnerToken ?? msg.metadata ?? msg.scope) {
|
|
132
|
+
body.metadata = {
|
|
133
|
+
event_type: "pi_agent_msg",
|
|
134
|
+
event_payload: {
|
|
135
|
+
...(msg.agentName ? { agent: msg.agentName } : {}),
|
|
136
|
+
...(msg.agentOwnerToken ? { agent_owner: msg.agentOwnerToken } : {}),
|
|
137
|
+
...(msg.agentEmoji ? { emoji: msg.agentEmoji } : {}),
|
|
138
|
+
...(scope ? { scope } : {}),
|
|
139
|
+
...msg.metadata,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
await this.callSlack("chat.postMessage", this.config.botToken, body);
|
|
144
|
+
if (this.shuttingDown)
|
|
145
|
+
return;
|
|
146
|
+
const pending = this.pendingEyes.get(msg.threadId);
|
|
147
|
+
if (pending) {
|
|
148
|
+
for (const entry of pending) {
|
|
149
|
+
void this.removeReaction(entry.channel, entry.messageTs, "eyes");
|
|
150
|
+
}
|
|
151
|
+
this.pendingEyes.delete(msg.threadId);
|
|
152
|
+
}
|
|
153
|
+
void this.clearThreadStatus(msg.channel, msg.threadId);
|
|
154
|
+
}
|
|
155
|
+
buildSlackApiCapabilityEffects(method, body, result) {
|
|
156
|
+
if (method !== "chat.postMessage") {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
const threadTs = typeof body.thread_ts === "string" ? body.thread_ts : "";
|
|
160
|
+
const messageTs = typeof result.ts === "string" ? result.ts : "";
|
|
161
|
+
const channel = typeof body.channel === "string"
|
|
162
|
+
? body.channel
|
|
163
|
+
: typeof result.channel === "string"
|
|
164
|
+
? result.channel
|
|
165
|
+
: undefined;
|
|
166
|
+
const threadId = threadTs || messageTs;
|
|
167
|
+
if (!threadId) {
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
effects: {
|
|
172
|
+
claimThread: {
|
|
173
|
+
threadId,
|
|
174
|
+
...(channel ? { channel } : {}),
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
getBotUserId() {
|
|
180
|
+
return this.socketMode?.getBotUserId() ?? this.botUserId;
|
|
181
|
+
}
|
|
182
|
+
getTrackedThreadIds() {
|
|
183
|
+
this.threads.sweep();
|
|
184
|
+
return new Set([...this.threads.entries()].map(([threadTs]) => threadTs));
|
|
185
|
+
}
|
|
186
|
+
isConnected() {
|
|
187
|
+
return this.socketMode?.isConnected() ?? false;
|
|
188
|
+
}
|
|
189
|
+
resolveScopeForThread(threadTs, channelId) {
|
|
190
|
+
return buildSlackThreadRuntimeScope({
|
|
191
|
+
channelId,
|
|
192
|
+
context: this.getThread(threadTs)?.context ?? null,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async onThreadStarted(event) {
|
|
196
|
+
if (this.shuttingDown)
|
|
197
|
+
return;
|
|
198
|
+
const parsed = isParsedThreadStarted(event) ? event : extractThreadStarted(event);
|
|
199
|
+
if (!parsed)
|
|
200
|
+
return;
|
|
201
|
+
const info = {
|
|
202
|
+
channelId: parsed.channelId,
|
|
203
|
+
threadTs: parsed.threadTs,
|
|
204
|
+
userId: parsed.userId,
|
|
205
|
+
};
|
|
206
|
+
if (parsed.context) {
|
|
207
|
+
info.context = parsed.context;
|
|
208
|
+
}
|
|
209
|
+
this.threads.set(info.threadTs, info);
|
|
210
|
+
try {
|
|
211
|
+
this.config.rememberKnownThread?.(info.threadTs, info.channelId, info.context ?? null);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
/* best effort — DB cache sync must not break Slack event handling */
|
|
215
|
+
}
|
|
216
|
+
await this.setSuggestedPrompts(info.channelId, info.threadTs);
|
|
217
|
+
}
|
|
218
|
+
onContextChanged(event) {
|
|
219
|
+
if (this.shuttingDown)
|
|
220
|
+
return;
|
|
221
|
+
const parsed = isParsedThreadContextChanged(event) ? event : extractThreadContextChanged(event);
|
|
222
|
+
if (!parsed)
|
|
223
|
+
return;
|
|
224
|
+
const existing = this.getThread(parsed.threadTs);
|
|
225
|
+
if (!existing || !parsed.context)
|
|
226
|
+
return;
|
|
227
|
+
existing.context = parsed.context;
|
|
228
|
+
this.threads.set(parsed.threadTs, existing);
|
|
229
|
+
try {
|
|
230
|
+
this.config.rememberKnownThread?.(parsed.threadTs, existing.channelId, existing.context);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
/* best effort — DB cache sync must not break Slack event handling */
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async onAppHomeOpened(event) {
|
|
237
|
+
if (this.shuttingDown)
|
|
238
|
+
return;
|
|
239
|
+
const parsed = isParsedAppHomeOpened(event) ? event : extractAppHomeOpened(event);
|
|
240
|
+
if (!parsed || parsed.tab !== "home") {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
await this.config.onAppHomeOpened?.(parsed);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error(`[slack-adapter] Home tab callback failed: ${errorMsg(error)}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async fetchMessageByTs(channel, messageTs) {
|
|
251
|
+
return fetchSlackMessageByTs({
|
|
252
|
+
slack: this.callSlack.bind(this),
|
|
253
|
+
token: this.config.botToken,
|
|
254
|
+
channel,
|
|
255
|
+
messageTs,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async onReactionAdded(evt) {
|
|
259
|
+
if (this.shuttingDown)
|
|
260
|
+
return;
|
|
261
|
+
const item = evt.item;
|
|
262
|
+
const userId = evt.user;
|
|
263
|
+
const rawReaction = evt.reaction;
|
|
264
|
+
if (!item || item.type !== "message" || !item.channel || !item.ts || !userId || !rawReaction) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (userId === this.botUserId)
|
|
268
|
+
return;
|
|
269
|
+
let reactionName;
|
|
270
|
+
try {
|
|
271
|
+
reactionName = normalizeReactionName(rawReaction);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const command = this.reactionCommands.get(reactionName);
|
|
277
|
+
if (!command || !isSlackUserAllowed(this.allowlist, userId)) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const isInterruptReaction = command.action === "interrupt";
|
|
282
|
+
const reactedMessage = await this.fetchMessageByTs(item.channel, item.ts);
|
|
283
|
+
if (!reactedMessage && isInterruptReaction) {
|
|
284
|
+
throw new Error(`Unable to identify Slack thread for interrupt reaction ${item.ts} in channel ${item.channel}`);
|
|
285
|
+
}
|
|
286
|
+
const reactedMessageFetchStatus = reactedMessage ? "found" : "unavailable";
|
|
287
|
+
const threadTs = reactedMessage?.thread_ts ??
|
|
288
|
+
reactedMessage?.ts ??
|
|
289
|
+
item.ts;
|
|
290
|
+
if (!this.getThread(threadTs)) {
|
|
291
|
+
this.threads.set(threadTs, {
|
|
292
|
+
channelId: item.channel,
|
|
293
|
+
threadTs,
|
|
294
|
+
userId: reactedMessage?.user ?? userId,
|
|
295
|
+
});
|
|
296
|
+
try {
|
|
297
|
+
this.config.rememberKnownThread?.(threadTs, item.channel, null);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
/* best effort — DB cache sync must not break reaction handling */
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const reactorName = isInterruptReaction
|
|
304
|
+
? await this.resolveUser(userId).catch(() => userId)
|
|
305
|
+
: await this.resolveUser(userId);
|
|
306
|
+
if (this.shuttingDown)
|
|
307
|
+
return;
|
|
308
|
+
const reactedMessageAuthorId = reactedMessage?.user ?? evt.item_user;
|
|
309
|
+
const reactedMessageAuthor = isInterruptReaction
|
|
310
|
+
? (reactedMessageAuthorId ??
|
|
311
|
+
(reactedMessage?.bot_id ? "bot" : "unknown"))
|
|
312
|
+
: reactedMessageAuthorId
|
|
313
|
+
? await this.resolveUser(reactedMessageAuthorId)
|
|
314
|
+
: reactedMessage?.bot_id
|
|
315
|
+
? "bot"
|
|
316
|
+
: "unknown";
|
|
317
|
+
if (this.shuttingDown)
|
|
318
|
+
return;
|
|
319
|
+
const reactedMessageText = isInterruptReaction
|
|
320
|
+
? "(interrupt control; reacted message text omitted)"
|
|
321
|
+
: typeof reactedMessage?.text === "string" && reactedMessage.text.trim().length > 0
|
|
322
|
+
? reactedMessage.text
|
|
323
|
+
: reactedMessage
|
|
324
|
+
? "(no text)"
|
|
325
|
+
: "(message text unavailable; Slack did not return the reacted message, so use the channel/thread/message ids for context)";
|
|
326
|
+
const threadInfo = this.getThread(threadTs);
|
|
327
|
+
const reactionEventTs = evt.event_ts ?? item.ts;
|
|
328
|
+
this.inboundHandler?.({
|
|
329
|
+
source: "slack",
|
|
330
|
+
threadId: threadTs,
|
|
331
|
+
channel: item.channel,
|
|
332
|
+
userId,
|
|
333
|
+
userName: reactorName,
|
|
334
|
+
text: isInterruptReaction
|
|
335
|
+
? buildPinetControlMessage("interrupt")
|
|
336
|
+
: buildReactionTriggerMessage({
|
|
337
|
+
reactionName,
|
|
338
|
+
command,
|
|
339
|
+
reactorName,
|
|
340
|
+
channel: item.channel,
|
|
341
|
+
threadTs,
|
|
342
|
+
messageTs: item.ts,
|
|
343
|
+
reactedMessageText,
|
|
344
|
+
reactedMessageAuthor,
|
|
345
|
+
}),
|
|
346
|
+
timestamp: reactionEventTs,
|
|
347
|
+
metadata: {
|
|
348
|
+
reactionTrigger: true,
|
|
349
|
+
reactionName,
|
|
350
|
+
reactionAction: command.action,
|
|
351
|
+
...(isInterruptReaction
|
|
352
|
+
? {
|
|
353
|
+
...buildPinetControlMetadata("interrupt"),
|
|
354
|
+
kind: "pinet_control",
|
|
355
|
+
command: "interrupt",
|
|
356
|
+
slackReactionControl: true,
|
|
357
|
+
}
|
|
358
|
+
: {}),
|
|
359
|
+
reactorUserId: userId,
|
|
360
|
+
reactorName,
|
|
361
|
+
reactionEventTs,
|
|
362
|
+
referencedSource: "slack",
|
|
363
|
+
referencedChannel: item.channel,
|
|
364
|
+
referencedThreadTs: threadTs,
|
|
365
|
+
referencedMessageTs: item.ts,
|
|
366
|
+
referencedExternalId: `${item.channel}:${item.ts}`,
|
|
367
|
+
reactedMessageFetchStatus,
|
|
368
|
+
reactedMessageAuthor,
|
|
369
|
+
...(reactedMessageAuthorId ? { reactedMessageAuthorId } : {}),
|
|
370
|
+
},
|
|
371
|
+
scope: buildSlackThreadRuntimeScope({
|
|
372
|
+
channelId: item.channel,
|
|
373
|
+
context: threadInfo?.context,
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
376
|
+
await this.addReaction(item.channel, item.ts, "white_check_mark");
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
console.error(`[slack-adapter] reaction trigger failed: ${errorMsg(error)}`);
|
|
380
|
+
await this.addReaction(item.channel, item.ts, "x");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async onMessage(evt) {
|
|
384
|
+
if (this.shuttingDown)
|
|
385
|
+
return;
|
|
386
|
+
const classified = classifyMessage(evt, this.botUserId, this.getTrackedThreadIds(), this.config.isKnownThread);
|
|
387
|
+
if (!classified.relevant)
|
|
388
|
+
return;
|
|
389
|
+
const { threadTs, channel, userId, text, isDM, isChannelMention, messageTs, metadata } = classified;
|
|
390
|
+
if (isDM &&
|
|
391
|
+
typeof evt.thread_ts === "string" &&
|
|
392
|
+
this.shouldSuppressLegacyThreadedDm(threadTs)) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (!this.getThread(threadTs)) {
|
|
396
|
+
this.threads.set(threadTs, {
|
|
397
|
+
channelId: channel,
|
|
398
|
+
threadTs,
|
|
399
|
+
userId,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
if (!isSlackUserAllowed(this.allowlist, userId))
|
|
403
|
+
return;
|
|
404
|
+
void this.threadStatuses.begin(channel, threadTs, DEFAULT_SLACK_THREAD_STATUS);
|
|
405
|
+
void this.addReaction(channel, messageTs, "eyes");
|
|
406
|
+
const pending = this.pendingEyes.get(threadTs) ?? [];
|
|
407
|
+
pending.push({ channel, messageTs });
|
|
408
|
+
if (pending.length > SLACK_PENDING_ATTENTION_MAX_MESSAGES_PER_THREAD) {
|
|
409
|
+
pending.splice(0, pending.length - SLACK_PENDING_ATTENTION_MAX_MESSAGES_PER_THREAD);
|
|
410
|
+
}
|
|
411
|
+
this.pendingEyes.set(threadTs, pending);
|
|
412
|
+
const userName = await this.resolveUser(userId);
|
|
413
|
+
if (this.shuttingDown)
|
|
414
|
+
return;
|
|
415
|
+
const threadInfo = this.getThread(threadTs);
|
|
416
|
+
this.inboundHandler?.({
|
|
417
|
+
source: "slack",
|
|
418
|
+
threadId: threadTs,
|
|
419
|
+
channel,
|
|
420
|
+
userId,
|
|
421
|
+
userName,
|
|
422
|
+
text,
|
|
423
|
+
timestamp: messageTs,
|
|
424
|
+
scope: buildSlackThreadRuntimeScope({
|
|
425
|
+
channelId: channel,
|
|
426
|
+
context: threadInfo?.context,
|
|
427
|
+
}),
|
|
428
|
+
...(isChannelMention ? { isChannelMention: true } : {}),
|
|
429
|
+
...(metadata ? { metadata } : {}),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
async emitInteractiveInbound(normalized) {
|
|
433
|
+
if (!this.getThread(normalized.threadTs)) {
|
|
434
|
+
this.threads.set(normalized.threadTs, {
|
|
435
|
+
channelId: normalized.channel,
|
|
436
|
+
threadTs: normalized.threadTs,
|
|
437
|
+
userId: normalized.userId,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
this.config.rememberKnownThread?.(normalized.threadTs, normalized.channel, null);
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
/* best effort — DB cache sync must not break Slack event handling */
|
|
445
|
+
}
|
|
446
|
+
if (!isSlackUserAllowed(this.allowlist, normalized.userId))
|
|
447
|
+
return;
|
|
448
|
+
const userName = await this.resolveUser(normalized.userId);
|
|
449
|
+
if (this.shuttingDown)
|
|
450
|
+
return;
|
|
451
|
+
const threadInfo = this.getThread(normalized.threadTs);
|
|
452
|
+
this.inboundHandler?.({
|
|
453
|
+
source: "slack",
|
|
454
|
+
threadId: normalized.threadTs,
|
|
455
|
+
channel: normalized.channel,
|
|
456
|
+
userId: normalized.userId,
|
|
457
|
+
userName,
|
|
458
|
+
text: normalized.text,
|
|
459
|
+
timestamp: normalized.timestamp,
|
|
460
|
+
metadata: normalized.metadata,
|
|
461
|
+
scope: buildSlackThreadRuntimeScope({
|
|
462
|
+
channelId: normalized.channel,
|
|
463
|
+
context: threadInfo?.context,
|
|
464
|
+
}),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
onMemberJoined(event) {
|
|
468
|
+
if (!event.isSelf)
|
|
469
|
+
return;
|
|
470
|
+
this.inboundHandler?.({
|
|
471
|
+
source: "slack",
|
|
472
|
+
threadId: "",
|
|
473
|
+
channel: event.channel,
|
|
474
|
+
userId: "system",
|
|
475
|
+
text: `Bot was added to channel ${event.channel}`,
|
|
476
|
+
timestamp: String(Date.now() / 1000),
|
|
477
|
+
scope: buildSlackThreadRuntimeScope({ channelId: event.channel }),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
shouldSuppressLegacyThreadedDm(threadTs) {
|
|
481
|
+
const cached = this.threads.get(threadTs);
|
|
482
|
+
if (cached)
|
|
483
|
+
return false;
|
|
484
|
+
const known = this.config.getKnownThread?.(threadTs);
|
|
485
|
+
return !!known && known.channelId.startsWith("D") && !known.context;
|
|
486
|
+
}
|
|
487
|
+
getThread(threadTs) {
|
|
488
|
+
const cached = this.threads.get(threadTs);
|
|
489
|
+
if (cached) {
|
|
490
|
+
this.threads.set(threadTs, cached);
|
|
491
|
+
return cached;
|
|
492
|
+
}
|
|
493
|
+
const known = this.config.getKnownThread?.(threadTs);
|
|
494
|
+
if (!known?.channelId)
|
|
495
|
+
return undefined;
|
|
496
|
+
const restored = {
|
|
497
|
+
channelId: known.channelId,
|
|
498
|
+
threadTs,
|
|
499
|
+
userId: "",
|
|
500
|
+
...(known.context ? { context: known.context } : {}),
|
|
501
|
+
};
|
|
502
|
+
this.threads.set(threadTs, restored);
|
|
503
|
+
return restored;
|
|
504
|
+
}
|
|
505
|
+
async addReaction(channel, ts, emoji) {
|
|
506
|
+
await addSlackReaction({
|
|
507
|
+
slack: this.callSlack.bind(this),
|
|
508
|
+
token: this.config.botToken,
|
|
509
|
+
channel,
|
|
510
|
+
timestamp: ts,
|
|
511
|
+
emoji,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async removeReaction(channel, ts, emoji) {
|
|
515
|
+
await removeSlackReaction({
|
|
516
|
+
slack: this.callSlack.bind(this),
|
|
517
|
+
token: this.config.botToken,
|
|
518
|
+
channel,
|
|
519
|
+
timestamp: ts,
|
|
520
|
+
emoji,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
async resolveUser(userId) {
|
|
524
|
+
return resolveSlackUserName({
|
|
525
|
+
slack: this.callSlack.bind(this),
|
|
526
|
+
token: this.config.botToken,
|
|
527
|
+
userId,
|
|
528
|
+
cache: this.userNames,
|
|
529
|
+
shouldUseResult: () => !this.shuttingDown,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
async clearThreadStatus(channelId, threadTs) {
|
|
533
|
+
await this.threadStatuses.clear(channelId, threadTs);
|
|
534
|
+
}
|
|
535
|
+
async setSuggestedPrompts(channelId, threadTs) {
|
|
536
|
+
const prompts = this.config.suggestedPrompts ?? [
|
|
537
|
+
{ title: "Status", message: "What are you working on right now?" },
|
|
538
|
+
{ title: "Help", message: "I need help with something in the codebase" },
|
|
539
|
+
{ title: "Review", message: "Summarise the recent changes" },
|
|
540
|
+
];
|
|
541
|
+
await setSlackSuggestedPrompts({
|
|
542
|
+
slack: this.callSlack.bind(this),
|
|
543
|
+
token: this.config.botToken,
|
|
544
|
+
channelId,
|
|
545
|
+
threadTs,
|
|
546
|
+
prompts,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function isParsedThreadStarted(value) {
|
|
551
|
+
return (typeof value.channelId === "string" &&
|
|
552
|
+
typeof value.threadTs === "string" &&
|
|
553
|
+
typeof value.userId === "string");
|
|
554
|
+
}
|
|
555
|
+
function isParsedThreadContextChanged(value) {
|
|
556
|
+
return typeof value.threadTs === "string";
|
|
557
|
+
}
|
|
558
|
+
function isParsedAppHomeOpened(value) {
|
|
559
|
+
return typeof value.userId === "string" && typeof value.tab === "string";
|
|
560
|
+
}
|
|
561
|
+
function errorMsg(error) {
|
|
562
|
+
return error instanceof Error ? error.message : String(error);
|
|
563
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { AdapterCapabilityRequest, AdapterCapabilityResult, InboundMessage, OutboundMessage, MessageAdapter, } from "@pinet/transport-core";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@pinet/broker-core/agent-messaging";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@pinet/broker-core/agent-messaging";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@pinet/broker-core/auth";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@pinet/broker-core/auth";
|