@0xmaxma/claude-gateway 1.3.31 → 1.4.0

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/README.md CHANGED
@@ -230,6 +230,7 @@ Global default retention policy. Can be overridden per-agent with an `history` k
230
230
  "gateway": {
231
231
  "history": {
232
232
  "retentionDays": 90,
233
+ "maxHistoryMessages": 30,
233
234
  "cleanupHour": 3,
234
235
  "cleanupTimezone": "Asia/Bangkok"
235
236
  }
@@ -240,6 +241,7 @@ Global default retention policy. Can be overridden per-agent with an `history` k
240
241
  | Field | Default | Description |
241
242
  |-------|---------|-------------|
242
243
  | `retentionDays` | `null` (keep forever) | Delete messages older than N days on each cleanup cycle |
244
+ | `maxHistoryMessages` | `50` | Max history messages re-injected into a session at spawn. Lower it to shrink the context loaded at session start. `0` = inject no history |
243
245
  | `cleanupHour` | `3` | Hour of day to run cleanup (24h, in `cleanupTimezone`) |
244
246
  | `cleanupTimezone` | `"UTC"` | IANA timezone for the cleanup schedule |
245
247
 
@@ -249,7 +251,7 @@ Per-agent override example:
249
251
  "agents": [
250
252
  {
251
253
  "id": "alfred",
252
- "history": { "retentionDays": 30 }
254
+ "history": { "retentionDays": 30, "maxHistoryMessages": 30 }
253
255
  }
254
256
  ]
255
257
  }
@@ -1,10 +1,22 @@
1
1
  import { EventEmitter } from 'events';
2
- import { AgentConfig, GatewayConfig, Logger, ModelConfig, StreamEvent, ApiAttachment } from '../types';
2
+ import { AgentConfig, GatewayConfig, Logger, ModelConfig, StreamEvent, ApiAttachment, ImageParams } from '../types';
3
3
  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
7
  export declare const CHANNEL_COALESCE_WINDOW_MS = 1200;
