@0xmaxma/claude-gateway 1.3.16 → 1.3.18

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 (62) hide show
  1. package/config.template.json +8 -4
  2. package/dist/agent/builtin-commands.d.ts +1 -1
  3. package/dist/agent/builtin-commands.d.ts.map +1 -1
  4. package/dist/agent/builtin-commands.js +5 -0
  5. package/dist/agent/builtin-commands.js.map +1 -1
  6. package/dist/agent/line-pure.d.ts +31 -0
  7. package/dist/agent/line-pure.d.ts.map +1 -0
  8. package/dist/agent/line-pure.js +99 -0
  9. package/dist/agent/line-pure.js.map +1 -0
  10. package/dist/agent/line-reply-manager.d.ts +68 -0
  11. package/dist/agent/line-reply-manager.d.ts.map +1 -0
  12. package/dist/agent/line-reply-manager.js +383 -0
  13. package/dist/agent/line-reply-manager.js.map +1 -0
  14. package/dist/agent/runner.d.ts +11 -2
  15. package/dist/agent/runner.d.ts.map +1 -1
  16. package/dist/agent/runner.js +106 -8
  17. package/dist/agent/runner.js.map +1 -1
  18. package/dist/api/gateway-router.d.ts.map +1 -1
  19. package/dist/api/gateway-router.js +4 -0
  20. package/dist/api/gateway-router.js.map +1 -1
  21. package/dist/api/line-access.d.ts +60 -0
  22. package/dist/api/line-access.d.ts.map +1 -0
  23. package/dist/api/line-access.js +69 -0
  24. package/dist/api/line-access.js.map +1 -0
  25. package/dist/api/line-mention.d.ts +35 -0
  26. package/dist/api/line-mention.d.ts.map +1 -0
  27. package/dist/api/line-mention.js +33 -0
  28. package/dist/api/line-mention.js.map +1 -0
  29. package/dist/api/line-pending-senders.d.ts +40 -0
  30. package/dist/api/line-pending-senders.d.ts.map +1 -0
  31. package/dist/api/line-pending-senders.js +107 -0
  32. package/dist/api/line-pending-senders.js.map +1 -0
  33. package/dist/api/line-webhook-router.d.ts +51 -0
  34. package/dist/api/line-webhook-router.d.ts.map +1 -0
  35. package/dist/api/line-webhook-router.js +416 -0
  36. package/dist/api/line-webhook-router.js.map +1 -0
  37. package/dist/api/router.d.ts.map +1 -1
  38. package/dist/api/router.js +244 -7
  39. package/dist/api/router.js.map +1 -1
  40. package/dist/history/types.d.ts +1 -1
  41. package/dist/history/types.d.ts.map +1 -1
  42. package/dist/session/compactor.d.ts +1 -1
  43. package/dist/session/compactor.d.ts.map +1 -1
  44. package/dist/session/compactor.js.map +1 -1
  45. package/dist/session/process.d.ts +2 -2
  46. package/dist/session/process.d.ts.map +1 -1
  47. package/dist/session/process.js +14 -3
  48. package/dist/session/process.js.map +1 -1
  49. package/dist/session/store.d.ts +13 -13
  50. package/dist/session/store.d.ts.map +1 -1
  51. package/dist/session/store.js.map +1 -1
  52. package/dist/shell/claude-pty-shell.js +12 -0
  53. package/dist/shell/claude-pty-shell.js.map +1 -1
  54. package/dist/types.d.ts +60 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/mcp/bun.lock +9 -2
  57. package/mcp/package.json +1 -0
  58. package/mcp/server.ts +2 -0
  59. package/mcp/tools/line/module.ts +127 -0
  60. package/mcp/tools/line/pure.ts +62 -0
  61. package/mcp/tools/telegram/receiver-server.ts +8 -5
  62. package/package.json +2 -1
@@ -46,6 +46,7 @@ const auth_1 = require("./auth");
46
46
  const media_store_1 = require("../history/media-store");
47
47
  const db_1 = require("../history/db");
48
48
  const wizard_state_1 = require("./wizard-state");
49
+ const line_pending_senders_1 = require("./line-pending-senders");
49
50
  const create_agent_prompts_1 = require("../agent/create-agent-prompts");
50
51
  const MAX_MESSAGE_LENGTH = 10000;
51
52
  const DEFAULT_TIMEOUT_MS = 60000;
@@ -504,6 +505,24 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
504
505
  telegram_token_preview: cfg.telegram?.botToken ? maskToken(cfg.telegram.botToken) : null,
