instar 0.24.13 → 0.24.14
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/.claude/skills/setup-wizard/skill.md +281 -5
- package/dashboard/index.html +341 -0
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +188 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/slack-cli.d.ts +16 -0
- package/dist/commands/slack-cli.d.ts.map +1 -0
- package/dist/commands/slack-cli.js +198 -0
- package/dist/commands/slack-cli.js.map +1 -0
- package/dist/core/AgentRegistry.d.ts.map +1 -1
- package/dist/core/AgentRegistry.js +24 -6
- package/dist/core/AgentRegistry.js.map +1 -1
- package/dist/core/SleepWakeDetector.d.ts +11 -0
- package/dist/core/SleepWakeDetector.d.ts.map +1 -1
- package/dist/core/SleepWakeDetector.js +16 -1
- package/dist/core/SleepWakeDetector.js.map +1 -1
- package/dist/lifeline/ServerSupervisor.d.ts +13 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +129 -0
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/messaging/SessionSummarySentinel.js +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +1 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +4 -1
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/slack/ChannelManager.d.ts +36 -0
- package/dist/messaging/slack/ChannelManager.d.ts.map +1 -0
- package/dist/messaging/slack/ChannelManager.js +100 -0
- package/dist/messaging/slack/ChannelManager.js.map +1 -0
- package/dist/messaging/slack/FileHandler.d.ts +30 -0
- package/dist/messaging/slack/FileHandler.d.ts.map +1 -0
- package/dist/messaging/slack/FileHandler.js +87 -0
- package/dist/messaging/slack/FileHandler.js.map +1 -0
- package/dist/messaging/slack/RingBuffer.d.ts +22 -0
- package/dist/messaging/slack/RingBuffer.d.ts.map +1 -0
- package/dist/messaging/slack/RingBuffer.js +48 -0
- package/dist/messaging/slack/RingBuffer.js.map +1 -0
- package/dist/messaging/slack/SlackAdapter.d.ts +136 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -0
- package/dist/messaging/slack/SlackAdapter.js +572 -0
- package/dist/messaging/slack/SlackAdapter.js.map +1 -0
- package/dist/messaging/slack/SlackApiClient.d.ts +51 -0
- package/dist/messaging/slack/SlackApiClient.d.ts.map +1 -0
- package/dist/messaging/slack/SlackApiClient.js +94 -0
- package/dist/messaging/slack/SlackApiClient.js.map +1 -0
- package/dist/messaging/slack/SocketModeClient.d.ts +44 -0
- package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -0
- package/dist/messaging/slack/SocketModeClient.js +209 -0
- package/dist/messaging/slack/SocketModeClient.js.map +1 -0
- package/dist/messaging/slack/index.d.ts +12 -0
- package/dist/messaging/slack/index.d.ts.map +1 -0
- package/dist/messaging/slack/index.js +15 -0
- package/dist/messaging/slack/index.js.map +1 -0
- package/dist/messaging/slack/sanitize.d.ts +39 -0
- package/dist/messaging/slack/sanitize.d.ts.map +1 -0
- package/dist/messaging/slack/sanitize.js +71 -0
- package/dist/messaging/slack/sanitize.js.map +1 -0
- package/dist/messaging/slack/types.d.ts +155 -0
- package/dist/messaging/slack/types.d.ts.map +1 -0
- package/dist/messaging/slack/types.js +54 -0
- package/dist/messaging/slack/types.js.map +1 -0
- package/dist/monitoring/PresenceProxy.d.ts +157 -0
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -0
- package/dist/monitoring/PresenceProxy.js +891 -0
- package/dist/monitoring/PresenceProxy.js.map +1 -0
- package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
- package/dist/monitoring/SessionWatchdog.js +2 -0
- package/dist/monitoring/SessionWatchdog.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +49 -47
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +1 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +213 -4
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +94 -78
- package/src/templates/hooks/slack-channel-context.sh +98 -0
- package/src/templates/scripts/slack-reply.sh +64 -0
- package/upgrades/0.24.11.md +23 -0
- package/upgrades/0.24.14.md +26 -0
- package/upgrades/0.24.6.md +20 -0
- package/upgrades/0.24.7.md +24 -0
- package/upgrades/0.24.8.md +19 -0
- package/upgrades/0.24.9.md +19 -0
- package/upgrades/NEXT.md +35 -0
- /package/.claude/skills/secret-setup/{skill.md → SKILL.md} +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SlackApiClient — Zero-SDK HTTP client for the Slack Web API.
|
|
3
|
+
*
|
|
4
|
+
* All Slack API calls go through this class, which handles:
|
|
5
|
+
* - Authentication (bot token vs app token)
|
|
6
|
+
* - Rate limit detection and retry (Retry-After header)
|
|
7
|
+
* - Error classification (permanent vs transient)
|
|
8
|
+
* - Token redaction in logs
|
|
9
|
+
*/
|
|
10
|
+
import { getTier } from './types.js';
|
|
11
|
+
import { redactToken } from './sanitize.js';
|
|
12
|
+
/** Errors that indicate the token is permanently invalid. */
|
|
13
|
+
const PERMANENT_ERRORS = new Set([
|
|
14
|
+
'invalid_auth',
|
|
15
|
+
'account_inactive',
|
|
16
|
+
'token_revoked',
|
|
17
|
+
'token_expired',
|
|
18
|
+
'org_login_required',
|
|
19
|
+
'ekm_access_denied',
|
|
20
|
+
'missing_scope',
|
|
21
|
+
'not_authed',
|
|
22
|
+
]);
|
|
23
|
+
export class SlackApiClient {
|
|
24
|
+
botToken;
|
|
25
|
+
appToken;
|
|
26
|
+
constructor(botToken, appToken) {
|
|
27
|
+
this.botToken = botToken;
|
|
28
|
+
this.appToken = appToken ?? null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Call a Slack Web API method.
|
|
32
|
+
*
|
|
33
|
+
* @param method - API method name (e.g., 'chat.postMessage')
|
|
34
|
+
* @param params - JSON body parameters
|
|
35
|
+
* @param options - Token selection and retry options
|
|
36
|
+
* @returns Parsed JSON response
|
|
37
|
+
* @throws Error on non-ok response (after retries for rate limits)
|
|
38
|
+
*/
|
|
39
|
+
async call(method, params = {}, options = {}) {
|
|
40
|
+
const token = options.useAppToken ? this.appToken : this.botToken;
|
|
41
|
+
if (!token) {
|
|
42
|
+
throw new Error(`[slack-api] No ${options.useAppToken ? 'app' : 'bot'} token configured`);
|
|
43
|
+
}
|
|
44
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
45
|
+
return this._callWithRetry(method, params, token, 0, maxRetries);
|
|
46
|
+
}
|
|
47
|
+
/** Get the rate limit tier for a method. */
|
|
48
|
+
getTier(method) {
|
|
49
|
+
return getTier(method);
|
|
50
|
+
}
|
|
51
|
+
async _callWithRetry(method, params, token, attempt, maxRetries) {
|
|
52
|
+
const response = await fetch(`https://slack.com/api/${method}`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Authorization': `Bearer ${token}`,
|
|
56
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(params),
|
|
59
|
+
});
|
|
60
|
+
const data = (await response.json());
|
|
61
|
+
// Rate limit handling
|
|
62
|
+
if (data.error === 'ratelimited' && attempt < maxRetries) {
|
|
63
|
+
const retryAfter = parseInt(response.headers.get('Retry-After') || '5', 10);
|
|
64
|
+
const tier = getTier(method);
|
|
65
|
+
console.warn(`[slack-api] Rate limited on ${method} (tier ${tier}). Retry in ${retryAfter}s (attempt ${attempt + 1}/${maxRetries})`);
|
|
66
|
+
await new Promise(r => setTimeout(r, retryAfter * 1000));
|
|
67
|
+
return this._callWithRetry(method, params, token, attempt + 1, maxRetries);
|
|
68
|
+
}
|
|
69
|
+
if (!data.ok) {
|
|
70
|
+
const isPermanent = PERMANENT_ERRORS.has(data.error || '');
|
|
71
|
+
const redacted = redactToken(token);
|
|
72
|
+
const err = new SlackApiError(`Slack API ${method} failed: ${data.error}`, method, data.error || 'unknown', isPermanent);
|
|
73
|
+
if (isPermanent) {
|
|
74
|
+
console.error(`[slack-api] Permanent error on ${method}: ${data.error} (token: ${redacted})`);
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Typed error for Slack API failures. */
|
|
82
|
+
export class SlackApiError extends Error {
|
|
83
|
+
method;
|
|
84
|
+
slackError;
|
|
85
|
+
permanent;
|
|
86
|
+
constructor(message, method, slackError, permanent) {
|
|
87
|
+
super(message);
|
|
88
|
+
this.name = 'SlackApiError';
|
|
89
|
+
this.method = method;
|
|
90
|
+
this.slackError = slackError;
|
|
91
|
+
this.permanent = permanent;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=SlackApiClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlackApiClient.js","sourceRoot":"","sources":["../../../src/messaging/slack/SlackApiClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocketModeClient — WebSocket connection manager for Slack Socket Mode.
|
|
3
|
+
*
|
|
4
|
+
* Handles the full lifecycle: connect, receive events, acknowledge,
|
|
5
|
+
* reconnect with hardened strategy (exponential backoff, active heartbeat,
|
|
6
|
+
* too_many_websockets handling, proactive rotation).
|
|
7
|
+
*
|
|
8
|
+
* Uses Node's built-in WebSocket (Node 22+) or falls back to 'ws' package.
|
|
9
|
+
*/
|
|
10
|
+
import { SlackApiClient } from './SlackApiClient.js';
|
|
11
|
+
export interface SocketModeHandlers {
|
|
12
|
+
onEvent: (type: string, payload: Record<string, unknown>) => Promise<void>;
|
|
13
|
+
onInteraction: (payload: Record<string, unknown>) => Promise<void>;
|
|
14
|
+
onConnected: () => void;
|
|
15
|
+
onDisconnected: (reason: string) => void;
|
|
16
|
+
onError: (error: Error, permanent: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare class SocketModeClient {
|
|
19
|
+
private apiClient;
|
|
20
|
+
private handlers;
|
|
21
|
+
private ws;
|
|
22
|
+
private started;
|
|
23
|
+
private reconnecting;
|
|
24
|
+
private consecutiveErrors;
|
|
25
|
+
private heartbeatTimer;
|
|
26
|
+
private lastEventAt;
|
|
27
|
+
private outboundQueue;
|
|
28
|
+
private connectionTime;
|
|
29
|
+
constructor(apiClient: SlackApiClient, handlers: SocketModeHandlers);
|
|
30
|
+
get isConnected(): boolean;
|
|
31
|
+
connect(): Promise<void>;
|
|
32
|
+
disconnect(): Promise<void>;
|
|
33
|
+
/** Queue an outbound message for sending (or send immediately if connected). */
|
|
34
|
+
queueOutbound(data: string): void;
|
|
35
|
+
private _openConnection;
|
|
36
|
+
private _connectWebSocket;
|
|
37
|
+
private _handleRawMessage;
|
|
38
|
+
private _handleDisconnect;
|
|
39
|
+
private _backoffReconnect;
|
|
40
|
+
private _startHeartbeat;
|
|
41
|
+
private _clearHeartbeat;
|
|
42
|
+
private _drainQueue;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=SocketModeClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SocketModeClient.d.ts","sourceRoot":"","sources":["../../../src/messaging/slack/SocketModeClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAiB,MAAM,qBAAqB,CAAC;AAGpE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAYD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,cAAc,CAAuB;gBAEjC,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB;IAKnE,IAAI,WAAW,IAAI,OAAO,CAEzB;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC,gFAAgF;IAChF,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;YAWnB,eAAe;IA4B7B,OAAO,CAAC,iBAAiB;YA6BX,iBAAiB;IAqC/B,OAAO,CAAC,iBAAiB;YA2BX,iBAAiB;IAe/B,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,WAAW;CAOpB"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocketModeClient — WebSocket connection manager for Slack Socket Mode.
|
|
3
|
+
*
|
|
4
|
+
* Handles the full lifecycle: connect, receive events, acknowledge,
|
|
5
|
+
* reconnect with hardened strategy (exponential backoff, active heartbeat,
|
|
6
|
+
* too_many_websockets handling, proactive rotation).
|
|
7
|
+
*
|
|
8
|
+
* Uses Node's built-in WebSocket (Node 22+) or falls back to 'ws' package.
|
|
9
|
+
*/
|
|
10
|
+
import { SlackApiError } from './SlackApiClient.js';
|
|
11
|
+
const MAX_OUTBOUND_QUEUE = 100;
|
|
12
|
+
const HEARTBEAT_TIMEOUT_MS = 60_000;
|
|
13
|
+
const MAX_BACKOFF_MS = 60_000;
|
|
14
|
+
const TOO_MANY_WS_DELAY_MS = 30_000;
|
|
15
|
+
export class SocketModeClient {
|
|
16
|
+
apiClient;
|
|
17
|
+
handlers;
|
|
18
|
+
ws = null;
|
|
19
|
+
started = false;
|
|
20
|
+
reconnecting = false;
|
|
21
|
+
consecutiveErrors = 0;
|
|
22
|
+
heartbeatTimer = null;
|
|
23
|
+
lastEventAt = 0;
|
|
24
|
+
outboundQueue = [];
|
|
25
|
+
connectionTime = null;
|
|
26
|
+
constructor(apiClient, handlers) {
|
|
27
|
+
this.apiClient = apiClient;
|
|
28
|
+
this.handlers = handlers;
|
|
29
|
+
}
|
|
30
|
+
get isConnected() {
|
|
31
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
32
|
+
}
|
|
33
|
+
async connect() {
|
|
34
|
+
this.started = true;
|
|
35
|
+
await this._openConnection();
|
|
36
|
+
}
|
|
37
|
+
async disconnect() {
|
|
38
|
+
this.started = false;
|
|
39
|
+
this._clearHeartbeat();
|
|
40
|
+
if (this.ws) {
|
|
41
|
+
this.ws.close(1000, 'client disconnect');
|
|
42
|
+
this.ws = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Queue an outbound message for sending (or send immediately if connected). */
|
|
46
|
+
queueOutbound(data) {
|
|
47
|
+
if (this.isConnected && this.ws) {
|
|
48
|
+
this.ws.send(data);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.outboundQueue.push({ data, enqueuedAt: Date.now() });
|
|
52
|
+
if (this.outboundQueue.length > MAX_OUTBOUND_QUEUE) {
|
|
53
|
+
this.outboundQueue.shift(); // Drop oldest
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async _openConnection() {
|
|
58
|
+
try {
|
|
59
|
+
const response = await this.apiClient.call('apps.connections.open', {}, { useAppToken: true });
|
|
60
|
+
if (!response.url) {
|
|
61
|
+
throw new Error('No WebSocket URL in apps.connections.open response');
|
|
62
|
+
}
|
|
63
|
+
this.connectionTime = response.approximate_connection_time ?? null;
|
|
64
|
+
this._connectWebSocket(response.url);
|
|
65
|
+
this.consecutiveErrors = 0;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (err instanceof SlackApiError && err.permanent) {
|
|
69
|
+
this.handlers.onError(err, true);
|
|
70
|
+
this.started = false; // Don't retry permanent failures
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.handlers.onError(err, false);
|
|
74
|
+
if (this.started) {
|
|
75
|
+
await this._backoffReconnect();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
_connectWebSocket(url) {
|
|
80
|
+
this.ws = new WebSocket(url);
|
|
81
|
+
this.ws.addEventListener('open', () => {
|
|
82
|
+
this.lastEventAt = Date.now();
|
|
83
|
+
this._startHeartbeat();
|
|
84
|
+
this._drainQueue();
|
|
85
|
+
this.handlers.onConnected();
|
|
86
|
+
});
|
|
87
|
+
this.ws.addEventListener('message', (event) => {
|
|
88
|
+
this.lastEventAt = Date.now();
|
|
89
|
+
this._handleRawMessage(typeof event.data === 'string' ? event.data : String(event.data));
|
|
90
|
+
});
|
|
91
|
+
this.ws.addEventListener('close', (event) => {
|
|
92
|
+
this._clearHeartbeat();
|
|
93
|
+
this.ws = null;
|
|
94
|
+
this.handlers.onDisconnected(event.reason || 'connection closed');
|
|
95
|
+
if (this.started) {
|
|
96
|
+
this._backoffReconnect();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
this.ws.addEventListener('error', () => {
|
|
100
|
+
// Error event is always followed by close event — handle reconnection there
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async _handleRawMessage(raw) {
|
|
104
|
+
let envelope;
|
|
105
|
+
try {
|
|
106
|
+
envelope = JSON.parse(raw);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
console.error('[slack-socket] Failed to parse WebSocket message');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Handle disconnect events (no envelope_id to ack)
|
|
113
|
+
if (envelope.type === 'disconnect') {
|
|
114
|
+
const reason = envelope.payload?.reason
|
|
115
|
+
?? envelope.reason
|
|
116
|
+
?? 'unknown';
|
|
117
|
+
this._handleDisconnect(reason);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Acknowledge immediately (must be within 3 seconds)
|
|
121
|
+
if (envelope.envelope_id) {
|
|
122
|
+
this.ws?.send(JSON.stringify({ envelope_id: envelope.envelope_id }));
|
|
123
|
+
}
|
|
124
|
+
// Process event with exception guard (post-ack — Slack won't redeliver)
|
|
125
|
+
try {
|
|
126
|
+
if (envelope.type === 'interactive') {
|
|
127
|
+
await this.handlers.onInteraction(envelope.payload);
|
|
128
|
+
}
|
|
129
|
+
else if (envelope.type === 'events_api') {
|
|
130
|
+
const event = envelope.payload.event;
|
|
131
|
+
const eventType = event?.type ?? 'unknown';
|
|
132
|
+
await this.handlers.onEvent(eventType, envelope.payload);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error('[slack-socket] Event processing failed after ack:', err.message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
_handleDisconnect(reason) {
|
|
140
|
+
this._clearHeartbeat();
|
|
141
|
+
// Prevent close event handler from triggering a second reconnect
|
|
142
|
+
const wasStarted = this.started;
|
|
143
|
+
this.started = false;
|
|
144
|
+
if (this.ws) {
|
|
145
|
+
this.ws.close();
|
|
146
|
+
this.ws = null;
|
|
147
|
+
}
|
|
148
|
+
this.started = wasStarted;
|
|
149
|
+
this.handlers.onDisconnected(reason);
|
|
150
|
+
if (!this.started)
|
|
151
|
+
return;
|
|
152
|
+
if (reason === 'refresh_requested') {
|
|
153
|
+
// Slack container rotation — reconnect immediately
|
|
154
|
+
this._openConnection();
|
|
155
|
+
}
|
|
156
|
+
else if (reason === 'too_many_websockets') {
|
|
157
|
+
// Wait 30s before reconnecting
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
if (this.started)
|
|
160
|
+
this._openConnection();
|
|
161
|
+
}, TOO_MANY_WS_DELAY_MS);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
this._backoffReconnect();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async _backoffReconnect() {
|
|
168
|
+
if (this.reconnecting || !this.started)
|
|
169
|
+
return;
|
|
170
|
+
this.reconnecting = true;
|
|
171
|
+
this.consecutiveErrors++;
|
|
172
|
+
// Exponential backoff from first attempt: 1s, 2s, 4s, 8s... max 60s
|
|
173
|
+
const delay = Math.min(1000 * Math.pow(2, this.consecutiveErrors - 1), MAX_BACKOFF_MS);
|
|
174
|
+
await new Promise(r => setTimeout(r, delay));
|
|
175
|
+
this.reconnecting = false;
|
|
176
|
+
if (this.started) {
|
|
177
|
+
await this._openConnection();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
_startHeartbeat() {
|
|
181
|
+
this._clearHeartbeat();
|
|
182
|
+
this.heartbeatTimer = setInterval(() => {
|
|
183
|
+
const elapsed = Date.now() - this.lastEventAt;
|
|
184
|
+
if (elapsed > HEARTBEAT_TIMEOUT_MS) {
|
|
185
|
+
console.warn('[slack-socket] No events for 60s — connection presumed dead, reconnecting');
|
|
186
|
+
if (this.ws) {
|
|
187
|
+
this.ws.close();
|
|
188
|
+
this.ws = null;
|
|
189
|
+
}
|
|
190
|
+
// Close event handler will trigger reconnect
|
|
191
|
+
}
|
|
192
|
+
}, HEARTBEAT_TIMEOUT_MS / 2); // Check every 30s
|
|
193
|
+
}
|
|
194
|
+
_clearHeartbeat() {
|
|
195
|
+
if (this.heartbeatTimer) {
|
|
196
|
+
clearInterval(this.heartbeatTimer);
|
|
197
|
+
this.heartbeatTimer = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
_drainQueue() {
|
|
201
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
202
|
+
return;
|
|
203
|
+
for (const item of this.outboundQueue) {
|
|
204
|
+
this.ws.send(item.data);
|
|
205
|
+
}
|
|
206
|
+
this.outboundQueue = [];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=SocketModeClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SocketModeClient.js","sourceRoot":"","sources":["../../../src/messaging/slack/SocketModeClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack messaging adapter — entry point and registry registration.
|
|
3
|
+
*/
|
|
4
|
+
export { SlackAdapter } from './SlackAdapter.js';
|
|
5
|
+
export { SlackApiClient, SlackApiError } from './SlackApiClient.js';
|
|
6
|
+
export { SocketModeClient } from './SocketModeClient.js';
|
|
7
|
+
export { ChannelManager } from './ChannelManager.js';
|
|
8
|
+
export { FileHandler } from './FileHandler.js';
|
|
9
|
+
export { RingBuffer } from './RingBuffer.js';
|
|
10
|
+
export type { SlackConfig, SlackMessage, SlackUser, SlackChannel } from './types.js';
|
|
11
|
+
export * from './sanitize.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/messaging/slack/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrF,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack messaging adapter — entry point and registry registration.
|
|
3
|
+
*/
|
|
4
|
+
export { SlackAdapter } from './SlackAdapter.js';
|
|
5
|
+
export { SlackApiClient, SlackApiError } from './SlackApiClient.js';
|
|
6
|
+
export { SocketModeClient } from './SocketModeClient.js';
|
|
7
|
+
export { ChannelManager } from './ChannelManager.js';
|
|
8
|
+
export { FileHandler } from './FileHandler.js';
|
|
9
|
+
export { RingBuffer } from './RingBuffer.js';
|
|
10
|
+
export * from './sanitize.js';
|
|
11
|
+
// Register with the adapter registry at module load time
|
|
12
|
+
import { registerAdapter } from '../AdapterRegistry.js';
|
|
13
|
+
import { SlackAdapter } from './SlackAdapter.js';
|
|
14
|
+
registerAdapter('slack', SlackAdapter);
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/messaging/slack/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,cAAc,eAAe,CAAC;AAE9B,yDAAyD;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAe,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input sanitization for Slack adapter.
|
|
3
|
+
*
|
|
4
|
+
* Prevents prompt injection, path traversal, and SSRF attacks
|
|
5
|
+
* by validating and cleaning user-controlled fields before use.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Sanitize a Slack display name for safe injection into session context.
|
|
9
|
+
*
|
|
10
|
+
* Strips brackets, angle brackets, newlines, control characters.
|
|
11
|
+
* Truncates to 64 chars.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sanitizeDisplayName(name: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Validate a Slack channel ID format.
|
|
16
|
+
* Must match ^[CDG][A-Z0-9]{8,12}$ (C = public, D = DM, G = group/private).
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateChannelId(id: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Validate a Slack channel name.
|
|
21
|
+
* Must be lowercase alphanumeric with hyphens/underscores, max 80 chars.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateChannelName(name: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Validate that a URL hostname belongs to *.slack.com.
|
|
26
|
+
* Used to prevent SSRF via manipulated upload URLs.
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateSlackHostname(url: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Escape text for Slack mrkdwn format.
|
|
31
|
+
* Escapes &, <, > to prevent mrkdwn injection in user-supplied fields.
|
|
32
|
+
*/
|
|
33
|
+
export declare function escapeMrkdwn(text: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Redact a Slack token for safe logging.
|
|
36
|
+
* Shows first 8 chars + "..." to identify the token type without exposing the secret.
|
|
37
|
+
*/
|
|
38
|
+
export declare function redactToken(token: string): string;
|
|
39
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../../src/messaging/slack/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO1D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input sanitization for Slack adapter.
|
|
3
|
+
*
|
|
4
|
+
* Prevents prompt injection, path traversal, and SSRF attacks
|
|
5
|
+
* by validating and cleaning user-controlled fields before use.
|
|
6
|
+
*/
|
|
7
|
+
const CHANNEL_ID_PATTERN = /^[CDG][A-Z0-9]{8,12}$/;
|
|
8
|
+
const CHANNEL_NAME_PATTERN = /^[a-z0-9][a-z0-9\-_]{0,79}$/;
|
|
9
|
+
// eslint-disable-next-line no-control-regex
|
|
10
|
+
const CONTROL_CHARS = /[\x00-\x1f\x7f]/g;
|
|
11
|
+
const INJECTION_CHARS = /[[\]<>]/g;
|
|
12
|
+
/**
|
|
13
|
+
* Sanitize a Slack display name for safe injection into session context.
|
|
14
|
+
*
|
|
15
|
+
* Strips brackets, angle brackets, newlines, control characters.
|
|
16
|
+
* Truncates to 64 chars.
|
|
17
|
+
*/
|
|
18
|
+
export function sanitizeDisplayName(name) {
|
|
19
|
+
return name
|
|
20
|
+
.replace(CONTROL_CHARS, '')
|
|
21
|
+
.replace(INJECTION_CHARS, '')
|
|
22
|
+
.trim()
|
|
23
|
+
.slice(0, 64);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate a Slack channel ID format.
|
|
27
|
+
* Must match ^[CDG][A-Z0-9]{8,12}$ (C = public, D = DM, G = group/private).
|
|
28
|
+
*/
|
|
29
|
+
export function validateChannelId(id) {
|
|
30
|
+
return CHANNEL_ID_PATTERN.test(id);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate a Slack channel name.
|
|
34
|
+
* Must be lowercase alphanumeric with hyphens/underscores, max 80 chars.
|
|
35
|
+
*/
|
|
36
|
+
export function validateChannelName(name) {
|
|
37
|
+
return CHANNEL_NAME_PATTERN.test(name);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validate that a URL hostname belongs to *.slack.com.
|
|
41
|
+
* Used to prevent SSRF via manipulated upload URLs.
|
|
42
|
+
*/
|
|
43
|
+
export function validateSlackHostname(url) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
return parsed.hostname === 'slack.com' || parsed.hostname.endsWith('.slack.com');
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Escape text for Slack mrkdwn format.
|
|
54
|
+
* Escapes &, <, > to prevent mrkdwn injection in user-supplied fields.
|
|
55
|
+
*/
|
|
56
|
+
export function escapeMrkdwn(text) {
|
|
57
|
+
return text
|
|
58
|
+
.replace(/&/g, '&')
|
|
59
|
+
.replace(/</g, '<')
|
|
60
|
+
.replace(/>/g, '>');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Redact a Slack token for safe logging.
|
|
64
|
+
* Shows first 8 chars + "..." to identify the token type without exposing the secret.
|
|
65
|
+
*/
|
|
66
|
+
export function redactToken(token) {
|
|
67
|
+
if (token.length <= 12)
|
|
68
|
+
return '***';
|
|
69
|
+
return token.slice(0, 8) + '...';
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../src/messaging/slack/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AACnD,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAC3D,4CAA4C;AAC5C,MAAM,aAAa,GAAG,kBAAkB,CAAC;AACzC,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI;SACR,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;SAC5B,IAAI,EAAE;SACN,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,OAAO,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,MAAM,CAAC,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;AACnC,CAAC"}
|