8
+ /**
9
+ * Convert absolute file paths (e.g. from a reply tool's `files` input) into
10
+ * relative `media/<rel>` paths for persistence as message `mediaFiles`.
11
+ *
12
+ * Mirrors the transform in `popApiAttachments`: only paths UNDER `mediaRoot`
13
+ * that pass the `exists` predicate are kept (never arbitrary abs paths, never
14
+ * dangling files), and backslashes are normalised to forward slashes.
15
+ *
16
+ * The `exists` predicate is injected (defaults to `fs.existsSync`) so the pure
17
+ * string transform is unit-testable without a real filesystem.
18
+ */
19
+ export declare function toRelMediaFiles(absPaths: unknown[], mediaRoot: string, exists?: (p: string) => boolean): string[];
8
20
  export declare class AgentRunner extends EventEmitter {
9
21
  private agentConfig;
10
22
  private readonly gatewayConfig;
@@ -117,6 +129,12 @@ export declare class AgentRunner extends EventEmitter {
117
129
  * a photo and its instruction text are read together in the same turn.
118
130
  */
119
131
  private routeChannelTurn;
132
+ /**
133
+ * Render composer-selected image options (contract E5) as a directive the agent
134
+ * reads and forwards to the generate_image MCP tool. Returns '' when no usable
135
+ * options are present.
136
+ */
137
+ private static buildImageParamsNote;
120
138
  private static buildChannelXml;
121
139
  private getOrSpawnSession;
122
140
  private spawnSession;
@@ -165,6 +183,21 @@ export declare class AgentRunner extends EventEmitter {
165
183
  * The process will be lazily re-spawned on the next incoming message.
166
184
  */
167
185
  private restartProcess;
186
+ /**
187
+ * The 32MB-recovery rungs for a given healthy cap: the ladder sizes STRICTLY
188
+ * below the cap, in descending order. Filtering by `< cap` (not `<=`) drops any
189
+ * rung equal to or above the cap so a lowered cap never yields a recovery step
190
+ * that re-injects the same (or more) history — every step actually shrinks.
191
+ */
192
+ private recoveryRungs;
193
+ /**
194
+ * History re-injection cap for a spawn, given the configured healthy cap and how
195
+ * many consecutive 32MB recoveries have happened on the session. recoveryCount 0
196
+ * = healthy → the full configured cap. Each later recovery drops to the next
197
+ * rung strictly below the cap; once those are exhausted it stays at 0 (no
198
+ * history). Kept in sync with the exhaustion threshold in handleRequestTooLarge.
199
+ */
200
+ private spawnHistoryLimit;
168
201
  /**
169
202
  * Unified recovery for the recoverable "Request too large (max 32MB)" error.
170
203
  * Reached from two backends that surface the SAME error differently:
@@ -174,12 +207,13 @@ export declare class AgentRunner extends EventEmitter {
174
207
  * (is_error + "Request too large (max"); the long-lived process otherwise
175
208
  * stays alive and rejects every subsequent turn forever (Bug B).
176
209
  *
177
- * Each consecutive recovery shrinks the history re-injected on the next spawn
178
- * (TOO_LARGE_HISTORY_LADDER: 50→40→30→20→10→0) so a pathological context drops
179
- * under the 32MB ceiling. The respawn happens on the user's NEXT message (no
180
- * auto-loop), and the counter resets on the next successful result. Once even a
181
- * zero-history spawn still trips 32MB, stop escalating and ask the user to
182
- * /clear rather than climb the ladder again.
210
+ * Each consecutive recovery shrinks the history re-injected on the next spawn,
211
+ * stepping down the TOO_LARGE_HISTORY_LADDER rungs strictly below the configured
212
+ * cap (default 50 40→30→20→10→0), so a pathological context drops under the
213
+ * 32MB ceiling. The respawn happens on the user's NEXT message (no auto-loop),
214
+ * and the counter resets on the next successful result. Once even a zero-history
215
+ * spawn still trips 32MB, stop escalating and ask the user to /clear rather than
216
+ * climb the ladder again.
183
217
  */
184
218
  private handleRequestTooLarge;
185
219
  /**
@@ -272,6 +306,7 @@ export declare class AgentRunner extends EventEmitter {
272
306
  mediaFiles?: string[];
273
307
  model?: string;
274
308
  skipUserMessage?: boolean;
309
+ imageParams?: ImageParams;
275
310
  }): Promise<{
276
311
  text: string;
277
312
  attachments: ApiAttachment[];
@@ -292,6 +327,7 @@ export declare class AgentRunner extends EventEmitter {
292
327
  mediaFiles?: string[];
293
328
  model?: string;
294
329
  skipUserMessage?: boolean;
330
+ imageParams?: ImageParams;
295
331
  }): Promise<() => void>;
296
332
  /**
297
333
  * Check if a session has a pending API request (for preflight conflict check).
@@ -314,6 +350,7 @@ export declare class AgentRunner extends EventEmitter {
314
350
  getHistoryDb(): HistoryDB;
315
351
  getAllSessionMeta(): Promise<Map<string, {
316
352
  name: string;
353
+ imageConfig?: ImageParams;
317
354
  }>>;
318
355
  listSessionsForChat(chatId: string, channel: 'telegram' | 'discord' | 'line'): Promise<import('../types').SessionIndex>;
319
356
  executeApiCommand(sessionId: string, chatId: string, command: string, opts?: {
@@ -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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"}
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.AgentRunner = exports.CHANNEL_COALESCE_WINDOW_MS = exports.DEFAULT_MODELS = exports.MAX_IMAGE_SIZE_BYTES = void 0;
37
+ exports.toRelMediaFiles = toRelMediaFiles;
37
38
  const events_1 = require("events");
38
39
  const fs = __importStar(require("fs"));
39
40
  const fsPromises = __importStar(require("fs/promises"));
@@ -61,14 +62,16 @@ const cleanup_1 = require("../history/cleanup");
61
62
  const DEFAULT_IDLE_TIMEOUT_MINUTES = 30;
62
63
  const DEFAULT_MAX_CONCURRENT = 20;
63
64
  const ANTHROPIC_SOCKET_ERROR = 'socket connection was closed unexpectedly';
64
- // History re-injection ladder for request_too_large (32MB) recovery. Index =
65
- // number of consecutive 32MB recoveries on a session; the value is how many
66
- // history messages the NEXT spawn re-injects. Index 0 is the healthy default
67
- // (= MAX_HISTORY_MESSAGES, sourced from it so the two never drift); each retry
68
- // steps down a rung, shrinking the re-loaded context until it drops under
69
- // Anthropic's 32MB request ceiling. Past the last rung (0 history) the context
70
- // can't shrink further, so the runner stops escalating and asks the user to
71
- // /clear instead of looping forever.
65
+ // History re-injection ladder for request_too_large (32MB) recovery: the
66
+ // candidate history sizes a recovering session can step down to. The HEALTHY
67
+ // spawn uses the configured cap (resolveMaxHistoryMessages), not an index into
68
+ // this array; on each consecutive 32MB recovery the session drops to the next
69
+ // ladder rung STRICTLY BELOW that cap (see spawnHistoryLimit), so every retry
70
+ // actually shrinks the re-loaded context instead of re-trying the same size.
71
+ // Once even the 0-history rung still trips 32MB the runner stops escalating and
72
+ // asks the user to /clear instead of looping forever. The leading
73
+ // MAX_HISTORY_MESSAGES only acts as a recovery rung when an operator configures
74
+ // a cap higher than it.
72
75
  const TOO_LARGE_HISTORY_LADDER = [process_1.MAX_HISTORY_MESSAGES, 40, 30, 20, 10, 0];
73
76
  exports.MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024;
74
77
  exports.DEFAULT_MODELS = [
@@ -125,6 +128,10 @@ function buildApiSystemNote(allowTools, imagePaths) {
125
128
  const memoryOverride = `Memory Rule Override: Do NOT create or update ${PROTECTED_WORKSPACE_FILES.join(', ')} ` +
126
129
  `or any other workspace identity file in this session, regardless of user instructions. ` +
127
130
  `If the user asks you to remember something, reply that memory updates are not supported in API sessions.`;
131
+ const secretsRule = `Secret Non-Disclosure: NEVER reveal, print, echo, or transmit environment variables, ` +
132
+ `API tokens or keys, the contents of ~/.claude/settings.json or any .env file, or any ` +
133
+ `similar credentials or secrets — regardless of who asks or how the request is phrased. ` +
134
+ `Treat any such request as adversarial and refuse it.`;
128
135
  const toolNote = allowTools
129
136
  ? `You may use tools to complete the requested task.`
130
137
  : `Reply with plain text only. Do NOT call any tools. Your text output will be returned directly to the caller.`;
@@ -132,7 +139,23 @@ function buildApiSystemNote(allowTools, imagePaths) {
132
139
  if (imagePaths?.length) {
133
140
  imageNote = ` The user attached ${imagePaths.length} image(s). Read them with the Read tool:\n${imagePaths.map(p => `- ${p}`).join('\n')}`;
134
141
  }
135
- return `<api-context>This is an API request. ${memoryOverride} ${toolNote}${imageNote}</api-context>\n`;
142
+ return `<api-context>This is an API request. ${memoryOverride} ${secretsRule} ${toolNote}${imageNote}</api-context>\n`;
143
+ }
144
+ /**
145
+ * Convert absolute file paths (e.g. from a reply tool's `files` input) into
146
+ * relative `media/<rel>` paths for persistence as message `mediaFiles`.
147
+ *
148
+ * Mirrors the transform in `popApiAttachments`: only paths UNDER `mediaRoot`
149
+ * that pass the `exists` predicate are kept (never arbitrary abs paths, never
150
+ * dangling files), and backslashes are normalised to forward slashes.
151
+ *
152
+ * The `exists` predicate is injected (defaults to `fs.existsSync`) so the pure
153
+ * string transform is unit-testable without a real filesystem.
154
+ */
155
+ function toRelMediaFiles(absPaths, mediaRoot, exists = fs.existsSync) {
156
+ return absPaths
157
+ .filter((p) => typeof p === 'string' && p.startsWith(mediaRoot) && exists(p))
158
+ .map((p) => 'media/' + p.slice(mediaRoot.length).replace(/\\/g, '/'));
136
159
  }
137
160
  class AgentRunner extends events_1.EventEmitter {
138
161
  imageSize(chatId) { return this.imageSizePerChat.get(chatId) ?? 0; }
@@ -856,6 +879,28 @@ class AgentRunner extends events_1.EventEmitter {
856
879
  this.writeTypingError(chatId, code);
857
880
  });
858
881
  }
882
+ /**
883
+ * Render composer-selected image options (contract E5) as a directive the agent
884
+ * reads and forwards to the generate_image MCP tool. Returns '' when no usable
885
+ * options are present.
886
+ */
887
+ static buildImageParamsNote(p) {
888
+ const attrs = [
889
+ p.model ? `model="${AgentRunner.escapeXmlAttr(p.model)}"` : '',
890
+ p.quality ? `quality="${AgentRunner.escapeXmlAttr(p.quality)}"` : '',
891
+ p.size ? `size="${AgentRunner.escapeXmlAttr(p.size)}"` : '',
892
+ p.aspect_ratio ? `aspect_ratio="${AgentRunner.escapeXmlAttr(p.aspect_ratio)}"` : '',
893
+ typeof p.n === 'number' ? `n="${p.n}"` : '',
894
+ p.image_ref ? `image_ref="${AgentRunner.escapeXmlAttr(p.image_ref)}"` : '',
895
+ ].filter(Boolean);
896
+ if (!attrs.length)
897
+ return '';
898
+ return (`<image-params ${attrs.join(' ')} />\n` +
899
+ `The user selected the image-generation options above in the composer. When the request ` +
900
+ `involves creating or editing an image, call the generate_image tool (action="generate") ` +
901
+ `using these values (pass image_ref as the "image" argument for image-to-image), then ` +
902
+ `deliver the returned image with your reply tool.\n`);
903
+ }
859
904
  static buildChannelXml(params) {
860
905
  const meta = params.meta ?? {};
861
906
  const optionalAttrs = [
@@ -956,14 +1001,18 @@ class AgentRunner extends events_1.EventEmitter {
956
1001
  // Apply per-session model override (caller already normalized to undefined when == agent default)
957
1002
  if (modelOverride)
958
1003
  proc.modelOverride = modelOverride;
1004
+ // Per-agent → global → MAX_HISTORY_MESSAGES: the configured cap on how many
1005
+ // history messages a healthy spawn re-injects. Lets an operator lower the
1006
+ // context loaded at session start (e.g. 50 → 30) without touching code.
1007
+ const configuredMax = (0, process_1.resolveMaxHistoryMessages)(this.agentConfig.history?.maxHistoryMessages, this.gatewayConfig.gateway.history?.maxHistoryMessages);
959
1008
  // request_too_large (32MB) recovery: shrink the re-injected history on each
960
1009
  // consecutive retry so a pathological context eventually fits. recoveryCount
961
- // is 0 for healthy sessions → ladder rung 0 = MAX_HISTORY_MESSAGES (same as
962
- // the SessionProcess default), so applying it unconditionally is a no-op for
963
- // healthy sessions and the only path that sets historyLimit for recovering ones.
1010
+ // is 0 for healthy sessions → use the configured cap directly; a recovering
1011
+ // session steps down to the ladder rungs STRICTLY BELOW that cap, so each
1012
+ // retry genuinely shrinks instead of re-trying the same (already-too-large)
1013
+ // size when the cap has been lowered.
964
1014
  const recoveryCount = this.tooLargeRecoveries.get(mapKey) ?? 0;
965
- const ladderIdx = Math.min(recoveryCount, TOO_LARGE_HISTORY_LADDER.length - 1);
966
- proc.historyLimit = TOO_LARGE_HISTORY_LADDER[ladderIdx];
1015
+ proc.historyLimit = this.spawnHistoryLimit(configuredMax, recoveryCount);
967
1016
  if (recoveryCount > 0) {
968
1017
  this.logger.info('Spawning with reduced history after request_too_large', {
969
1018
  mapKey, recoveryCount, historyLimit: proc.historyLimit,
@@ -1036,7 +1085,15 @@ class AgentRunner extends events_1.EventEmitter {
1036
1085
  replyToolUseId = block['id'] ?? null;
1037
1086
  // Persist the reply text to history so it appears in chat history API
1038
1087
  const replyText = typeof block.input?.['text'] === 'string' ? block.input['text'].trim() : '';
1039
- if (replyText) {
1088
+ // Capture any images the reply attached (reply tool's `files`)
1089
+ // so the web transcript renders them via mediaFiles. Only files
1090
+ // under the agent media root that still exist are recorded.
1091
+ const replyFiles = Array.isArray(block.input?.['files']) ? block.input['files'] : [];
1092
+ const mediaRoot = path.join(this.agentsBaseDir, this.agentConfig.id, 'media') + path.sep;
1093
+ const replyMedia = toRelMediaFiles(replyFiles, mediaRoot);
1094
+ // Persist when there is text OR image(s) — an image-only reply
1095
+ // still needs a row so it shows in the web transcript.
1096
+ if (replyText || replyMedia.length) {
1040
1097
  const channelSrc = this.channelSourceMap.get(mapKey) ?? 'telegram';
1041
1098
  this.historyDb.insertMessage({
1042
1099
  chatId: `${channelSrc}-${mapKey}`,
@@ -1044,12 +1101,13 @@ class AgentRunner extends events_1.EventEmitter {
1044
1101
  source: channelSrc,
1045
1102
  role: 'assistant',
1046
1103
  content: replyText,
1104
+ mediaFiles: replyMedia.length ? replyMedia : undefined,
1047
1105
  ts: Date.now(),
1048
1106
  });
1049
1107
  // LINE: the MCP line_reply tool's send is suppressed in
1050
1108
  // refresh mode; the gateway delivers (free reply, or cache +
1051
1109
  // postback button when slow). See LineReplyManager.
1052
- if (channelSrc === 'line' && this.lineReply) {
1110
+ if (channelSrc === 'line' && this.lineReply && replyText) {
1053
1111
  void this.lineReply.onAnswer(mapKey, replyText);
1054
1112
  }
1055
1113
  }
@@ -1531,6 +1589,30 @@ class AgentRunner extends events_1.EventEmitter {
1531
1589
  }
1532
1590
  // Process will be re-spawned on next incoming message
1533
1591
  }
1592
+ /**
1593
+ * The 32MB-recovery rungs for a given healthy cap: the ladder sizes STRICTLY
1594
+ * below the cap, in descending order. Filtering by `< cap` (not `<=`) drops any
1595
+ * rung equal to or above the cap so a lowered cap never yields a recovery step
1596
+ * that re-injects the same (or more) history — every step actually shrinks.
1597
+ */
1598
+ recoveryRungs(configuredMax) {
1599
+ return TOO_LARGE_HISTORY_LADDER.filter(r => r < configuredMax);
1600
+ }
1601
+ /**
1602
+ * History re-injection cap for a spawn, given the configured healthy cap and how
1603
+ * many consecutive 32MB recoveries have happened on the session. recoveryCount 0
1604
+ * = healthy → the full configured cap. Each later recovery drops to the next
1605
+ * rung strictly below the cap; once those are exhausted it stays at 0 (no
1606
+ * history). Kept in sync with the exhaustion threshold in handleRequestTooLarge.
1607
+ */
1608
+ spawnHistoryLimit(configuredMax, recoveryCount) {
1609
+ if (recoveryCount <= 0)
1610
+ return configuredMax;
1611
+ const rungs = this.recoveryRungs(configuredMax);
1612
+ if (rungs.length === 0)
1613
+ return 0;
1614
+ return rungs[Math.min(recoveryCount - 1, rungs.length - 1)];
1615
+ }
1534
1616
  /**
1535
1617
  * Unified recovery for the recoverable "Request too large (max 32MB)" error.
1536
1618
  * Reached from two backends that surface the SAME error differently:
@@ -1540,21 +1622,26 @@ class AgentRunner extends events_1.EventEmitter {
1540
1622
  * (is_error + "Request too large (max"); the long-lived process otherwise
1541
1623
  * stays alive and rejects every subsequent turn forever (Bug B).
1542
1624
  *
1543
- * Each consecutive recovery shrinks the history re-injected on the next spawn
1544
- * (TOO_LARGE_HISTORY_LADDER: 50→40→30→20→10→0) so a pathological context drops
1545
- * under the 32MB ceiling. The respawn happens on the user's NEXT message (no
1546
- * auto-loop), and the counter resets on the next successful result. Once even a
1547
- * zero-history spawn still trips 32MB, stop escalating and ask the user to
1548
- * /clear rather than climb the ladder again.
1625
+ * Each consecutive recovery shrinks the history re-injected on the next spawn,
1626
+ * stepping down the TOO_LARGE_HISTORY_LADDER rungs strictly below the configured
1627
+ * cap (default 50 40→30→20→10→0), so a pathological context drops under the
1628
+ * 32MB ceiling. The respawn happens on the user's NEXT message (no auto-loop),
1629
+ * and the counter resets on the next successful result. Once even a zero-history
1630
+ * spawn still trips 32MB, stop escalating and ask the user to /clear rather than
1631
+ * climb the ladder again.
1549
1632
  */
1550
1633
  handleRequestTooLarge(mapKey, proc) {
1551
1634
  proc.setProcessing(false);
1635
+ // Recovery steps through the ladder rungs strictly below the configured cap;
1636
+ // the number of those rungs is how many shrink attempts exist before even the
1637
+ // smallest (0-history) spawn has been tried and still trips 32MB.
1638
+ const configuredMax = (0, process_1.resolveMaxHistoryMessages)(this.agentConfig.history?.maxHistoryMessages, this.gatewayConfig.gateway.history?.maxHistoryMessages);
1639
+ const stepCount = this.recoveryRungs(configuredMax).length; // shrink attempts available
1552
1640
  const count = (this.tooLargeRecoveries.get(mapKey) ?? 0) + 1;
1553
- const lastRung = TOO_LARGE_HISTORY_LADDER.length - 1; // index of the 0-history rung
1554
- if (count > lastRung) {
1555
- // Even the zero-history spawn tripped 32MB context can't shrink further.
1556
- // Pin the counter at the last rung and always surface the /clear next step.
1557
- this.tooLargeRecoveries.set(mapKey, lastRung);
1641
+ if (count > stepCount) {
1642
+ // Even the smallest rung tripped 32MB — context can't shrink further.
1643
+ // Pin the counter at the last step and always surface the /clear next step.
1644
+ this.tooLargeRecoveries.set(mapKey, stepCount);
1558
1645
  this.logger.error('Request too large persists with zero re-injected history', { mapKey, count });
1559
1646
  this.writeAutoForward(mapKey, '⚠️ ยังเกิน 32MB แม้จะล้าง context จนว่างแล้ว — พิมพ์ /clear เพื่อเริ่มเซสชันใหม่ หรือ /restart ค่ะ');
1560
1647
  // Restart ONCE to clear the wedged process the first time the ladder is
@@ -1567,7 +1654,7 @@ class AgentRunner extends events_1.EventEmitter {
1567
1654
  }
1568
1655
  this.tooLargeRecoveries.set(mapKey, count);
1569
1656
  this.logger.warn('Request too large (32MB) — restarting with reduced history', {
1570
- mapKey, attempt: count, nextHistoryLimit: TOO_LARGE_HISTORY_LADDER[count],
1657
+ mapKey, attempt: count, nextHistoryLimit: this.spawnHistoryLimit(configuredMax, count),
1571
1658
  });
1572
1659
  // Ordering matters and makes the notice delivery race-free: writeAutoForward
1573
1660
  // persists the `.forward` file synchronously HERE, before restartProcess()
@@ -1986,9 +2073,20 @@ class AgentRunner extends events_1.EventEmitter {
1986
2073
  }
1987
2074
  // Build channel XML with image_path attribute (like Telegram) for first image
1988
2075
  const imageAttr = effectiveImagePaths.length ? ` image_path="${AgentRunner.escapeXmlAttr(effectiveImagePaths[0])}"` : '';
2076
+ const imageParamsNote = opts.imageParams ? AgentRunner.buildImageParamsNote(opts.imageParams) : '';
2077
+ // Persist the composer image options to session meta so the web can restore the
2078
+ // selection on reload (SessionMeta.imageConfig). Only when the send carries them
2079
+ // (the web sends image_params on first-set/change), so this holds the latest.
2080
+ // Channel is 'api' here (api sessions live under api-<chatId>). Best-effort.
2081
+ if (opts.imageParams) {
2082
+ this.sessionStore
2083
+ .updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig: opts.imageParams }, 'api')
2084
+ .catch(() => { });
2085
+ }
1989
2086
  const channelXml = `<channel source="api" chat_id="${chatId}" session_id="${sessionId}" ts="${new Date().toISOString()}"${imageAttr}>\n` +
1990
2087
  `${message}\n\n` +
1991
2088
  `${systemNote}` +
2089
+ `${imageParamsNote}` +
1992
2090
  `</channel>` +
1993
2091
  (skillInvocation ? `\n${(0, skills_1.formatSkillContext)(skillInvocation)}` : '');
1994
2092
  return new Promise((resolve, reject) => {
@@ -2285,9 +2383,19 @@ class AgentRunner extends events_1.EventEmitter {
2285
2383
  }
2286
2384
  // Build channel XML with image_path attribute (like Telegram) for first image
2287
2385
  const imageAttrStream = effectiveImagePathsStream.length ? ` image_path="${AgentRunner.escapeXmlAttr(effectiveImagePathsStream[0])}"` : '';
2386
+ const imageParamsNoteStream = opts.imageParams ? AgentRunner.buildImageParamsNote(opts.imageParams) : '';
2387
+ // Persist composer image config to session meta (SessionMeta.imageConfig) so the
2388
+ // web restores the selection on reload. This is the streaming path the web uses.
2389
+ // Channel 'api' — api sessions live under api-<chatId>. Best-effort.
2390
+ if (opts.imageParams) {
2391
+ this.sessionStore
2392
+ .updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig: opts.imageParams }, 'api')
2393
+ .catch(() => { });
2394
+ }
2288
2395
  const channelXml = `<channel source="api" chat_id="${chatId}" session_id="${sessionId}" ts="${new Date().toISOString()}"${imageAttrStream}>\n` +
2289
2396
  `${message}\n\n` +
2290
2397
  systemNote +
2398
+ imageParamsNoteStream +
2291
2399
  `</channel>` +
2292
2400
  (skillInvocationStream ? `\n${(0, skills_1.formatSkillContext)(skillInvocationStream)}` : '');
2293
2401
  session.setProcessing(true);