505
506
  discord_token_preview: cfg.discord?.botToken ? maskToken(cfg.discord.botToken) : null,
506
507
  telegram_dm_policy: cfg.telegram?.botToken ? readTelegramAccess(id).dmPolicy : null,
508
+ line_connected: !!cfg.line?.channelSecret,
509
+ line_token_preview: cfg.line?.channelAccessToken ? maskToken(cfg.line.channelAccessToken) : null,
510
+ line_webhook_path: cfg.line?.channelSecret ? `/line/webhook/${id}` : null,
511
+ // DM access (Tier 1). Stored directly in the line config (no separate
512
+ // access file like Telegram). `dmPolicy` absent ⇒ closed/allowlist
513
+ // semantics at runtime; surface the raw value (null when unset) so the
514
+ // UI can render the effective state.
515
+ line_dm_policy: cfg.line?.channelSecret ? (cfg.line?.dmPolicy ?? null) : null,
516
+ line_dm_allowlist: cfg.line?.channelSecret ? (cfg.line?.dmAllowlist ?? []) : null,
517
+ // Group/room access (Tier 3). Same storage + semantics as DM, keyed on
518
+ // group/room ids. `requireMention` absent ⇒ true (only answer when the
519
+ // bot is @mentioned in a group).
520
+ line_group_policy: cfg.line?.channelSecret ? (cfg.line?.groupPolicy ?? null) : null,
521
+ line_group_allowlist: cfg.line?.channelSecret ? (cfg.line?.groupAllowlist ?? []) : null,
522
+ line_require_mention: cfg.line?.channelSecret ? (cfg.line?.requireMention ?? null) : null,
523
+ // Pairing toggle (orthogonal to dm/groupPolicy). Absent ⇒ on; surface a
524
+ // concrete boolean so the UI toggle reflects the effective default.
525
+ line_pairing: cfg.line?.channelSecret ? (cfg.line?.pairing ?? true) : null,
507
526
  }));
508
527
  res.json({ agents });
509
528
  });
@@ -870,8 +889,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
870
889
  avatarFilename = undefined;
871
890
  }
872
891
  }
873
- const defaultModel = (models ?? runner_1.DEFAULT_MODELS).find((m) => m.alias === 'sonnet')?.id
874
- ?? runner_1.DEFAULT_MODELS[2].id;
892
+ const defaultModel = 'claude-sonnet-4-6';
875
893
  const newAgent = {
876
894
  id: agentId,
877
895
  description: wizard.prompt.slice(0, 200).trim(),
@@ -1111,7 +1129,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1111
1129
  return;
1112
1130
  }
1113
1131
  const body = req.body;
