@0xmaxma/claude-gateway 2.0.4 → 2.0.5

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 (228) hide show
  1. package/README.md +10 -1
  2. package/dist/agent/builtin-commands.d.ts +1 -1
  3. package/dist/agent/builtin-commands.d.ts.map +1 -1
  4. package/dist/agent/builtin-commands.js +3 -2
  5. package/dist/agent/builtin-commands.js.map +1 -1
  6. package/dist/agent/runner.d.ts +109 -9
  7. package/dist/agent/runner.d.ts.map +1 -1
  8. package/dist/agent/runner.js +148 -119
  9. package/dist/agent/runner.js.map +1 -1
  10. package/dist/agent/skill-learning/index.d.ts.map +1 -1
  11. package/dist/agent/skill-learning/index.js +27 -8
  12. package/dist/agent/skill-learning/index.js.map +1 -1
  13. package/dist/agent/skill-learning/reviewer.d.ts +3 -1
  14. package/dist/agent/skill-learning/reviewer.d.ts.map +1 -1
  15. package/dist/agent/skill-learning/reviewer.js +6 -2
  16. package/dist/agent/skill-learning/reviewer.js.map +1 -1
  17. package/dist/agent/turn-stream.d.ts +3 -1
  18. package/dist/agent/turn-stream.d.ts.map +1 -1
  19. package/dist/agent/turn-stream.js +8 -4
  20. package/dist/agent/turn-stream.js.map +1 -1
  21. package/dist/api/gateway-router.d.ts +2 -0
  22. package/dist/api/gateway-router.d.ts.map +1 -1
  23. package/dist/api/gateway-router.js +229 -12
  24. package/dist/api/gateway-router.js.map +1 -1
  25. package/dist/api/router.js +11 -11
  26. package/dist/api/router.js.map +1 -1
  27. package/dist/api/slack-webhook-router.js +1 -1
  28. package/dist/api/slack-webhook-router.js.map +1 -1
  29. package/dist/api/voice-router.d.ts +1 -0
  30. package/dist/api/voice-router.d.ts.map +1 -1
  31. package/dist/api/voice-router.js +129 -7
  32. package/dist/api/voice-router.js.map +1 -1
  33. package/dist/config/claude-settings.d.ts +14 -0
  34. package/dist/config/claude-settings.d.ts.map +1 -1
  35. package/dist/config/claude-settings.js +20 -0
  36. package/dist/config/claude-settings.js.map +1 -1
  37. package/dist/orchestration/agent-cli-session.d.ts +46 -0
  38. package/dist/orchestration/agent-cli-session.d.ts.map +1 -0
  39. package/dist/orchestration/agent-cli-session.js +121 -0
  40. package/dist/orchestration/agent-cli-session.js.map +1 -0
  41. package/dist/orchestration/agent-tool-schemas.d.ts +6 -0
  42. package/dist/orchestration/agent-tool-schemas.d.ts.map +1 -0
  43. package/dist/orchestration/agent-tool-schemas.js +23 -0
  44. package/dist/orchestration/agent-tool-schemas.js.map +1 -0
  45. package/dist/orchestration/bridge.d.ts +6 -3
  46. package/dist/orchestration/bridge.d.ts.map +1 -1
  47. package/dist/orchestration/bridge.js +31 -14
  48. package/dist/orchestration/bridge.js.map +1 -1
  49. package/dist/orchestration/browser-voice.d.ts +14 -0
  50. package/dist/orchestration/browser-voice.d.ts.map +1 -0
  51. package/dist/orchestration/browser-voice.js +30 -0
  52. package/dist/orchestration/browser-voice.js.map +1 -0
  53. package/dist/orchestration/context-footprint.d.ts +14 -0
  54. package/dist/orchestration/context-footprint.d.ts.map +1 -0
  55. package/dist/orchestration/context-footprint.js +57 -0
  56. package/dist/orchestration/context-footprint.js.map +1 -0
  57. package/dist/orchestration/dashboard-reader-worker.d.ts +2 -0
  58. package/dist/orchestration/dashboard-reader-worker.d.ts.map +1 -0
  59. package/dist/orchestration/dashboard-reader-worker.js +155 -0
  60. package/dist/orchestration/dashboard-reader-worker.js.map +1 -0
  61. package/dist/orchestration/dashboard-reader.d.ts +15 -0
  62. package/dist/orchestration/dashboard-reader.d.ts.map +1 -0
  63. package/dist/orchestration/dashboard-reader.js +87 -0
  64. package/dist/orchestration/dashboard-reader.js.map +1 -0
  65. package/dist/orchestration/decision-context.d.ts +15 -0
  66. package/dist/orchestration/decision-context.d.ts.map +1 -0
  67. package/dist/orchestration/decision-context.js +57 -0
  68. package/dist/orchestration/decision-context.js.map +1 -0
  69. package/dist/orchestration/display-stream.d.ts +21 -2
  70. package/dist/orchestration/display-stream.d.ts.map +1 -1
  71. package/dist/orchestration/display-stream.js +61 -8
  72. package/dist/orchestration/display-stream.js.map +1 -1
  73. package/dist/orchestration/inference-errors.d.ts.map +1 -1
  74. package/dist/orchestration/inference-errors.js +19 -21
  75. package/dist/orchestration/inference-errors.js.map +1 -1
  76. package/dist/orchestration/mutation-recovery.d.ts +12 -0
  77. package/dist/orchestration/mutation-recovery.d.ts.map +1 -0
  78. package/dist/orchestration/mutation-recovery.js +41 -0
  79. package/dist/orchestration/mutation-recovery.js.map +1 -0
  80. package/dist/orchestration/native-compact.d.ts +5 -0
  81. package/dist/orchestration/native-compact.d.ts.map +1 -0
  82. package/dist/orchestration/native-compact.js +29 -0
  83. package/dist/orchestration/native-compact.js.map +1 -0
  84. package/dist/orchestration/notification-mailbox.d.ts +3 -1
  85. package/dist/orchestration/notification-mailbox.d.ts.map +1 -1
  86. package/dist/orchestration/notification-mailbox.js +4 -2
  87. package/dist/orchestration/notification-mailbox.js.map +1 -1
  88. package/dist/orchestration/process-turn.d.ts +15 -2
  89. package/dist/orchestration/process-turn.d.ts.map +1 -1
  90. package/dist/orchestration/process-turn.js +78 -14
  91. package/dist/orchestration/process-turn.js.map +1 -1
  92. package/dist/orchestration/progress-review.d.ts +8 -18
  93. package/dist/orchestration/progress-review.d.ts.map +1 -1
  94. package/dist/orchestration/progress-review.js +12 -14
  95. package/dist/orchestration/progress-review.js.map +1 -1
  96. package/dist/orchestration/provider-message.d.ts +7 -0
  97. package/dist/orchestration/provider-message.d.ts.map +1 -0
  98. package/dist/orchestration/provider-message.js +63 -0
  99. package/dist/orchestration/provider-message.js.map +1 -0
  100. package/dist/orchestration/response-errors.d.ts +6 -0
  101. package/dist/orchestration/response-errors.d.ts.map +1 -0
  102. package/dist/orchestration/response-errors.js +88 -0
  103. package/dist/orchestration/response-errors.js.map +1 -0
  104. package/dist/orchestration/response-schema.d.ts +33 -0
  105. package/dist/orchestration/response-schema.d.ts.map +1 -0
  106. package/dist/orchestration/response-schema.js +61 -0
  107. package/dist/orchestration/response-schema.js.map +1 -0
  108. package/dist/orchestration/runtime.d.ts +67 -0
  109. package/dist/orchestration/runtime.d.ts.map +1 -1
  110. package/dist/orchestration/runtime.js +266 -62
  111. package/dist/orchestration/runtime.js.map +1 -1
  112. package/dist/orchestration/skills.d.ts.map +1 -1
  113. package/dist/orchestration/skills.js +3 -2
  114. package/dist/orchestration/skills.js.map +1 -1
  115. package/dist/orchestration/source-policy.d.ts +1 -1
  116. package/dist/orchestration/source-policy.d.ts.map +1 -1
  117. package/dist/orchestration/source-policy.js +1 -1
  118. package/dist/orchestration/speech.d.ts +15 -17
  119. package/dist/orchestration/speech.d.ts.map +1 -1
  120. package/dist/orchestration/speech.js +90 -30
  121. package/dist/orchestration/speech.js.map +1 -1
  122. package/dist/orchestration/store.d.ts +2 -0
  123. package/dist/orchestration/store.d.ts.map +1 -1
  124. package/dist/orchestration/store.js +16 -1
  125. package/dist/orchestration/store.js.map +1 -1
  126. package/dist/orchestration/tasks/driver.d.ts.map +1 -1
  127. package/dist/orchestration/tasks/driver.js +8 -3
  128. package/dist/orchestration/tasks/driver.js.map +1 -1
  129. package/dist/orchestration/tasks/pool.d.ts.map +1 -1
  130. package/dist/orchestration/tasks/pool.js +8 -4
  131. package/dist/orchestration/tasks/pool.js.map +1 -1
  132. package/dist/orchestration/tasks/service.d.ts +48 -1
  133. package/dist/orchestration/tasks/service.d.ts.map +1 -1
  134. package/dist/orchestration/tasks/service.js +41 -17
  135. package/dist/orchestration/tasks/service.js.map +1 -1
  136. package/dist/orchestration/token-ledger.d.ts +125 -0
  137. package/dist/orchestration/token-ledger.d.ts.map +1 -0
  138. package/dist/orchestration/token-ledger.js +232 -0
  139. package/dist/orchestration/token-ledger.js.map +1 -0
  140. package/dist/orchestration/token-usage.d.ts +46 -0
  141. package/dist/orchestration/token-usage.d.ts.map +1 -0
  142. package/dist/orchestration/token-usage.js +102 -0
  143. package/dist/orchestration/token-usage.js.map +1 -0
  144. package/dist/orchestration/tool-activity.d.ts.map +1 -1
  145. package/dist/orchestration/tool-activity.js +5 -3
  146. package/dist/orchestration/tool-activity.js.map +1 -1
  147. package/dist/orchestration/tool-name.d.ts +10 -0
  148. package/dist/orchestration/tool-name.d.ts.map +1 -0
  149. package/dist/orchestration/tool-name.js +14 -0
  150. package/dist/orchestration/tool-name.js.map +1 -0
  151. package/dist/orchestration/transcript-checkpoint.d.ts +14 -0
  152. package/dist/orchestration/transcript-checkpoint.d.ts.map +1 -0
  153. package/dist/orchestration/transcript-checkpoint.js +83 -0
  154. package/dist/orchestration/transcript-checkpoint.js.map +1 -0
  155. package/dist/orchestration/unanswered-inputs.d.ts +6 -0
  156. package/dist/orchestration/unanswered-inputs.d.ts.map +1 -0
  157. package/dist/orchestration/unanswered-inputs.js +18 -0
  158. package/dist/orchestration/unanswered-inputs.js.map +1 -0
  159. package/dist/session/process.d.ts +10 -0
  160. package/dist/session/process.d.ts.map +1 -1
  161. package/dist/session/process.js +91 -9
  162. package/dist/session/process.js.map +1 -1
  163. package/dist/session/request-tool-capture.d.ts +31 -0
  164. package/dist/session/request-tool-capture.d.ts.map +1 -0
  165. package/dist/session/request-tool-capture.js +194 -0
  166. package/dist/session/request-tool-capture.js.map +1 -0
  167. package/dist/session/runtime-profile.d.ts +7 -3
  168. package/dist/session/runtime-profile.d.ts.map +1 -1
  169. package/dist/session/runtime-profile.js +3 -1
  170. package/dist/session/runtime-profile.js.map +1 -1
  171. package/dist/session/store.d.ts +7 -0
  172. package/dist/session/store.d.ts.map +1 -1
  173. package/dist/session/store.js +27 -0
  174. package/dist/session/store.js.map +1 -1
  175. package/dist/shell/tailer.d.ts +7 -5
  176. package/dist/shell/tailer.d.ts.map +1 -1
  177. package/dist/shell/tailer.js +6 -17
  178. package/dist/shell/tailer.js.map +1 -1
  179. package/dist/ui/cli-viewer-ui.js +1 -1
  180. package/dist/ui/dashboard-client.d.ts +3 -0
  181. package/dist/ui/dashboard-client.d.ts.map +1 -0
  182. package/dist/ui/dashboard-client.js +188 -0
  183. package/dist/ui/dashboard-client.js.map +1 -0
  184. package/dist/ui/dashboard-presentation.d.ts +9 -0
  185. package/dist/ui/dashboard-presentation.d.ts.map +1 -0
  186. package/dist/ui/dashboard-presentation.js +56 -0
  187. package/dist/ui/dashboard-presentation.js.map +1 -0
  188. package/dist/ui/dashboard-range.d.ts +5 -0
  189. package/dist/ui/dashboard-range.d.ts.map +1 -0
  190. package/dist/ui/dashboard-range.js +20 -0
  191. package/dist/ui/dashboard-range.js.map +1 -0
  192. package/dist/ui/dashboard-theme.d.ts +4 -0
  193. package/dist/ui/dashboard-theme.d.ts.map +1 -0
  194. package/dist/ui/dashboard-theme.js +485 -0
  195. package/dist/ui/dashboard-theme.js.map +1 -0
  196. package/dist/ui/token-report-client.d.ts +3 -0
  197. package/dist/ui/token-report-client.d.ts.map +1 -0
  198. package/dist/ui/token-report-client.js +34 -0
  199. package/dist/ui/token-report-client.js.map +1 -0
  200. package/dist/ui/token-report.d.ts +89 -0
  201. package/dist/ui/token-report.d.ts.map +1 -0
  202. package/dist/ui/token-report.js +200 -0
  203. package/dist/ui/token-report.js.map +1 -0
  204. package/dist/ui/web-ui.d.ts.map +1 -1
  205. package/dist/ui/web-ui.js +124 -181
  206. package/dist/ui/web-ui.js.map +1 -1
  207. package/dist/voice/replay.d.ts +8 -0
  208. package/dist/voice/replay.d.ts.map +1 -0
  209. package/dist/voice/replay.js +48 -0
  210. package/dist/voice/replay.js.map +1 -0
  211. package/dist/voice/session.d.ts +9 -0
  212. package/dist/voice/session.d.ts.map +1 -1
  213. package/dist/voice/session.js +72 -14
  214. package/dist/voice/session.js.map +1 -1
  215. package/mcp/lazy-connector.test.ts +58 -0
  216. package/mcp/lazy-connector.ts +67 -0
  217. package/mcp/lazy-tools.test.ts +152 -0
  218. package/mcp/lazy-tools.ts +41 -0
  219. package/mcp/server.ts +53 -34
  220. package/mcp/tools/discord/commands.ts +2 -0
  221. package/mcp/tools/tasks/module.ts +1 -12
  222. package/mcp/tools/telegram/receiver-server.ts +14 -14
  223. package/package.json +2 -1
  224. package/resource/dashboard/OFL.txt +93 -0
  225. package/resource/dashboard/poppins-300.woff2 +0 -0
  226. package/resource/dashboard/poppins-400.woff2 +0 -0
  227. package/resource/dashboard/poppins-500.woff2 +0 -0
  228. package/resource/dashboard/poppins-600.woff2 +0 -0
