@0xmaxma/claude-gateway 1.6.2 → 1.6.4

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.
@@ -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 by sending SIGINT to the subprocess.
195
- * Leaves session history and metadata intact. Queued messages still process afterwards.
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":"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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
@@ -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 carry an image (or that arrive while an image is
387
- // already buffered) so a photo and its instruction text are injected as
388
- // ONE turn Telegram can split them across updates (long-caption split,
389
- // album burst, or photo sent just before/after the text). Plain text with
390
- // nothing buffered takes the fast path and is injected immediately.
391
- const hasImage = !!meta['image_path'];
392
- const pending = this.channelCoalesce.get(chatId);
393
- if (hasImage || pending) {
394
- const buf = pending ?? {
395
- channelSource,
396
- entries: [],
397
- timer: undefined,
398
- };
399
- buf.channelSource = channelSource;
400
- buf.entries.push(params);
401
- if (buf.timer)
402
- clearTimeout(buf.timer);
403
- buf.timer = setTimeout(() => {
404
- const flushed = this.channelCoalesce.get(chatId);
405
- this.channelCoalesce.delete(chatId);
406
- if (flushed)
407
- this.routeChannelTurn(chatId, flushed.channelSource, flushed.entries);
408
- }, this.coalesceWindowMs);
409
- this.channelCoalesce.set(chatId, buf);
410
- return;
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 — continue without media
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
  /**
@@ -1348,6 +1426,19 @@ class AgentRunner extends events_1.EventEmitter {
1348
1426
  }
1349
1427
  if (obj['type'] === 'result') {
1350
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
+ // Skip when a deferred restart is armed: this session is about to be
1436
+ // torn down (deferredRestartReady), so draining the queue into it now
1437
+ // would kill the queued turn with the process. The restart handler
1438
+ // re-drives the preserved queue into the fresh session instead.
1439
+ if (proc.backend !== 'pty-shell' && !proc.hasPendingRestart()) {
1440
+ this.flushNextTurn(mapKey);
1441
+ }
1351
1442
  // Telegram: accumulate image size via stat of local file (FIFO, one per turn)
1352
1443
  const queue = this.pendingImagePaths.get(mapKey);
1353
1444
  const imgPath = queue?.shift();
@@ -1479,6 +1570,15 @@ class AgentRunner extends events_1.EventEmitter {
1479
1570
  // event. Start the typing-done timer here so the indicator stays alive
1480
1571
  // through multi-turn tool-call sequences and only stops when all work is done.
1481
1572
  if (obj['type'] === 'session_idle' && proc.backend === 'pty-shell') {
1573
+ // Gateway turn queue: on pty-shell this is the true end of the user's
1574
+ // turn (the wrapper only emits it when its own `this.turn` is null, so
1575
+ // it never fires between sub-turns) — flush the next queued turn.
1576
+ // Skip when a deferred restart is armed (see the headless `result`
1577
+ // path above): the restart handler re-drives the queue into the fresh
1578
+ // session so the queued turn is not killed with this one.
1579
+ if (!proc.hasPendingRestart()) {
1580
+ this.flushNextTurn(mapKey);
1581
+ }
1482
1582
  typingDoneTimer = setTimeout(() => {
1483
1583
  this.writeTypingDone(mapKey);
1484
1584
  typingDoneTimer = null;
@@ -1529,6 +1629,16 @@ class AgentRunner extends events_1.EventEmitter {
1529
1629
  typingDoneTimer = null;
1530
1630
  }
1531
1631
  this.writeTypingDone(mapKey);
1632
+ // Gateway turn queue: the in-flight turn's end signal will never arrive
1633
+ // now, so release the active slot. If turns were queued behind it, deliver
1634
+ // the next one (injectTurn respawns the session) so a crash mid-turn does
1635
+ // not strand the messages the user already sent.
1636
+ // Skip on a deferred restart: this exit is the intentional teardown, and
1637
+ // its handler re-drives the queue into the fresh session AFTER deleting
1638
+ // the dead one from the pool — flushing here would reuse the dead proc.
1639
+ if (!proc.hasPendingRestart()) {
1640
+ this.flushNextTurn(mapKey);
1641
+ }
1532
1642
  // LINE: a process exit with a button whose answer never arrived (crash /
1533
1643
  // teardown mid-turn) → surface the interrupted notice so a tap returns it
1534
1644
  // instead of a stale "still thinking". markInterrupted no-ops on a READY
@@ -1544,6 +1654,12 @@ class AgentRunner extends events_1.EventEmitter {
1544
1654
  this.logger.info('Deferred restart: stopping session after turn completed', { mapKey });
1545
1655
  await proc.stop();
1546
1656
  this.sessions.delete(mapKey);
1657
+ // Re-drive any turn queued behind the just-completed turn. The turn-end
1658
+ // and exit flush paths deliberately skipped it (hasPendingRestart guards)
1659
+ // so it was not drained into this now-dead session. Flushing here — after
1660
+ // the dead proc is removed from the pool — re-injects it into a freshly
1661
+ // spawned session instead of losing it with the restart.
1662
+ this.flushNextTurn(mapKey);
1547
1663
  });
1548
1664
  }
1549
1665
  this.sessions.set(mapKey, proc);
@@ -1674,12 +1790,33 @@ class AgentRunner extends events_1.EventEmitter {
1674
1790
  this.writeAutoForward(chatId, `✅ Session renamed to "${name}"`);
1675
1791
  }
1676
1792
  /**
1677
- * /stop — interrupt the in-flight turn for this chat by sending SIGINT to the subprocess.
1678
- * Leaves session history and metadata intact. Queued messages still process afterwards.
1793
+ * /stop — interrupt the in-flight turn for this chat and cancel anything queued
1794
+ * behind it. Drops the coalesce buffer (messages not yet routed) and the turn
1795
+ * queue (turns waiting behind the active one), bumps the per-chat stop epoch so
1796
+ * a turn still mid-spawn aborts before it submits (closing the spawn-race), and
1797
+ * sends SIGINT to interrupt an actively-processing turn. Session history/metadata
1798
+ * are left intact. Reports Stopped whenever there was work to cancel — an active
1799
+ * turn, a spawning turn, or queued/buffered messages — rather than the old
1800
+ * "No turn in progress." that fired whenever interrupt() no-op'd mid-spawn.
1679
1801
  */
1680
1802
  async handleCommandStop(chatId) {
1803
+ // Cancel queued + buffered work first so nothing flushes in behind the interrupt.
1804
+ const buffered = this.channelCoalesce.get(chatId);
1805
+ if (buffered?.timer)
1806
+ clearTimeout(buffered.timer);
1807
+ this.channelCoalesce.delete(chatId);
1808
+ const hadQueued = (this.turnQueue.get(chatId)?.length ?? 0) > 0;
1809
+ this.turnQueue.delete(chatId);
1810
+ // Bump the stop epoch: any injectTurn() that began before this point detects
1811
+ // the mismatch just before submitting and aborts (spawn-race close). The
1812
+ // turnActive slot is released by that abort's flushNextTurn(), or by the
1813
+ // interrupted turn's end signal — never cleared here, to avoid racing a
1814
+ // stale end signal against a turn the user starts right after /stop.
1815
+ const hadActive = this.turnActive.has(chatId);
1816
+ this.stopEpoch.set(chatId, (this.stopEpoch.get(chatId) ?? 0) + 1);
1681
1817
  const session = this.sessions.get(chatId);
1682
- const stopped = session ? session.interrupt() : false;
1818
+ const interrupted = session ? session.interrupt() : false;
1819
+ const stopped = interrupted || hadActive || hadQueued || !!buffered;
1683
1820
  this.writeAutoForward(chatId, stopped ? 'Stopped.' : 'No turn in progress.');
1684
1821
  }
1685
1822
  /**