1114
- const { description, model, allow_tools, telegram_bot_token, discord_bot_token } = body;
1132
+ const { 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;
1115
1133
  if (description !== undefined && (typeof description !== 'string' || !description.trim())) {
1116
1134
  res.status(400).json({ error: 'description must be a non-empty string' });
1117
1135
  return;
@@ -1132,6 +1150,63 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1132
1150
  res.status(400).json({ error: 'discord_bot_token must be a string or null' });
1133
1151
  return;
1134
1152
  }
1153
+ if (line_channel_access_token !== undefined && line_channel_access_token !== null && typeof line_channel_access_token !== 'string') {
1154
+ res.status(400).json({ error: 'line_channel_access_token must be a string or null' });
1155
+ return;
1156
+ }
1157
+ if (line_channel_secret !== undefined && line_channel_secret !== null && typeof line_channel_secret !== 'string') {
1158
+ res.status(400).json({ error: 'line_channel_secret must be a string or null' });
1159
+ return;
1160
+ }
1161
+ // LINE needs BOTH credentials together. Reject a half-set (one without the other),
1162
+ // unless both are being cleared (disconnect).
1163
+ const lineTouched = line_channel_access_token !== undefined || line_channel_secret !== undefined;
1164
+ if (lineTouched) {
1165
+ const at = typeof line_channel_access_token === 'string' ? line_channel_access_token.trim() : '';
1166
+ const sec = typeof line_channel_secret === 'string' ? line_channel_secret.trim() : '';
1167
+ const bothSet = at !== '' && sec !== '';
1168
+ const bothClear = at === '' && sec === '';
1169
+ if (!bothSet && !bothClear) {
1170
+ res.status(400).json({ error: 'line_channel_access_token and line_channel_secret must be provided together' });
1171
+ return;
1172
+ }
1173
+ }
1174
+ // LINE DM access control (Tier 1). Validated independently of the credentials so
1175
+ // policy/allowlist can be changed without re-sending the tokens.
1176
+ if (line_dm_policy !== undefined && line_dm_policy !== null &&
1177
+ !(typeof line_dm_policy === 'string' && ['open', 'allowlist', 'disabled'].includes(line_dm_policy))) {
1178
+ res.status(400).json({ error: "line_dm_policy must be 'open', 'allowlist', 'disabled', or null" });
1179
+ return;
1180
+ }
1181
+ if (line_dm_allowlist !== undefined && line_dm_allowlist !== null &&
1182
+ !(Array.isArray(line_dm_allowlist) && line_dm_allowlist.every((u) => typeof u === 'string'))) {
1183
+ res.status(400).json({ error: 'line_dm_allowlist must be an array of strings or null' });
1184
+ return;
1185
+ }
1186
+ // LINE group/room access control (Tier 3). Same independence as DM access.
1187
+ if (line_group_policy !== undefined && line_group_policy !== null &&
1188
+ !(typeof line_group_policy === 'string' && ['open', 'allowlist', 'disabled'].includes(line_group_policy))) {
1189
+ res.status(400).json({ error: "line_group_policy must be 'open', 'allowlist', 'disabled', or null" });
1190
+ return;
1191
+ }
1192
+ if (line_group_allowlist !== undefined && line_group_allowlist !== null &&
1193
+ !(Array.isArray(line_group_allowlist) && line_group_allowlist.every((u) => typeof u === 'string'))) {
1194
+ res.status(400).json({ error: 'line_group_allowlist must be an array of strings or null' });
1195
+ return;
1196
+ }
1197
+ if (line_require_mention !== undefined && line_require_mention !== null &&
1198
+ typeof line_require_mention !== 'boolean') {
1199
+ res.status(400).json({ error: 'line_require_mention must be a boolean or null' });
1200
+ return;
1201
+ }
1202
+ if (line_pairing !== undefined && line_pairing !== null &&
1203
+ typeof line_pairing !== 'boolean') {
1204
+ res.status(400).json({ error: 'line_pairing must be a boolean or null' });
1205
+ return;
1206
+ }
1207
+ const lineAccessTouched = line_dm_policy !== undefined || line_dm_allowlist !== undefined ||
1208
+ line_group_policy !== undefined || line_group_allowlist !== undefined || line_require_mention !== undefined ||
1209
+ line_pairing !== undefined;
1135
1210
  try {
1136
1211
  await writeAgentsToConfig(configPath, (agents) => {
1137
1212
  const agent = agents.find((a) => a.id === agentId);
@@ -1163,6 +1238,61 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1163
1238
  agent.discord = { ...(existing ?? {}), botToken: discord_bot_token.trim() };
1164
1239
  }
1165
1240
  }
1241
+ if (lineTouched) {
1242
+ const at = typeof line_channel_access_token === 'string' ? line_channel_access_token.trim() : '';
1243
+ const sec = typeof line_channel_secret === 'string' ? line_channel_secret.trim() : '';
1244
+ if (at === '' && sec === '') {
1245
+ delete agent.line;
1246
+ }
1247
+ else {
1248
+ const existing = agent.line;
1249
+ agent.line = { ...(existing ?? {}), channelAccessToken: at, channelSecret: sec };
1250
+ }
1251
+ }
1252
+ // DM access — merge into the existing line block (re-read after the token
1253
+ // block above, which may have just created or deleted it). Skip silently
1254
+ // when no line channel exists; policy without credentials is meaningless.
1255
+ if (lineAccessTouched) {
1256
+ const existing = agent.line;
1257
+ if (existing) {
1258
+ if (line_dm_policy !== undefined) {
1259
+ if (line_dm_policy === null)
1260
+ delete existing.dmPolicy;
1261
+ else
1262
+ existing.dmPolicy = line_dm_policy;
1263
+ }
1264
+ if (line_dm_allowlist !== undefined) {
1265
+ if (line_dm_allowlist === null)
1266
+ delete existing.dmAllowlist;
1267
+ else
1268
+ existing.dmAllowlist = line_dm_allowlist;
1269
+ }
1270
+ if (line_group_policy !== undefined) {
1271
+ if (line_group_policy === null)
1272
+ delete existing.groupPolicy;
1273
+ else
1274
+ existing.groupPolicy = line_group_policy;
1275
+ }
1276
+ if (line_group_allowlist !== undefined) {
1277
+ if (line_group_allowlist === null)
1278
+ delete existing.groupAllowlist;
1279
+ else
1280
+ existing.groupAllowlist = line_group_allowlist;
1281
+ }
1282
+ if (line_require_mention !== undefined) {
1283
+ if (line_require_mention === null)
1284
+ delete existing.requireMention;
1285
+ else
1286
+ existing.requireMention = line_require_mention;
1287
+ }
1288
+ if (line_pairing !== undefined) {
1289
+ if (line_pairing === null)
1290
+ delete existing.pairing;
1291
+ else
1292
+ existing.pairing = line_pairing;
1293
+ }
1294
+ }
1295
+ }
1166
1296
  });
