@0xmaxma/claude-gateway 1.8.12 → 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.
- package/README.md +8 -0
- package/dist/agent/create-agent-prompts.d.ts +6 -0
- package/dist/agent/create-agent-prompts.d.ts.map +1 -1
- package/dist/agent/create-agent-prompts.js +30 -0
- package/dist/agent/create-agent-prompts.js.map +1 -1
- package/dist/agent/runner.d.ts +17 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +187 -14
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +309 -1
- package/dist/api/router.js.map +1 -1
- package/dist/api/wechat-access.d.ts +45 -0
- package/dist/api/wechat-access.d.ts.map +1 -0
- package/dist/api/wechat-access.js +48 -0
- package/dist/api/wechat-access.js.map +1 -0
- package/dist/history/types.d.ts +10 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/history/types.js +10 -1
- package/dist/history/types.js.map +1 -1
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +1 -0
- package/dist/session/process.js.map +1 -1
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wechat/ilink-client.d.ts +194 -0
- package/dist/wechat/ilink-client.d.ts.map +1 -0
- package/dist/wechat/ilink-client.js +641 -0
- package/dist/wechat/ilink-client.js.map +1 -0
- package/dist/wechat/manager.d.ts +93 -0
- package/dist/wechat/manager.d.ts.map +1 -0
- package/dist/wechat/manager.js +409 -0
- package/dist/wechat/manager.js.map +1 -0
- package/mcp/server.ts +2 -0
- package/mcp/tools/wechat/client.ts +49 -0
- package/mcp/tools/wechat/module.ts +88 -0
- package/package.json +1 -1
package/dist/api/router.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/api/router.js
CHANGED
|
@@ -449,6 +449,20 @@ const TELEGRAM_API_BASE = process.env.TELEGRAM_API_BASE ?? 'https://api.telegram
|
|
|
449
449
|
/** Max simultaneous wizard/start Claude subprocesses to prevent resource exhaustion. */
|
|
450
450
|
let wizardStartsInFlight = 0;
|
|
451
451
|
const WIZARD_MAX_CONCURRENT = 2;
|
|
452
|
+
/**
|
|
453
|
+
* Max simultaneous describe/rewrite Claude subprocesses. Kept separate from
|
|
454
|
+
* wizardStartsInFlight so a burst of rewrite calls can't starve wizard-start
|
|
455
|
+
* slots (or vice versa) — the two features don't share a concurrency budget.
|
|
456
|
+
*/
|
|
457
|
+
let rewritesInFlight = 0;
|
|
458
|
+
const REWRITE_MAX_CONCURRENT = 2;
|
|
459
|
+
/**
|
|
460
|
+
* Max input length for a describe/rewrite. A real "describe your agent" draft is
|
|
461
|
+
* far shorter than this; the cap keeps an oversized payload from pinning a Claude
|
|
462
|
+
* subprocess for the full 120s timeout (two such requests would occupy both
|
|
463
|
+
* REWRITE_MAX_CONCURRENT slots and block everyone else).
|
|
464
|
+
*/
|
|
465
|
+
const REWRITE_MAX_TEXT_LENGTH = 8000;
|
|
452
466
|
/** Call Claude --print with stdin prompt; resolves with stdout on exit 0. */
|
|
453
467
|
function runClaude(prompt, timeoutMs = 120000) {
|
|
454
468
|
return new Promise((resolve, reject) => {
|
|
@@ -994,6 +1008,13 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
994
1008
|
whatsapp_cloud_dm_allowlist: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.dmAllowlist ?? []) : null,
|
|
995
1009
|
whatsapp_cloud_pairing: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.pairing ?? true) : null,
|
|
996
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,
|
|
997
1018
|
}));
|
|
998
1019
|
res.json({ agents });
|
|
999
1020
|
});
|
|
@@ -1362,6 +1383,64 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
1362
1383
|
expiresAt: new Date(state.expiresAt).toISOString(),
|
|
1363
1384
|
});
|
|
1364
1385
|
});
|
|
1386
|
+
/**
|
|
1387
|
+
* POST /api/v1/agents/describe/rewrite
|
|
1388
|
+
* Re-write a rough "describe your agent" draft into cleaner, moderately
|
|
1389
|
+
* clarified text. Stateless: no wizardId/agentId involved, single string
|
|
1390
|
+
* in, single string out.
|
|
1391
|
+
*/
|
|
1392
|
+
router.post('/v1/agents/describe/rewrite', auth, async (req, res) => {
|
|
1393
|
+
const apiKey = req.apiKey;
|
|
1394
|
+
if (!(0, auth_1.isAdmin)(apiKey)) {
|
|
1395
|
+
res.status(403).json({ error: 'Admin key required' });
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const body = req.body;
|
|
1399
|
+
const { text } = body;
|
|
1400
|
+
if (!text || typeof text !== 'string' || !text.trim()) {
|
|
1401
|
+
res.status(400).json({ error: 'text is required' });
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (text.length > REWRITE_MAX_TEXT_LENGTH) {
|
|
1405
|
+
res.status(400).json({ error: `text too long (max ${REWRITE_MAX_TEXT_LENGTH} characters)` });
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (rewritesInFlight >= REWRITE_MAX_CONCURRENT) {
|
|
1409
|
+
res.status(429).json({ error: 'Too many re-write requests in progress, please retry later' });
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
let rawOutput;
|
|
1413
|
+
rewritesInFlight++;
|
|
1414
|
+
try {
|
|
1415
|
+
rawOutput = await runClaude((0, create_agent_prompts_1.buildRewriteDescriptionPrompt)(text.trim()));
|
|
1416
|
+
}
|
|
1417
|
+
catch (err) {
|
|
1418
|
+
console.error(`[api] describe/rewrite failed: ${err.message}`);
|
|
1419
|
+
res.status(500).json({ error: 'Failed to re-write description' });
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
finally {
|
|
1423
|
+
rewritesInFlight--;
|
|
1424
|
+
}
|
|
1425
|
+
let rewritten = rawOutput.trim();
|
|
1426
|
+
// Strip a wrapping code fence if the model added one, regardless of the
|
|
1427
|
+
// language hint (```markdown, ```md, ```text, ```plaintext, ```html, …) or
|
|
1428
|
+
// none at all. Requires the opening fence to sit on its own line so inline
|
|
1429
|
+
// backticks in a single-line answer are never mistaken for a wrapper; the
|
|
1430
|
+
// closing newline is optional and CRLF is tolerated.
|
|
1431
|
+
const fenceMatch = rewritten.match(/^```[^\r\n`]*\r?\n([\s\S]*?)\r?\n?```\s*$/);
|
|
1432
|
+
if (fenceMatch)
|
|
1433
|
+
rewritten = (fenceMatch[1] ?? '').trim();
|
|
1434
|
+
// Claude occasionally returns an empty/whitespace-only completion. Never
|
|
1435
|
+
// answer 200 with an empty string: the frontend would overwrite the user's
|
|
1436
|
+
// draft with nothing and silently destroy their input. Surface an error so
|
|
1437
|
+
// the client can keep the original text and let the user retry.
|
|
1438
|
+
if (!rewritten) {
|
|
1439
|
+
res.status(502).json({ error: 'Re-write produced no output, please try again' });
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
res.status(200).json({ text: rewritten });
|
|
1443
|
+
});
|
|
1365
1444
|
/**
|
|
1366
1445
|
* PUT /api/v1/agents/wizard/:wizardId/avatar
|
|
1367
1446
|
* Upload avatar into wizard state (in-memory until confirm).
|
|
@@ -1688,7 +1767,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
1688
1767
|
return;
|
|
1689
1768
|
}
|
|
1690
1769
|
const body = req.body;
|
|
1691
|
-
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;
|
|
1692
1771
|
if (name !== undefined && name !== null && typeof name !== 'string') {
|
|
1693
1772
|
res.status(400).json({ error: 'name must be a string or null' });
|
|
1694
1773
|
return;
|
|
@@ -1840,6 +1919,29 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
1840
1919
|
const slackAccessTouched = slack_dm_policy !== undefined || slack_dm_allowlist !== undefined ||
|
|
1841
1920
|
slack_group_policy !== undefined || slack_group_allowlist !== undefined || slack_require_mention !== undefined ||
|
|
1842
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;
|
|
1843
1945
|
// connectors: a partial map of { [connectorId]: { enabled: boolean } } to merge.
|
|
1844
1946
|
const connectorPatch = {};
|
|
1845
1947
|
if (connectors !== undefined) {
|
|
@@ -2159,6 +2261,31 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
2159
2261
|
}
|
|
2160
2262
|
}
|
|
2161
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
|
+
}
|
|
2162
2289
|
if (connectors !== undefined) {
|
|
2163
2290
|
const existing = agent.connectors ?? {};
|
|
2164
2291
|
agent.connectors = { ...existing, ...connectorPatch };
|
|
@@ -2420,6 +2547,38 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
2420
2547
|
(0, pending_senders_1.clearPendingSender)('slack', agentId, id);
|
|
2421
2548
|
}
|
|
2422
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
|
+
}
|
|
2423
2582
|
if (connectors !== undefined) {
|
|
2424
2583
|
const before = cfg.connectors ?? {};
|
|
2425
2584
|
const merged = { ...before, ...connectorPatch };
|
|
@@ -2592,6 +2751,10 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
2592
2751
|
whatsapp_cloud_dm_allowlist: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.dmAllowlist ?? []) : null,
|
|
2593
2752
|
whatsapp_cloud_pairing: cfg.whatsapp_cloud?.appSecret ? (cfg.whatsapp_cloud?.pairing ?? true) : null,
|
|
2594
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,
|
|
2595
2758
|
},
|
|
2596
2759
|
});
|
|
2597
2760
|
});
|
|
@@ -2868,6 +3031,151 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
2868
3031
|
(0, pending_senders_1.clearPendingSender)('slack', agentId, senderId);
|
|
2869
3032
|
res.json({ ok: true });
|
|
2870
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
|
+
});
|
|
2871
3179
|
/**
|
|
2872
3180
|
* GET /api/v1/agents/:agentId/whatsapp/pending?account_id=...
|
|
2873
3181
|
* Recently denied WhatsApp senders/groups (Tier 1/3 discovery aid) for ONE
|