@0xmaxma/claude-gateway 1.8.13 → 1.8.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.
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAW,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOpD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,EAAe,WAAW,EAAe,MAAM,UAAU,CAAC;AAoFtF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAExD;AAMD,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAEtD;AAuED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CA4CjD;AAmUD,wBAAgB,eAAe,CAC7B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACtC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACtC,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,EAAE,GACrB,MAAM,CAgiIR"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAW,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOpD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,EAAe,WAAW,EAAe,MAAM,UAAU,CAAC;AAoFtF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAExD;AAMD,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAEtD;AAuED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CA4CjD;AAmUD,wBAAgB,eAAe,CAC7B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACtC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACtC,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,EAAE,GACrB,MAAM,CAytIR"}
@@ -1008,6 +1008,13 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1008
1008
  whatsapp_cloud_dm_allowlist: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.dmAllowlist ?? []) : null,
1009
1009
  whatsapp_cloud_pairing: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.pairing ?? true) : null,
1010
1010
  whatsapp_cloud_templates_enabled: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.templatesEnabled ?? false) : null,
1011
+ // WeChat — "connected" is LIVE manager state (a linked QR session),
1012
+ // never config-derived (there is no credential field to check — see
1013
+ // AgentConfig.wechat's doc comment). DM-only: no group_* fields exist.
1014
+ wechat_connected: agentRunners.get(id)?.getWeChatStatus?.()?.status === 'linked',
1015
+ wechat_dm_policy: cfg.wechat?.dmPolicy ?? null,
1016
+ wechat_dm_allowlist: cfg.wechat?.dmAllowlist ?? [],
1017
+ wechat_pairing: cfg.wechat?.pairing ?? true,
1011
1018
  }));
1012
1019
  res.json({ agents });
1013
1020
  });
@@ -1760,7 +1767,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1760
1767
  return;
1761
1768
  }
1762
1769
  const body = req.body;
1763
- 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, connectors, whatsapp_account_id, whatsapp_dm_policy, whatsapp_dm_allowlist, whatsapp_group_policy, whatsapp_group_allowlist, whatsapp_require_mention, whatsapp_pairing, whatsapp_cloud_access_token, whatsapp_cloud_phone_number_id, whatsapp_cloud_app_secret, whatsapp_cloud_verify_token, whatsapp_cloud_dm_policy, whatsapp_cloud_dm_allowlist, whatsapp_cloud_pairing, whatsapp_cloud_templates_enabled } = body;
1770
+ 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, connectors, whatsapp_account_id, whatsapp_dm_policy, whatsapp_dm_allowlist, whatsapp_group_policy, whatsapp_group_allowlist, whatsapp_require_mention, whatsapp_pairing, whatsapp_cloud_access_token, whatsapp_cloud_phone_number_id, whatsapp_cloud_app_secret, whatsapp_cloud_verify_token, whatsapp_cloud_dm_policy, whatsapp_cloud_dm_allowlist, whatsapp_cloud_pairing, whatsapp_cloud_templates_enabled, wechat_dm_policy, wechat_dm_allowlist, wechat_pairing } = body;
1764
1771
  if (name !== undefined && name !== null && typeof name !== 'string') {
1765
1772
  res.status(400).json({ error: 'name must be a string or null' });
1766
1773
  return;
@@ -1912,6 +1919,29 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1912
1919
  const slackAccessTouched = slack_dm_policy !== undefined || slack_dm_allowlist !== undefined ||
1913
1920
  slack_group_policy !== undefined || slack_group_allowlist !== undefined || slack_require_mention !== undefined ||
1914
1921
  slack_pairing !== undefined;
1922
+ // WeChat — DM-only access control (no credential, no group tier: see
1923
+ // AgentConfig.wechat's doc comment for why). Unlike LINE/Slack's access
1924
+ // fields, this is NOT gated on an existing wechat block already being
1925
+ // present — linking happens out-of-band (QR scan, via the /wechat/link
1926
+ // route below) and never touches config.json, so policy must be settable
1927
+ // independent of link status.
1928
+ if (wechat_dm_policy !== undefined && wechat_dm_policy !== null &&
1929
+ !(typeof wechat_dm_policy === 'string' && ['open', 'allowlist', 'disabled'].includes(wechat_dm_policy))) {
1930
+ res.status(400).json({ error: "wechat_dm_policy must be 'open', 'allowlist', 'disabled', or null" });
1931
+ return;
1932
+ }
1933
+ if (wechat_dm_allowlist !== undefined && wechat_dm_allowlist !== null &&
1934
+ !(Array.isArray(wechat_dm_allowlist) && wechat_dm_allowlist.every((u) => typeof u === 'string'))) {
1935
+ res.status(400).json({ error: 'wechat_dm_allowlist must be an array of strings or null' });
1936
+ return;
1937
+ }
1938
+ if (wechat_pairing !== undefined && wechat_pairing !== null &&
1939
+ typeof wechat_pairing !== 'boolean') {
1940
+ res.status(400).json({ error: 'wechat_pairing must be a boolean or null' });
1941
+ return;
1942
+ }
1943
+ const wechatAccessTouched = wechat_dm_policy !== undefined || wechat_dm_allowlist !== undefined ||
1944
+ wechat_pairing !== undefined;
1915
1945
  // connectors: a partial map of { [connectorId]: { enabled: boolean } } to merge.
1916
1946
  const connectorPatch = {};
1917
1947
  if (connectors !== undefined) {
@@ -2231,6 +2261,31 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
2231
2261
  }
2232
2262
  }