1167
1297
  }
1168
1298
  catch (err) {
@@ -1209,6 +1339,68 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1209
1339
  agentRunners.get(agentId)?.stopDiscordReceiver();
1210
1340
  }
1211
1341
  }
1342
+ if (lineTouched) {
1343
+ const at = typeof line_channel_access_token === 'string' ? line_channel_access_token.trim() : '';
1344
+ const sec = typeof line_channel_secret === 'string' ? line_channel_secret.trim() : '';
1345
+ if (at && sec) {
1346
+ cfg.line = { ...(cfg.line ?? {}), channelAccessToken: at, channelSecret: sec };
1347
+ }
1348
+ else {
1349
+ delete cfg.line;
1350
+ }
1351
+ // LINE is webhook-based — no receiver to start/stop. The webhook router reads
1352
+ // config live via runner.getAgentConfig(); just keep the runner's copy in sync.
1353
+ agentRunners.get(agentId)?.updateAgentConfig(cfg);
1354
+ }
1355
+ if (lineAccessTouched && cfg.line) {
1356
+ if (line_dm_policy !== undefined) {
1357
+ if (line_dm_policy === null)
1358
+ delete cfg.line.dmPolicy;
1359
+ else
1360
+ cfg.line.dmPolicy = line_dm_policy;
1361
+ }
1362
+ if (line_dm_allowlist !== undefined) {
1363
+ if (line_dm_allowlist === null)
1364
+ delete cfg.line.dmAllowlist;
1365
+ else
1366
+ cfg.line.dmAllowlist = line_dm_allowlist;
1367
+ }
1368
+ if (line_group_policy !== undefined) {
1369
+ if (line_group_policy === null)
1370
+ delete cfg.line.groupPolicy;
1371
+ else
1372
+ cfg.line.groupPolicy = line_group_policy;
1373
+ }
1374
+ if (line_group_allowlist !== undefined) {
1375
+ if (line_group_allowlist === null)
1376
+ delete cfg.line.groupAllowlist;
1377
+ else
1378
+ cfg.line.groupAllowlist = line_group_allowlist;
1379
+ }
1380
+ if (line_require_mention !== undefined) {
1381
+ if (line_require_mention === null)
1382
+ delete cfg.line.requireMention;
1383
+ else
1384
+ cfg.line.requireMention = line_require_mention;
1385
+ }
1386
+ if (line_pairing !== undefined) {
1387
+ if (line_pairing === null)
1388
+ delete cfg.line.pairing;
1389
+ else
1390
+ cfg.line.pairing = line_pairing;
1391
+ }
1392
+ agentRunners.get(agentId)?.updateAgentConfig(cfg);
1393
+ // Anyone just added to an allowlist is now allowed — drop them from the
1394
+ // in-memory knock list so the discovery UI stops surfacing them.
1395
+ if (Array.isArray(line_dm_allowlist)) {
1396
+ for (const userId of line_dm_allowlist)
1397
+ (0, line_pending_senders_1.clearPendingSender)(agentId, userId);
1398
+ }
1399
+ if (Array.isArray(line_group_allowlist)) {
1400
+ for (const id of line_group_allowlist)
1401
+ (0, line_pending_senders_1.clearPendingSender)(agentId, id);
1402
+ }
1403
+ }
1212
1404
  res.json({
1213
1405
  agent: {
1214
1406
  id: agentId,
@@ -1220,6 +1412,9 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1220
1412
  telegram_token_preview: cfg.telegram?.botToken ? maskToken(cfg.telegram.botToken) : null,
1221
1413
  discord_token_preview: cfg.discord?.botToken ? maskToken(cfg.discord.botToken) : null,
1222
1414
  telegram_dm_policy: cfg.telegram?.botToken ? readTelegramAccess(agentId).dmPolicy : null,
1415
+ line_connected: !!cfg.line?.channelSecret,
1416
+ line_token_preview: cfg.line?.channelAccessToken ? maskToken(cfg.line.channelAccessToken) : null,
1417
+ line_webhook_path: cfg.line?.channelSecret ? `/line/webhook/${agentId}` : null,
1223
1418
  },
1224
1419
  });
