@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
@@ -47,11 +47,36 @@ const runner_1 = require("../agent/runner");
47
47
  const auth_1 = require("./auth");
48
48
  const media_store_1 = require("../history/media-store");
49
49
  const db_1 = require("../history/db");
50
+ const types_1 = require("../history/types");
50
51
  const wizard_state_1 = require("./wizard-state");
51
- const line_pending_senders_1 = require("./line-pending-senders");
52
+ const pending_senders_1 = require("./pending-senders");
52
53
  const create_agent_prompts_1 = require("../agent/create-agent-prompts");
53
54
  const MAX_MESSAGE_LENGTH = 10000;
54
55
  const DEFAULT_TIMEOUT_MS = 60000;
56
+ /**
57
+ * Standalone `auth.test` call for the Slack connect flow's Save-time token
58
+ * check (see the PATCH /agents/:agentId handler below). Not SlackClient
59
+ * (src/api/slack-client.ts) — that class requires a logDir-backed logger,
60
+ * which this router has no other reason to plumb through for one validation
61
+ * call. form-urlencoded per slack-client.ts's own finding (JSON is not
62
+ * reliably parsed by every Slack Web API method).
63
+ */
64
+ async function verifySlackBotToken(botToken) {
65
+ try {
66
+ const res = await fetch('https://slack.com/api/auth.test', {
67
+ method: 'POST',
68
+ headers: {
69
+ Authorization: `Bearer ${botToken}`,
70
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
71
+ },
72
+ });
73
+ const json = (await res.json());
74
+ return { ok: json.ok, error: json.error };
75
+ }
76
+ catch (err) {
77
+ return { ok: false, error: err instanceof Error ? err.message : 'network error' };
78
+ }
79
+ }
55
80
  const AGENT_ID_RE = /^[a-z][a-z0-9_-]{1,31}$/;
56
81
  const SAFE_FILENAME_RE = /^[a-zA-Z0-9._\-() ]+$/;
57
82
  // session_id becomes a filesystem key (sessions/<id>.jsonl, .sessions/<id>/) so it
@@ -577,6 +602,18 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
577
602
  // Pairing toggle (orthogonal to dm/groupPolicy). Absent ⇒ on; surface a
578
603
  // concrete boolean so the UI toggle reflects the effective default.
579
604
  line_pairing: cfg.line?.channelSecret ? (cfg.line?.pairing ?? true) : null,
605
+ // Slack — same shape/semantics as LINE above, field-for-field
606
+ // (dmPolicy/groupPolicy/requireMention/pairing), gated on
607
+ // signingSecret the way LINE gates on channelSecret.
608
+ slack_connected: !!cfg.slack?.signingSecret,
609
+ slack_token_preview: cfg.slack?.botToken ? maskToken(cfg.slack.botToken) : null,
610
+ slack_webhook_path: cfg.slack?.signingSecret ? `/webhooks/slack/${id}` : null,
611
+ slack_dm_policy: cfg.slack?.signingSecret ? (cfg.slack?.dmPolicy ?? null) : null,
612
+ slack_dm_allowlist: cfg.slack?.signingSecret ? (cfg.slack?.dmAllowlist ?? []) : null,
613
+ slack_group_policy: cfg.slack?.signingSecret ? (cfg.slack?.groupPolicy ?? null) : null,
614
+ slack_group_allowlist: cfg.slack?.signingSecret ? (cfg.slack?.groupAllowlist ?? []) : null,
615
+ slack_require_mention: cfg.slack?.signingSecret ? (cfg.slack?.requireMention ?? null) : null,
616
+ slack_pairing: cfg.slack?.signingSecret ? (cfg.slack?.pairing ?? true) : null,
580
617
  }));
581
618
  res.json({ agents });
582
619
  });
@@ -1264,7 +1301,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1264
1301
  return;
1265
1302
  }
1266
1303
  const body = req.body;