2233
2263
  }
2264
+ // WeChat access fields — get-or-create `agent.wechat` (not gated on it
2265
+ // already existing, unlike LINE/Slack: see the validation block above
2266
+ // for why).
2267
+ if (wechatAccessTouched) {
2268
+ const existing = agent.wechat ?? {};
2269
+ if (wechat_dm_policy !== undefined) {
2270
+ if (wechat_dm_policy === null)
2271
+ delete existing.dmPolicy;
2272
+ else
2273
+ existing.dmPolicy = wechat_dm_policy;
2274
+ }
2275
+ if (wechat_dm_allowlist !== undefined) {
2276
+ if (wechat_dm_allowlist === null)
2277
+ delete existing.dmAllowlist;
2278
+ else
2279
+ existing.dmAllowlist = wechat_dm_allowlist;
2280
+ }
2281
+ if (wechat_pairing !== undefined) {
2282
+ if (wechat_pairing === null)
2283
+ delete existing.pairing;
2284
+ else
2285
+ existing.pairing = wechat_pairing;
2286
+ }
2287
+ agent.wechat = existing;
2288
+ }
2234
2289
  if (connectors !== undefined) {
2235
2290
  const existing = agent.connectors ?? {};
2236
2291
  agent.connectors = { ...existing, ...connectorPatch };
@@ -2492,6 +2547,38 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
2492
2547
  (0, pending_senders_1.clearPendingSender)('slack', agentId, id);
2493
2548
  }
2494
2549
  }
2550
+ if (wechatAccessTouched) {
2551
+ const existing = cfg.wechat ?? {};
2552
+ if (wechat_dm_policy !== undefined) {
2553
+ if (wechat_dm_policy === null)
2554
+ delete existing.dmPolicy;
2555
+ else
2556
+ existing.dmPolicy = wechat_dm_policy;
2557
+ }
2558
+ if (wechat_dm_allowlist !== undefined) {
2559
+ if (wechat_dm_allowlist === null)
2560
+ delete existing.dmAllowlist;
2561
+ else
2562
+ existing.dmAllowlist = wechat_dm_allowlist;
2563
+ }
2564
+ if (wechat_pairing !== undefined) {
2565
+ if (wechat_pairing === null)
2566
+ delete existing.pairing;
2567
+ else
2568
+ existing.pairing = wechat_pairing;
2569
+ }
2570
+ cfg.wechat = existing;
2571
+ // WeChat has no receiver/webhook to start/stop — WeChatManager reads
2572
+ // access control live off the agent config on every inbound message
2573
+ // (see AgentRunner.handleWeChatInboundMessage); just keep it in sync.
2574
+ agentRunners.get(agentId)?.updateAgentConfig(cfg);
2575
+ // Anyone just added to an allowlist is now allowed — drop them from the
2576
+ // in-memory knock list so the discovery UI stops surfacing them.
2577
+ if (Array.isArray(wechat_dm_allowlist)) {
2578
+ for (const userId of wechat_dm_allowlist)
2579
+ (0, pending_senders_1.clearPendingSender)('wechat', agentId, userId);
2580
+ }
2581
+ }
2495
2582
  if (connectors !== undefined) {
2496
2583
  const before = cfg.connectors ?? {};
2497
2584
  const merged = { ...before, ...connectorPatch };
@@ -2664,6 +2751,10 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
2664
2751
  whatsapp_cloud_dm_allowlist: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.dmAllowlist ?? []) : null,
