@0xmaxma/claude-gateway 1.7.6 → 1.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/builtin-commands.d.ts +2 -1
- package/dist/agent/builtin-commands.d.ts.map +1 -1
- package/dist/agent/builtin-commands.js.map +1 -1
- package/dist/agent/incident.d.ts +1 -1
- package/dist/agent/incident.d.ts.map +1 -1
- package/dist/agent/runner.d.ts +7 -2
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +91 -7
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/line-webhook-router.js +8 -8
- package/dist/api/line-webhook-router.js.map +1 -1
- package/dist/api/{line-pending-senders.d.ts → pending-senders.d.ts} +13 -13
- package/dist/api/pending-senders.d.ts.map +1 -0
- package/dist/api/pending-senders.js +128 -0
- package/dist/api/pending-senders.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +296 -10
- package/dist/api/router.js.map +1 -1
- package/dist/api/slack-access.d.ts +74 -0
- package/dist/api/slack-access.d.ts.map +1 -0
- package/dist/api/slack-access.js +78 -0
- package/dist/api/slack-access.js.map +1 -0
- package/dist/api/slack-client.d.ts +68 -0
- package/dist/api/slack-client.d.ts.map +1 -0
- package/dist/api/slack-client.js +121 -0
- package/dist/api/slack-client.js.map +1 -0
- package/dist/api/slack-webhook-router.d.ts +34 -0
- package/dist/api/slack-webhook-router.d.ts.map +1 -0
- package/dist/api/slack-webhook-router.js +280 -0
- package/dist/api/slack-webhook-router.js.map +1 -0
- package/dist/api/webhooks-router.d.ts +18 -9
- package/dist/api/webhooks-router.d.ts.map +1 -1
- package/dist/api/webhooks-router.js +12 -7
- package/dist/api/webhooks-router.js.map +1 -1
- package/dist/cli-viewer/pairing-store.d.ts +3 -2
- package/dist/cli-viewer/pairing-store.d.ts.map +1 -1
- package/dist/cli-viewer/pairing-store.js +3 -5
- package/dist/cli-viewer/pairing-store.js.map +1 -1
- package/dist/history/types.d.ts +29 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/history/types.js +32 -0
- package/dist/history/types.js.map +1 -1
- package/dist/session/compactor.d.ts +2 -1
- package/dist/session/compactor.d.ts.map +1 -1
- package/dist/session/compactor.js.map +1 -1
- package/dist/session/process.d.ts +3 -2
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +17 -6
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts +14 -13
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +7 -2
- package/dist/session/store.js.map +1 -1
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/cli-viewer-ui.d.ts +2 -1
- package/dist/ui/cli-viewer-ui.d.ts.map +1 -1
- package/dist/ui/cli-viewer-ui.js.map +1 -1
- package/mcp/server.ts +2 -0
- package/mcp/tools/slack/module.ts +113 -0
- package/package.json +1 -1
- package/dist/api/line-pending-senders.d.ts.map +0 -1
- package/dist/api/line-pending-senders.js +0 -112
- package/dist/api/line-pending-senders.js.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack DM/channel access control — pure, stateless sender gate.
|
|
3
|
+
*
|
|
4
|
+
* Direct structural port of `line-access.ts` (same closed-by-default posture,
|
|
5
|
+
* same policy/allowlist shape) — see that file's own doc comment for the
|
|
6
|
+
* broader lineage (hermes-agent's three-list gate, openclaw's `dmPolicy`).
|
|
7
|
+
* The webhook router reads `slack` config directly and calls this once per
|
|
8
|
+
* inbound event — no env plumbing, no state files.
|
|
9
|
+
*
|
|
10
|
+
* `kind` uses LINE's own `'user' | 'group' | 'room' | 'other'` vocabulary,
|
|
11
|
+
* narrowed to `'user' | 'group'` here since Slack has no room-equivalent —
|
|
12
|
+
* kept as a subset (not a separate type) so `isResolvedSourceAllowed`-shaped
|
|
13
|
+
* dispatch logic reads identically across both channels.
|
|
14
|
+
*
|
|
15
|
+
* Default (policy absent) is CLOSED: only ids in the allowlist pass. Set
|
|
16
|
+
* `dmPolicy'/'groupPolicy': 'open'` to restore "reply to anyone" behavior.
|
|
17
|
+
*
|
|
18
|
+
* Allowlist entries MUST be stable Slack ids ("U..." for users, "C..." for
|
|
19
|
+
* channels) — never display names or channel names. Slack channel names can
|
|
20
|
+
* be renamed or are ambiguous across workspaces; a name-keyed allowlist entry
|
|
21
|
+
* silently never matches (see openclaw's docs, which call this out as a
|
|
22
|
+
* common footgun).
|
|
23
|
+
*/
|
|
24
|
+
export declare function isSlackSenderAllowed(policy: 'open' | 'allowlist' | 'disabled' | undefined, allowlist: string[] | undefined, id: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Structural view of a Slack Events API `event` — the only fields the gate
|
|
27
|
+
* needs. Kept local (not the Slack SDK type) so this module stays pure and
|
|
28
|
+
* trivially testable. Slack sources are one of:
|
|
29
|
+
* im (DM) → { channel_type: 'im', channel, user }
|
|
30
|
+
* channel → { channel_type: 'channel', channel, user } (also 'group'/'mpim', both treated as 'group' here)
|
|
31
|
+
*
|
|
32
|
+
* `app_mention` events are a special case: Slack does NOT include
|
|
33
|
+
* `channel_type` on them at all (confirmed live — only `message` events
|
|
34
|
+
* carry it) — only `type: 'app_mention'`, `channel`, `user`. Since
|
|
35
|
+
* `app_mention` only ever fires for channel/group mentions (never DMs — a
|
|
36
|
+
* DM mention is just a plain `message.im` event), `type` is accepted here
|
|
37
|
+
* specifically to classify that case without needing `channel_type`.
|
|
38
|
+
*/
|
|
39
|
+
export interface SlackEventLike {
|
|
40
|
+
type?: string;
|
|
41
|
+
channel?: string;
|
|
42
|
+
channel_type?: string;
|
|
43
|
+
user?: string;
|
|
44
|
+
}
|
|
45
|
+
export type SlackSourceKind = 'user' | 'group' | 'other';
|
|
46
|
+
export interface ResolvedSlackSource {
|
|
47
|
+
/** Conversation key — the reply target: the Slack channel id (DM channel id for `im`, channel id for `channel`/`group`/`mpim`). */
|
|
48
|
+
conversationId: string;
|
|
49
|
+
/** The human who sent the event. */
|
|
50
|
+
senderId: string;
|
|
51
|
+
kind: SlackSourceKind;
|
|
52
|
+
}
|
|
53
|
+
/** Map a raw Slack event to {conversationId, senderId, kind}. */
|
|
54
|
+
export declare function resolveSlackSource(event: SlackEventLike | undefined | null): ResolvedSlackSource;
|
|
55
|
+
/** The subset of `slack` config the conversation gate reads. */
|
|
56
|
+
export interface SlackAccessConfig {
|
|
57
|
+
dmPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
58
|
+
dmAllowlist?: string[];
|
|
59
|
+
groupPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
60
|
+
groupAllowlist?: string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Gate an already-resolved Slack source. DMs are gated on the sender's user
|
|
64
|
+
* id against `dmPolicy`/`dmAllowlist`; channels are gated on the
|
|
65
|
+
* conversation id (the channel id) against `groupPolicy`/`groupAllowlist`,
|
|
66
|
+
* closed by default — same posture as DMs. Unknown source kinds are denied.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isResolvedSourceAllowed(cfg: SlackAccessConfig | undefined, resolved: ResolvedSlackSource): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Convenience wrapper that resolves a raw event and gates it in one call.
|
|
71
|
+
* Equivalent to `isResolvedSourceAllowed(cfg, resolveSlackSource(event))`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function isSlackConversationAllowed(cfg: SlackAccessConfig | undefined, event: SlackEventLike | undefined | null): boolean;
|
|
74
|
+
//# sourceMappingURL=slack-access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-access.d.ts","sourceRoot":"","sources":["../../src/api/slack-access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,EACrD,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,EAAE,EAAE,MAAM,GACT,OAAO,CAKT;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,mIAAmI;IACnI,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,GAAG,mBAAmB,CAYhG;AAED,gEAAgE;AAChE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAChD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,iBAAiB,GAAG,SAAS,EAClC,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAST;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,iBAAiB,GAAG,SAAS,EAClC,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,GACvC,OAAO,CAET"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSlackSenderAllowed = isSlackSenderAllowed;
|
|
4
|
+
exports.resolveSlackSource = resolveSlackSource;
|
|
5
|
+
exports.isResolvedSourceAllowed = isResolvedSourceAllowed;
|
|
6
|
+
exports.isSlackConversationAllowed = isSlackConversationAllowed;
|
|
7
|
+
/**
|
|
8
|
+
* Slack DM/channel access control — pure, stateless sender gate.
|
|
9
|
+
*
|
|
10
|
+
* Direct structural port of `line-access.ts` (same closed-by-default posture,
|
|
11
|
+
* same policy/allowlist shape) — see that file's own doc comment for the
|
|
12
|
+
* broader lineage (hermes-agent's three-list gate, openclaw's `dmPolicy`).
|
|
13
|
+
* The webhook router reads `slack` config directly and calls this once per
|
|
14
|
+
* inbound event — no env plumbing, no state files.
|
|
15
|
+
*
|
|
16
|
+
* `kind` uses LINE's own `'user' | 'group' | 'room' | 'other'` vocabulary,
|
|
17
|
+
* narrowed to `'user' | 'group'` here since Slack has no room-equivalent —
|
|
18
|
+
* kept as a subset (not a separate type) so `isResolvedSourceAllowed`-shaped
|
|
19
|
+
* dispatch logic reads identically across both channels.
|
|
20
|
+
*
|
|
21
|
+
* Default (policy absent) is CLOSED: only ids in the allowlist pass. Set
|
|
22
|
+
* `dmPolicy'/'groupPolicy': 'open'` to restore "reply to anyone" behavior.
|
|
23
|
+
*
|
|
24
|
+
* Allowlist entries MUST be stable Slack ids ("U..." for users, "C..." for
|
|
25
|
+
* channels) — never display names or channel names. Slack channel names can
|
|
26
|
+
* be renamed or are ambiguous across workspaces; a name-keyed allowlist entry
|
|
27
|
+
* silently never matches (see openclaw's docs, which call this out as a
|
|
28
|
+
* common footgun).
|
|
29
|
+
*/
|
|
30
|
+
function isSlackSenderAllowed(policy, allowlist, id) {
|
|
31
|
+
if (policy === 'open')
|
|
32
|
+
return true;
|
|
33
|
+
if (policy === 'disabled')
|
|
34
|
+
return false;
|
|
35
|
+
// 'allowlist' OR undefined (closed default) → only listed ids pass.
|
|
36
|
+
return !!id && (allowlist ?? []).includes(id);
|
|
37
|
+
}
|
|
38
|
+
/** Map a raw Slack event to {conversationId, senderId, kind}. */
|
|
39
|
+
function resolveSlackSource(event) {
|
|
40
|
+
const channelType = event?.channel_type;
|
|
41
|
+
const conversationId = event?.channel ?? '';
|
|
42
|
+
const senderId = event?.user ?? '';
|
|
43
|
+
if (!conversationId)
|
|
44
|
+
return { conversationId: '', senderId: '', kind: 'other' };
|
|
45
|
+
// app_mention has no channel_type — it's always a channel/group context.
|
|
46
|
+
if (event?.type === 'app_mention')
|
|
47
|
+
return { conversationId, senderId, kind: 'group' };
|
|
48
|
+
if (channelType === 'im')
|
|
49
|
+
return { conversationId, senderId, kind: 'user' };
|
|
50
|
+
if (channelType === 'channel' || channelType === 'group' || channelType === 'mpim') {
|
|
51
|
+
return { conversationId, senderId, kind: 'group' };
|
|
52
|
+
}
|
|
53
|
+
return { conversationId: '', senderId: '', kind: 'other' };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gate an already-resolved Slack source. DMs are gated on the sender's user
|
|
57
|
+
* id against `dmPolicy`/`dmAllowlist`; channels are gated on the
|
|
58
|
+
* conversation id (the channel id) against `groupPolicy`/`groupAllowlist`,
|
|
59
|
+
* closed by default — same posture as DMs. Unknown source kinds are denied.
|
|
60
|
+
*/
|
|
61
|
+
function isResolvedSourceAllowed(cfg, resolved) {
|
|
62
|
+
const { conversationId, senderId, kind } = resolved;
|
|
63
|
+
if (kind === 'user') {
|
|
64
|
+
return isSlackSenderAllowed(cfg?.dmPolicy, cfg?.dmAllowlist, senderId || conversationId);
|
|
65
|
+
}
|
|
66
|
+
if (kind === 'group') {
|
|
67
|
+
return isSlackSenderAllowed(cfg?.groupPolicy, cfg?.groupAllowlist, conversationId);
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convenience wrapper that resolves a raw event and gates it in one call.
|
|
73
|
+
* Equivalent to `isResolvedSourceAllowed(cfg, resolveSlackSource(event))`.
|
|
74
|
+
*/
|
|
75
|
+
function isSlackConversationAllowed(cfg, event) {
|
|
76
|
+
return isResolvedSourceAllowed(cfg, resolveSlackSource(event));
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=slack-access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-access.js","sourceRoot":"","sources":["../../src/api/slack-access.ts"],"names":[],"mappings":";;AAuBA,oDASC;AAkCD,gDAYC;AAgBD,0DAYC;AAMD,gEAKC;AArHD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAgB,oBAAoB,CAClC,MAAqD,EACrD,SAA+B,EAC/B,EAAU;IAEV,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACxC,oEAAoE;IACpE,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAChD,CAAC;AAiCD,iEAAiE;AACjE,SAAgB,kBAAkB,CAAC,KAAwC;IACzE,MAAM,WAAW,GAAG,KAAK,EAAE,YAAY,CAAC;IACxC,MAAM,cAAc,GAAG,KAAK,EAAE,OAAO,IAAI,EAAE,CAAC;IAC5C,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAChF,yEAAyE;IACzE,IAAI,KAAK,EAAE,IAAI,KAAK,aAAa;QAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACtF,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC5E,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,OAAO,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;QACnF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACrD,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC7D,CAAC;AAUD;;;;;GAKG;AACH,SAAgB,uBAAuB,CACrC,GAAkC,EAClC,QAA6B;IAE7B,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IACpD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,oBAAoB,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,IAAI,cAAc,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,OAAO,oBAAoB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,SAAgB,0BAA0B,CACxC,GAAkC,EAClC,KAAwC;IAExC,OAAO,uBAAuB,CAAC,GAAG,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface SlackClientOptions {
|
|
2
|
+
botToken: string;
|
|
3
|
+
logDir: string;
|
|
4
|
+
/** Test-only override for the Web API base URL. Production uses the real default. */
|
|
5
|
+
apiBase?: string;
|
|
6
|
+
}
|
|
7
|
+
interface SlackApiResponse {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare class SlackClient {
|
|
13
|
+
private readonly botToken;
|
|
14
|
+
private readonly apiBase;
|
|
15
|
+
private readonly logger;
|
|
16
|
+
constructor(opts: SlackClientOptions);
|
|
17
|
+
/**
|
|
18
|
+
* form-urlencoded, not JSON: write-oriented methods (chat.postMessage,
|
|
19
|
+
* reactions.*) accept a JSON body fine, but read/info methods
|
|
20
|
+
* (users.info, conversations.info) do NOT reliably parse it — confirmed
|
|
21
|
+
* live (users.info returned "user_not_found" for a real user id via JSON,
|
|
22
|
+
* "ok":true via form-encoding). form-urlencoded is the one format every
|
|
23
|
+
* Slack Web API method accepts, so use it uniformly rather than branching
|
|
24
|
+
* per method.
|
|
25
|
+
*/
|
|
26
|
+
private call;
|
|
27
|
+
/**
|
|
28
|
+
* Verify the token and fetch the workspace/team name — used as the
|
|
29
|
+
* connect-flow's "Save" validation step (see 2b in the plan: reused from
|
|
30
|
+
* openclaw's startup `auth.test` call), not just a startup check.
|
|
31
|
+
*/
|
|
32
|
+
authTest(): Promise<{
|
|
33
|
+
ok: boolean;
|
|
34
|
+
team?: string;
|
|
35
|
+
error?: string;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Post a reply. `unfurl_links: false` by default (see 2c — avoids link-preview
|
|
39
|
+
* spam on every URL in an agent's reply; openclaw defaults the same way).
|
|
40
|
+
* `threadTs`, when passed, replies in-thread instead of top-level.
|
|
41
|
+
*/
|
|
42
|
+
postMessage(channel: string, text: string, threadTs?: string): Promise<SlackApiResponse>;
|
|
43
|
+
/**
|
|
44
|
+
* Ack-reaction (see 2c) — added to the inbound message on receipt, removed
|
|
45
|
+
* once the reply posts. Replaces LINE's reply-token-TTL-driven slow-response
|
|
46
|
+
* postback button: Slack has no such TTL, so a simple "seen" signal is a
|
|
47
|
+
* proportionate substitute, not a scaled-down version of LINE's mechanism.
|
|
48
|
+
* Best-effort: failures (e.g. already reacted, message deleted) are logged,
|
|
49
|
+
* never thrown — an ack reaction is UX polish, not correctness-critical.
|
|
50
|
+
*/
|
|
51
|
+
addReaction(channel: string, timestamp: string, name?: string): Promise<void>;
|
|
52
|
+
removeReaction(channel: string, timestamp: string, name?: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Best-effort display name for a pending-knock DM sender — mirrors LINE's
|
|
55
|
+
* `getProfile()` backfill in line-webhook-router.ts. Needs the `users:read`
|
|
56
|
+
* scope; returns undefined (never throws) if missing or the call fails, so
|
|
57
|
+
* the pending list just falls back to "Unknown" same as before this existed.
|
|
58
|
+
*/
|
|
59
|
+
getUserDisplayName(userId: string): Promise<string | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Best-effort channel name for a pending-knock channel — mirrors LINE's
|
|
62
|
+
* `getGroupSummary()` backfill. Needs `channels:read` (public) / `groups:read`
|
|
63
|
+
* (private); same graceful undefined-on-failure contract as above.
|
|
64
|
+
*/
|
|
65
|
+
getChannelName(channelId: string): Promise<string | undefined>;
|
|
66
|
+
}
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=slack-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-client.d.ts","sourceRoot":"","sources":["../../src/api/slack-client.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,gBAAgB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;gBAE7C,IAAI,EAAE,kBAAkB;IAMpC;;;;;;;;OAQG;YACW,IAAI;IAoBlB;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAKzE;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAU9F;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,SAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/F,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,SAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxG;;;;;OAKG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IASrE;;;;OAIG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAMrE"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SlackClient = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Thin Slack Web API wrapper — the outbound half of the Slack channel.
|
|
6
|
+
*
|
|
7
|
+
* Unlike LINE (`line-webhook-router.ts` uses `@line/bot-sdk`'s
|
|
8
|
+
* `MessagingApiClient` + a 402-line reply-token TTL state machine in
|
|
9
|
+
* `line-reply-manager.ts`), Slack has no reply-token expiry: `chat.postMessage`
|
|
10
|
+
* works with the bot token at any time, so no reply-manager equivalent is
|
|
11
|
+
* needed here — this is a plain fetch wrapper around the handful of Web API
|
|
12
|
+
* methods the channel actually uses.
|
|
13
|
+
*/
|
|
14
|
+
const logger_1 = require("../logger");
|
|
15
|
+
const SLACK_API_BASE = 'https://slack.com/api';
|
|
16
|
+
class SlackClient {
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
this.botToken = opts.botToken;
|
|
19
|
+
this.apiBase = opts.apiBase ?? SLACK_API_BASE;
|
|
20
|
+
this.logger = (0, logger_1.createLogger)('slack-client', opts.logDir);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* form-urlencoded, not JSON: write-oriented methods (chat.postMessage,
|
|
24
|
+
* reactions.*) accept a JSON body fine, but read/info methods
|
|
25
|
+
* (users.info, conversations.info) do NOT reliably parse it — confirmed
|
|
26
|
+
* live (users.info returned "user_not_found" for a real user id via JSON,
|
|
27
|
+
* "ok":true via form-encoding). form-urlencoded is the one format every
|
|
28
|
+
* Slack Web API method accepts, so use it uniformly rather than branching
|
|
29
|
+
* per method.
|
|
30
|
+
*/
|
|
31
|
+
async call(method, body) {
|
|
32
|
+
const params = new URLSearchParams();
|
|
33
|
+
for (const [k, v] of Object.entries(body)) {
|
|
34
|
+
if (v !== undefined)
|
|
35
|
+
params.set(k, String(v));
|
|
36
|
+
}
|
|
37
|
+
const res = await fetch(`${this.apiBase}/${method}`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
41
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
|
42
|
+
},
|
|
43
|
+
body: params,
|
|
44
|
+
});
|
|
45
|
+
const json = (await res.json());
|
|
46
|
+
if (!json.ok) {
|
|
47
|
+
this.logger.warn(`Slack API ${method} failed`, { error: json.error });
|
|
48
|
+
}
|
|
49
|
+
return json;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Verify the token and fetch the workspace/team name — used as the
|
|
53
|
+
* connect-flow's "Save" validation step (see 2b in the plan: reused from
|
|
54
|
+
* openclaw's startup `auth.test` call), not just a startup check.
|
|
55
|
+
*/
|
|
56
|
+
async authTest() {
|
|
57
|
+
const json = await this.call('auth.test', {});
|
|
58
|
+
return { ok: json.ok, team: json.team, error: json.error };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Post a reply. `unfurl_links: false` by default (see 2c — avoids link-preview
|
|
62
|
+
* spam on every URL in an agent's reply; openclaw defaults the same way).
|
|
63
|
+
* `threadTs`, when passed, replies in-thread instead of top-level.
|
|
64
|
+
*/
|
|
65
|
+
async postMessage(channel, text, threadTs) {
|
|
66
|
+
return this.call('chat.postMessage', {
|
|
67
|
+
channel,
|
|
68
|
+
text,
|
|
69
|
+
unfurl_links: false,
|
|
70
|
+
unfurl_media: false,
|
|
71
|
+
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Ack-reaction (see 2c) — added to the inbound message on receipt, removed
|
|
76
|
+
* once the reply posts. Replaces LINE's reply-token-TTL-driven slow-response
|
|
77
|
+
* postback button: Slack has no such TTL, so a simple "seen" signal is a
|
|
78
|
+
* proportionate substitute, not a scaled-down version of LINE's mechanism.
|
|
79
|
+
* Best-effort: failures (e.g. already reacted, message deleted) are logged,
|
|
80
|
+
* never thrown — an ack reaction is UX polish, not correctness-critical.
|
|
81
|
+
*/
|
|
82
|
+
async addReaction(channel, timestamp, name = 'hourglass_flowing_sand') {
|
|
83
|
+
const json = await this.call('reactions.add', { channel, timestamp, name });
|
|
84
|
+
if (!json.ok && json.error !== 'already_reacted') {
|
|
85
|
+
this.logger.debug('addReaction failed', { error: json.error });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async removeReaction(channel, timestamp, name = 'hourglass_flowing_sand') {
|
|
89
|
+
const json = await this.call('reactions.remove', { channel, timestamp, name });
|
|
90
|
+
if (!json.ok && json.error !== 'no_reaction') {
|
|
91
|
+
this.logger.debug('removeReaction failed', { error: json.error });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Best-effort display name for a pending-knock DM sender — mirrors LINE's
|
|
96
|
+
* `getProfile()` backfill in line-webhook-router.ts. Needs the `users:read`
|
|
97
|
+
* scope; returns undefined (never throws) if missing or the call fails, so
|
|
98
|
+
* the pending list just falls back to "Unknown" same as before this existed.
|
|
99
|
+
*/
|
|
100
|
+
async getUserDisplayName(userId) {
|
|
101
|
+
const json = await this.call('users.info', { user: userId });
|
|
102
|
+
if (!json.ok)
|
|
103
|
+
return undefined;
|
|
104
|
+
const user = json.user;
|
|
105
|
+
return user?.profile?.display_name || user?.profile?.real_name || user?.real_name || undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Best-effort channel name for a pending-knock channel — mirrors LINE's
|
|
109
|
+
* `getGroupSummary()` backfill. Needs `channels:read` (public) / `groups:read`
|
|
110
|
+
* (private); same graceful undefined-on-failure contract as above.
|
|
111
|
+
*/
|
|
112
|
+
async getChannelName(channelId) {
|
|
113
|
+
const json = await this.call('conversations.info', { channel: channelId });
|
|
114
|
+
if (!json.ok)
|
|
115
|
+
return undefined;
|
|
116
|
+
const channel = json.channel;
|
|
117
|
+
return channel?.name || undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.SlackClient = SlackClient;
|
|
121
|
+
//# sourceMappingURL=slack-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-client.js","sourceRoot":"","sources":["../../src/api/slack-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentRunner } from '../agent/runner';
|
|
2
|
+
import { type ResolvedSlackSource } from './slack-access';
|
|
3
|
+
import type { WebhookAppHandler } from './webhooks-router';
|
|
4
|
+
export type NormalizedSlackMessage = {
|
|
5
|
+
content: string;
|
|
6
|
+
meta: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
interface SlackEvent {
|
|
9
|
+
type: string;
|
|
10
|
+
channel?: string;
|
|
11
|
+
channel_type?: string;
|
|
12
|
+
user?: string;
|
|
13
|
+
bot_id?: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
ts?: string;
|
|
16
|
+
thread_ts?: string;
|
|
17
|
+
event_ts?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Normalize a Slack Events API `event` into the gateway's {content, meta}
|
|
21
|
+
* intake shape. Returns null for anything not handled in v1 (bot-authored
|
|
22
|
+
* events, missing channel/user, non message/app_mention types — see 2c for
|
|
23
|
+
* what's deliberately deferred: files, edits, reactions, etc.).
|
|
24
|
+
*/
|
|
25
|
+
export declare function normalizeSlackEvent(event: SlackEvent, resolved?: ResolvedSlackSource, botUserId?: string): NormalizedSlackMessage | null;
|
|
26
|
+
/** Verify `X-Slack-Signature` (HMAC-SHA256 of "v0:{timestamp}:{rawBody}") and reject stale requests. */
|
|
27
|
+
export declare function verifySlackSignature(rawBody: Buffer, signingSecret: string, timestampHeader: string | undefined, signatureHeader: string | undefined): boolean;
|
|
28
|
+
export interface SlackWebhookOptions {
|
|
29
|
+
/** Test-only Web API base override (see SlackClient). */
|
|
30
|
+
apiBase?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function createSlackWebhookHandler(agents: Map<string, AgentRunner>, logDir: string, opts?: SlackWebhookOptions): WebhookAppHandler;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=slack-webhook-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-webhook-router.d.ts","sourceRoot":"","sources":["../../src/api/slack-webhook-router.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAGL,KAAK,mBAAmB,EAEzB,MAAM,gBAAgB,CAAC;AAQxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAwB3D,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,CAAC;AAEF,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAgBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,mBAAmB,EAC9B,SAAS,CAAC,EAAE,MAAM,GACjB,sBAAsB,GAAG,IAAI,CAqB/B;AAED,wGAAwG;AACxG,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,OAAO,CAYT;AAiBD,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AASD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,mBAAwB,GAC7B,iBAAiB,CA2LnB"}
|