1225
1420
  });
@@ -1431,6 +1626,46 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1431
1626
  const access = readTelegramAccess(agentId);
1432
1627
  res.json({ allowFrom: access.allowFrom });
1433
1628
  });
1629
+ /**
1630
+ * GET /api/v1/agents/:agentId/line/pending
1631
+ * Recently denied LINE senders (Tier 1 allowlist discovery aid). Admin only.
1632
+ * In-memory + ephemeral — populated by the webhook gate when it drops a sender.
1633
+ */
1634
+ router.get('/v1/agents/:agentId/line/pending', auth, (req, res) => {
1635
+ const { agentId } = req.params;
1636
+ const apiKey = req.apiKey;
1637
+ if (!(0, auth_1.isAdmin)(apiKey)) {
1638
+ res.status(403).json({ error: 'Admin key required' });
1639
+ return;
1640
+ }
1641
+ if (!agentConfigs.has(agentId)) {
1642
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
1643
+ return;
1644
+ }
1645
+ res.json({ senders: (0, line_pending_senders_1.getPendingSenders)(agentId) });
1646
+ });
1647
+ /**
1648
+ * DELETE /api/v1/agents/:agentId/line/pending/:senderId
1649
+ * Dismiss one knock from the in-memory pending list (admin only). Used
1650
+ * by the UI "Delete" action — distinct from "+ Add" (which allowlists). The
1651
+ * id is a LINE userId/groupId/roomId (U/C/R + hex), so no numeric validation.
1652
+ * Idempotent: clearing an unknown id is a no-op. Ephemeral — a later message
1653
+ * from the same sender re-adds it (and, under pairing, re-mints a code).
1654
+ */
1655
+ router.delete('/v1/agents/:agentId/line/pending/:senderId', auth, (req, res) => {
1656
+ const apiKey = req.apiKey;
1657
+ if (!(0, auth_1.isAdmin)(apiKey)) {
1658
+ res.status(403).json({ error: 'Admin key required' });
1659
+ return;
1660
+ }
1661
+ const { agentId, senderId } = req.params;
1662
+ if (!agentConfigs.has(agentId)) {
1663
+ res.status(404).json({ error: `Agent '${agentId}' not found` });
1664
+ return;
1665
+ }
1666
+ (0, line_pending_senders_1.clearPendingSender)(agentId, senderId);
1667
+ res.json({ ok: true });
1668
+ });
1434
1669
  /**
1435
1670
  * DELETE /api/v1/agents/:agentId/telegram/allow/:userId
1436
1671
  * Remove a user from the allowFrom list. Admin only.
@@ -1543,8 +1778,8 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1543
1778
  return;
1544
1779
  }
1545
1780
  const { source, rawChatId } = parseHistoryChatId(chatId);
1546
- if (source !== 'telegram' && source !== 'discord') {
1547
- res.status(400).json({ error: 'Sessions endpoint only supports telegram/discord chats' });
1781
+ if (source !== 'telegram' && source !== 'discord' && source !== 'line') {
1782
+ res.status(400).json({ error: 'Sessions endpoint only supports telegram/discord/line chats' });
1548
1783
  return;
1549
1784
  }
1550
1785
  try {
@@ -1627,8 +1862,8 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
1627
1862
  return;
1628
1863
  }
1629
1864
  const { source, rawChatId } = parseHistoryChatId(chatId);
1630
- if (source !== 'telegram' && source !== 'discord') {
1631
- res.status(400).json({ error: 'Cross-channel messaging only supported for telegram/discord chats' });
1865
+ if (source !== 'telegram' && source !== 'discord' && source !== 'line') {
1866
+ res.status(400).json({ error: 'Cross-channel messaging only supported for telegram/discord/line chats' });
1632
1867
  return;
1633
1868
  }
1634
1869
  const body = req.body;
@@ -2404,6 +2639,8 @@ function parseHistoryChatId(fullChatId) {
2404
2639
  return { source: 'telegram', rawChatId: fullChatId.slice(9) };
2405
2640
  if (fullChatId.startsWith('discord-'))
2406
2641
  return { source: 'discord', rawChatId: fullChatId.slice(8) };
2642
+ if (fullChatId.startsWith('line-'))
2643
+ return { source: 'line', rawChatId: fullChatId.slice(5) };
2407
2644
  return { source: 'api', rawChatId: fullChatId };
2408
2645
  }
2409
2646
  //# sourceMappingURL=router.js.map