@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
@@ -45,6 +45,7 @@ const store_1 = require("../session/store");
45
45
  const compactor_1 = require("../session/compactor");
46
46
  const receiver_1 = require("../telegram/receiver");
47
47
  const receiver_2 = require("../discord/receiver");
48
+ const line_reply_manager_1 = require("./line-reply-manager");
48
49
  const markdown_1 = require("../telegram/markdown");
49
50
  const skills_1 = require("../skills");
50
51
  const builtin_commands_1 = require("./builtin-commands");
@@ -66,10 +67,13 @@ const ANTHROPIC_SOCKET_ERROR = 'socket connection was closed unexpectedly';
66
67
  const TOO_LARGE_HISTORY_LADDER = [process_1.MAX_HISTORY_MESSAGES, 40, 30, 20, 10, 0];
67
68
  exports.MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024;
68
69
  exports.DEFAULT_MODELS = [
69
- { id: 'claude-opus-4-8', label: 'Opus 4.8', alias: 'opus', contextWindow: 1000000 },
70
- { id: 'claude-opus-4-7', label: 'Opus 4.7', alias: 'opus47', contextWindow: 1000000 },
71
- { id: 'claude-opus-4-6', label: 'Opus 4.6', alias: 'opus46', contextWindow: 1000000 },
72
- { id: 'claude-sonnet-4-6', label: 'Sonnet 4.6', alias: 'sonnet', contextWindow: 1000000 },
70
+ { id: 'claude-fable-5', label: 'Fable 5', alias: 'fable', contextWindow: 1000000 },
71
+ { id: 'claude-opus-4-8[1m]', label: 'Opus 4.8 (1M)', alias: 'opus[1m]', contextWindow: 1000000 },
72
+ { id: 'claude-sonnet-5[1m]', label: 'Sonnet 5 (1M)', alias: 'sonnet[1m]', contextWindow: 1000000 },
73
+ { id: 'claude-opus-4-8', label: 'Opus 4.8', alias: 'opus', contextWindow: 200000 },
74
+ { id: 'claude-opus-4-6', label: 'Opus 4.6', alias: 'opus46', contextWindow: 200000 },
75
+ { id: 'claude-sonnet-5', label: 'Sonnet 5', alias: 'sonnet', contextWindow: 200000 },
76
+ { id: 'claude-sonnet-4-6', label: 'Sonnet 4.6', alias: 'sonnet46', contextWindow: 200000 },
73
77
  { id: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5', alias: 'haiku', contextWindow: 200000 },
74
78
  ];
75
79
  const PROTECTED_WORKSPACE_FILES = [
@@ -135,6 +139,8 @@ class AgentRunner extends events_1.EventEmitter {
135
139
  this.channelSourceMap = new Map();
136
140
  this.receiver = null;
137
141
  this.discordReceiver = null;
142
+ // LINE slow-LLM postback button manager (null when LINE disabled or threshold=0).
143
+ this.lineReply = null;
138
144
  this.idleCleanerTimer = null;
139
145
  // Tracks session IDs with an in-flight API request (prevents concurrent turns)
140
146
  this.pendingApiSessions = new Set();
@@ -236,8 +242,22 @@ class AgentRunner extends events_1.EventEmitter {
236
242
  const chatId = meta['chat_id'] ?? '';
237
243
  const content = params.content ?? '';
238
244
  // Set channel early so all handlers (including session commands) have it
239
- const channelSource = (meta['source'] === 'discord' ? 'discord' : 'telegram');
245
+ const channelSource = (meta['source'] === 'discord'
246
+ ? 'discord'
247
+ : meta['source'] === 'line'
248
+ ? 'line'
249
+ : 'telegram');
240
250
  this.channelSourceMap.set(chatId, channelSource);
251
+ // LINE slow-LLM postback: stash this turn's reply token + arm the
252
+ // button timer before the token expires. In groups/rooms the shared
253
+ // button is disabled (armButton:false) — the token is still stashed so
254
+ // onAnswer delivers (reply→push), just without a button.
255
+ if (channelSource === 'line' && this.lineReply && meta['reply_token']) {
256
+ const chatType = meta['line_chat_type'];
257
+ this.lineReply.onInbound(chatId, meta['reply_token'], {
258
+ armButton: chatType === undefined || chatType === 'user',
259
+ });
260
+ }
241
261
  // Check if this is a built-in channel command
242
262
  const trimmedContent = content.trim();
243
263
  if ((0, builtin_commands_1.isBuiltinCommand)(trimmedContent, channelSource)) {
@@ -603,6 +623,8 @@ class AgentRunner extends events_1.EventEmitter {
603
623
  'attachment_kind',
604
624
  'attachment_mime',
605
625
  'attachment_name',
626
+ 'user_id', // LINE: the userId the session passes back to line_reply
627
+ 'reply_token', // LINE: single-use reply token (push is preferred; surfaced for completeness)
606
628
  ]
607
629
  .filter(k => meta[k])
608
630
  .map(k => ` ${k}="${meta[k].replace(/"/g, '"')}"`)
@@ -715,6 +737,10 @@ class AgentRunner extends events_1.EventEmitter {
715
737
  proc.once('failed', () => {
716
738
  this.writeTypingError(mapKey, 'PROCESS_FAILED');
717
739
  this.sessions.delete(mapKey);
740
+ // LINE: surface an error for any outstanding postback button so a tap
741
+ // returns the interrupted notice instead of a stale "still thinking".
742
+ if (!proc.queryMode)
743
+ this.lineReply?.markInterrupted(mapKey);
718
744
  });
719
745
  // Stop typing loop when Claude's turn truly ends.
720
746
  // Typing done is delayed 3s after result event — if new output arrives within
@@ -727,7 +753,11 @@ class AgentRunner extends events_1.EventEmitter {
727
753
  let menuSentThisTurn = false;
728
754
  let typingDoneTimer = null;
729
755
  const TYPING_DONE_DELAY_MS = 3000;
730
- const replyToolName = source === 'discord' ? 'mcp__gateway__discord_reply' : 'mcp__gateway__telegram_reply';
756
+ const replyToolName = source === 'discord'
757
+ ? 'mcp__gateway__discord_reply'
758
+ : source === 'line'
759
+ ? 'mcp__gateway__line_reply'
760
+ : 'mcp__gateway__telegram_reply';
731
761
  proc.on('output', (line) => {
732
762
  try {
733
763
  const obj = JSON.parse(line);
@@ -756,6 +786,12 @@ class AgentRunner extends events_1.EventEmitter {
756
786
  content: replyText,
757
787
  ts: Date.now(),
758
788
  });
789
+ // LINE: the MCP line_reply tool's send is suppressed in
790
+ // refresh mode; the gateway delivers (free reply, or cache +
791
+ // postback button when slow). See LineReplyManager.
792
+ if (channelSrc === 'line' && this.lineReply) {
793
+ void this.lineReply.onAnswer(mapKey, replyText);
794
+ }
759
795
  }
760
796
  }
761
797
  }
@@ -875,8 +911,13 @@ class AgentRunner extends events_1.EventEmitter {
875
911
  content: text,
876
912
  ts: Date.now(),
877
913
  });
878
- // Forward to channel
879
- if (channelSrcForResult !== 'discord' && (0, markdown_1.hasMarkdown)(text)) {
914
+ // Forward to channel. LINE has no .forward consumer — the gateway
915
+ // delivers via LineReplyManager (free reply, or cache + postback
916
+ // button when slow) instead of writeAutoForward.
917
+ if (channelSrcForResult === 'line' && this.lineReply) {
918
+ void this.lineReply.onAnswer(mapKey, text);
919
+ }
920
+ else if (channelSrcForResult !== 'discord' && (0, markdown_1.hasMarkdown)(text)) {
880
921
  this.writeAutoForward(mapKey, (0, markdown_1.toTelegramHtml)(text), 'html');
881
922
  }
882
923
  else {
@@ -952,6 +993,15 @@ class AgentRunner extends events_1.EventEmitter {
952
993
  typingDoneTimer = null;
953
994
  }
954
995
  this.writeTypingDone(mapKey);
996
+ // LINE: a process exit with a button whose answer never arrived (crash /
997
+ // teardown mid-turn) → surface the interrupted notice so a tap returns it
998
+ // instead of a stale "still thinking". markInterrupted no-ops on a READY
999
+ // entry, so an already-answered, untapped button stays deliverable. Then
1000
+ // clear the armed timer + stashed reply token for this chat.
1001
+ if (!proc.queryMode) {
1002
+ this.lineReply?.markInterrupted(mapKey);
1003
+ this.lineReply?.disposeChat(mapKey);
1004
+ }
955
1005
  });
956
1006
  // Deferred restart: stop session after its current turn completes
957
1007
  proc.once('deferredRestartReady', async () => {
@@ -1359,6 +1409,12 @@ class AgentRunner extends events_1.EventEmitter {
1359
1409
  this.pendingRestarts.add(chatId);
1360
1410
  }
1361
1411
  writeAutoForward(chatId, text, format = 'text') {
1412
+ // LINE has no .forward consumer — route through LineReplyManager's push path.
1413
+ if (this.channelFor(chatId) === 'line') {
1414
+ if (this.lineReply)
1415
+ void this.lineReply.onAnswer(chatId, text);
1416
+ return;
1417
+ }
1362
1418
  const typingDir = this.getTypingDir(chatId);
1363
1419
  try {
1364
1420
  fs.mkdirSync(typingDir, { recursive: true });
@@ -1412,12 +1468,43 @@ class AgentRunner extends events_1.EventEmitter {
1412
1468
  this.discordReceiver = new receiver_2.DiscordReceiver(this.agentConfig, this.callbackPort, this.gatewayConfig.gateway.logDir);
1413
1469
  this.discordReceiver.start();
1414
1470
  }
1471
+ // LINE slow-LLM postback button: gateway-side token lifecycle + cache.
1472
+ // Enabled for any LINE agent unless its threshold is 0 (then the MCP
1473
+ // line_reply tool keeps the plain reply-first → push-fallback path).
1474
+ this.startLineReply();
1415
1475
  this.startIdleCleaner();
1416
1476
  this._startCleanupScheduler();
1417
1477
  this.logger.info('AgentRunner started', { agentId: this.agentConfig.id });
1418
1478
  }
1419
1479
  updateAgentConfig(newConfig) {
1420
1480
  this.agentConfig = newConfig;
1481
+ // Restart LineReplyManager if the LINE config changed so the live instance
1482
+ // picks up a new access token, threshold, or labels without a full restart.
1483
+ this.stopLineReply();
1484
+ this.startLineReply();
1485
+ }
1486
+ startLineReply() {
1487
+ const lineThreshold = this.agentConfig.line?.slowResponseThreshold ?? 45;
1488
+ if (!this.agentConfig.line?.channelSecret || !this.agentConfig.line?.channelAccessToken || lineThreshold <= 0)
1489
+ return;
1490
+ if (this.lineReply)
1491
+ return; // already running
1492
+ this.lineReply = new line_reply_manager_1.LineReplyManager({
1493
+ agentId: this.agentConfig.id,
1494
+ logDir: this.gatewayConfig.gateway.logDir,
1495
+ accessToken: this.agentConfig.line.channelAccessToken,
1496
+ thresholdSeconds: lineThreshold,
1497
+ buttonLabel: this.agentConfig.line.slowButtonLabel,
1498
+ pendingText: this.agentConfig.line.slowPendingText,
1499
+ });
1500
+ this.logger.info('LineReplyManager hot-started', { agentId: this.agentConfig.id });
1501
+ }
1502
+ stopLineReply() {
1503
+ if (!this.lineReply)
1504
+ return;
1505
+ this.lineReply.disposeAll();
1506
+ this.lineReply = null;
1507
+ this.logger.info('LineReplyManager stopped', { agentId: this.agentConfig.id });
1421
1508
  }
1422
1509
  getAgentConfig() {
1423
1510
  return { ...this.agentConfig };
@@ -1474,9 +1561,20 @@ class AgentRunner extends events_1.EventEmitter {
1474
1561
  this.channelCoalesce.clear();
1475
1562
  this.receiver?.stop();
1476
1563
  this.discordReceiver?.stop();
1564
+ this.stopLineReply();
1477
1565
  await Promise.all([...this.sessions.values()].map((s) => s.stop()));
1478
1566
  this.sessions.clear();
1479
1567
  }
1568
+ /**
1569
+ * Deliver a tapped LINE postback (the slow-LLM "Get answer" button).
1570
+ * Returns true when it was our refresh button (caller must NOT forward it to
1571
+ * the agent); false for any other postback.
1572
+ */
1573
+ async handleLinePostback(chatId, replyToken, data) {
1574
+ if (!this.lineReply)
1575
+ return false;
1576
+ return this.lineReply.handlePostback(chatId, replyToken, data);
1577
+ }
1480
1578
  _startCleanupScheduler() {
1481
1579
  const gw = this.gatewayConfig.gateway;
1482
1580
  const retentionDays = (0, cleanup_1.resolveRetentionDays)(this.agentConfig.history?.retentionDays, gw.history?.retentionDays);