@0xmaxma/claude-gateway 1.6.1 → 1.6.3
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 +0 -1
- package/dist/agent/runner.d.ts +24 -2
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +190 -44
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/share-router.d.ts.map +1 -1
- package/dist/api/share-router.js +9 -5
- package/dist/api/share-router.js.map +1 -1
- package/dist/apps/agent-manager.d.ts.map +1 -1
- package/dist/apps/agent-manager.js +16 -2
- package/dist/apps/agent-manager.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +9 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/shell/claude-pty-shell.js +7 -0
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/dist/shell/screen.d.ts +21 -1
- package/dist/shell/screen.d.ts.map +1 -1
- package/dist/shell/screen.js +21 -1
- package/dist/shell/screen.js.map +1 -1
- package/package.json +1 -1
package/config.template.json
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"logDir": "~/.claude-gateway/logs",
|
|
16
16
|
"timezone": "Asia/Bangkok",
|
|
17
17
|
"bind": "127.0.0.1",
|
|
18
|
-
"publicUrl": "",
|
|
19
18
|
"models": [
|
|
20
19
|
{ "id": "claude-fable-5[1m]", "label": "Fable 5 (1M)", "alias": "fable[1m]", "contextWindow": 1000000 },
|
|
21
20
|
{ "id": "claude-opus-5[1m]", "label": "Opus 5 (1M)", "alias": "opus[1m]", "contextWindow": 1000000 },
|
package/dist/agent/runner.d.ts
CHANGED
|
@@ -50,6 +50,9 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
50
50
|
private readonly pendingImagePaths;
|
|
51
51
|
private readonly channelCoalesce;
|
|
52
52
|
private readonly coalesceWindowMs;
|
|
53
|
+
private readonly turnActive;
|
|
54
|
+
private readonly turnQueue;
|
|
55
|
+
private readonly stopEpoch;
|
|
53
56
|
private readonly pendingApiAttachments;
|
|
54
57
|
private readonly sessionHistory;
|
|
55
58
|
private readonly apiChatIds;
|
|
@@ -143,6 +146,19 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
143
146
|
* a photo and its instruction text are read together in the same turn.
|
|
144
147
|
*/
|
|
145
148
|
private routeChannelTurn;
|
|
149
|
+
/**
|
|
150
|
+
* Inject the next queued turn for this chat, or release the active slot when the
|
|
151
|
+
* queue is empty. Called when a turn truly ends — `result` (headless), or
|
|
152
|
+
* `session_idle` (pty-shell), or on process exit. `turnActive` is kept set while
|
|
153
|
+
* a queued turn exists so no incoming message can inject concurrently.
|
|
154
|
+
*/
|
|
155
|
+
private flushNextTurn;
|
|
156
|
+
/**
|
|
157
|
+
* Inject one coalesced turn into the session immediately. The caller must have
|
|
158
|
+
* already marked the chat active in `turnActive`. Records each buffered message
|
|
159
|
+
* individually in history, then writes the merged turn to the subprocess.
|
|
160
|
+
*/
|
|
161
|
+
private injectTurn;
|
|
146
162
|
/**
|
|
147
163
|
* Render composer-selected image options (contract E5) as a directive the agent
|
|
148
164
|
* reads and forwards to the generate_image MCP tool. Returns '' when no usable
|
|
@@ -191,8 +207,14 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
191
207
|
*/
|
|
192
208
|
private handleCommandRename;
|
|
193
209
|
/**
|
|
194
|
-
* /stop — interrupt the in-flight turn for this chat
|
|
195
|
-
*
|
|
210
|
+
* /stop — interrupt the in-flight turn for this chat and cancel anything queued
|
|
211
|
+
* behind it. Drops the coalesce buffer (messages not yet routed) and the turn
|
|
212
|
+
* queue (turns waiting behind the active one), bumps the per-chat stop epoch so
|
|
213
|
+
* a turn still mid-spawn aborts before it submits (closing the spawn-race), and
|
|
214
|
+
* sends SIGINT to interrupt an actively-processing turn. Session history/metadata
|
|
215
|
+
* are left intact. Reports Stopped whenever there was work to cancel — an active
|
|
216
|
+
* turn, a spawning turn, or queued/buffered messages — rather than the old
|
|
217
|
+
* "No turn in progress." that fired whenever interrupt() no-op'd mid-spawn.
|
|
196
218
|
*/
|
|
197
219
|
private handleCommandStop;
|
|
198
220
|
/**
|
|
@@ -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,WAAW,EAAE,MAAM,UAAU,CAAC;AAS7H,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAOvF,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAG7F,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAqB1C,eAAO,MAAM,oBAAoB,QAAmB,CAAC;AAErD,eAAO,MAAM,cAAc,EAAE,WAAW,EAYvC,CAAC;AA0BF,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAkD/C;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,OAAO,EAAE,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAuB,GAC7C,MAAM,EAAE,CAIV;AAED,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAC9D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,SAAS,CAAoC;IAErD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IACjC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAEvC,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsD;IACvF,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,eAAe,CAAgC;IAEvD,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,gBAAgB,CAA+C;IAGvE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IAGxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA8C;IAMhF,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAKhE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IAOvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAQtB;IAIH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkC;IAKlE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4E;IAGrG,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA+B;IAOjE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAO5B;IAIJ,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;
|
|
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
|
@@ -231,6 +231,22 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
231
231
|
// injected as ONE turn instead of two. Keyed by chatId. A trailing-edge timer
|
|
232
232
|
// flushes the buffer once no new related update has arrived for the window.
|
|
233
233
|
this.channelCoalesce = new Map();
|
|
234
|
+
// Gateway-layer turn queue (both backends). A chat is "active" from the moment
|
|
235
|
+
// we inject a turn until its TRUE end signal arrives — `result` on the headless
|
|
236
|
+
// backend (one per user message) or `session_idle` on pty-shell (fires only when
|
|
237
|
+
// the wrapper's `this.turn` is null, i.e. after turn_duration, so it never fires
|
|
238
|
+
// between PTY sub-turns). While active, further channel turns are enqueued here
|
|
239
|
+
// instead of being written straight to stdin, then flushed one at a time on the
|
|
240
|
+
// end signal. Without this, a mid-turn message is merged by the headless CLI as
|
|
241
|
+
// steering (2 messages → 1 result, non-deterministic) or races the PTY paste.
|
|
242
|
+
this.turnActive = new Set();
|
|
243
|
+
this.turnQueue = new Map();
|
|
244
|
+
// Per-chat monotonic epoch bumped by /stop. injectTurn() captures it at entry and
|
|
245
|
+
// re-checks just before submitting; a mismatch means /stop landed during the
|
|
246
|
+
// async spawn/persist window, so the injection aborts instead of submitting the
|
|
247
|
+
// very turn the user asked to cancel (spawn-race close). Using an epoch (not a
|
|
248
|
+
// boolean flag) avoids a stale /stop poisoning a turn the user starts afterwards.
|
|
249
|
+
this.stopEpoch = new Map();
|
|
234
250
|
// Buffers attachment file paths registered via api_reply tool for the current API session turn.
|
|
235
251
|
this.pendingApiAttachments = new Map();
|
|
236
252
|
// Session history ring-buffer (last 10 spawned, newest first)
|
|
@@ -383,33 +399,32 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
383
399
|
});
|
|
384
400
|
return;
|
|
385
401
|
}
|
|
386
|
-
// Coalesce messages that
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
buf.timer
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
this.routeChannelTurn(chatId, channelSource, [params]);
|
|
402
|
+
// Coalesce channel messages that arrive close together into ONE turn:
|
|
403
|
+
// a photo and its caption (Telegram may split them across updates —
|
|
404
|
+
// long-caption split, album burst, or photo sent just before/after the
|
|
405
|
+
// text), OR consecutive text messages that are conceptually a single
|
|
406
|
+
// prompt (A1). Each message is still recorded individually in history
|
|
407
|
+
// by routeChannelTurn(); only the injected turn is merged. A
|
|
408
|
+
// trailing-edge timer flushes the buffer once the window passes with no
|
|
409
|
+
// new message; the gateway turn queue then serialises this merged turn
|
|
410
|
+
// behind any turn already in flight.
|
|
411
|
+
const buf = this.channelCoalesce.get(chatId) ?? {
|
|
412
|
+
channelSource,
|
|
413
|
+
entries: [],
|
|
414
|
+
timer: undefined,
|
|
415
|
+
};
|
|
416
|
+
buf.channelSource = channelSource;
|
|
417
|
+
buf.entries.push(params);
|
|
418
|
+
if (buf.timer)
|
|
419
|
+
clearTimeout(buf.timer);
|
|
420
|
+
buf.timer = setTimeout(() => {
|
|
421
|
+
const flushed = this.channelCoalesce.get(chatId);
|
|
422
|
+
this.channelCoalesce.delete(chatId);
|
|
423
|
+
if (flushed)
|
|
424
|
+
this.routeChannelTurn(chatId, flushed.channelSource, flushed.entries);
|
|
425
|
+
}, this.coalesceWindowMs);
|
|
426
|
+
this.channelCoalesce.set(chatId, buf);
|
|
427
|
+
return;
|
|
413
428
|
}
|
|
414
429
|
catch (err) {
|
|
415
430
|
this.logger.warn('Failed to parse channel callback body', {
|
|
@@ -850,6 +865,49 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
850
865
|
routeChannelTurn(chatId, channelSource, entries) {
|
|
851
866
|
if (entries.length === 0)
|
|
852
867
|
return;
|
|
868
|
+
// Gateway turn queue: if a turn is already in flight for this chat, enqueue
|
|
869
|
+
// this one and return. It is injected when the active turn truly ends
|
|
870
|
+
// (flushNextTurn, wired to the backend-aware end signal). The check-and-set
|
|
871
|
+
// is synchronous, so two near-simultaneous routes cannot both inject, and
|
|
872
|
+
// the active flag stays set continuously across a flush (no re-entrancy gap).
|
|
873
|
+
if (this.turnActive.has(chatId)) {
|
|
874
|
+
const q = this.turnQueue.get(chatId) ?? [];
|
|
875
|
+
q.push({ channelSource, entries });
|
|
876
|
+
this.turnQueue.set(chatId, q);
|
|
877
|
+
this.logger.debug('Turn queued behind in-flight turn', { chatId, queued: q.length });
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
this.turnActive.add(chatId);
|
|
881
|
+
this.injectTurn(chatId, channelSource, entries);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Inject the next queued turn for this chat, or release the active slot when the
|
|
885
|
+
* queue is empty. Called when a turn truly ends — `result` (headless), or
|
|
886
|
+
* `session_idle` (pty-shell), or on process exit. `turnActive` is kept set while
|
|
887
|
+
* a queued turn exists so no incoming message can inject concurrently.
|
|
888
|
+
*/
|
|
889
|
+
flushNextTurn(chatId) {
|
|
890
|
+
const q = this.turnQueue.get(chatId);
|
|
891
|
+
if (q && q.length > 0) {
|
|
892
|
+
const next = q.shift();
|
|
893
|
+
if (q.length === 0)
|
|
894
|
+
this.turnQueue.delete(chatId);
|
|
895
|
+
this.injectTurn(chatId, next.channelSource, next.entries);
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
this.turnActive.delete(chatId);
|
|
899
|
+
this.turnQueue.delete(chatId);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Inject one coalesced turn into the session immediately. The caller must have
|
|
904
|
+
* already marked the chat active in `turnActive`. Records each buffered message
|
|
905
|
+
* individually in history, then writes the merged turn to the subprocess.
|
|
906
|
+
*/
|
|
907
|
+
injectTurn(chatId, channelSource, entries) {
|
|
908
|
+
// Snapshot the stop epoch: if /stop bumps it during the async window below, we
|
|
909
|
+
// abort just before submitting (see the check before setProcessing/sendMessage).
|
|
910
|
+
const stopEpochAtStart = this.stopEpoch.get(chatId) ?? 0;
|
|
853
911
|
this.sessionStore.getActiveSessionId(this.agentConfig.id, chatId, channelSource)
|
|
854
912
|
.then(async (sessionId) => {
|
|
855
913
|
// Record each buffered message individually (history + session context).
|
|
@@ -869,9 +927,16 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
869
927
|
try {
|
|
870
928
|
const rel = media_store_1.MediaStore.copyToMedia(this.agentsBaseDir, this.agentConfig.id, `${channelSource}-${chatId}`, meta['image_path']);
|
|
871
929
|
mediaFiles.push(rel);
|
|
930
|
+
// Surface the MediaStore copy to the agent instead of the raw staging
|
|
931
|
+
// path. app-agents run in a container where only the MediaStore dir is
|
|
932
|
+
// bind-mounted at an identical absolute path; the raw source (/tmp for
|
|
933
|
+
// LINE, <workspace>/.*-state for TG/Discord) is invisible inside it.
|
|
934
|
+
// Mutating meta['image_path'] here (same object as entry.meta) makes the
|
|
935
|
+
// channel XML and image-size tracker below both use the readable path.
|
|
936
|
+
meta['image_path'] = media_store_1.MediaStore.resolvePath(this.agentsBaseDir, this.agentConfig.id, rel);
|
|
872
937
|
}
|
|
873
938
|
catch {
|
|
874
|
-
// Non-fatal —
|
|
939
|
+
// Non-fatal — leave the original path so host agents still read it
|
|
875
940
|
}
|
|
876
941
|
}
|
|
877
942
|
this.historyDb.insertMessage({
|
|
@@ -922,6 +987,15 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
922
987
|
this.pendingImagePaths.set(chatId, queue);
|
|
923
988
|
}
|
|
924
989
|
}
|
|
990
|
+
// Spawn-race close: if /stop bumped the stop epoch during the async
|
|
991
|
+
// getActiveSessionId/spawn window above, abort before submitting — do not
|
|
992
|
+
// inject the very turn the user just asked to cancel. flushNextTurn()
|
|
993
|
+
// releases the (now empty, /stop cleared it) queue and the active slot.
|
|
994
|
+
if ((this.stopEpoch.get(chatId) ?? 0) !== stopEpochAtStart) {
|
|
995
|
+
this.logger.info('Turn injection aborted — /stop landed during spawn', { chatId });
|
|
996
|
+
this.flushNextTurn(chatId);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
925
999
|
session.setProcessing(true);
|
|
926
1000
|
const turnText = blocks.join('\n');
|
|
927
1001
|
session.sendMessage(turnText);
|
|
@@ -943,6 +1017,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
943
1017
|
});
|
|
944
1018
|
const code = err.message.includes('pool full') ? 'POOL_FULL' : 'SPAWN_FAILED';
|
|
945
1019
|
this.writeTypingError(chatId, code);
|
|
1020
|
+
// This turn never reached the subprocess, so no end signal will fire —
|
|
1021
|
+
// release the active slot and inject the next queued turn so the queue
|
|
1022
|
+
// cannot wedge on a spawn error.
|
|
1023
|
+
this.flushNextTurn(chatId);
|
|
946
1024
|
});
|
|
947
1025
|
}
|
|
948
1026
|
/**
|
|
@@ -1186,6 +1264,11 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1186
1264
|
// block id — otherwise one sent image lands in the transcript N times and
|
|
1187
1265
|
// skews the session image catalog's "image N" ordinal.
|
|
1188
1266
|
const seenLineImageIds = new Set();
|
|
1267
|
+
// A line_image call queued here (id -> media) is only written to history
|
|
1268
|
+
// once its matching tool_result confirms the send succeeded — a failed
|
|
1269
|
+
// attempt followed by a retried, successful one must not leave two copies
|
|
1270
|
+
// of the same image in the transcript.
|
|
1271
|
+
const pendingLineImageMedia = new Map();
|
|
1189
1272
|
// Set when an interactive-menu prompt was rendered to the channel this turn,
|
|
1190
1273
|
// so the result's plain-text auto-forward is skipped (no duplicate message).
|
|
1191
1274
|
let menuSentThisTurn = false;
|
|
@@ -1216,25 +1299,23 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1216
1299
|
if (block.type === 'tool_use' && block.name === 'mcp__gateway__line_image') {
|
|
1217
1300
|
const lineBlockId = block['id'];
|
|
1218
1301
|
const dedupeKey = typeof lineBlockId === 'string' ? lineBlockId : '';
|
|
1219
|
-
// Skip if this tool_use block was already
|
|
1302
|
+
// Skip if this tool_use block was already queued this turn
|
|
1220
1303
|
// (cumulative assistant snapshots re-emit completed blocks).
|
|
1221
1304
|
if (dedupeKey && seenLineImageIds.has(dedupeKey))
|
|
1222
1305
|
continue;
|
|
1223
1306
|
const imgPath = typeof block.input?.['image'] === 'string' ? block.input['image'] : '';
|
|
1224
1307
|
const mediaRootLine = path.join(this.agentsBaseDir, this.agentConfig.id, 'media') + path.sep;
|
|
1225
1308
|
const lineMedia = toRelMediaFiles(imgPath ? [imgPath] : [], mediaRootLine);
|
|
1226
|
-
if (lineMedia.length) {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
mediaFiles: lineMedia,
|
|
1237
|
-
ts: Date.now(),
|
|
1309
|
+
if (lineMedia.length && dedupeKey) {
|
|
1310
|
+
seenLineImageIds.add(dedupeKey);
|
|
1311
|
+
// Queue instead of writing now — the send can still fail (mint
|
|
1312
|
+
// error, LINE API error, transient socket blip). Only the
|
|
1313
|
+
// matching tool_result below commits it to history, so a
|
|
1314
|
+
// failed attempt followed by a retried success doesn't leave
|
|
1315
|
+
// two copies of the same image in the transcript.
|
|
1316
|
+
pendingLineImageMedia.set(dedupeKey, {
|
|
1317
|
+
lineMedia,
|
|
1318
|
+
channelSrcLine: this.channelSourceMap.get(mapKey) ?? 'line',
|
|
1238
1319
|
});
|
|
1239
1320
|
}
|
|
1240
1321
|
}
|
|
@@ -1285,6 +1366,31 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1285
1366
|
}
|
|
1286
1367
|
}
|
|
1287
1368
|
}
|
|
1369
|
+
// Commit a queued line_image send to history only once its tool_result
|
|
1370
|
+
// confirms success — see the queuing comment above. A failed send is
|
|
1371
|
+
// simply dropped, so a same-image retry never double-writes.
|
|
1372
|
+
if (obj['type'] === 'user' && pendingLineImageMedia.size) {
|
|
1373
|
+
const msg = obj['message'];
|
|
1374
|
+
if (Array.isArray(msg?.content)) {
|
|
1375
|
+
for (const block of msg.content) {
|
|
1376
|
+
if (block.type === 'tool_result' && block.tool_use_id && pendingLineImageMedia.has(block.tool_use_id)) {
|
|
1377
|
+
const pending = pendingLineImageMedia.get(block.tool_use_id);
|
|
1378
|
+
pendingLineImageMedia.delete(block.tool_use_id);
|
|
1379
|
+
if (!block.is_error) {
|
|
1380
|
+
this.historyDb.insertMessage({
|
|
1381
|
+
chatId: `${pending.channelSrcLine}-${mapKey}`,
|
|
1382
|
+
sessionId: actualSessionId,
|
|
1383
|
+
source: pending.channelSrcLine,
|
|
1384
|
+
role: 'assistant',
|
|
1385
|
+
content: '',
|
|
1386
|
+
mediaFiles: pending.lineMedia,
|
|
1387
|
+
ts: Date.now(),
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1288
1394
|
// Interactive select menu blocking the PTY: render it as channel-native
|
|
1289
1395
|
// UI (inline buttons). This output handler only runs for Telegram/Discord
|
|
1290
1396
|
// sessions; API uses a separate path and falls back to the result text's
|
|
@@ -1320,6 +1426,15 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1320
1426
|
}
|
|
1321
1427
|
if (obj['type'] === 'result') {
|
|
1322
1428
|
proc.setProcessing(false);
|
|
1429
|
+
// Gateway turn queue: on the headless backend `result` is the true end
|
|
1430
|
+
// of the user's turn (exactly one per message), so flush the next
|
|
1431
|
+
// queued turn here. On pty-shell `result` fires per sub-turn (across
|
|
1432
|
+
// tool-call gaps), so its flush is driven by `session_idle` instead
|
|
1433
|
+
// (below) — flushing on every sub-turn `result` would inject the next
|
|
1434
|
+
// message mid-work.
|
|
1435
|
+
if (proc.backend !== 'pty-shell') {
|
|
1436
|
+
this.flushNextTurn(mapKey);
|
|
1437
|
+
}
|
|
1323
1438
|
// Telegram: accumulate image size via stat of local file (FIFO, one per turn)
|
|
1324
1439
|
const queue = this.pendingImagePaths.get(mapKey);
|
|
1325
1440
|
const imgPath = queue?.shift();
|
|
@@ -1431,6 +1546,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1431
1546
|
replyCalled = false; // reset for next turn
|
|
1432
1547
|
replyToolUseId = null;
|
|
1433
1548
|
seenLineImageIds.clear();
|
|
1549
|
+
pendingLineImageMedia.clear();
|
|
1434
1550
|
menuSentThisTurn = false;
|
|
1435
1551
|
menuPromptTextThisTurn = '';
|
|
1436
1552
|
// In pty-shell mode, a `result` fires after every Claude API sub-turn
|
|
@@ -1450,6 +1566,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1450
1566
|
// event. Start the typing-done timer here so the indicator stays alive
|
|
1451
1567
|
// through multi-turn tool-call sequences and only stops when all work is done.
|
|
1452
1568
|
if (obj['type'] === 'session_idle' && proc.backend === 'pty-shell') {
|
|
1569
|
+
// Gateway turn queue: on pty-shell this is the true end of the user's
|
|
1570
|
+
// turn (the wrapper only emits it when its own `this.turn` is null, so
|
|
1571
|
+
// it never fires between sub-turns) — flush the next queued turn.
|
|
1572
|
+
this.flushNextTurn(mapKey);
|
|
1453
1573
|
typingDoneTimer = setTimeout(() => {
|
|
1454
1574
|
this.writeTypingDone(mapKey);
|
|
1455
1575
|
typingDoneTimer = null;
|
|
@@ -1500,6 +1620,11 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1500
1620
|
typingDoneTimer = null;
|
|
1501
1621
|
}
|
|
1502
1622
|
this.writeTypingDone(mapKey);
|
|
1623
|
+
// Gateway turn queue: the in-flight turn's end signal will never arrive
|
|
1624
|
+
// now, so release the active slot. If turns were queued behind it, deliver
|
|
1625
|
+
// the next one (injectTurn respawns the session) so a crash mid-turn does
|
|
1626
|
+
// not strand the messages the user already sent.
|
|
1627
|
+
this.flushNextTurn(mapKey);
|
|
1503
1628
|
// LINE: a process exit with a button whose answer never arrived (crash /
|
|
1504
1629
|
// teardown mid-turn) → surface the interrupted notice so a tap returns it
|
|
1505
1630
|
// instead of a stale "still thinking". markInterrupted no-ops on a READY
|
|
@@ -1645,12 +1770,33 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1645
1770
|
this.writeAutoForward(chatId, `✅ Session renamed to "${name}"`);
|
|
1646
1771
|
}
|
|
1647
1772
|
/**
|
|
1648
|
-
* /stop — interrupt the in-flight turn for this chat
|
|
1649
|
-
*
|
|
1773
|
+
* /stop — interrupt the in-flight turn for this chat and cancel anything queued
|
|
1774
|
+
* behind it. Drops the coalesce buffer (messages not yet routed) and the turn
|
|
1775
|
+
* queue (turns waiting behind the active one), bumps the per-chat stop epoch so
|
|
1776
|
+
* a turn still mid-spawn aborts before it submits (closing the spawn-race), and
|
|
1777
|
+
* sends SIGINT to interrupt an actively-processing turn. Session history/metadata
|
|
1778
|
+
* are left intact. Reports Stopped whenever there was work to cancel — an active
|
|
1779
|
+
* turn, a spawning turn, or queued/buffered messages — rather than the old
|
|
1780
|
+
* "No turn in progress." that fired whenever interrupt() no-op'd mid-spawn.
|
|
1650
1781
|
*/
|
|
1651
1782
|
async handleCommandStop(chatId) {
|
|
1783
|
+
// Cancel queued + buffered work first so nothing flushes in behind the interrupt.
|
|
1784
|
+
const buffered = this.channelCoalesce.get(chatId);
|
|
1785
|
+
if (buffered?.timer)
|
|
1786
|
+
clearTimeout(buffered.timer);
|
|
1787
|
+
this.channelCoalesce.delete(chatId);
|
|
1788
|
+
const hadQueued = (this.turnQueue.get(chatId)?.length ?? 0) > 0;
|
|
1789
|
+
this.turnQueue.delete(chatId);
|
|
1790
|
+
// Bump the stop epoch: any injectTurn() that began before this point detects
|
|
1791
|
+
// the mismatch just before submitting and aborts (spawn-race close). The
|
|
1792
|
+
// turnActive slot is released by that abort's flushNextTurn(), or by the
|
|
1793
|
+
// interrupted turn's end signal — never cleared here, to avoid racing a
|
|
1794
|
+
// stale end signal against a turn the user starts right after /stop.
|
|
1795
|
+
const hadActive = this.turnActive.has(chatId);
|
|
1796
|
+
this.stopEpoch.set(chatId, (this.stopEpoch.get(chatId) ?? 0) + 1);
|
|
1652
1797
|
const session = this.sessions.get(chatId);
|
|
1653
|
-
const
|
|
1798
|
+
const interrupted = session ? session.interrupt() : false;
|
|
1799
|
+
const stopped = interrupted || hadActive || hadQueued || !!buffered;
|
|
1654
1800
|
this.writeAutoForward(chatId, stopped ? 'Stopped.' : 'No turn in progress.');
|
|
1655
1801
|
}
|
|
1656
1802
|
/**
|