1267
- const { name, description, model, allow_tools, telegram_bot_token, discord_bot_token, line_channel_access_token, line_channel_secret, line_dm_policy, line_dm_allowlist, line_group_policy, line_group_allowlist, line_require_mention, line_pairing } = body;
1304
+ const { name, description, model, allow_tools, telegram_bot_token, discord_bot_token, line_channel_access_token, line_channel_secret, line_dm_policy, line_dm_allowlist, line_group_policy, line_group_allowlist, line_require_mention, line_pairing, slack_bot_token, slack_signing_secret, slack_dm_policy, slack_dm_allowlist, slack_group_policy, slack_group_allowlist, slack_require_mention, slack_pairing } = body;
1268
1305
  if (name !== undefined && name !== null && typeof name !== 'string') {
1269
1306
  res.status(400).json({ error: 'name must be a string or null' });
1270
1307
  return;
@@ -1346,6 +1383,76 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1346
1383
  const lineAccessTouched = line_dm_policy !== undefined || line_dm_allowlist !== undefined ||
1347
1384
  line_group_policy !== undefined || line_group_allowlist !== undefined || line_require_mention !== undefined ||
1348
1385
  line_pairing !== undefined;
1386
+ // Slack — same validation shape as LINE above, field-for-field.
1387
+ if (slack_bot_token !== undefined && slack_bot_token !== null && typeof slack_bot_token !== 'string') {
1388
+ res.status(400).json({ error: 'slack_bot_token must be a string or null' });
1389
+ return;
1390
+ }
1391
+ if (slack_signing_secret !== undefined && slack_signing_secret !== null && typeof slack_signing_secret !== 'string') {
1392
+ res.status(400).json({ error: 'slack_signing_secret must be a string or null' });
1393
+ return;
1394
+ }
1395
+ // Slack needs BOTH credentials together, same as LINE's access token + secret pair.
1396
+ const slackTouched = slack_bot_token !== undefined || slack_signing_secret !== undefined;
1397
+ if (slackTouched) {
1398
+ const tok = typeof slack_bot_token === 'string' ? slack_bot_token.trim() : '';
1399
+ const sec = typeof slack_signing_secret === 'string' ? slack_signing_secret.trim() : '';
1400
+ const bothSet = tok !== '' && sec !== '';
1401
+ const bothClear = tok === '' && sec === '';
1402
+ if (!bothSet && !bothClear) {
1403
+ res.status(400).json({ error: 'slack_bot_token and slack_signing_secret must be provided together' });
1404
+ return;
1405
+ }
1406
+ // Reject a bad/expired token at Save time instead of persisting it silently
1407
+ // (the failure would otherwise only surface later, when the agent tries to
1408
+ // reply and gets a Slack API error). Mirrors openclaw's startup `auth.test`
1409
+ // call — see slack-client.ts's authTest() doc comment, which named this as
1410
+ // the intended Save-time check. A standalone fetch rather than SlackClient
1411
+ // itself: SlackClient's constructor requires a logDir-backed logger this
1412
+ // router has no other reason to plumb through for one validation call.
1413
+ if (bothSet) {
1414
+ const verify = await verifySlackBotToken(tok);
1415
+ if (!verify.ok) {
1416
+ res.status(400).json({
1417
+ error: `Invalid Slack bot token — auth.test failed: ${verify.error ?? 'unknown error'}`,
1418
+ });
1419
+ return;
1420
+ }
1421
+ }
1422
+ }
1423
+ if (slack_dm_policy !== undefined && slack_dm_policy !== null &&
1424
+ !(typeof slack_dm_policy === 'string' && ['open', 'allowlist', 'disabled'].includes(slack_dm_policy))) {
1425
+ res.status(400).json({ error: "slack_dm_policy must be 'open', 'allowlist', 'disabled', or null" });
1426
+ return;
1427
+ }
1428
+ if (slack_dm_allowlist !== undefined && slack_dm_allowlist !== null &&
1429
+ !(Array.isArray(slack_dm_allowlist) && slack_dm_allowlist.every((u) => typeof u === 'string'))) {
1430
+ res.status(400).json({ error: 'slack_dm_allowlist must be an array of strings or null' });
1431
+ return;
1432
+ }
1433
+ if (slack_group_policy !== undefined && slack_group_policy !== null &&
1434
+ !(typeof slack_group_policy === 'string' && ['open', 'allowlist', 'disabled'].includes(slack_group_policy))) {
1435
+ res.status(400).json({ error: "slack_group_policy must be 'open', 'allowlist', 'disabled', or null" });
1436
+ return;
1437
+ }
1438
+ if (slack_group_allowlist !== undefined && slack_group_allowlist !== null &&
1439
+ !(Array.isArray(slack_group_allowlist) && slack_group_allowlist.every((u) => typeof u === 'string'))) {
1440
+ res.status(400).json({ error: 'slack_group_allowlist must be an array of strings or null' });
1441
+ return;
1442
+ }
1443
+ if (slack_require_mention !== undefined && slack_require_mention !== null &&
1444
+ typeof slack_require_mention !== 'boolean') {
1445
+ res.status(400).json({ error: 'slack_require_mention must be a boolean or null' });
1446
+ return;
1447
+ }
1448
+ if (slack_pairing !== undefined && slack_pairing !== null &&
1449
+ typeof slack_pairing !== 'boolean') {
1450
+ res.status(400).json({ error: 'slack_pairing must be a boolean or null' });
1451
+ return;
1452
+ }
1453
+ const slackAccessTouched = slack_dm_policy !== undefined || slack_dm_allowlist !== undefined ||
1454
+ slack_group_policy !== undefined || slack_group_allowlist !== undefined || slack_require_mention !== undefined ||
1455
+ slack_pairing !== undefined;
1349
1456
  try {
1350
1457
  await writeAgentsToConfig(configPath, (agents) => {
1351
1458
  const agent = agents.find((a) => a.id === agentId);
@@ -1439,6 +1546,62 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1439
1546
  }
1440
1547
  }
1441
1548
  }
1549
+ if (slackTouched) {
1550
+ const tok = typeof slack_bot_token === 'string' ? slack_bot_token.trim() : '';
1551
+ const sec = typeof slack_signing_secret === 'string' ? slack_signing_secret.trim() : '';
1552
+ if (tok === '' && sec === '') {
1553
+ delete agent.slack;
1554
+ }
1555
+ else {
1556
+ const existing = agent.slack;
1557
+ agent.slack = { ...(existing ?? {}), botToken: tok, signingSecret: sec };
1558
+ }
1559
+ }
1560
+ // Access fields — merge into the existing slack block (re-read after the
1561
+ // credential block above, which may have just created or deleted it).
1562
+ // Skip silently when no slack channel exists; policy without credentials
1563
+ // is meaningless.
1564
+ if (slackAccessTouched) {
1565
+ const existing = agent.slack;
1566
+ if (existing) {
1567
+ if (slack_dm_policy !== undefined) {
1568
+ if (slack_dm_policy === null)
1569
+ delete existing.dmPolicy;
1570
+ else
1571
+ existing.dmPolicy = slack_dm_policy;
1572
+ }
1573
+ if (slack_dm_allowlist !== undefined) {
1574
+ if (slack_dm_allowlist === null)
1575
+ delete existing.dmAllowlist;
1576
+ else
1577
+ existing.dmAllowlist = slack_dm_allowlist;
1578
+ }
1579
+ if (slack_group_policy !== undefined) {
1580
+ if (slack_group_policy === null)
1581
+ delete existing.groupPolicy;
1582
+ else
1583
+ existing.groupPolicy = slack_group_policy;
1584
+ }
1585
+ if (slack_group_allowlist !== undefined) {
1586
+ if (slack_group_allowlist === null)
1587
+ delete existing.groupAllowlist;
1588
+ else
1589
+ existing.groupAllowlist = slack_group_allowlist;
1590
+ }
1591
+ if (slack_require_mention !== undefined) {
1592
+ if (slack_require_mention === null)
1593
+ delete existing.requireMention;
1594
+ else
1595
+ existing.requireMention = slack_require_mention;
1596
+ }
1597
+ if (slack_pairing !== undefined) {
1598
+ if (slack_pairing === null)
1599
+ delete existing.pairing;
1600
+ else
1601
+ existing.pairing = slack_pairing;
1602
+ }
1603
+ }
1604
+ }
1442
1605
  });
