@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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/builtin-commands.d.ts +2 -1
  3. package/dist/agent/builtin-commands.d.ts.map +1 -1
  4. package/dist/agent/builtin-commands.js.map +1 -1
  5. package/dist/agent/incident.d.ts +1 -1
  6. package/dist/agent/incident.d.ts.map +1 -1
  7. package/dist/agent/runner.d.ts +7 -2
  8. package/dist/agent/runner.d.ts.map +1 -1
  9. package/dist/agent/runner.js +91 -7
  10. package/dist/agent/runner.js.map +1 -1
  11. package/dist/api/line-webhook-router.js +8 -8
  12. package/dist/api/line-webhook-router.js.map +1 -1
  13. package/dist/api/{line-pending-senders.d.ts → pending-senders.d.ts} +13 -13
  14. package/dist/api/pending-senders.d.ts.map +1 -0
  15. package/dist/api/pending-senders.js +128 -0
  16. package/dist/api/pending-senders.js.map +1 -0
  17. package/dist/api/router.d.ts.map +1 -1
  18. package/dist/api/router.js +296 -10
  19. package/dist/api/router.js.map +1 -1
  20. package/dist/api/slack-access.d.ts +74 -0
  21. package/dist/api/slack-access.d.ts.map +1 -0
  22. package/dist/api/slack-access.js +78 -0
  23. package/dist/api/slack-access.js.map +1 -0
  24. package/dist/api/slack-client.d.ts +68 -0
  25. package/dist/api/slack-client.d.ts.map +1 -0
  26. package/dist/api/slack-client.js +121 -0
  27. package/dist/api/slack-client.js.map +1 -0
  28. package/dist/api/slack-webhook-router.d.ts +34 -0
  29. package/dist/api/slack-webhook-router.d.ts.map +1 -0
  30. package/dist/api/slack-webhook-router.js +280 -0
  31. package/dist/api/slack-webhook-router.js.map +1 -0
  32. package/dist/api/webhooks-router.d.ts +18 -9
  33. package/dist/api/webhooks-router.d.ts.map +1 -1
  34. package/dist/api/webhooks-router.js +12 -7
  35. package/dist/api/webhooks-router.js.map +1 -1
  36. package/dist/cli-viewer/pairing-store.d.ts +3 -2
  37. package/dist/cli-viewer/pairing-store.d.ts.map +1 -1
  38. package/dist/cli-viewer/pairing-store.js +3 -5
  39. package/dist/cli-viewer/pairing-store.js.map +1 -1
  40. package/dist/history/types.d.ts +29 -1
  41. package/dist/history/types.d.ts.map +1 -1
  42. package/dist/history/types.js +32 -0
  43. package/dist/history/types.js.map +1 -1
  44. package/dist/session/compactor.d.ts +2 -1
  45. package/dist/session/compactor.d.ts.map +1 -1
  46. package/dist/session/compactor.js.map +1 -1
  47. package/dist/session/process.d.ts +3 -2
  48. package/dist/session/process.d.ts.map +1 -1
  49. package/dist/session/process.js +17 -6
  50. package/dist/session/process.js.map +1 -1
  51. package/dist/session/store.d.ts +14 -13
  52. package/dist/session/store.d.ts.map +1 -1
  53. package/dist/session/store.js +7 -2
  54. package/dist/session/store.js.map +1 -1
  55. package/dist/types.d.ts +37 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/ui/cli-viewer-ui.d.ts +2 -1
  58. package/dist/ui/cli-viewer-ui.d.ts.map +1 -1
  59. package/dist/ui/cli-viewer-ui.js.map +1 -1
  60. package/mcp/server.ts +2 -0
  61. package/mcp/tools/slack/module.ts +113 -0
  62. package/package.json +1 -1
  63. package/dist/api/line-pending-senders.d.ts.map +0 -1
  64. package/dist/api/line-pending-senders.js +0 -112
  65. 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"}