@@ -35,8 +35,10 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.AgentRunner = exports.CHANNEL_COALESCE_WINDOW_MS = exports.WHATSAPP_CLOUD_WINDOW_NOTE = exports.DEFAULT_MODELS = exports.MAX_IMAGE_SIZE_BYTES = void 0;
37
37
  exports.toRelMediaFiles = toRelMediaFiles;
38
+ const response_errors_1 = require("../orchestration/response-errors");
38
39
  const channel_media_error_1 = require("../orchestration/channel-media-error");
39
40
  const store_1 = require("../orchestration/store");
41
+ const config_1 = require("../orchestration/config");
40
42
  const channel_input_media_1 = require("../orchestration/channel-input-media");
41
43
  const chunks_1 = require("../telegram/chunks");
42
44
  const file_delivery_1 = require("../orchestration/file-delivery");
@@ -56,7 +58,7 @@ const config_write_lock_1 = require("../config/config-write-lock");
56
58
  const logger_1 = require("../logger");
57
59
  const process_1 = require("../session/process");
58
60
  const store_2 = require("../session/store");
59
- const compactor_1 = require("../session/compactor");
61
+ const native_compact_1 = require("../orchestration/native-compact");
60
62
  const receiver_1 = require("../telegram/receiver");
61
63
  const receiver_2 = require("../discord/receiver");