1443
1606
  }
1444
1607
  catch (err) {
@@ -1561,11 +1724,74 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1561
1724
  // in-memory knock list so the discovery UI stops surfacing them.
1562
1725
  if (Array.isArray(line_dm_allowlist)) {
1563
1726
  for (const userId of line_dm_allowlist)
1564
- (0, line_pending_senders_1.clearPendingSender)(agentId, userId);
1727
+ (0, pending_senders_1.clearPendingSender)('line', agentId, userId);
1565
1728
  }
1566
1729
  if (Array.isArray(line_group_allowlist)) {
1567
1730
  for (const id of line_group_allowlist)
1568
- (0, line_pending_senders_1.clearPendingSender)(agentId, id);
1731
+ (0, pending_senders_1.clearPendingSender)('line', agentId, id);
1732
+ }
1733
+ }
1734
+ if (slackTouched) {
1735
+ const tok = typeof slack_bot_token === 'string' ? slack_bot_token.trim() : '';
1736
+ const sec = typeof slack_signing_secret === 'string' ? slack_signing_secret.trim() : '';
1737
+ if (tok && sec) {
1738
+ cfg.slack = { ...(cfg.slack ?? {}), botToken: tok, signingSecret: sec };
1739
+ }
1740
+ else {
1741
+ delete cfg.slack;
1742
+ }
1743
+ // Slack is webhook-based — no receiver to start/stop. The webhook router
1744
+ // reads config live via runner.getAgentConfig(); just keep the runner's
1745
+ // copy in sync (same as LINE above).
1746
+ agentRunners.get(agentId)?.updateAgentConfig(cfg);
1747
+ }
1748
+ if (slackAccessTouched && cfg.slack) {
1749
+ if (slack_dm_policy !== undefined) {
1750
+ if (slack_dm_policy === null)
1751
+ delete cfg.slack.dmPolicy;
1752
+ else
1753
+ cfg.slack.dmPolicy = slack_dm_policy;
1754
+ }
1755
+ if (slack_dm_allowlist !== undefined) {
1756
+ if (slack_dm_allowlist === null)
1757
+ delete cfg.slack.dmAllowlist;
1758
+ else
1759
+ cfg.slack.dmAllowlist = slack_dm_allowlist;
1760
+ }
1761
+ if (slack_group_policy !== undefined) {
1762
+ if (slack_group_policy === null)
1763
+ delete cfg.slack.groupPolicy;
1764
+ else
1765
+ cfg.slack.groupPolicy = slack_group_policy;
1766
+ }
1767
+ if (slack_group_allowlist !== undefined) {
1768
+ if (slack_group_allowlist === null)
1769
+ delete cfg.slack.groupAllowlist;
1770
+ else
1771
+ cfg.slack.groupAllowlist = slack_group_allowlist;
1772
+ }
1773
+ if (slack_require_mention !== undefined) {
1774
+ if (slack_require_mention === null)
1775
+ delete cfg.slack.requireMention;
1776
+ else
1777
+ cfg.slack.requireMention = slack_require_mention;
1778
+ }
1779
+ if (slack_pairing !== undefined) {
1780
+ if (slack_pairing === null)
1781
+ delete cfg.slack.pairing;
1782
+ else
1783
+ cfg.slack.pairing = slack_pairing;
1784
+ }
1785
+ agentRunners.get(agentId)?.updateAgentConfig(cfg);
1786
+ // Anyone just added to an allowlist is now allowed — drop them from the
1787
+ // in-memory knock list so the discovery UI stops surfacing them.
1788
+ if (Array.isArray(slack_dm_allowlist)) {
1789
+ for (const userId of slack_dm_allowlist)
1790
+ (0, pending_senders_1.clearPendingSender)('slack', agentId, userId);
1791
+ }
1792
+ if (Array.isArray(slack_group_allowlist)) {
1793
+ for (const id of slack_group_allowlist)
1794
+ (0, pending_senders_1.clearPendingSender)('slack', agentId, id);
1569
1795
  }
1570
1796
  }