2665
2752
  whatsapp_cloud_pairing: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.pairing ?? true) : null,
2666
2753
  whatsapp_cloud_templates_enabled: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.templatesEnabled ?? false) : null,
2754
+ wechat_connected: agentRunners.get(agentId)?.getWeChatStatus?.()?.status === 'linked',
2755
+ wechat_dm_policy: cfg.wechat?.dmPolicy ?? null,
2756
+ wechat_dm_allowlist: cfg.wechat?.dmAllowlist ?? [],
2757
+ wechat_pairing: cfg.wechat?.pairing ?? true,
2667
2758
  },
2668
2759
  });
2669
2760
  });
@@ -2940,6 +3031,151 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
2940
3031
  (0, pending_senders_1.clearPendingSender)('slack', agentId, senderId);
2941
3032
  res.json({ ok: true });
2942
3033
  });
3034
+ /**
3035
+ * GET /api/v1/agents/:agentId/wechat/status
3036
+ * Live link status — status/qr straight from the in-process WeChatManager,
3037
+ * never config-derived (there is no credential field to derive it from —
3038
+ * see AgentConfig.wechat's doc comment). Polled by the web UI during
3039
+ * linking and while showing the connected card. Requires write access
3040
+ * (same as every other channel's connect surface), not just read. Mirrors
3041
+ * WhatsApp's `/whatsapp/status` route shape, once that channel lands.
3042
+ */
3043
+ router.get('/v1/agents/:agentId/wechat/status', auth, (req, res) => {
3044
+ const { agentId } = req.params;
3045
+ const apiKey = req.apiKey;
3046
+ if (!(0, auth_1.canWriteAgent)(apiKey, agentId)) {
3047
+ res.status(403).json({ error: 'Write permission required' });
3048
+ return;
3049
+ }
3050
+ const runner = agentRunners.get(agentId);
3051
+ if (!runner) {
3052
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
3053
+ return;
3054
+ }
3055
+ res.json(runner.getWeChatStatus());
3056
+ });
3057
+ /**
3058
+ * POST /api/v1/agents/:agentId/wechat/link
3059
+ * Start (or restart) a QR-code linking flow. Returns once linked or once
3060
+ * the attempt window elapses — unlike WhatsApp's fire-and-forget
3061
+ * `/whatsapp/link`, there is no separate pairing-code path to race against,
3062
+ * so the client can simply await this and then re-check `/status` once.
3063
+ */
3064
+ router.post('/v1/agents/:agentId/wechat/link', auth, async (req, res) => {
3065
+ const { agentId } = req.params;
3066
+ const apiKey = req.apiKey;
3067
+ if (!(0, auth_1.canWriteAgent)(apiKey, agentId)) {
3068
+ res.status(403).json({ error: 'Write permission required' });
3069
+ return;
3070
+ }
3071
+ const runner = agentRunners.get(agentId);
3072
+ if (!runner) {
3073
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
3074
+ return;
3075
+ }
3076
+ try {
3077
+ await runner.startWeChatLinking();
3078
+ res.json({ ok: true, status: runner.getWeChatStatus() });
3079
+ }
3080
+ catch (err) {
3081
+ res.status(500).json({ error: err.message });
3082
+ }
3083
+ });
3084
+ /**
3085
+ * POST /api/v1/agents/:agentId/wechat/unlink
3086
+ * Logout and wipe the linked session. The user must scan fresh afterward.
3087
+ */
3088
+ router.post('/v1/agents/:agentId/wechat/unlink', auth, async (req, res) => {
3089
+ const { agentId } = req.params;
3090
+ const apiKey = req.apiKey;
3091
+ if (!(0, auth_1.canWriteAgent)(apiKey, agentId)) {
3092
+ res.status(403).json({ error: 'Write permission required' });
3093
+ return;
3094
+ }
3095
+ const runner = agentRunners.get(agentId);
3096
+ if (!runner) {
3097
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
3098
+ return;
3099
+ }
3100
+ await runner.unlinkWeChat();
3101
+ res.json({ ok: true });
3102
+ });
3103
+ /**
3104
+ * POST /api/v1/agents/:agentId/wechat/send
3105
+ * INTERNAL — called by the `wechat_reply` MCP tool (via GATEWAY_API_URL/
3106
+ * GATEWAY_API_KEY, same as every other MCP subprocess reaches the
3107
+ * gateway), never by the web UI. Exists because the manager's live iLink
3108
+ * session/credentials only exist inside this process — same reasoning
3109
+ * WhatsApp's `/whatsapp/send` route documents for its Baileys socket.
3110
+ */
3111
+ router.post('/v1/agents/:agentId/wechat/send', auth, async (req, res) => {
3112
+ const { agentId } = req.params;
3113
+ const apiKey = req.apiKey;
3114
+ if (!(0, auth_1.canWriteAgent)(apiKey, agentId)) {
3115
+ res.status(403).json({ error: 'Write permission required' });
3116
+ return;
3117
+ }
3118
+ const runner = agentRunners.get(agentId);
3119
+ if (!runner) {
3120
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
3121
+ return;
3122
+ }
3123
+ const { to_id, text } = req.body;
3124
+ if (typeof to_id !== 'string' || !to_id) {
3125
+ res.status(400).json({ error: 'to_id is required' });
3126
+ return;
3127
+ }
3128
+ if (typeof text !== 'string' || !text) {
3129
+ res.status(400).json({ error: 'text is required' });
3130
+ return;
3131
+ }
3132
+ try {
3133
+ await runner.sendWeChatMessage(to_id, text);
3134
+ res.json({ ok: true });
3135
+ }
3136
+ catch (err) {
3137
+ res.status(500).json({ error: err.message });
3138
+ }
3139
+ });
3140
+ /**
3141
+ * GET /api/v1/agents/:agentId/wechat/pending
3142
+ * Recently denied WeChat senders (Tier 1 allowlist discovery aid). Admin
3143
+ * only. Mirrors GET .../line/pending exactly, keyed under the 'wechat'
3144
+ * channel namespace in the shared pending-senders store.
3145
+ */
3146
+ router.get('/v1/agents/:agentId/wechat/pending', auth, (req, res) => {
3147
+ const { agentId } = req.params;
3148
+ const apiKey = req.apiKey;
3149
+ if (!(0, auth_1.isAdmin)(apiKey)) {
3150
+ res.status(403).json({ error: 'Admin key required' });
3151
+ return;
3152
+ }
3153
+ if (!agentConfigs.has(agentId)) {
3154
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
3155
+ return;
3156
+ }
3157
+ res.json({ senders: (0, pending_senders_1.getPendingSenders)('wechat', agentId) });
3158
+ });
3159
+ /**
3160
+ * DELETE /api/v1/agents/:agentId/wechat/pending/:senderId
3161
+ * Dismiss one knock from the in-memory pending list (admin only). Mirrors
3162
+ * DELETE .../line/pending/:senderId exactly. The id is an iLink sender id,
3163
+ * so no format validation.
3164
+ */
3165
+ router.delete('/v1/agents/:agentId/wechat/pending/:senderId', auth, (req, res) => {
3166
+ const apiKey = req.apiKey;
3167
+ if (!(0, auth_1.isAdmin)(apiKey)) {
3168
+ res.status(403).json({ error: 'Admin key required' });
3169
+ return;
3170
+ }
3171
+ const { agentId, senderId } = req.params;
3172
+ if (!agentConfigs.has(agentId)) {
3173
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
3174
+ return;
3175
+ }
3176
+ (0, pending_senders_1.clearPendingSender)('wechat', agentId, senderId);
3177
+ res.json({ ok: true });
3178
+ });
2943
3179
  /**
2944
3180
  * GET /api/v1/agents/:agentId/whatsapp/pending?account_id=...
2945
3181
  * Recently denied WhatsApp senders/groups (Tier 1/3 discovery aid) for ONE