@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.
- package/config.template.json +8 -4
- package/dist/agent/builtin-commands.d.ts +1 -1
- package/dist/agent/builtin-commands.d.ts.map +1 -1
- package/dist/agent/builtin-commands.js +5 -0
- package/dist/agent/builtin-commands.js.map +1 -1
- package/dist/agent/line-pure.d.ts +31 -0
- package/dist/agent/line-pure.d.ts.map +1 -0
- package/dist/agent/line-pure.js +99 -0
- package/dist/agent/line-pure.js.map +1 -0
- package/dist/agent/line-reply-manager.d.ts +68 -0
- package/dist/agent/line-reply-manager.d.ts.map +1 -0
- package/dist/agent/line-reply-manager.js +383 -0
- package/dist/agent/line-reply-manager.js.map +1 -0
- package/dist/agent/runner.d.ts +11 -2
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +106 -8
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +4 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/line-access.d.ts +60 -0
- package/dist/api/line-access.d.ts.map +1 -0
- package/dist/api/line-access.js +69 -0
- package/dist/api/line-access.js.map +1 -0
- package/dist/api/line-mention.d.ts +35 -0
- package/dist/api/line-mention.d.ts.map +1 -0
- package/dist/api/line-mention.js +33 -0
- package/dist/api/line-mention.js.map +1 -0
- package/dist/api/line-pending-senders.d.ts +40 -0
- package/dist/api/line-pending-senders.d.ts.map +1 -0
- package/dist/api/line-pending-senders.js +107 -0
- package/dist/api/line-pending-senders.js.map +1 -0
- package/dist/api/line-webhook-router.d.ts +51 -0
- package/dist/api/line-webhook-router.d.ts.map +1 -0
- package/dist/api/line-webhook-router.js +416 -0
- package/dist/api/line-webhook-router.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +244 -7
- package/dist/api/router.js.map +1 -1
- package/dist/history/types.d.ts +1 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/session/compactor.d.ts +1 -1
- package/dist/session/compactor.d.ts.map +1 -1
- package/dist/session/compactor.js.map +1 -1
- package/dist/session/process.d.ts +2 -2
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +14 -3
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts +13 -13
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js.map +1 -1
- package/dist/shell/claude-pty-shell.js +12 -0
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -1
- package/mcp/bun.lock +9 -2
- package/mcp/package.json +1 -0
- package/mcp/server.ts +2 -0
- package/mcp/tools/line/module.ts +127 -0
- package/mcp/tools/line/pure.ts +62 -0
- package/mcp/tools/telegram/receiver-server.ts +8 -5
- package/package.json +2 -1
package/dist/agent/runner.js
CHANGED
|
@@ -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-
|
|
70
|
-
{ id: 'claude-opus-4-
|
|
71
|
-
{ id: 'claude-
|
|
72
|
-
{ id: 'claude-
|
|
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'
|
|
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'
|
|
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
|
-
|
|
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);
|