1571
1797
  res.json({
@@ -1592,6 +1818,24 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1592
1818
  line_connected: !!cfg.line?.channelSecret,
1593
1819
  line_token_preview: cfg.line?.channelAccessToken ? maskToken(cfg.line.channelAccessToken) : null,
1594
1820
  line_webhook_path: cfg.line?.channelSecret ? `/webhooks/line/${agentId}` : null,
1821
+ line_dm_policy: cfg.line?.channelSecret ? (cfg.line?.dmPolicy ?? null) : null,
1822
+ line_dm_allowlist: cfg.line?.channelSecret ? (cfg.line?.dmAllowlist ?? []) : null,
1823
+ line_group_policy: cfg.line?.channelSecret ? (cfg.line?.groupPolicy ?? null) : null,
1824
+ line_group_allowlist: cfg.line?.channelSecret ? (cfg.line?.groupAllowlist ?? []) : null,
1825
+ line_require_mention: cfg.line?.channelSecret ? (cfg.line?.requireMention ?? null) : null,
1826
+ line_pairing: cfg.line?.channelSecret ? (cfg.line?.pairing ?? true) : null,
1827
+ // Slack — same shape/semantics as LINE above, mirrors the GET /agents
1828
+ // list response exactly (this PATCH response never carried these
1829
+ // fields at all before — the UI fell back to the next GET refetch).
1830
+ slack_connected: !!cfg.slack?.signingSecret,
1831
+ slack_token_preview: cfg.slack?.botToken ? maskToken(cfg.slack.botToken) : null,
1832
+ slack_webhook_path: cfg.slack?.signingSecret ? `/webhooks/slack/${agentId}` : null,
1833
+ slack_dm_policy: cfg.slack?.signingSecret ? (cfg.slack?.dmPolicy ?? null) : null,
1834
+ slack_dm_allowlist: cfg.slack?.signingSecret ? (cfg.slack?.dmAllowlist ?? []) : null,
1835
+ slack_group_policy: cfg.slack?.signingSecret ? (cfg.slack?.groupPolicy ?? null) : null,
1836
+ slack_group_allowlist: cfg.slack?.signingSecret ? (cfg.slack?.groupAllowlist ?? []) : null,
1837
+ slack_require_mention: cfg.slack?.signingSecret ? (cfg.slack?.requireMention ?? null) : null,
1838
+ slack_pairing: cfg.slack?.signingSecret ? (cfg.slack?.pairing ?? true) : null,
1595
1839
  },
1596
1840
  });