62
64
  const line_reply_manager_1 = require("./line-reply-manager");
@@ -73,7 +75,7 @@ const skills_1 = require("../skills");
73
75
  const builtin_commands_1 = require("./builtin-commands");
74
76
  const model_catalog_1 = require("./model-catalog");
75
77
  const safe_mode_1 = require("./safe-mode");
76
- const config_1 = require("./skill-learning/config");
78
+ const config_2 = require("./skill-learning/config");
77
79
  const turn_stream_1 = require("./turn-stream");
78
80
  const recovery_executor_1 = require("./recovery-executor");
79
81
  const recovery_policy_1 = require("./recovery-policy");
@@ -348,9 +350,21 @@ class AgentRunner extends events_1.EventEmitter {
348
350
  async subscribeResponseText(sessionId, principalId, receive) {
349
351
  return (await this.getOrchestration()).subscribeText(sessionId, principalId, receive);
350
352
  }
353
+ async setBrowserVoice(sessionId, principalId, enabled) {
354
+ return (await this.getOrchestration()).setBrowserVoice(sessionId, principalId, enabled);
355
+ }
356
+ async pendingVoiceSpeech(sessionId, principalId, claimed = []) {
357
+ return (await this.getOrchestration()).pendingVoiceSpeech(sessionId, principalId, claimed);
358
+ }
351
359
  async voiceReplay(sessionId, principalId, responseId) {
352
360
  return (await this.getOrchestration()).voiceAudio(sessionId, principalId, responseId);
353
361
  }
362
+ async replayableVoiceResponses(sessionId, principalId) {
363
+ return (await this.getOrchestration()).replayableVoiceResponses(sessionId, principalId);
364
+ }
365
+ async voiceReplaySpeech(sessionId, principalId, responseId) {
366
+ return (await this.getOrchestration()).voiceReplaySpeech(sessionId, principalId, responseId);
367
+ }
354
368
  saveVoiceReplay(sessionId, principalId, responseId, audio) {
355
369
  this.orchestration?.saveVoiceAudio(sessionId, principalId, responseId, audio);
356
370
  }
@@ -642,6 +656,7 @@ class AgentRunner extends events_1.EventEmitter {
642
656
  }
643
657
  });
