@0xmaxma/claude-gateway 1.5.8 → 1.6.1

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.
Files changed (65) hide show
  1. package/README.md +22 -0
  2. package/dist/agent/runner.d.ts +30 -0
  3. package/dist/agent/runner.d.ts.map +1 -1
  4. package/dist/agent/runner.js +223 -16
  5. package/dist/agent/runner.js.map +1 -1
  6. package/dist/api/apps-router.d.ts.map +1 -1
  7. package/dist/api/apps-router.js +7 -2
  8. package/dist/api/apps-router.js.map +1 -1
  9. package/dist/api/gateway-router.d.ts.map +1 -1
  10. package/dist/api/gateway-router.js +27 -0
  11. package/dist/api/gateway-router.js.map +1 -1
  12. package/dist/api/line-webhook-router.d.ts.map +1 -1
  13. package/dist/api/line-webhook-router.js +68 -0
  14. package/dist/api/line-webhook-router.js.map +1 -1
  15. package/dist/api/router.d.ts +2 -0
  16. package/dist/api/router.d.ts.map +1 -1
  17. package/dist/api/router.js +59 -3
  18. package/dist/api/router.js.map +1 -1
  19. package/dist/api/share-router.d.ts +11 -0
  20. package/dist/api/share-router.d.ts.map +1 -0
  21. package/dist/api/share-router.js +402 -0
  22. package/dist/api/share-router.js.map +1 -0
  23. package/dist/apps/installer.d.ts +65 -0
  24. package/dist/apps/installer.d.ts.map +1 -1
  25. package/dist/apps/installer.js +171 -4
  26. package/dist/apps/installer.js.map +1 -1
  27. package/dist/config/loader.d.ts.map +1 -1
  28. package/dist/config/loader.js +9 -0
  29. package/dist/config/loader.js.map +1 -1
  30. package/dist/config/public-url.d.ts +11 -0
  31. package/dist/config/public-url.d.ts.map +1 -0
  32. package/dist/config/public-url.js +41 -0
  33. package/dist/config/public-url.js.map +1 -0
  34. package/dist/config/watcher.d.ts.map +1 -1
  35. package/dist/config/watcher.js +1 -0
  36. package/dist/config/watcher.js.map +1 -1
  37. package/dist/history/db.d.ts +14 -0
  38. package/dist/history/db.d.ts.map +1 -1
  39. package/dist/history/db.js +48 -4
  40. package/dist/history/db.js.map +1 -1
  41. package/dist/history/types.d.ts +5 -0
  42. package/dist/history/types.d.ts.map +1 -1
  43. package/dist/session/process.d.ts.map +1 -1
  44. package/dist/session/process.js +17 -1
  45. package/dist/session/process.js.map +1 -1
  46. package/dist/session/store.d.ts.map +1 -1
  47. package/dist/session/store.js +9 -1
  48. package/dist/session/store.js.map +1 -1
  49. package/dist/share/session-image-catalog.d.ts +22 -0
  50. package/dist/share/session-image-catalog.d.ts.map +1 -0
  51. package/dist/share/session-image-catalog.js +151 -0
  52. package/dist/share/session-image-catalog.js.map +1 -0
  53. package/dist/share/share-store.d.ts +137 -0
  54. package/dist/share/share-store.d.ts.map +1 -0
  55. package/dist/share/share-store.js +340 -0
  56. package/dist/share/share-store.js.map +1 -0
  57. package/dist/types.d.ts +17 -7
  58. package/dist/types.d.ts.map +1 -1
  59. package/mcp/instructions.ts +1 -0
  60. package/mcp/server.ts +2 -0
  61. package/mcp/tools/image/module.ts +240 -26
  62. package/mcp/tools/line/module.ts +186 -0
  63. package/mcp/tools/share-image/module.ts +120 -0
  64. package/mcp/tools/shared/share-client.ts +197 -0
  65. package/package.json +3 -1
package/README.md CHANGED
@@ -214,6 +214,28 @@ Config lives at `~/.claude-gateway/config.json` (or set `GATEWAY_CONFIG` env var
214
214
  }