1597
1841
  });
@@ -1804,7 +2048,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1804
2048
  res.status(404).json({ error: `Agent '${agentId}' not found` });
1805
2049
  return;
1806
2050
  }
1807
- res.json({ senders: (0, line_pending_senders_1.getPendingSenders)(agentId) });
2051
+ res.json({ senders: (0, pending_senders_1.getPendingSenders)('line', agentId) });
1808
2052
  });
1809
2053
  /**
1810
2054
  * DELETE /api/v1/agents/:agentId/line/pending/:senderId
@@ -1825,7 +2069,47 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1825
2069
  res.status(404).json({ error: `Agent '${agentId}' not found` });
1826
2070
  return;
1827
2071
  }
1828
- (0, line_pending_senders_1.clearPendingSender)(agentId, senderId);
2072
+ (0, pending_senders_1.clearPendingSender)('line', agentId, senderId);
2073
+ res.json({ ok: true });
2074
+ });
2075
+ /**
2076
+ * GET /api/v1/agents/:agentId/slack/pending
2077
+ * Recently denied Slack senders (Tier 1 allowlist discovery aid). Admin only.
2078
+ * Mirrors GET .../line/pending exactly, keyed under the 'slack' channel
2079
+ * namespace in the shared pending-senders store so LINE and Slack knocks on
2080
+ * the same agent never mix.
2081
+ */
2082
+ router.get('/v1/agents/:agentId/slack/pending', auth, (req, res) => {
2083
+ const { agentId } = req.params;
2084
+ const apiKey = req.apiKey;
2085
+ if (!(0, auth_1.isAdmin)(apiKey)) {
2086
+ res.status(403).json({ error: 'Admin key required' });
2087
+ return;
2088
+ }
2089
+ if (!agentConfigs.has(agentId)) {
2090
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
2091
+ return;
2092
+ }
2093
+ res.json({ senders: (0, pending_senders_1.getPendingSenders)('slack', agentId) });
2094
+ });
2095
+ /**
2096
+ * DELETE /api/v1/agents/:agentId/slack/pending/:senderId
2097
+ * Dismiss one knock from the in-memory pending list (admin only). Mirrors
2098
+ * DELETE .../line/pending/:senderId exactly. The id is a Slack user id (DM)
2099
+ * or channel id (channel/group/mpim), so no numeric validation.
2100
+ */
2101
+ router.delete('/v1/agents/:agentId/slack/pending/:senderId', auth, (req, res) => {
2102
+ const apiKey = req.apiKey;
2103
+ if (!(0, auth_1.isAdmin)(apiKey)) {
2104
+ res.status(403).json({ error: 'Admin key required' });
2105
+ return;
2106
+ }
2107
+ const { agentId, senderId } = req.params;
2108
+ if (!agentConfigs.has(agentId)) {
2109
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
2110
+ return;
2111
+ }
2112
+ (0, pending_senders_1.clearPendingSender)('slack', agentId, senderId);
1829
2113
  res.json({ ok: true });
1830
2114
  });
