@0xmaxma/claude-gateway 1.3.12 → 1.3.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/dist/agent/runner.d.ts +29 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +247 -101
- package/dist/agent/runner.js.map +1 -1
- package/dist/session/process.d.ts +2 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +26 -7
- package/dist/session/process.js.map +1 -1
- package/dist/shell/claude-pty-shell.js +41 -20
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/dist/shell/screen.d.ts +51 -2
- package/dist/shell/screen.d.ts.map +1 -1
- package/dist/shell/screen.js +82 -5
- package/dist/shell/screen.js.map +1 -1
- package/dist/shell/tailer.d.ts +24 -0
- package/dist/shell/tailer.d.ts.map +1 -1
- package/dist/shell/tailer.js +53 -0
- package/dist/shell/tailer.js.map +1 -1
- package/package.json +1 -1
package/dist/agent/runner.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type SkillRegistry } from '../skills';
|
|
|
4
4
|
import { HistoryDB } from '../history/db';
|
|
5
5
|
export declare const MAX_IMAGE_SIZE_BYTES: number;
|
|
6
6
|
export declare const DEFAULT_MODELS: ModelConfig[];
|
|
7
|
+
export declare const CHANNEL_COALESCE_WINDOW_MS = 1200;
|
|
7
8
|
export declare class AgentRunner extends EventEmitter {
|
|
8
9
|
private agentConfig;
|
|
9
10
|
private readonly gatewayConfig;
|
|
@@ -27,7 +28,11 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
27
28
|
private idleCleanerTimer;
|
|
28
29
|
private readonly pendingApiSessions;
|
|
29
30
|
private readonly sessionSpawnLocks;
|
|
31
|
+
private readonly tooLargeRecoveries;
|
|
32
|
+
private readonly tooLargeExhausted;
|
|
30
33
|
private readonly pendingImagePaths;
|
|
34
|
+
private readonly channelCoalesce;
|
|
35
|
+
private readonly coalesceWindowMs;
|
|
31
36
|
private readonly pendingApiAttachments;
|
|
32
37
|
private readonly sessionHistory;
|
|
33
38
|
private readonly apiChatIds;
|
|
@@ -60,6 +65,13 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
60
65
|
private persistModelToConfig;
|
|
61
66
|
private static escapeXmlAttr;
|
|
62
67
|
private resolveMediaPaths;
|
|
68
|
+
/**
|
|
69
|
+
* Inject one or more coalesced channel messages as a SINGLE turn. Each entry is
|
|
70
|
+
* still recorded individually (session history + permanent history DB), but their
|
|
71
|
+
* channel XML blocks are concatenated and delivered in one sendMessage() call so
|
|
72
|
+
* a photo and its instruction text are read together in the same turn.
|
|
73
|
+
*/
|
|
74
|
+
private routeChannelTurn;
|
|
63
75
|
private static buildChannelXml;
|
|
64
76
|
private getOrSpawnSession;
|
|
65
77
|
private spawnSession;
|
|
@@ -108,6 +120,23 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
108
120
|
* The process will be lazily re-spawned on the next incoming message.
|
|
109
121
|
*/
|
|
110
122
|
private restartProcess;
|
|
123
|
+
/**
|
|
124
|
+
* Unified recovery for the recoverable "Request too large (max 32MB)" error.
|
|
125
|
+
* Reached from two backends that surface the SAME error differently:
|
|
126
|
+
* - PTY shell (headless=false): a `system/request_too_large` event emitted by
|
|
127
|
+
* the transcript tailer from the authoritative <synthetic> record (Bug A).
|
|
128
|
+
* - Headless (headless=true): claude --print emits a synthetic `result`
|
|
129
|
+
* (is_error + "Request too large (max"); the long-lived process otherwise
|
|
130
|
+
* stays alive and rejects every subsequent turn forever (Bug B).
|
|
131
|
+
*
|
|
132
|
+
* Each consecutive recovery shrinks the history re-injected on the next spawn
|
|
133
|
+
* (TOO_LARGE_HISTORY_LADDER: 50→40→30→20→10→0) so a pathological context drops
|
|
134
|
+
* under the 32MB ceiling. The respawn happens on the user's NEXT message (no
|
|
135
|
+
* auto-loop), and the counter resets on the next successful result. Once even a
|
|
136
|
+
* zero-history spawn still trips 32MB, stop escalating and ask the user to
|
|
137
|
+
* /clear rather than climb the ladder again.
|
|
138
|
+
*/
|
|
139
|
+
private handleRequestTooLarge;
|
|
111
140
|
/**
|
|
112
141
|
* Stop all idle session subprocesses so they re-spawn with the latest
|
|
113
142
|
* system prompt on the next incoming message.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAW,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAQhH,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAGvF,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AgentRunner = exports.DEFAULT_MODELS = exports.MAX_IMAGE_SIZE_BYTES = void 0;
|
|
36
|
+
exports.AgentRunner = exports.CHANNEL_COALESCE_WINDOW_MS = exports.DEFAULT_MODELS = exports.MAX_IMAGE_SIZE_BYTES = void 0;
|
|
37
37
|
const events_1 = require("events");
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const fsPromises = __importStar(require("fs/promises"));
|
|
@@ -55,6 +55,15 @@ const cleanup_1 = require("../history/cleanup");
|
|
|
55
55
|
const DEFAULT_IDLE_TIMEOUT_MINUTES = 30;
|
|
56
56
|
const DEFAULT_MAX_CONCURRENT = 20;
|
|
57
57
|
const ANTHROPIC_SOCKET_ERROR = 'socket connection was closed unexpectedly';
|
|
58
|
+
// History re-injection ladder for request_too_large (32MB) recovery. Index =
|
|
59
|
+
// number of consecutive 32MB recoveries on a session; the value is how many
|
|
60
|
+
// history messages the NEXT spawn re-injects. Index 0 is the healthy default
|
|
61
|
+
// (= MAX_HISTORY_MESSAGES, sourced from it so the two never drift); each retry
|
|
62
|
+
// steps down a rung, shrinking the re-loaded context until it drops under
|
|
63
|
+
// Anthropic's 32MB request ceiling. Past the last rung (0 history) the context
|
|
64
|
+
// can't shrink further, so the runner stops escalating and asks the user to
|
|
65
|
+
// /clear instead of looping forever.
|
|
66
|
+
const TOO_LARGE_HISTORY_LADDER = [process_1.MAX_HISTORY_MESSAGES, 40, 30, 20, 10, 0];
|
|
58
67
|
exports.MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
59
68
|
exports.DEFAULT_MODELS = [
|
|
60
69
|
{ id: 'claude-opus-4-8', label: 'Opus 4.8', alias: 'opus', contextWindow: 1000000 },
|
|
@@ -68,6 +77,11 @@ const PROTECTED_WORKSPACE_FILES = [
|
|
|
68
77
|
'IDENTITY.md', 'USER.md', 'HEARTBEAT.md',
|
|
69
78
|
];
|
|
70
79
|
const MAX_API_IMAGES = 5;
|
|
80
|
+
// Trailing-edge window for coalescing channel messages that carry an image (or that
|
|
81
|
+
// arrive while an image is already buffered) into a single turn. Reset on every new
|
|
82
|
+
// related update, so an album burst or a client-split photo+caption is gathered
|
|
83
|
+
// whole. Plain text with no pending buffer bypasses this entirely (zero added latency).
|
|
84
|
+
exports.CHANNEL_COALESCE_WINDOW_MS = 1200;
|
|
71
85
|
/**
|
|
72
86
|
* Move UI-uploaded files from staging (ui-upload/) to permanent per-session storage
|
|
73
87
|
* (media/api-{sessionId}/), matching the same pattern Telegram uses.
|
|
@@ -126,8 +140,24 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
126
140
|
this.pendingApiSessions = new Set();
|
|
127
141
|
// Serialises concurrent getOrSpawnSession calls for the same key to prevent double-spawn
|
|
128
142
|
this.sessionSpawnLocks = new Map();
|
|
143
|
+
// Consecutive request_too_large (32MB) recoveries per mapKey. Survives the
|
|
144
|
+
// session respawn (SessionProcess is recreated on each restart) so the history
|
|
145
|
+
// re-injection can escalate-shrink across retries (TOO_LARGE_HISTORY_LADDER).
|
|
146
|
+
// Reset to 0 on the next successful result; read by spawnSession.
|
|
147
|
+
this.tooLargeRecoveries = new Map();
|
|
148
|
+
// mapKeys that exhausted the ladder (zero-history spawn still tripped 32MB).
|
|
149
|
+
// Once here, further 32MB events only re-notify "/clear" and do NOT restart —
|
|
150
|
+
// restarting would just churn a context that cannot shrink. Cleared together
|
|
151
|
+
// with the counter on a successful result and on /clear.
|
|
152
|
+
this.tooLargeExhausted = new Set();
|
|
129
153
|
// Tracks pending Telegram image paths per chatId (queue) for size accumulation after each turn.
|
|
130
154
|
this.pendingImagePaths = new Map();
|
|
155
|
+
// Coalescing buffer: collects channel messages that arrive close together so a
|
|
156
|
+
// photo and its instruction text (which Telegram may deliver as separate updates
|
|
157
|
+
// — long caption split, album burst, or photo sent before/after the text) are
|
|
158
|
+
// injected as ONE turn instead of two. Keyed by chatId. A trailing-edge timer
|
|
159
|
+
// flushes the buffer once no new related update has arrived for the window.
|
|
160
|
+
this.channelCoalesce = new Map();
|
|
131
161
|
// Buffers attachment file paths registered via api_reply tool for the current API session turn.
|
|
132
162
|
this.pendingApiAttachments = new Map();
|
|
133
163
|
// Session history ring-buffer (last 10 spawned, newest first)
|
|
@@ -156,6 +186,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
156
186
|
this.idleTimeoutMs =
|
|
157
187
|
(agentConfig.session?.idleTimeoutMinutes ?? DEFAULT_IDLE_TIMEOUT_MINUTES) * 60 * 1000;
|
|
158
188
|
this.maxConcurrent = agentConfig.session?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
189
|
+
this.coalesceWindowMs =
|
|
190
|
+
Number(process.env.CHANNEL_COALESCE_WINDOW_MS) || exports.CHANNEL_COALESCE_WINDOW_MS;
|
|
159
191
|
}
|
|
160
192
|
/**
|
|
161
193
|
* Update the skill registry used for detecting /skill-name commands.
|
|
@@ -217,87 +249,33 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
217
249
|
});
|
|
218
250
|
return;
|
|
219
251
|
}
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
this.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
content: userContent,
|
|
248
|
-
senderName: meta['user'] ?? undefined,
|
|
249
|
-
senderId: meta['user_id'] ?? meta['chat_id'] ?? undefined,
|
|
250
|
-
platformMessageId: meta['message_id'] ?? undefined,
|
|
251
|
-
mediaFiles: mediaFiles.length > 0 ? mediaFiles : undefined,
|
|
252
|
-
ts: userTs,
|
|
253
|
-
});
|
|
254
|
-
// Restart session before this turn if accumulated image size exceeded threshold
|
|
255
|
-
if (this.pendingRestarts.has(chatId)) {
|
|
256
|
-
const existingSession = this.sessions.get(chatId);
|
|
257
|
-
if (existingSession) {
|
|
258
|
-
await existingSession.stop();
|
|
259
|
-
this.sessions.delete(chatId);
|
|
260
|
-
}
|
|
261
|
-
this.pendingRestarts.delete(chatId);
|
|
262
|
-
this.imageSizePerChat.delete(chatId);
|
|
263
|
-
}
|
|
264
|
-
// Route to session process (map key = chatId, actual sessionId passed separately)
|
|
265
|
-
// Channel sessions use agent-level model (not per-session)
|
|
266
|
-
const session = await this.getOrSpawnSession(chatId, channelSource, sessionId);
|
|
267
|
-
let channelXml = AgentRunner.buildChannelXml(params);
|
|
268
|
-
// Detect skill commands and inject skill content
|
|
269
|
-
const skillInvocation = (0, skills_1.detectSkillCommand)(content, this.skillRegistry);
|
|
270
|
-
if (skillInvocation) {
|
|
271
|
-
channelXml += (0, skills_1.formatSkillContext)(skillInvocation);
|
|
272
|
-
this.logger.info('Skill invoked', {
|
|
273
|
-
skill: skillInvocation.skillKey,
|
|
274
|
-
args: skillInvocation.args,
|
|
275
|
-
chatId,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
const imagePath = meta['image_path'];
|
|
279
|
-
if (imagePath) {
|
|
280
|
-
const queue = this.pendingImagePaths.get(chatId) ?? [];
|
|
281
|
-
queue.push(imagePath);
|
|
282
|
-
this.pendingImagePaths.set(chatId, queue);
|
|
283
|
-
}
|
|
284
|
-
session.setProcessing(true);
|
|
285
|
-
session.sendMessage(channelXml);
|
|
286
|
-
session.touch();
|
|
287
|
-
this.logger.debug('Injected channel turn into session', {
|
|
288
|
-
chatId,
|
|
289
|
-
sessionId,
|
|
290
|
-
user: meta['user'],
|
|
291
|
-
});
|
|
292
|
-
})
|
|
293
|
-
.catch((err) => {
|
|
294
|
-
this.logger.error('Failed to route message to session', {
|
|
295
|
-
chatId,
|
|
296
|
-
error: err.message,
|
|
297
|
-
});
|
|
298
|
-
const code = err.message.includes('pool full') ? 'POOL_FULL' : 'SPAWN_FAILED';
|
|
299
|
-
this.writeTypingError(chatId, code);
|
|
300
|
-
});
|
|
252
|
+
// Coalesce messages that carry an image (or that arrive while an image is
|
|
253
|
+
// already buffered) so a photo and its instruction text are injected as
|
|
254
|
+
// ONE turn — Telegram can split them across updates (long-caption split,
|
|
255
|
+
// album burst, or photo sent just before/after the text). Plain text with
|
|
256
|
+
// nothing buffered takes the fast path and is injected immediately.
|
|
257
|
+
const hasImage = !!meta['image_path'];
|
|
258
|
+
const pending = this.channelCoalesce.get(chatId);
|
|
259
|
+
if (hasImage || pending) {
|
|
260
|
+
const buf = pending ?? {
|
|
261
|
+
channelSource,
|
|
262
|
+
entries: [],
|
|
263
|
+
timer: undefined,
|
|
264
|
+
};
|
|
265
|
+
buf.channelSource = channelSource;
|
|
266
|
+
buf.entries.push(params);
|
|
267
|
+
if (buf.timer)
|
|
268
|
+
clearTimeout(buf.timer);
|
|
269
|
+
buf.timer = setTimeout(() => {
|
|
270
|
+
const flushed = this.channelCoalesce.get(chatId);
|
|
271
|
+
this.channelCoalesce.delete(chatId);
|
|
272
|
+
if (flushed)
|
|
273
|
+
this.routeChannelTurn(chatId, flushed.channelSource, flushed.entries);
|
|
274
|
+
}, this.coalesceWindowMs);
|
|
275
|
+
this.channelCoalesce.set(chatId, buf);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.routeChannelTurn(chatId, channelSource, [params]);
|
|
301
279
|
}
|
|
302
280
|
catch (err) {
|
|
303
281
|
this.logger.warn('Failed to parse channel callback body', {
|
|
@@ -518,6 +496,105 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
518
496
|
}
|
|
519
497
|
return paths;
|
|
520
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Inject one or more coalesced channel messages as a SINGLE turn. Each entry is
|
|
501
|
+
* still recorded individually (session history + permanent history DB), but their
|
|
502
|
+
* channel XML blocks are concatenated and delivered in one sendMessage() call so
|
|
503
|
+
* a photo and its instruction text are read together in the same turn.
|
|
504
|
+
*/
|
|
505
|
+
routeChannelTurn(chatId, channelSource, entries) {
|
|
506
|
+
if (entries.length === 0)
|
|
507
|
+
return;
|
|
508
|
+
this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, channelSource)
|
|
509
|
+
.then(async (sessionId) => {
|
|
510
|
+
// Record each buffered message individually (history + session context).
|
|
511
|
+
for (const entry of entries) {
|
|
512
|
+
const meta = entry.meta ?? {};
|
|
513
|
+
const content = entry.content ?? '';
|
|
514
|
+
const userContent = content || (meta['attachment_file_id'] || meta['image_path'] ? '(photo)' : '');
|
|
515
|
+
const userTs = Date.now();
|
|
516
|
+
await this.sessionStore.appendTelegramMessage(this.agentConfig.id, chatId, sessionId, {
|
|
517
|
+
role: 'user',
|
|
518
|
+
content: userContent,
|
|
519
|
+
ts: userTs,
|
|
520
|
+
}, channelSource);
|
|
521
|
+
// Persist to permanent history DB (separate from session context)
|
|
522
|
+
const mediaFiles = [];
|
|
523
|
+
if (meta['image_path']) {
|
|
524
|
+
try {
|
|
525
|
+
const rel = media_store_1.MediaStore.copyToMedia(this.agentsBaseDir, this.agentConfig.id, `${channelSource}-${chatId}`, meta['image_path']);
|
|
526
|
+
mediaFiles.push(rel);
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
// Non-fatal — continue without media
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
this.historyDb.insertMessage({
|
|
533
|
+
chatId: `${channelSource}-${chatId}`,
|
|
534
|
+
sessionId,
|
|
535
|
+
source: channelSource,
|
|
536
|
+
role: 'user',
|
|
537
|
+
content: userContent,
|
|
538
|
+
senderName: meta['user'] ?? undefined,
|
|
539
|
+
senderId: meta['user_id'] ?? meta['chat_id'] ?? undefined,
|
|
540
|
+
platformMessageId: meta['message_id'] ?? undefined,
|
|
541
|
+
mediaFiles: mediaFiles.length > 0 ? mediaFiles : undefined,
|
|
542
|
+
ts: userTs,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
// Restart session before this turn if accumulated image size exceeded threshold
|
|
546
|
+
if (this.pendingRestarts.has(chatId)) {
|
|
547
|
+
const existingSession = this.sessions.get(chatId);
|
|
548
|
+
if (existingSession) {
|
|
549
|
+
await existingSession.stop();
|
|
550
|
+
this.sessions.delete(chatId);
|
|
551
|
+
}
|
|
552
|
+
this.pendingRestarts.delete(chatId);
|
|
553
|
+
this.imageSizePerChat.delete(chatId);
|
|
554
|
+
}
|
|
555
|
+
// Route to session process (map key = chatId, actual sessionId passed separately)
|
|
556
|
+
// Channel sessions use agent-level model (not per-session)
|
|
557
|
+
const session = await this.getOrSpawnSession(chatId, channelSource, sessionId);
|
|
558
|
+
// Merge buffered messages into one turn: concat channel XML blocks, append
|
|
559
|
+
// any skill context, and accumulate image paths for size tracking.
|
|
560
|
+
const blocks = [];
|
|
561
|
+
for (const entry of entries) {
|
|
562
|
+
let channelXml = AgentRunner.buildChannelXml(entry);
|
|
563
|
+
const skillInvocation = (0, skills_1.detectSkillCommand)(entry.content ?? '', this.skillRegistry);
|
|
564
|
+
if (skillInvocation) {
|
|
565
|
+
channelXml += (0, skills_1.formatSkillContext)(skillInvocation);
|
|
566
|
+
this.logger.info('Skill invoked', {
|
|
567
|
+
skill: skillInvocation.skillKey,
|
|
568
|
+
args: skillInvocation.args,
|
|
569
|
+
chatId,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
blocks.push(channelXml);
|
|
573
|
+
const imagePath = entry.meta?.['image_path'];
|
|
574
|
+
if (imagePath) {
|
|
575
|
+
const queue = this.pendingImagePaths.get(chatId) ?? [];
|
|
576
|
+
queue.push(imagePath);
|
|
577
|
+
this.pendingImagePaths.set(chatId, queue);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
session.setProcessing(true);
|
|
581
|
+
session.sendMessage(blocks.join('\n'));
|
|
582
|
+
session.touch();
|
|
583
|
+
this.logger.debug('Injected channel turn into session', {
|
|
584
|
+
chatId,
|
|
585
|
+
sessionId,
|
|
586
|
+
messages: entries.length,
|
|
587
|
+
});
|
|
588
|
+
})
|
|
589
|
+
.catch((err) => {
|
|
590
|
+
this.logger.error('Failed to route message to session', {
|
|
591
|
+
chatId,
|
|
592
|
+
error: err.message,
|
|
593
|
+
});
|
|
594
|
+
const code = err.message.includes('pool full') ? 'POOL_FULL' : 'SPAWN_FAILED';
|
|
595
|
+
this.writeTypingError(chatId, code);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
521
598
|
static buildChannelXml(params) {
|
|
522
599
|
const meta = params.meta ?? {};
|
|
523
600
|
const optionalAttrs = [
|
|
@@ -616,6 +693,19 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
616
693
|
// Apply per-session model override (caller already normalized to undefined when == agent default)
|
|
617
694
|
if (modelOverride)
|
|
618
695
|
proc.modelOverride = modelOverride;
|
|
696
|
+
// request_too_large (32MB) recovery: shrink the re-injected history on each
|
|
697
|
+
// consecutive retry so a pathological context eventually fits. recoveryCount
|
|
698
|
+
// is 0 for healthy sessions → ladder rung 0 = MAX_HISTORY_MESSAGES (same as
|
|
699
|
+
// the SessionProcess default), so applying it unconditionally is a no-op for
|
|
700
|
+
// healthy sessions and the only path that sets historyLimit for recovering ones.
|
|
701
|
+
const recoveryCount = this.tooLargeRecoveries.get(mapKey) ?? 0;
|
|
702
|
+
const ladderIdx = Math.min(recoveryCount, TOO_LARGE_HISTORY_LADDER.length - 1);
|
|
703
|
+
proc.historyLimit = TOO_LARGE_HISTORY_LADDER[ladderIdx];
|
|
704
|
+
if (recoveryCount > 0) {
|
|
705
|
+
this.logger.info('Spawning with reduced history after request_too_large', {
|
|
706
|
+
mapKey, recoveryCount, historyLimit: proc.historyLimit,
|
|
707
|
+
});
|
|
708
|
+
}
|
|
619
709
|
await proc.start();
|
|
620
710
|
// Forward all session output lines so listeners on AgentRunner (GatewayRouter,
|
|
621
711
|
// CronScheduler, tests) receive them without needing individual session references.
|
|
@@ -709,23 +799,11 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
709
799
|
}
|
|
710
800
|
}
|
|
711
801
|
// PTY shell hit the recoverable "Request too large (max 32MB)" error.
|
|
712
|
-
// The
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
// from the store, so the next message isn't doomed to re-hit 32MB. The
|
|
716
|
-
// proc exit handler clears the typing indicator; tell the user to resend.
|
|
802
|
+
// The transcript tailer emits this from the authoritative <synthetic>
|
|
803
|
+
// record (Bug A fix). Recovery is unified in handleRequestTooLarge()
|
|
804
|
+
// so the headless path (Bug B) gets identical treatment.
|
|
717
805
|
if (obj['type'] === 'system' && obj['subtype'] === 'request_too_large') {
|
|
718
|
-
this.
|
|
719
|
-
proc.setProcessing(false);
|
|
720
|
-
// Ordering matters and makes the notice delivery race-free: writeAutoForward
|
|
721
|
-
// persists the `.forward` file synchronously HERE, before restartProcess()
|
|
722
|
-
// begins its async stop/kill. The typing-loop tear-down (stop() in typing.ts)
|
|
723
|
-
// drains and sends `.forward` synchronously before it removes the typing
|
|
724
|
-
// signal, so by the time the proc exit fires and the loop stops, the file is
|
|
725
|
-
// already on disk and is delivered — no dependency on poll timing. Do not
|
|
726
|
-
// reorder restartProcess() ahead of writeAutoForward().
|
|
727
|
-
this.writeAutoForward(mapKey, '⚠️ Context too large — hit Anthropic\'s 32MB request limit (usually from large images or files in context). Restarting this session with a fresh context. Your last message was not processed — please resend it.');
|
|
728
|
-
void this.restartProcess(mapKey);
|
|
806
|
+
this.handleRequestTooLarge(mapKey, proc);
|
|
729
807
|
}
|
|
730
808
|
if (obj['type'] === 'result') {
|
|
731
809
|
proc.setProcessing(false);
|
|
@@ -767,10 +845,22 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
767
845
|
if (isSocketError) {
|
|
768
846
|
this.writeAutoForward(mapKey, '⚡ Connection to Anthropic API dropped. Please resend your message.');
|
|
769
847
|
}
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
848
|
+
// Bug B: headless (claude --print) reports a too-large request as a
|
|
849
|
+
// synthetic `result` (is_error + "Request too large (max"), NOT as the
|
|
850
|
+
// PTY `system/request_too_large` event. Previously this result was only
|
|
851
|
+
// suppressed — the long-lived process stayed alive and rejected every
|
|
852
|
+
// turn forever ("typing then silent"). Route it through the SAME unified
|
|
853
|
+
// recovery (notify + escalate-shrink history + restart) as the PTY path.
|
|
773
854
|
const isRequestTooLarge = obj['is_error'] === true && resultText.includes(screen_1.TUI_REQUEST_TOO_LARGE);
|
|
855
|
+
if (isRequestTooLarge) {
|
|
856
|
+
this.handleRequestTooLarge(mapKey, proc);
|
|
857
|
+
}
|
|
858
|
+
else if (obj['is_error'] !== true) {
|
|
859
|
+
// Genuine successful turn — clear any request_too_large escalation so
|
|
860
|
+
// the next 32MB (if any) starts fresh at the top of the ladder.
|
|
861
|
+
this.tooLargeRecoveries.delete(mapKey);
|
|
862
|
+
this.tooLargeExhausted.delete(mapKey);
|
|
863
|
+
}
|
|
774
864
|
// Skip when a menu was rendered to buttons this turn — the result
|
|
775
865
|
// text is the same numbered list and would duplicate the menu message.
|
|
776
866
|
if (!isSocketError && !isRequestTooLarge && resultText.trim() && !proc.queryMode && !replyCalled && !isThinkingCorruption && !menuSentThisTurn) {
|
|
@@ -1027,6 +1117,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1027
1117
|
this.historyDb.clearChat(historyChatId);
|
|
1028
1118
|
// Delete persisted media files for this chat
|
|
1029
1119
|
media_store_1.MediaStore.clearChatMedia(this.agentsBaseDir, agentId, historyChatId);
|
|
1120
|
+
// Reset any request_too_large escalation — the context is now empty, so the
|
|
1121
|
+
// next spawn should start fresh at the top of the history ladder.
|
|
1122
|
+
this.tooLargeRecoveries.delete(chatId);
|
|
1123
|
+
this.tooLargeExhausted.delete(chatId);
|
|
1030
1124
|
// Kill old process so next message spawns fresh
|
|
1031
1125
|
this.restartProcess(chatId).catch(() => { });
|
|
1032
1126
|
}
|
|
@@ -1098,6 +1192,54 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1098
1192
|
}
|
|
1099
1193
|
// Process will be re-spawned on next incoming message
|
|
1100
1194
|
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Unified recovery for the recoverable "Request too large (max 32MB)" error.
|
|
1197
|
+
* Reached from two backends that surface the SAME error differently:
|
|
1198
|
+
* - PTY shell (headless=false): a `system/request_too_large` event emitted by
|
|
1199
|
+
* the transcript tailer from the authoritative <synthetic> record (Bug A).
|
|
1200
|
+
* - Headless (headless=true): claude --print emits a synthetic `result`
|
|
1201
|
+
* (is_error + "Request too large (max"); the long-lived process otherwise
|
|
1202
|
+
* stays alive and rejects every subsequent turn forever (Bug B).
|
|
1203
|
+
*
|
|
1204
|
+
* Each consecutive recovery shrinks the history re-injected on the next spawn
|
|
1205
|
+
* (TOO_LARGE_HISTORY_LADDER: 50→40→30→20→10→0) so a pathological context drops
|
|
1206
|
+
* under the 32MB ceiling. The respawn happens on the user's NEXT message (no
|
|
1207
|
+
* auto-loop), and the counter resets on the next successful result. Once even a
|
|
1208
|
+
* zero-history spawn still trips 32MB, stop escalating and ask the user to
|
|
1209
|
+
* /clear rather than climb the ladder again.
|
|
1210
|
+
*/
|
|
1211
|
+
handleRequestTooLarge(mapKey, proc) {
|
|
1212
|
+
proc.setProcessing(false);
|
|
1213
|
+
const count = (this.tooLargeRecoveries.get(mapKey) ?? 0) + 1;
|
|
1214
|
+
const lastRung = TOO_LARGE_HISTORY_LADDER.length - 1; // index of the 0-history rung
|
|
1215
|
+
if (count > lastRung) {
|
|
1216
|
+
// Even the zero-history spawn tripped 32MB — context can't shrink further.
|
|
1217
|
+
// Pin the counter at the last rung and always surface the /clear next step.
|
|
1218
|
+
this.tooLargeRecoveries.set(mapKey, lastRung);
|
|
1219
|
+
this.logger.error('Request too large persists with zero re-injected history', { mapKey, count });
|
|
1220
|
+
this.writeAutoForward(mapKey, '⚠️ ยังเกิน 32MB แม้จะล้าง context จนว่างแล้ว — พิมพ์ /clear เพื่อเริ่มเซสชันใหม่ หรือ /restart ค่ะ');
|
|
1221
|
+
// Restart ONCE to clear the wedged process the first time the ladder is
|
|
1222
|
+
// exhausted; thereafter stop restarting (no churn) and let the user /clear.
|
|
1223
|
+
if (!this.tooLargeExhausted.has(mapKey)) {
|
|
1224
|
+
this.tooLargeExhausted.add(mapKey);
|
|
1225
|
+
void this.restartProcess(mapKey);
|
|
1226
|
+
}
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
this.tooLargeRecoveries.set(mapKey, count);
|
|
1230
|
+
this.logger.warn('Request too large (32MB) — restarting with reduced history', {
|
|
1231
|
+
mapKey, attempt: count, nextHistoryLimit: TOO_LARGE_HISTORY_LADDER[count],
|
|
1232
|
+
});
|
|
1233
|
+
// Ordering matters and makes the notice delivery race-free: writeAutoForward
|
|
1234
|
+
// persists the `.forward` file synchronously HERE, before restartProcess()
|
|
1235
|
+
// begins its async stop/kill. The typing-loop tear-down (stop() in typing.ts)
|
|
1236
|
+
// drains and sends `.forward` synchronously before it removes the typing
|
|
1237
|
+
// signal, so by the time the proc exit fires and the loop stops, the file is
|
|
1238
|
+
// already on disk and is delivered — no dependency on poll timing. Do not
|
|
1239
|
+
// reorder restartProcess() ahead of writeAutoForward().
|
|
1240
|
+
this.writeAutoForward(mapKey, '⚠️ Context too large — hit Anthropic\'s 32MB request limit (usually from large images or files in context). Restarting this session with a fresh context. Your last message was not processed — please resend it.');
|
|
1241
|
+
void this.restartProcess(mapKey);
|
|
1242
|
+
}
|
|
1101
1243
|
/**
|
|
1102
1244
|
* Stop all idle session subprocesses so they re-spawn with the latest
|
|
1103
1245
|
* system prompt on the next incoming message.
|
|
@@ -1326,6 +1468,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1326
1468
|
srv.closeAllConnections?.();
|
|
1327
1469
|
await new Promise((resolve) => srv.close(() => resolve()));
|
|
1328
1470
|
}
|
|
1471
|
+
for (const buf of this.channelCoalesce.values()) {
|
|
1472
|
+
clearTimeout(buf.timer);
|
|
1473
|
+
}
|
|
1474
|
+
this.channelCoalesce.clear();
|
|
1329
1475
|
this.receiver?.stop();
|
|
1330
1476
|
this.discordReceiver?.stop();
|
|
1331
1477
|
await Promise.all([...this.sessions.values()].map((s) => s.stop()));
|