644
658
  };
659
+ this.compactingSessions = new Set();
645
660
  this.telegramCommandConfig = '';
646
661
  this.agentConfig = (0, gateway_config_1.applyGatewayOrchestration)(agentConfig, gatewayConfig);
647
662
  this.gatewayConfig = gatewayConfig;
@@ -907,6 +922,7 @@ class AgentRunner extends events_1.EventEmitter {
907
922
  .then(() => this.writeTypingDone(chatId))
908
923
  .catch((err) => {
909
924
  this.logger.error('Session command failed', { error: err.message });
925
+ this.writeAutoForward(chatId, 'Command failed: ' + err.message);
910
926
  this.writeTypingDone(chatId);
911
927
  });
912
928
  return;
@@ -1375,7 +1391,7 @@ class AgentRunner extends events_1.EventEmitter {
1375
1391
  return;
1376
1392
  }
1377
1393
  const restarted = await this.restartChannelSessions();
1378
- respond({ success: true, model: newModel, restarted });
1394
+ respond({ success: true, model: newModel, restarted, appliesNextTurn: !restarted });
1379
1395
  return;
1380
1396
  }
1381
1397
  if (command === 'restart') {
@@ -2189,6 +2205,7 @@ class AgentRunner extends events_1.EventEmitter {
2189
2205
  // size when the cap has been lowered.
2190
2206
  const recoveryCount = this.tooLargeRecoveries.get(mapKey) ?? 0;
2191
2207
  proc.historyLimit = this.spawnHistoryLimit(configuredMax, recoveryCount);
2208
+ proc.historyRecoveryActive = recoveryCount > 0;
2192
2209
  if (recoveryCount > 0) {
2193
2210
  this.logger.info('Spawning with reduced history after request_too_large', {
2194
2211
  mapKey, recoveryCount, historyLimit: proc.historyLimit,
@@ -2800,6 +2817,7 @@ class AgentRunner extends events_1.EventEmitter {
2800
2817
  }
2801
2818
  else if (content.startsWith('/clear')) {
2802
2819
  await this.handleCommandClear(agentId, chatId);
2820
+ this.writeAutoForward(chatId, 'Claude Code context reset. Chat history is unchanged. The next message loads the latest 50 messages.');
2803
2821
  }
2804
2822
  else if (content.startsWith('/compact')) {
2805
2823
  await this.handleCommandCompact(agentId, chatId);
@@ -2869,7 +2887,7 @@ class AgentRunner extends events_1.EventEmitter {
2869
2887
  }
2870
2888
  if (!this.isDirectChat(meta)) {
2871
2889
  this.writeAutoForward(chatId, 'Switching models is only available in a direct message with the bot — '
2872
- + 'it changes the model for every chat of this agent and restarts them. '
2890
+ + 'it changes the model for every chat of this agent. '
2873
2891
  + 'Use /models here to see the list.');
2874
2892
  return;
2875
2893
  }
@@ -2885,10 +2903,10 @@ class AgentRunner extends events_1.EventEmitter {
2885
2903
  return;
2886
2904
  }
2887
2905
  await this.setModel(match.id);
2888
- // Same restart the Telegram picker's set_model does the running session
2889
- // was spawned with the old model and would otherwise keep using it.
2906
+ // Legacy sessions need respawning; managed decisions pick up the new model
2907
+ // on their next spawn without terminating the response in progress.
2890
2908
  const restarted = await this.restartChannelSessions();
2891
- this.writeAutoForward(chatId, `Model set to ${match.label} (${match.id}).${restarted ? ' Restarting the session…' : ''}`);
2909
+ this.writeAutoForward(chatId, `Model set to ${match.label} (${match.id}).${restarted ? ' Restarting the session…' : ' Applies to the next response; current work continues.'}`);
2892
2910
  }
2893
2911
  /**
2894
2912
  * /sessions — list all sessions for this chat.
@@ -3005,87 +3023,76 @@ class AgentRunner extends events_1.EventEmitter {
3005
3023
  const stopped = interrupted || hadActive || hadQueued || !!buffered;
3006
3024
  this.writeAutoForward(chatId, stopped ? 'Stopped.' : 'No turn in progress.');
3007
3025
  }
3008
- /**
3009
- * /clear — clear history of the current session and restart the process.
3010
- * Also clears the permanent history DB and media files for this chat.
3011
- */
3026
+ /** /clear rotates model context while preserving history, media and tasks. */
3027
+ async clearContext(sessionId) {
3028
+ if (this.compactingSessions.has(sessionId))
3029
+ throw new Error('Context maintenance is already running for this session.');
3030
+ this.compactingSessions.add(sessionId);
3031
+ try {
3032
+ const runtime = this.orchestration ?? (this.agentConfig.orchestration?.enabled ? await this.getOrchestration() : undefined);
3033
+ if (runtime?.isBusy(sessionId))
3034
+ throw new Error('The agent is responding. Try /clear after the current response finishes.');
3035
+ const sessions = [...this.sessions.entries()].filter(([, process]) => process.sessionId === sessionId);
3036
+ if (sessions.some(([, process]) => process.isProcessing))
3037
+ throw new Error('The agent is responding. Try /clear after the current response finishes.');
3038
+ for (const [key, process] of sessions) {
3039
+ await process.stop();
3040
+ if (this.sessions.get(key) === process)
3041
+ this.sessions.delete(key);
3042
+ }
3043
+ // No await between persisting the seed window and dropping the resume mapping.
3044
+ // The marker survives gateway restarts until a successful turn consumes it.
3045
+ if (runtime?.isBusy(sessionId))
3046
+ throw new Error('The agent is responding. Try /clear after the current response finishes.');
3047
+ this.sessionStore.requestContextReset(this.agentConfig.id, sessionId);
3048
+ if (runtime?.ownsSession(sessionId))
3049
+ runtime.resetSessionContext(sessionId);
3050
+ for (const key of [sessionId, ...sessions.map(([key]) => key)]) {
3051
+ this.tooLargeRecoveries.delete(key);
3052
+ this.tooLargeExhausted.delete(key);
3053
+ }
3054
+ }
3055
+ finally {
3056
+ this.compactingSessions.delete(sessionId);
3057
+ }
3058
+ }
3012
3059
  async handleCommandClear(agentId, chatId) {
3013
- const ch = this.channelFor(chatId);
3014
- const sessionId = await this.sessionStore.getActiveSessionId(agentId, chatId, ch);
3015
- // Clear messages and reset all metadata in-place (preserves session ID and name)
3016
- await this.sessionStore.clearTelegramSessionHistory(agentId, chatId, sessionId, ch);
3017
- await this.sessionStore.updateSessionMeta(agentId, chatId, sessionId, {
3018
- totalTokensUsed: 0,
3019
- lastInputTokens: 0,
3020
- archivedCount: 0,
3021
- loadedAtSpawn: undefined,
3022
- messageCountAtSpawn: undefined,
3023
- }, ch);
3024
- // Clear permanent history DB for this chat
3025
- const historyChatId = `${ch}-${chatId}`;
3026
- this.historyDb.clearChat(historyChatId);
3027
- // Delete persisted media files for this chat
3028
- media_store_1.MediaStore.clearChatMedia(this.agentsBaseDir, agentId, historyChatId);
3029
- // Reset any request_too_large escalation the context is now empty, so the
3030
- // next spawn should start fresh at the top of the history ladder.
3031
- this.tooLargeRecoveries.delete(chatId);
3032
- this.tooLargeExhausted.delete(chatId);
3033
- // Kill old process so next message spawns fresh
3034
- this.restartProcess(chatId).catch(() => { });
3060
+ const sessionId = await this.sessionStore.getActiveSessionId(agentId, chatId, this.channelFor(chatId));
3061
+ await this.clearContext(sessionId);
3062
+ }
3063
+ /** Native CLI compaction leaves canonical chat history intact. */
3064
+ async compactContext(sessionId, model) {
3065
+ if (this.compactingSessions.has(sessionId))
3066
+ throw new Error('Context compaction is already running for this session.');
3067
+ this.compactingSessions.add(sessionId);
3068
+ try {
3069
+ const runtime = this.orchestration ?? (this.agentConfig.orchestration?.enabled ? await this.getOrchestration() : undefined);
3070
+ if (runtime?.ownsSession(sessionId))
3071
+ return await runtime.compactSession(sessionId, model);
3072
+ const process = [...this.sessions.values()].find(p => p.sessionId === sessionId && p.isRunning());
3073
+ if (!process)
3074
+ throw new Error('No active Claude Code context to compact. Send a message first; chat history was not changed.');
3075
+ if (process.isProcessing)
3076
+ throw new Error('The agent is responding. Try /compact after the current response finishes.');
3077
+ if (this.gatewayConfig.gateway.headless === false)
3078
+ throw new Error('Native /compact currently requires the headless Claude Code backend. Chat history was not changed.');
3079
+ await (0, native_compact_1.startNativeCompact)(process, true).result;
3080
+ }
3081
+ finally {
3082
+ this.compactingSessions.delete(sessionId);
3083
+ }
3035
3084
  }
3036
- /**
3037
- * /compact — summarise old history and keep only recent messages.
3038
- */
3039
3085
  async handleCommandCompact(agentId, chatId) {
3040
3086
  const ch = this.channelFor(chatId);
3041
3087
  const sessionId = await this.sessionStore.getActiveSessionId(agentId, chatId, ch);
3042
- const index = await this.sessionStore.listSessions(agentId, chatId, ch);
3043
- const meta = index.sessions.find(s => s.id === sessionId);
3044
- const name = meta?.name ?? 'Session';
3045
- const compactModel = this.agentConfig.claude.model;
3046
- const contextWindow = await this.contextWindowFor(compactModel);
3047
- this.writeAutoForward(chatId, `⏳ Compacting session "${name}"...`);
3088
+ this.writeAutoForward(chatId, '⏳ Compacting Claude Code context…');
3048
3089
  try {
3049
- const compactor = new compactor_1.SessionCompactor(this.sessionStore);
3050
- // Large sessions summarize in many chunks (see compactor.ts); post a status
3051
- // update at each ~25% step so the channel doesn't look dead mid-compact.
3052
- // Throttled (not per-chunk) to avoid spamming the chat on a 16+ chunk job.
3053
- let lastReportedPct = -1;
3054
- const onProgress = (done, total) => {
3055
- if (total <= 1)
3056
- return;
3057
- const pct = Math.floor((done / total) * 100);
3058
- if (pct - lastReportedPct >= 25) {
3059
- lastReportedPct = pct;
3060
- this.writeAutoForward(chatId, `⏳ Compacting session "${name}"... ${done}/${total} parts summarized`);
3061
- }
3062
- };
3063
- const result = await compactor.compact(agentId, chatId, sessionId, compactModel, contextWindow, ch, onProgress);
3064
- await this.sessionStore.updateSessionMeta(agentId, chatId, sessionId, {
3065
- loadedAtSpawn: undefined,
3066
- archivedCount: undefined,
3067
- messageCountAtSpawn: undefined,
3068
- }, ch);
3069
- await this.restartProcess(chatId);
3070
- const summary = [
3071
- `✅ Session compacted`,
3072
- '',
3073
- `Before: ${result.beforeMessages} messages (~${result.beforeTokens.toLocaleString()} tokens) → ${result.contextPctBefore}% of context`,
3074
- `After: ${result.afterMessages} messages (~${result.afterTokens.toLocaleString()} tokens) → ${result.contextPctAfter}% of context`,
3075
- `Reduced by: ${result.reductionPct}%`,
3076
- '',
3077
- 'Summary preserved. Full history before compaction is archived.',
3078
- ].join('\n');
3079
- this.writeAutoForward(chatId, summary);
3090
+ await this.compactContext(sessionId);
3091
+ this.writeAutoForward(chatId, '✅ Claude Code context compacted. Chat history is unchanged.');
3080
3092
  }
3081
3093
  catch (err) {
3082
- if (err.name === 'NotEnoughMessagesError') {
3083
- this.writeAutoForward(chatId, `⚠️ ${err.message}`);
3084
- }
3085
- else {
3086
- this.logger.error('Compact failed', { error: err.message });
3087
- this.writeAutoForward(chatId, `❌ Compact failed: ${err.message}\n\nYour session history is unchanged.`);
3088
- }
3094
+ this.logger.error('Compact failed', { error: err.message });
3095
+ this.writeAutoForward(chatId, 'Context compaction failed: ' + err.message + '\nChat history is unchanged.');
3089
3096
  }
3090
3097
  }
3091
3098
  /**
@@ -4161,9 +4168,9 @@ class AgentRunner extends events_1.EventEmitter {
4161
4168
  // Each precedence level is validated in turn — an invalid-but-present
4162
4169
  // cleanupTimezone does not count as "set" and falls through to gw.timezone
4163
4170
  // rather than jumping straight to UTC (issue #462 review).
4164
- const cleanupTimezone = (0, config_1.isValidTimezone)(gw.history?.cleanupTimezone)
4171
+ const cleanupTimezone = (0, config_2.isValidTimezone)(gw.history?.cleanupTimezone)
4165
4172
  ? gw.history.cleanupTimezone
4166
- : (0, config_1.isValidTimezone)(gw.timezone)
4173
+ : (0, config_2.isValidTimezone)(gw.timezone)
4167
4174
  ? gw.timezone
4168
4175
  : 'UTC';
4169
4176
  const agentMediaRoot = media_store_1.MediaStore.agentMediaRoot(this.agentsBaseDir, this.agentConfig.id);
@@ -4185,6 +4192,23 @@ class AgentRunner extends events_1.EventEmitter {
4185
4192
  isRunning() {
4186
4193
  return this.receiver?.isRunning() ?? false;
4187
4194
  }
4195
+ getDashboardSource() {
4196
+ return { workspace: this.agentConfig.type === 'app-agent' ? undefined : this.agentConfig.workspace, filename: path.join(this.agentDir, 'orchestration.db'), historyFilename: path.join(this.agentDir, 'history.db'),
4197
+ enabled: Boolean(this.orchestration || this.agentConfig.orchestration?.enabled),
4198
+ workspaceMode: this.agentConfig.type === 'app-agent' ? 'container' : this.agentConfig.orchestration?.tasks?.workspaceMode ?? 'host',
4199
+ maxWorkers: this.agentConfig.orchestration?.tasks?.maxConcurrentPerAgent ?? config_1.ORCHESTRATION_DEFAULTS.tasks.maxConcurrentPerAgent,
4200
+ idleTtlMs: this.agentConfig.orchestration?.tasks?.workerIdleTtlMs ?? config_1.ORCHESTRATION_DEFAULTS.tasks.workerIdleTtlMs };
4201
+ }
4202
+ async dashboardContextWindow(model) {
4203
+ const models = await this.availableModels();
4204
+ return models.find(m => m.id === model)?.contextWindow
4205
+ ?? (this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS).find(m => m.id === model)?.contextWindow
4206
+ ?? null;
4207
+ }
4208
+ getTokenReport(sessionId) {
4209
+ const report = this.orchestration?.tokenReport(sessionId);
4210
+ return report ? { ...report, backgroundReviews: this.historyDb.listReviewRuns().filter(run => run.sessionId === sessionId) } : undefined;
4211
+ }
4188
4212
  getOrchestrationSummary() {
4189
4213
  if (!this.agentConfig.orchestration?.enabled && !this.orchestration)
4190
4214
  return undefined;
@@ -4224,6 +4248,29 @@ class AgentRunner extends events_1.EventEmitter {
4224
4248
  }
4225
4249
  return out;
4226
4250
  }
4251
+ /**
4252
+ * Session IDs currently alive in the in-memory session map. Display-only
4253
+ * liveness signal for the dashboard: a session removed by the idle cleaner
4254
+ * (kept-alive window elapsed) or released is no longer present here.
4255
+ */
4256
+ liveSessionIds() {
4257
+ return [...this.sessions.values()].filter(p => p.isRunning()).map(p => p.sessionId);
4258
+ }
4259
+ /**
4260
+ * Live status of one agent session derived from the in-memory session map:
4261
+ * - 'running' → alive and processing a turn right now
4262
+ * - 'idle' → alive and kept warm, not currently processing
4263
+ * - 'stopped' → gone from the map (idle-timeout kill or otherwise released)
4264
+ * Display-only; does not touch token accounting.
4265
+ */
4266
+ isSessionCompacting(sessionId) { return this.orchestration?.isCompacting(sessionId) ?? false; }
4267
+ agentSessionLiveStatus(sessionId) {
4268
+ for (const p of this.sessions.values()) {
4269
+ if (p.sessionId === sessionId && p.isRunning())
4270
+ return p.isProcessing ? 'running' : 'idle';
4271
+ }
4272
+ return 'stopped';
4273
+ }
4227
4274
  /**
4228
4275
  * Send a message to an API session and wait for the response.
4229
4276
  *
@@ -4526,7 +4573,10 @@ class AgentRunner extends events_1.EventEmitter {
4526
4573
  void this.sendOrchestratedApi(sessionId, chatId, message, { ...opts, requestId: turn.requestId }, text => turn.emit({ type: 'text_delta', text }), event => { if (event.type === 'tool_use')
4527
4574
  turn.emit({ ...event, type: 'tool_use' }); })
4528
4575
  .then(result => this.turnStreams.complete(turn, (0, turn_stream_1.resultEvent)(result.text, result.attachments)))
4529
- .catch(error => this.turnStreams.complete(turn, (0, turn_stream_1.errorEvent)(error), error));
4576
+ .catch(error => {
4577
+ const publicError = (0, response_errors_1.presentedResponseError)(error);
4578
+ this.turnStreams.complete(turn, (0, turn_stream_1.errorEvent)(publicError), publicError);
4579
+ });
4530
4580
  return () => turn.detach(sink);
4531
4581
  }
4532
4582
  if (this.pendingApiSessions.has(sessionId)) {
@@ -5022,10 +5072,7 @@ class AgentRunner extends events_1.EventEmitter {
5022
5072
  this.historyDb.insertMessage({ chatId: dbChatId, sessionId, source: 'api', role, content, ts: Date.now() });
5023
5073
  };
5024
5074
  // Persist the full user command before executing so it appears in history.
5025
- // Skip for /clear — clearSession() below wipes the table anyway; only the response survives.
5026
- if (cmd !== '/clear') {
5027
- persist('user', opts?.displayCommand ?? command);
5028
- }
5075
+ persist('user', opts?.displayCommand ?? command);
5029
5076
  let result;
5030
5077
  let responseText;
5031
5078
  let forcePersist = false;
@@ -5130,36 +5177,14 @@ class AgentRunner extends events_1.EventEmitter {
5130
5177
  }
5131
5178
  }
5132
5179
  else if (cmd === '/clear') {
5133
- const ch = 'api';
5134
- await this.sessionStore.clearTelegramSessionHistory(agentId, storeChatId, sessionId, ch);
5135
- await this.sessionStore.updateSessionMeta(agentId, storeChatId, sessionId, {
5136
- totalTokensUsed: 0,
5137
- lastInputTokens: 0,
5138
- archivedCount: 0,
5139
- loadedAtSpawn: undefined,
5140
- messageCountAtSpawn: undefined,
5141
- }, ch);
5142
- const mediaPaths = this.historyDb.clearSession(dbChatId, sessionId);
5143
- media_store_1.MediaStore.deleteMediaFiles(this.agentsBaseDir, agentId, mediaPaths);
5144
- this.restartProcess(sessionId).catch(() => { });
5145
- result = { success: true };
5146
- responseText = 'Session cleared.';
5180
+ await this.clearContext(sessionId);
5181
+ result = { success: true, historyUnchanged: true, historyLimit: 50 };
5182
+ responseText = 'Claude Code context reset. Chat history is unchanged. Your next message starts a new context with the latest 50 messages.';
5147
5183
  }
5148
5184
  else if (cmd === '/compact') {
5149
- const ch = 'api';
5150
- const activeSession = this.sessions.get(sessionId);
5151
- const compactEffectiveModel = opts?.model ?? activeSession?.modelOverride ?? this.agentConfig.claude.model;
5152
- const contextWindow = await this.contextWindowFor(compactEffectiveModel);
5153
- const compactor = new compactor_1.SessionCompactor(this.sessionStore);
5154
- const compactResult = await compactor.compact(agentId, storeChatId, sessionId, compactEffectiveModel, contextWindow, ch);
5155
- await this.sessionStore.updateSessionMeta(agentId, storeChatId, sessionId, {
5156
- loadedAtSpawn: undefined,
5157
- archivedCount: undefined,
5158
- messageCountAtSpawn: undefined,
5159
- }, ch);
5160
- await this.restartProcess(sessionId);
5161
- result = { success: true, keptMessages: compactResult.afterMessages, archivedMessages: compactResult.beforeMessages - compactResult.afterMessages };
5162
- responseText = `Session compacted. Kept ${compactResult.afterMessages} messages, archived ${compactResult.beforeMessages - compactResult.afterMessages}.`;
5185
+ await this.compactContext(sessionId, opts?.model);
5186
+ result = { success: true, native: true, historyUnchanged: true };
5187
+ responseText = 'Claude Code context compacted. Chat history is unchanged.';
5163
5188
  }
5164
5189
  else {
5165
5190
  // Passed the gate but has no dispatch branch — BUILTIN_COMMANDS drifted from this
@@ -5205,17 +5230,20 @@ class AgentRunner extends events_1.EventEmitter {
5205
5230
  return kind === 'direct' || kind === 'user';
5206
5231
  }
5207
5232
  /**
5208
- * Restart every non-api session so a model change actually takes effect.
5233
+ * Restart legacy non-api sessions so a model change actually takes effect.
5209
5234
  *
5210
5235
  * `setModel` only rewrites config — a session process was spawned with the
5211
5236
  * old model on its command line and keeps using it until it is restarted.
5212
5237
  * Reporting "model set" without this is a false success: the next turn still
5213
- * runs on the previous model. Returns whether anything was restarted.
5238
+ * runs on the previous model. Managed decisions already spawn per turn and
5239
+ * must finish with the model they started with. Returns whether anything restarted.
5214
5240
  */
5215
5241
  async restartChannelSessions() {
5216
5242
  const restartPromises = [];
5217
5243
  for (const [key, session] of this.sessions) {
5218
- if (session.source !== 'api')
5244
+ // Managed decisions spawn a new process on every turn. Killing the current
5245
+ // one here bypasses orchestration cancellation and reports PROCESS_EXITED.
5246
+ if (session.source !== 'api' && !session.runtimeProfile)
5219
5247
  restartPromises.push(this.restartProcess(key));
5220
5248
  }
5221
5249
  await Promise.all(restartPromises);
@@ -5421,7 +5449,8 @@ class AgentRunner extends events_1.EventEmitter {
5421
5449
  drain();
5422
5450
  }
5423
5451
  catch { /* Preserve the original stream/provider failure. */ }
5424
- this.turnStreams.completeAndRelease(turn, (0, turn_stream_1.errorEvent)(error), error);
5452
+ const publicError = (0, response_errors_1.presentedResponseError)(error);
5453
+ this.turnStreams.completeAndRelease(turn, (0, turn_stream_1.errorEvent)(publicError), publicError);
5425
5454
  close();
5426
5455
  };
5427
5456
  void (async () => {