1831
2115
  /**
@@ -2267,8 +2551,8 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
2267
2551
  return;
2268
2552
  }
2269
2553
  const { source, rawChatId } = parseHistoryChatId(chatId);
2270
- if (source !== 'telegram' && source !== 'discord' && source !== 'line') {
2271
- res.status(400).json({ error: 'Sessions endpoint only supports telegram/discord/line chats' });
2554
+ if (!(0, types_1.isChatChannel)(source)) {
2555
+ res.status(400).json({ error: 'Sessions endpoint only supports telegram/discord/line/slack chats' });
2272
2556
  return;
2273
2557
  }
2274
2558
  try {
@@ -2453,8 +2737,8 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
2453
2737
  return;
2454
2738
  }
2455
2739
  const { source, rawChatId } = parseHistoryChatId(chatId);
2456
- if (source !== 'telegram' && source !== 'discord' && source !== 'line') {
2457
- res.status(400).json({ error: 'Cross-channel messaging only supported for telegram/discord/line chats' });
2740
+ if (!(0, types_1.isChatChannel)(source)) {
2741
+ res.status(400).json({ error: 'Cross-channel messaging only supported for telegram/discord/line/slack chats' });
2458
2742
  return;
2459
2743
  }
2460
2744
  const body = req.body;
@@ -3251,6 +3535,8 @@ function parseHistoryChatId(fullChatId) {
3251
3535
  return { source: 'discord', rawChatId: fullChatId.slice(8) };
3252
3536
  if (fullChatId.startsWith('line-'))
3253
3537
  return { source: 'line', rawChatId: fullChatId.slice(5) };
3538
+ if (fullChatId.startsWith('slack-'))
3539
+ return { source: 'slack', rawChatId: fullChatId.slice(6) };
3254
3540
  return { source: 'api', rawChatId: fullChatId };
3255
3541
  }
3256
3542
  //# sourceMappingURL=router.js.map