215
215
  ```
216
216
 
217
+ ### `gateway.publicUrl` (optional)
218
+
219
+ The externally reachable gateway base URL. Set it manually to enable short-lived
220
+ public image shares used by `generate_image` reference edits and `share_image`.
221
+ The URL must end in `/gateway`; changing it requires a gateway restart.
222
+
223
+ ```json
224
+ {
225
+ "gateway": {
226
+ "publicUrl": "https://vm.example.com/gateway"
227
+ }
228
+ }
229
+ ```
230
+
231
+ Minted share URLs have the stable form
232
+ `https://vm.example.com/gateway/shared/TOKEN`. When `publicUrl` is set the mint
233
+ response includes this ready-built `url`; when it is unset the response still
234
+ returns the `token` (the share endpoint stays enabled) and callers with their own
235
+ public base — e.g. LINE, which derives its host from the inbound webhook — build
236
+ `<base>/shared/<token>` themselves. HTTP is accepted only for local development
237
+ hosts such as `http://host.docker.internal:10850/gateway`.
238
+
217
239
  ### `session`
218
240
 
219
241
  | Field | Default | Description |
@@ -149,6 +149,23 @@ export declare class AgentRunner extends EventEmitter {
149
149
  * options are present.
150
150
  */
151
151
  private static buildImageParamsNote;
152
+ /**
153
+ * The durable slice of the composer image options — everything except
154
+ * `image_refs`, which is a per-turn explicit selection (#73) and must never be
155
+ * restored as sticky session config. Returns undefined when nothing is durable.
156
+ */
157
+ private static durableImageConfig;
158
+ /**
159
+ * The web builds image_ref/image_refs from the paths the upload endpoint
160
+ * returned — the ui-upload STAGING paths. promoteUiUploads then MOVES those
161
+ * files into per-session storage, so a note or history row built from the
162
+ * raw params would hand out dead paths (the agent's generate_image call
163
+ * fails image_ref_not_found and has to guess its way to the real file).
164
+ * Substitute every ref that matches a promoted staging path with its new
165
+ * session path; refs that were never staged (catalog refs, artifact:<id>)
166
+ * pass through untouched. (#74)
167
+ */
168
+ static remapImageParamsRefs(p: ImageParams | undefined, stagedPaths: readonly string[] | undefined, promotedPaths: readonly string[] | undefined): ImageParams | undefined;
152
169
  private static buildChannelXml;
153
170
  private getOrSpawnSession;
154
171
  private spawnSession;
@@ -281,6 +298,10 @@ export declare class AgentRunner extends EventEmitter {
281
298
  startLineReply(): void;
282
299
  stopLineReply(): void;
283
300
  getAgentConfig(): AgentConfig;
301
+ /** Configured gateway.publicUrl, or undefined. The trusted source for the
302
+ * gateway's own public origin — preferred over request-derived hosts, which
303
+ * a client can spoof via X-Forwarded-Host. */
304
+ getGatewayPublicUrl(): string | undefined;
284
305
  startTelegramReceiver(): void;
285
306
  stopTelegramReceiver(): void;
286
307
  startDiscordReceiver(): void;
@@ -357,6 +378,15 @@ export declare class AgentRunner extends EventEmitter {
357
378
  * Pop and return buffered attachment paths for a session, then clear the buffer.
358
379
  * Converts absolute paths to relative media URLs for the API response.
359
380
  */
381
+ /**
382
+ * Close a turn that died without a result (#75): drain any attachments the
383
+ * turn already registered (the files exist on disk — deliver them rather
384
+ * than leak them into the NEXT turn's reply) and record a terminal
385
+ * assistant row so history never ends on a dangling user message.
386
+ * System-initiated turns (skipUserMessage) have nothing dangling to close —
387
+ * only the buffer is drained.
388
+ */
389
+ private persistFailedApiTurn;
360
390
  popApiAttachments(sessionId: string): ApiAttachment[];
361
391
  private evictApiSessionMapping;
362
392
  getAgentsBaseDir(): string;
@@ -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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"}
@@ -94,6 +94,11 @@ const PROTECTED_WORKSPACE_FILES = [
94
94
  'IDENTITY.md', 'USER.md', 'HEARTBEAT.md',
95
95
  ];
96
96
  const MAX_API_IMAGES = 5;
97
+ /** Extra time an api turn may keep running AFTER its soft timeout already
98
+ * answered the caller (#75). The soft timeout only abandons the WAIT — the
99
+ * CLI turn is still producing a result that must reach history, exactly like
100
+ * the client-disconnect path. This cap bounds a genuinely hung turn. */
101
+ const API_TIMEOUT_HARD_CAP_EXTRA_MS = 600000;
97
102
  // Hard timeout for the one-shot local `claude -p` triage during recovery
98
103
  // (Epic #195, Phase 3b). A slow/hung triage collapses to a safe notify-only.
99
104
  const RECOVERY_TRIAGE_TIMEOUT_MS = 15000;
@@ -954,13 +959,72 @@ class AgentRunner extends events_1.EventEmitter {
954
959
  typeof p.n === 'number' ? `n="${p.n}"` : '',
955
960
  p.image_ref ? `image_ref="${AgentRunner.escapeXmlAttr(p.image_ref)}"` : '',
956
961
  ].filter(Boolean);
957
- if (!attrs.length)
962
+ const refs = (p.image_refs ?? []).filter((r) => typeof r === 'string' && r.trim().length > 0);
963
+ if (!attrs.length && !refs.length)
958
964
  return '';
959
- return (`<image-params ${attrs.join(' ')} />\n` +
965
+ if (!refs.length) {
966
+ return (`<image-params ${attrs.join(' ')} />\n` +
967
+ `The user selected the image-generation options above in the composer. When the request ` +
968
+ `involves creating or editing an image, call the generate_image tool (action="generate") ` +
969
+ `using these values (pass image_ref as the "image" argument for image-to-image), then ` +
970
+ `deliver the returned image with your reply tool.\n`);
971
+ }
972
+ // Explicitly selected reference images (#73). Rendered as nested elements so a
973
+ // ref containing separators (commas, quotes) stays unambiguous.
974
+ const openTag = attrs.length ? `<image-params ${attrs.join(' ')}>` : '<image-params>';
975
+ const refLines = refs
976
+ .map((r, i) => ` <ref index="${i + 1}">${AgentRunner.escapeXmlAttr(r.trim())}</ref>`)
977
+ .join('\n');
978
+ const passInstruction = refs.length === 1
979
+ ? `Pass that single ref as the "image" argument of generate_image.`
980
+ : `Pass all ${refs.length} refs as the "images" argument of generate_image, in the same order.`;
981
+ return (`${openTag}\n${refLines}\n</image-params>\n` +
960
982
  `The user selected the image-generation options above in the composer. When the request ` +
961
983
  `involves creating or editing an image, call the generate_image tool (action="generate") ` +
962
- `using these values (pass image_ref as the "image" argument for image-to-image), then ` +
963
- `deliver the returned image with your reply tool.\n`);
984
+ `using these values, then deliver the returned image with your reply tool.\n` +
985
+ `The user explicitly SELECTED the reference image(s) listed above in the composer, in this order. ` +
986
+ `${passInstruction} Do NOT reinterpret which images they are, do NOT call list_refs to ` +
987
+ `second-guess an explicit selection, and do not drop any of them. ` +
988
+ `Do NOT open or Read the referenced files first — the image model receives the actual files; ` +
989
+ `reading them wastes minutes and can push the request past its timeout. Go straight to generate_image.\n`);
990
+ }
991
+ /**
992
+ * The durable slice of the composer image options — everything except
993
+ * `image_refs`, which is a per-turn explicit selection (#73) and must never be
994
+ * restored as sticky session config. Returns undefined when nothing is durable.
995
+ */
996
+ static durableImageConfig(p) {
997
+ const { image_refs: _refs, ...durable } = p;
998
+ return Object.keys(durable).length ? durable : undefined;
999
+ }
1000
+ /**
1001
+ * The web builds image_ref/image_refs from the paths the upload endpoint
1002
+ * returned — the ui-upload STAGING paths. promoteUiUploads then MOVES those
1003
+ * files into per-session storage, so a note or history row built from the
1004
+ * raw params would hand out dead paths (the agent's generate_image call
1005
+ * fails image_ref_not_found and has to guess its way to the real file).
1006
+ * Substitute every ref that matches a promoted staging path with its new
1007
+ * session path; refs that were never staged (catalog refs, artifact:<id>)
1008
+ * pass through untouched. (#74)
1009
+ */
1010
+ static remapImageParamsRefs(p, stagedPaths, promotedPaths) {
1011
+ if (!p || !stagedPaths?.length || !promotedPaths?.length)
1012
+ return p;
1013
+ const map = new Map();
1014
+ for (let i = 0; i < Math.min(stagedPaths.length, promotedPaths.length); i++) {
1015
+ const from = stagedPaths[i];
1016
+ const to = promotedPaths[i];
1017
+ if (from !== to)
1018
+ map.set(from, to);
1019
+ }
1020
+ if (!map.size)
1021
+ return p;
1022
+ const remap = (r) => map.get(r) ?? r;
1023
+ return {
1024
+ ...p,
1025
+ ...(p.image_ref ? { image_ref: remap(p.image_ref) } : {}),
1026
+ ...(p.image_refs?.length ? { image_refs: p.image_refs.map(remap) } : {}),
1027
+ };
964
1028
  }
965
1029
  static buildChannelXml(params) {
966
1030
  const meta = params.meta ?? {};
@@ -1117,6 +1181,11 @@ class AgentRunner extends events_1.EventEmitter {
1117
1181
  // Auto-forward result text to channel if agent didn't call reply tool.
1118
1182
  let replyCalled = false;
1119
1183
  let replyToolUseId = null; // track id to detect failed tool calls
1184
+ // line_image tool_use blocks re-appear across cumulative `assistant` stream
1185
+ // snapshots (--include-partial-messages), so dedupe history inserts by the
1186
+ // block id — otherwise one sent image lands in the transcript N times and
1187
+ // skews the session image catalog's "image N" ordinal.
1188
+ const seenLineImageIds = new Set();
1120
1189
  // Set when an interactive-menu prompt was rendered to the channel this turn,
1121
1190
  // so the result's plain-text auto-forward is skipped (no duplicate message).
1122
1191
  let menuSentThisTurn = false;
@@ -1141,6 +1210,34 @@ class AgentRunner extends events_1.EventEmitter {
1141
1210
  const msg = obj['message'];
1142
1211
  if (Array.isArray(msg?.content)) {
1143
1212
  for (const block of msg.content) {
1213
+ // LINE sends images through its own line_image tool (not the reply
1214
+ // tool's `files`), so capture those sends into history too — the
1215
+ // web transcript and the session image catalog key off mediaFiles.
1216
+ if (block.type === 'tool_use' && block.name === 'mcp__gateway__line_image') {
1217
+ const lineBlockId = block['id'];
1218
+ const dedupeKey = typeof lineBlockId === 'string' ? lineBlockId : '';
1219
+ // Skip if this tool_use block was already persisted this turn
1220
+ // (cumulative assistant snapshots re-emit completed blocks).
1221
+ if (dedupeKey && seenLineImageIds.has(dedupeKey))
1222
+ continue;
1223
+ const imgPath = typeof block.input?.['image'] === 'string' ? block.input['image'] : '';
1224
+ const mediaRootLine = path.join(this.agentsBaseDir, this.agentConfig.id, 'media') + path.sep;
1225
+ const lineMedia = toRelMediaFiles(imgPath ? [imgPath] : [], mediaRootLine);
1226
+ if (lineMedia.length) {
1227
+ if (dedupeKey)
1228
+ seenLineImageIds.add(dedupeKey);
1229
+ const channelSrcLine = this.channelSourceMap.get(mapKey) ?? 'line';
1230
+ this.historyDb.insertMessage({
1231
+ chatId: `${channelSrcLine}-${mapKey}`,
1232
+ sessionId: actualSessionId,
1233
+ source: channelSrcLine,
1234
+ role: 'assistant',
1235
+ content: '',
1236
+ mediaFiles: lineMedia,
1237
+ ts: Date.now(),
1238
+ });
1239
+ }
1240
+ }
1144
1241
  if (block.type === 'tool_use' && block.name === replyToolName && !replyCalled) {
1145
1242
  replyCalled = true;
1146
1243
  replyToolUseId = block['id'] ?? null;
@@ -1333,6 +1430,7 @@ class AgentRunner extends events_1.EventEmitter {
1333
1430
  }
1334
1431
  replyCalled = false; // reset for next turn
1335
1432
  replyToolUseId = null;
1433
+ seenLineImageIds.clear();
1336
1434
  menuSentThisTurn = false;
1337
1435
  menuPromptTextThisTurn = '';
1338
1436
  // In pty-shell mode, a `result` fires after every Claude API sub-turn
@@ -1947,6 +2045,12 @@ class AgentRunner extends events_1.EventEmitter {
1947
2045
  getAgentConfig() {
1948
2046
  return { ...this.agentConfig };
1949
2047
  }
2048
+ /** Configured gateway.publicUrl, or undefined. The trusted source for the
2049
+ * gateway's own public origin — preferred over request-derived hosts, which
2050
+ * a client can spoof via X-Forwarded-Host. */
2051
+ getGatewayPublicUrl() {
2052
+ return this.gatewayConfig.gateway.publicUrl;
2053
+ }
1950
2054
  startTelegramReceiver() {
1951
2055
  if (this.receiver?.isRunning())
1952
2056
  return;
@@ -2089,6 +2193,9 @@ class AgentRunner extends events_1.EventEmitter {
2089
2193
  const finalMediaFiles = opts.mediaFiles?.length
2090
2194
  ? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger)
2091
2195
  : undefined;
2196
+ // Refs built from staging paths must follow the files to their promoted
2197
+ // location — see remapImageParamsRefs (#74).
2198
+ const imageParams = AgentRunner.remapImageParamsRefs(opts.imageParams, opts.mediaFiles, finalMediaFiles);
2092
2199
  // Resolve media files to absolute paths for file-path based image passing
2093
2200
  // (same pattern as Telegram — Claude Code reads files via Read tool instead of base64 inline)
2094
2201
  const imagePaths = finalMediaFiles?.length ? this.resolveMediaPaths(finalMediaFiles) : [];
@@ -2112,10 +2219,18 @@ class AgentRunner extends events_1.EventEmitter {
2112
2219
  role: 'user',
2113
2220
  content: message,
2114
2221
  mediaFiles: finalMediaFiles?.length ? finalMediaFiles : undefined,
2222
+ // Display-only (#74): lets the UI show which earlier images this turn
2223
+ // referenced after a reload. The generation path reads the refs from the
2224
+ // image-params note, never from here.
2225
+ imageRefs: imageParams?.image_refs?.length ? imageParams.image_refs : undefined,
2115
2226
  ts: apiUserTs,
2116
2227
  });
2117
2228
  }
2118
2229
  this.pendingApiSessions.add(sessionId);
2230
+ // A previous turn that died (hard timeout, crash) may have left attachments
2231
+ // buffered under this session — they belong to THAT turn; never let them
2232
+ // ride along on this one (#75).
2233
+ this.pendingApiAttachments.delete(sessionId);
2119
2234
  session.touch();
2120
2235
  // Image paths only work when allowTools:true — Claude needs the Read tool to access them
2121
2236
  const allowTools = opts.allowTools ?? false;
@@ -2135,14 +2250,16 @@ class AgentRunner extends events_1.EventEmitter {
2135
2250
  }
2136
2251
  // Build channel XML with image_path attribute (like Telegram) for first image
2137
2252
  const imageAttr = effectiveImagePaths.length ? ` image_path="${AgentRunner.escapeXmlAttr(effectiveImagePaths[0])}"` : '';
2138
- const imageParamsNote = opts.imageParams ? AgentRunner.buildImageParamsNote(opts.imageParams) : '';
2253
+ const imageParamsNote = imageParams ? AgentRunner.buildImageParamsNote(imageParams) : '';
2139
2254
  // Persist the composer image options to session meta so the web can restore the
2140
2255
  // selection on reload (SessionMeta.imageConfig). Only when the send carries them
2141
2256
  // (the web sends image_params on first-set/change), so this holds the latest.
2257
+ // image_refs are per-turn and deliberately excluded (#73).
2142
2258
  // Channel is 'api' here (api sessions live under api-<chatId>). Best-effort.
2143
- if (opts.imageParams) {
2259
+ const durableImageConfig = imageParams ? AgentRunner.durableImageConfig(imageParams) : undefined;
2260
+ if (durableImageConfig) {
2144
2261
  this.sessionStore
2145
- .updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig: opts.imageParams }, 'api')
2262
+ .updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig: durableImageConfig }, 'api')
2146
2263
  .catch(() => { });
2147
2264
  }
2148
2265
  const channelXml = `<channel source="api" chat_id="${chatId}" session_id="${sessionId}" ts="${new Date().toISOString()}"${imageAttr}>\n` +
@@ -2183,11 +2300,17 @@ class AgentRunner extends events_1.EventEmitter {
2183
2300
  resolve({ text: result.trim(), attachments });
2184
2301
  };
2185
2302
  const fail = (err) => {
2303
+ // Terminal close for a turn that produced no result (#75): record it so
2304
+ // history does not end on a dangling user message, and drain the
2305
+ // attachment buffer so nothing leaks into the next turn.
2306
+ this.persistFailedApiTurn(chatId, sessionId, err, opts.skipUserMessage);
2186
2307
  cleanup();
2187
- reject(err);
2308
+ reject(err); // no-op when the soft timeout already rejected
2188
2309
  };
2189
2310
  const cleanup = () => {
2190
2311
  clearTimeout(globalTimer);
2312
+ if (hardCapTimer)
2313
+ clearTimeout(hardCapTimer);
2191
2314
  if (quietTimer)
2192
2315
  clearTimeout(quietTimer);
2193
2316
  session.off('output', onOutput);
@@ -2239,9 +2362,17 @@ class AgentRunner extends events_1.EventEmitter {
2239
2362
  /* non-JSON stdout line */
2240
2363
  }
2241
2364
  };
2365
+ // Soft timeout (#75): unblock the caller now, but KEEP the output listener
2366
+ // attached — a turn finishing moments late must still persist its result
2367
+ // and attachments, mirroring the client-disconnect path. The hard cap
2368
+ // bounds a genuinely hung turn.
2369
+ let hardCapTimer;
2242
2370
  const globalTimer = setTimeout(() => {
2243
- session.setProcessing(false);
2244
- fail(Object.assign(new Error('Agent response timeout'), { code: 'TIMEOUT' }));
2371
+ reject(Object.assign(new Error('Agent response timeout'), { code: 'TIMEOUT' }));
2372
+ hardCapTimer = setTimeout(() => {
2373
+ session.setProcessing(false);
2374
+ fail(Object.assign(new Error('Agent response timed out.'), { code: 'TIMEOUT' }));
2375
+ }, API_TIMEOUT_HARD_CAP_EXTRA_MS);
2245
2376
  }, opts.timeoutMs);
2246
2377
  session.on('output', onOutput);
2247
2378
  session.setProcessing(true);
@@ -2273,6 +2404,9 @@ class AgentRunner extends events_1.EventEmitter {
2273
2404
  const finalMediaFilesStream = opts.mediaFiles?.length
2274
2405
  ? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger)
2275
2406
  : undefined;
2407
+ // Refs built from staging paths must follow the files to their promoted
2408
+ // location — see remapImageParamsRefs (#74).
2409
+ const imageParamsStream = AgentRunner.remapImageParamsRefs(opts.imageParams, opts.mediaFiles, finalMediaFilesStream);
2276
2410
  // Resolve media files to absolute paths for file-path based image passing
2277
2411
  const imagePathsStream = finalMediaFilesStream?.length ? this.resolveMediaPaths(finalMediaFilesStream) : [];
2278
2412
  if (!opts.skipUserMessage) {
@@ -2291,14 +2425,32 @@ class AgentRunner extends events_1.EventEmitter {
2291
2425
  role: 'user',
2292
2426
  content: message,
2293
2427
  mediaFiles: finalMediaFilesStream?.length ? finalMediaFilesStream : undefined,
2428
+ // Display-only (#74): lets the UI show which earlier images this turn
2429
+ // referenced after a reload. The generation path reads the refs from the
2430
+ // image-params note, never from here.
2431
+ imageRefs: imageParamsStream?.image_refs?.length ? imageParamsStream.image_refs : undefined,
2294
2432
  ts: streamUserTs,
2295
2433
  });
2296
2434
  }
2297
2435
  this.pendingApiSessions.add(sessionId);
2436
+ // A previous turn that died (hard timeout, crash) may have left attachments
2437
+ // buffered under this session — they belong to THAT turn; never let them
2438
+ // ride along on this one (#75).
2439
+ this.pendingApiAttachments.delete(sessionId);
2298
2440
  session.touch();
2299
2441
  const buffer = [];
2300
2442
  let settled = false;
2301
2443
  let clientGone = false;
2444
+ // onError must fire at most once per turn. The soft timeout notifies the
2445
+ // client directly (below) WITHOUT setting `settled`, so the later hard-cap
2446
+ // fail() would otherwise call onError a second time.
2447
+ let errorNotified = false;
2448
+ const notifyError = (err) => {
2449
+ if (errorNotified)
2450
+ return;
2451
+ errorNotified = true;
2452
+ callbacks.onError(err);
2453
+ };
2302
2454
  // Track partial message text for delta computation (--include-partial-messages)
2303
2455
  let lastPartialText = '';
2304
2456
  // Accumulate tool_use blocks from stream_event (content_block_start → delta → stop)
@@ -2338,10 +2490,17 @@ class AgentRunner extends events_1.EventEmitter {
2338
2490
  return;
2339
2491
  settled = true;
2340
2492
  cleanup();
2341
- callbacks.onError(err);
2493
+ // Terminal close for a turn that produced no result (#75): record it so
2494
+ // history does not end on a dangling user message (the web reads that
2495
+ // state as "still thinking" and spins forever), and drain the attachment
2496
+ // buffer so nothing leaks into the next turn.
2497
+ this.persistFailedApiTurn(chatId, sessionId, err, opts.skipUserMessage);
2498
+ notifyError(err);
2342
2499
  };
2343
2500
  const cleanup = () => {
2344
2501
  clearTimeout(globalTimer);
2502
+ if (hardCapTimer)
2503
+ clearTimeout(hardCapTimer);
2345
2504
  session.off('output', onOutput);
2346
2505
  this.pendingApiSessions.delete(sessionId);
2347
2506
  };
@@ -2422,9 +2581,26 @@ class AgentRunner extends events_1.EventEmitter {
2422
2581
  /* non-JSON stdout line */
2423
2582
  }
2424
2583
  };
2584
+ // Soft timeout (#75): tell the SSE client now, but KEEP the output listener
2585
+ // attached — exactly like onClientDisconnect — so a turn that finishes a few
2586
+ // seconds past the budget still lands in history with its attachments
2587
+ // (observed: 304s image turn vs 300s budget → image generated, reply lost,
2588
+ // spinner stuck). The hard cap bounds a genuinely hung turn.
2589
+ let hardCapTimer;
2425
2590
  const globalTimer = setTimeout(() => {
2426
- session.setProcessing(false);
2427
- fail(Object.assign(new Error('Agent response timeout'), { code: 'TIMEOUT' }));
2591
+ clientGone = true;
2592
+ try {
2593
+ notifyError(Object.assign(new Error('Agent response timeout'), { code: 'TIMEOUT' }));
2594
+ }
2595
+ catch { /* client already gone */ }
2596
+ // The session stays in-flight (pendingApiSessions) until the hard cap so a
2597
+ // retry on the SAME session_id gets a 409 CONFLICT rather than interleaving
2598
+ // with the subprocess that is still processing this turn. This lock is
2599
+ // intentionally held for the whole soft→hard window (see API.md §409).
2600
+ hardCapTimer = setTimeout(() => {
2601
+ session.setProcessing(false);
2602
+ fail(Object.assign(new Error('Agent response timed out.'), { code: 'TIMEOUT' }));
2603
+ }, API_TIMEOUT_HARD_CAP_EXTRA_MS);
2428
2604
  }, opts.timeoutMs);
2429
2605
  session.on('output', onOutput);
2430
2606
  // Image paths only work when allowTools:true — Claude needs the Read tool to access them
@@ -2445,13 +2621,17 @@ class AgentRunner extends events_1.EventEmitter {
2445
2621
  }
2446
2622
  // Build channel XML with image_path attribute (like Telegram) for first image
2447
2623
  const imageAttrStream = effectiveImagePathsStream.length ? ` image_path="${AgentRunner.escapeXmlAttr(effectiveImagePathsStream[0])}"` : '';
2448
- const imageParamsNoteStream = opts.imageParams ? AgentRunner.buildImageParamsNote(opts.imageParams) : '';
2624
+ const imageParamsNoteStream = imageParamsStream ? AgentRunner.buildImageParamsNote(imageParamsStream) : '';
2449
2625
  // Persist composer image config to session meta (SessionMeta.imageConfig) so the
2450
2626
  // web restores the selection on reload. This is the streaming path the web uses.
2627
+ // image_refs are per-turn and deliberately excluded (#73).
2451
2628
  // Channel 'api' — api sessions live under api-<chatId>. Best-effort.
2452
- if (opts.imageParams) {
2629
+ const durableImageConfigStream = imageParamsStream
2630
+ ? AgentRunner.durableImageConfig(imageParamsStream)
2631
+ : undefined;
2632
+ if (durableImageConfigStream) {
2453
2633
  this.sessionStore
2454
- .updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig: opts.imageParams }, 'api')
2634
+ .updateSessionMeta(this.agentConfig.id, chatId, sessionId, { imageConfig: durableImageConfigStream }, 'api')
2455
2635
  .catch(() => { });
2456
2636
  }
2457
2637
  const channelXml = `<channel source="api" chat_id="${chatId}" session_id="${sessionId}" ts="${new Date().toISOString()}"${imageAttrStream}>\n` +
@@ -2483,6 +2663,33 @@ class AgentRunner extends events_1.EventEmitter {
2483
2663
  * Pop and return buffered attachment paths for a session, then clear the buffer.
2484
2664
  * Converts absolute paths to relative media URLs for the API response.
2485
2665
  */
2666
+ /**
2667
+ * Close a turn that died without a result (#75): drain any attachments the
2668
+ * turn already registered (the files exist on disk — deliver them rather
2669
+ * than leak them into the NEXT turn's reply) and record a terminal
2670
+ * assistant row so history never ends on a dangling user message.
2671
+ * System-initiated turns (skipUserMessage) have nothing dangling to close —
2672
+ * only the buffer is drained.
2673
+ */
2674
+ persistFailedApiTurn(chatId, sessionId, err, skipUserMessage) {
2675
+ const attachments = this.popApiAttachments(sessionId);
2676
+ if (skipUserMessage)
2677
+ return;
2678
+ const failTs = Date.now();
2679
+ const content = `\u26a0\ufe0f ${err.message}`;
2680
+ this.sessionStore
2681
+ .appendMessage(this.agentConfig.id, sessionId, { role: 'assistant', content, ts: failTs })
2682
+ .catch(() => { });
2683
+ this.historyDb.insertMessage({
2684
+ chatId: `api-${chatId}`,
2685
+ sessionId,
2686
+ source: 'api',
2687
+ role: 'assistant',
2688
+ content,
2689
+ mediaFiles: attachments.length ? attachments.map((a) => `media/${a.relPath}`) : undefined,
2690
+ ts: failTs,
2691
+ });
2692
+ }
2486
2693
  popApiAttachments(sessionId) {
2487
2694
  const paths = this.pendingApiAttachments.get(sessionId) ?? [];
2488
2695
  this.pendingApiAttachments.delete(sessionId);