@yu_robotics/remote-cli 1.6.170 → 1.6.176

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 (94) hide show
  1. package/README.md +25 -0
  2. package/dist/client/GoalCommand.d.ts +15 -0
  3. package/dist/client/GoalCommand.d.ts.map +1 -0
  4. package/dist/client/GoalCommand.js +59 -0
  5. package/dist/client/GoalCommand.js.map +1 -0
  6. package/dist/client/MessageHandler.d.ts +4 -0
  7. package/dist/client/MessageHandler.d.ts.map +1 -1
  8. package/dist/client/MessageHandler.js +186 -46
  9. package/dist/client/MessageHandler.js.map +1 -1
  10. package/dist/client/TaskRecovery.d.ts +3 -1
  11. package/dist/client/TaskRecovery.d.ts.map +1 -1
  12. package/dist/client/TaskRecovery.js +9 -4
  13. package/dist/client/TaskRecovery.js.map +1 -1
  14. package/dist/client/WebSocketClient.d.ts +2 -1
  15. package/dist/client/WebSocketClient.d.ts.map +1 -1
  16. package/dist/client/WebSocketClient.js +9 -1
  17. package/dist/client/WebSocketClient.js.map +1 -1
  18. package/dist/delegation/DelegationManager.js +1 -1
  19. package/dist/delegation/DelegationManager.js.map +1 -1
  20. package/dist/executor/AcpExecutor.d.ts +6 -0
  21. package/dist/executor/AcpExecutor.d.ts.map +1 -1
  22. package/dist/executor/AcpExecutor.js +81 -10
  23. package/dist/executor/AcpExecutor.js.map +1 -1
  24. package/dist/executor/CodexAppServerClient.d.ts +1 -0
  25. package/dist/executor/CodexAppServerClient.d.ts.map +1 -1
  26. package/dist/executor/CodexAppServerClient.js +12 -4
  27. package/dist/executor/CodexAppServerClient.js.map +1 -1
  28. package/dist/executor/CodexAppServerExecutor.d.ts +39 -2
  29. package/dist/executor/CodexAppServerExecutor.d.ts.map +1 -1
  30. package/dist/executor/CodexAppServerExecutor.js +712 -32
  31. package/dist/executor/CodexAppServerExecutor.js.map +1 -1
  32. package/dist/executor/IExecutor.d.ts +43 -0
  33. package/dist/executor/IExecutor.d.ts.map +1 -1
  34. package/dist/executor/KimiExecutor.d.ts +9 -0
  35. package/dist/executor/KimiExecutor.d.ts.map +1 -1
  36. package/dist/executor/KimiExecutor.js +45 -0
  37. package/dist/executor/KimiExecutor.js.map +1 -1
  38. package/dist/executor/codex/CodexGoals.d.ts +6 -0
  39. package/dist/executor/codex/CodexGoals.d.ts.map +1 -0
  40. package/dist/executor/codex/CodexGoals.js +49 -0
  41. package/dist/executor/codex/CodexGoals.js.map +1 -0
  42. package/dist/executor/codex/CodexImages.d.ts +22 -0
  43. package/dist/executor/codex/CodexImages.d.ts.map +1 -0
  44. package/dist/executor/codex/CodexImages.js +201 -0
  45. package/dist/executor/codex/CodexImages.js.map +1 -0
  46. package/dist/executor/index.d.ts +2 -1
  47. package/dist/executor/index.d.ts.map +1 -1
  48. package/dist/executor/index.js +1 -0
  49. package/dist/executor/index.js.map +1 -1
  50. package/dist/executor/kimi/KimiAccountUsage.d.ts +2 -6
  51. package/dist/executor/kimi/KimiAccountUsage.d.ts.map +1 -1
  52. package/dist/executor/kimi/KimiAccountUsage.js +11 -71
  53. package/dist/executor/kimi/KimiAccountUsage.js.map +1 -1
  54. package/dist/executor/kimi/KimiLocalServer.d.ts +28 -0
  55. package/dist/executor/kimi/KimiLocalServer.d.ts.map +1 -0
  56. package/dist/executor/kimi/KimiLocalServer.js +192 -0
  57. package/dist/executor/kimi/KimiLocalServer.js.map +1 -0
  58. package/dist/executor/kimi/KimiModelCatalog.d.ts +6 -0
  59. package/dist/executor/kimi/KimiModelCatalog.d.ts.map +1 -0
  60. package/dist/executor/kimi/KimiModelCatalog.js +86 -0
  61. package/dist/executor/kimi/KimiModelCatalog.js.map +1 -0
  62. package/dist/maintenance/release-notes.json +1 -1
  63. package/dist/settings/SettingsCatalog.d.ts +1 -1
  64. package/dist/settings/SettingsCatalog.d.ts.map +1 -1
  65. package/dist/settings/SettingsCatalog.js +6 -2
  66. package/dist/settings/SettingsCatalog.js.map +1 -1
  67. package/dist/settings/SettingsService.d.ts +3 -1
  68. package/dist/settings/SettingsService.d.ts.map +1 -1
  69. package/dist/settings/SettingsService.js +17 -5
  70. package/dist/settings/SettingsService.js.map +1 -1
  71. package/dist/thread/ThreadExecutorPool.d.ts +3 -1
  72. package/dist/thread/ThreadExecutorPool.d.ts.map +1 -1
  73. package/dist/thread/ThreadExecutorPool.js +13 -1
  74. package/dist/thread/ThreadExecutorPool.js.map +1 -1
  75. package/dist/thread/ThreadManager.d.ts +1 -1
  76. package/dist/thread/ThreadManager.d.ts.map +1 -1
  77. package/dist/thread/ThreadManager.js.map +1 -1
  78. package/dist/thread/types.d.ts +2 -0
  79. package/dist/thread/types.d.ts.map +1 -1
  80. package/dist/types/Settings.d.ts +8 -0
  81. package/dist/types/Settings.d.ts.map +1 -1
  82. package/dist/utils/ImageDelivery.d.ts +67 -0
  83. package/dist/utils/ImageDelivery.d.ts.map +1 -0
  84. package/dist/utils/ImageDelivery.js +270 -0
  85. package/dist/utils/ImageDelivery.js.map +1 -0
  86. package/dist/utils/ImageValidation.d.ts +11 -0
  87. package/dist/utils/ImageValidation.d.ts.map +1 -0
  88. package/dist/utils/ImageValidation.js +62 -0
  89. package/dist/utils/ImageValidation.js.map +1 -0
  90. package/dist/utils/LocalImageDetector.d.ts +12 -3
  91. package/dist/utils/LocalImageDetector.d.ts.map +1 -1
  92. package/dist/utils/LocalImageDetector.js +153 -55
  93. package/dist/utils/LocalImageDetector.js.map +1 -1
  94. package/package.json +1 -1
@@ -12,9 +12,11 @@ const fs_1 = __importDefault(require("fs"));
12
12
  const os_1 = __importDefault(require("os"));
13
13
  const path_1 = __importDefault(require("path"));
14
14
  const DelegationSettings_1 = require("../thread/DelegationSettings");
15
+ const GoalCommand_1 = require("./GoalCommand");
15
16
  const hooks_1 = require("../hooks");
16
17
  const FileReadDetector_1 = require("../utils/FileReadDetector");
17
18
  const LocalImageDetector_1 = require("../utils/LocalImageDetector");
19
+ const ImageDelivery_1 = require("../utils/ImageDelivery");
18
20
  const WorkingDirectory_1 = require("../utils/WorkingDirectory");
19
21
  const MachineCommands_1 = require("../machines/MachineCommands");
20
22
  const child_process_1 = require("child_process");
@@ -167,6 +169,7 @@ class MessageHandler {
167
169
  });
168
170
  activeThreadOperations = new Map();
169
171
  abortOperations = new Map();
172
+ pendingGoalActivations = new Map();
170
173
  automaticUpdateInProgress = false;
171
174
  approvalCardsSupported = false;
172
175
  streamingContextSupported = false;
@@ -176,6 +179,7 @@ class MessageHandler {
176
179
  activityProgressSupported = false;
177
180
  workerContextResetSupported = false;
178
181
  settingsCardsSupported = false;
182
+ modelSpeedSettingsSupported = false;
179
183
  directoryCardsSupported = false;
180
184
  directoryBrowser;
181
185
  delegationCardsSupported = false;
@@ -186,6 +190,8 @@ class MessageHandler {
186
190
  delegation;
187
191
  delegationBridges = new Map();
188
192
  fileInbox;
193
+ imageDeliveries = new Map();
194
+ imageConnectionEpoch = 0;
189
195
  constructor(wsClient, threadPool, threadManager, directoryGuard, config) {
190
196
  this.wsClient = wsClient;
191
197
  this.threadPool = threadPool;
@@ -193,8 +199,11 @@ class MessageHandler {
193
199
  this.directoryGuard = directoryGuard;
194
200
  this.config = config;
195
201
  this.wsClient.onClose?.(() => {
202
+ this.imageConnectionEpoch++;
196
203
  this.routerVersion = undefined;
197
204
  this.fileInbox?.disconnect();
205
+ for (const delivery of this.imageDeliveries.values())
206
+ delivery.invalidatePending();
198
207
  });
199
208
  this.delegation = new DelegationManager_1.DelegationManager(directoryGuard);
200
209
  this.settingsCatalogReader = new SettingsCatalog_1.SettingsCatalogReader(directoryGuard);
@@ -216,7 +225,7 @@ class MessageHandler {
216
225
  if (!thread)
217
226
  throw new Error('This thread no longer exists.');
218
227
  const config = this.executorConfiguration();
219
- return this.settingsCatalogReader.read(config, thread, this.threadPool.getBackendKey(id), target, kind);
228
+ return this.settingsCatalogReader.read(config, thread, this.threadPool.getBackendKey(id), target, kind, this.modelSpeedSettingsSupported);
220
229
  },
221
230
  busy: (id, except, queue = true) => this.settingsBusy(id, except, queue),
222
231
  threads: () => this.threadManager.listThreads().map(thread => thread.id),
@@ -234,6 +243,7 @@ class MessageHandler {
234
243
  preference: (id, target, kind, value) => this.applyPreference(id, target, kind, value),
235
244
  delegation: (id, enabled) => this.applyDelegationSetting(id, enabled),
236
245
  delegationSupported: id => Boolean(this.threadPool.getExecutor(id).configureDelegation),
246
+ speedSettingsSupported: () => this.modelSpeedSettingsSupported,
237
247
  });
238
248
  this.directoryBrowser = new DirectoryBrowser_1.DirectoryBrowser({
239
249
  guard: this.directoryGuard,
@@ -344,6 +354,7 @@ class MessageHandler {
344
354
  this.delegationProgressSupported = data.capabilities?.delegationProgress === true;
345
355
  this.workerContextResetSupported = this.delegationProgressSupported && data.capabilities?.workerContextReset === true;
346
356
  this.settingsCardsSupported = data.capabilities?.settingsCards === true;
357
+ this.modelSpeedSettingsSupported = this.settingsCardsSupported && data.capabilities?.modelSpeedSettings === true;
347
358
  this.directoryCardsSupported = this.settingsCardsSupported && data.capabilities?.directoryCards === true;
348
359
  this.delegationCardsSupported = this.settingsCardsSupported && data.capabilities?.delegationCards === true;
349
360
  this.delegationProgressTextSupported = this.delegationProgressSupported
@@ -525,6 +536,53 @@ class MessageHandler {
525
536
  }
526
537
  const executor = this.threadPool.getExecutor(resolvedThreadId);
527
538
  this.sendStreamContext(messageId, resolvedThreadId, executor);
539
+ const goalCommand = (0, GoalCommand_1.parseGoalCommand)(settingsCommand);
540
+ let goalRequest;
541
+ if (goalCommand) {
542
+ if (goalCommand.kind === 'invalid' || goalCommand.kind === 'help') {
543
+ this.sendResponse(messageId, resolvedThreadId, goalCommand.kind === 'invalid'
544
+ ? { success: false, error: goalCommand.error } : { success: true, output: GoalCommand_1.GOAL_USAGE });
545
+ return;
546
+ }
547
+ if (this.threadPool.getBackendKey(resolvedThreadId) !== 'codex'
548
+ || !executor.getGoal || !executor.executeGoal || !executor.pauseGoal || !executor.clearGoal) {
549
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
550
+ error: '/goal requires the Codex app-server backend and an executor version with native goal support.' });
551
+ return;
552
+ }
553
+ if (attachments?.length || message.fileIds?.length) {
554
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
555
+ error: 'Send attachments in a normal message before starting a goal. No goal was changed.' });
556
+ return;
557
+ }
558
+ if (goalCommand.kind !== 'run') {
559
+ try {
560
+ const pending = this.pendingGoalActivations.get(resolvedThreadId);
561
+ if (pending && goalCommand.kind !== 'show')
562
+ pending.cancelled = true;
563
+ const result = goalCommand.kind === 'show' ? { success: true, output: (0, GoalCommand_1.formatGoal)(await executor.getGoal()) }
564
+ : goalCommand.kind === 'pause' ? await executor.pauseGoal() : await executor.clearGoal();
565
+ this.sendResponse(messageId, resolvedThreadId, { ...result, threads: this.threadPool.getSummaries() });
566
+ }
567
+ catch (error) {
568
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
569
+ error: `Goal control failed: ${error instanceof Error ? error.message : String(error)}` });
570
+ }
571
+ return;
572
+ }
573
+ if ((0, DelegationSettings_1.isDelegationEnabled)(thread) || this.delegation.hasActiveTasks(resolvedThreadId)) {
574
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
575
+ error: 'Native goals require managed delegation to be off. Wait for workers to finish and send /delegation off in this thread, then retry /goal.' });
576
+ return;
577
+ }
578
+ if (this.threadPool.isThreadBusy(resolvedThreadId) || executor.isBusy?.()
579
+ || this.hasThreadQueueState(resolvedThreadId) || this.abortOperations.has(resolvedThreadId)) {
580
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
581
+ error: 'Start or resume a goal only when this thread and its queue are idle. Use /goal pause or /abort to stop current goal work.' });
582
+ return;
583
+ }
584
+ goalRequest = goalCommand.request;
585
+ }
528
586
  // Handle /abort for this specific thread (bypasses busy check)
529
587
  if (content?.trim() === '/abort') {
530
588
  await this.handleAbortCommand(messageId, resolvedThreadId, executor);
@@ -594,7 +652,7 @@ class MessageHandler {
594
652
  return;
595
653
  }
596
654
  const inputExecutor = this.delegation.waitingExecutor(resolvedThreadId) ?? executor;
597
- if ('isWaitingInput' in inputExecutor && typeof inputExecutor.isWaitingInput === 'function') {
655
+ if (!goalRequest && 'isWaitingInput' in inputExecutor && typeof inputExecutor.isWaitingInput === 'function') {
598
656
  const ex = inputExecutor;
599
657
  if (ex.isWaitingInput()) {
600
658
  const input = content?.trim();
@@ -682,6 +740,9 @@ class MessageHandler {
682
740
  });
683
741
  return;
684
742
  }
743
+ const goalActivation = goalRequest ? { cancelled: false } : undefined;
744
+ if (goalActivation)
745
+ this.pendingGoalActivations.set(resolvedThreadId, goalActivation);
685
746
  const operationToken = this.beginThreadOperation(resolvedThreadId);
686
747
  try {
687
748
  this.trackTask(messageId, resolvedThreadId, content || '');
@@ -716,6 +777,10 @@ class MessageHandler {
716
777
  await this.executePendingReplace(messageId, resolvedThreadId, pendingKey, content);
717
778
  return;
718
779
  }
780
+ if (goalRequest) {
781
+ await this.executeCommand(messageId, resolvedThreadId, content, executor, undefined, false, goalRequest, goalActivation);
782
+ return;
783
+ }
719
784
  const builtInResult = await this.handleBuiltInCommand(messageId, resolvedThreadId, content, executor);
720
785
  if (builtInResult)
721
786
  return;
@@ -736,6 +801,9 @@ class MessageHandler {
736
801
  });
737
802
  }
738
803
  finally {
804
+ if (goalActivation && this.pendingGoalActivations.get(resolvedThreadId) === goalActivation) {
805
+ this.pendingGoalActivations.delete(resolvedThreadId);
806
+ }
739
807
  if (this.finishThreadOperation(resolvedThreadId, operationToken) && !this.pausedQueues.has(resolvedThreadId)) {
740
808
  void this.startNextQueuedCommand(resolvedThreadId);
741
809
  }
@@ -746,9 +814,20 @@ class MessageHandler {
746
814
  */
747
815
  async handleAbortCommand(messageId, threadId, executor) {
748
816
  this.fileInbox?.cancelThread(threadId);
817
+ this.imageDeliveries.get(threadId)?.cancel();
818
+ const pendingGoal = this.pendingGoalActivations.get(threadId);
819
+ if (pendingGoal)
820
+ pendingGoal.cancelled = true;
749
821
  const existingAbort = this.abortOperations.get(threadId);
750
822
  if (existingAbort) {
751
823
  await existingAbort;
824
+ if (executor.isGoalRunning?.()) {
825
+ this.sendResponse(messageId, threadId, {
826
+ success: false,
827
+ error: 'Native goal stop could not be confirmed. This thread remains busy; retry /goal pause or /abort.',
828
+ });
829
+ return;
830
+ }
752
831
  this.sendResponse(messageId, threadId, {
753
832
  success: true,
754
833
  output: 'ℹ️ The abort operation has already completed.',
@@ -763,6 +842,9 @@ class MessageHandler {
763
842
  if ((0, DelegationSettings_1.isDelegationEnabled)(this.threadManager.getThread(threadId)))
764
843
  await this.delegation.cancelThread(threadId);
765
844
  aborted = await executor.abort();
845
+ if (!aborted && executor.isGoalRunning?.()) {
846
+ throw new Error('Native goal stop could not be confirmed. This thread remains busy; retry /goal pause or /abort.');
847
+ }
766
848
  if (activeOperation)
767
849
  await activeOperation.done;
768
850
  })();
@@ -1051,6 +1133,7 @@ Use /compact to reduce conversation context or /clear to start a fresh context.`
1051
1133
  - /cd <directory> - Change working directory for this thread
1052
1134
  - /model [name] - Open model settings, or set the active backend model with a text argument
1053
1135
  - /effort [auto|level] - Open effort settings, or set a native supported level with a text argument
1136
+ - /goal [objective|pause|resume|clear] - Manage a native Codex goal; optional --budget <tokens> on start or resume
1054
1137
  - /sandbox [on|off|read-only|default|allow <directory>|remove <directory>|network on|off] - Configure this thread's sandbox (Codex, Claude Code)
1055
1138
  - /delegation [on|off|reset [backend]] - Delegate tasks between installed agent backends; reset saved worker context when needed
1056
1139
  - /backend - Open backend settings (Current thread by default; Confirm applies)
@@ -2010,12 +2093,13 @@ You can also use natural language commands to control Claude Code CLI.`,
2010
2093
  /**
2011
2094
  * Execute AI command on a specific executor
2012
2095
  */
2013
- async executeCommand(messageId, threadId, content, executor, attachments, fromQueue = false) {
2096
+ async executeCommand(messageId, threadId, content, executor, attachments, fromQueue = false, goalRequest, goalActivation) {
2014
2097
  this.sendStreamContext(messageId, threadId, executor);
2015
2098
  let delegationScope;
2016
2099
  let delegationBridge;
2017
2100
  let executionMetadata;
2018
2101
  let requestFinished = false;
2102
+ let imageDelivery;
2019
2103
  let executionActive = false;
2020
2104
  let lastProgramState;
2021
2105
  const activity = new ActivityProgress_1.ActivityProgress(snapshot => {
@@ -2040,6 +2124,9 @@ You can also use natural language commands to control Claude Code CLI.`,
2040
2124
  lastProgramState = text;
2041
2125
  };
2042
2126
  const complete = async (success, error) => {
2127
+ await imageDelivery?.finish();
2128
+ if (this.imageDeliveries.get(threadId) === imageDelivery)
2129
+ this.imageDeliveries.delete(threadId);
2043
2130
  requestFinished = true;
2044
2131
  activity.dispose();
2045
2132
  delegationBridge?.activate(undefined);
@@ -2079,6 +2166,9 @@ You can also use natural language commands to control Claude Code CLI.`,
2079
2166
  }
2080
2167
  const openId = this.getMessageOpenId(messageId);
2081
2168
  const thread = this.threadManager.getThread(threadId);
2169
+ if (goalRequest && thread && (0, DelegationSettings_1.isDelegationEnabled)(thread)) {
2170
+ throw new Error('Turn managed delegation off before starting a native goal.');
2171
+ }
2082
2172
  if (thread && (0, DelegationSettings_1.isDelegationEnabled)(thread) && executor.configureDelegation) {
2083
2173
  if (this.delegation.blocksWorkspace(executor.getCurrentWorkingDirectory(), threadId)) {
2084
2174
  throw new Error('A delegated worker holds this workspace. Wait for it to finish; if worker cleanup failed, stop the worker and restart the CLI.');
@@ -2133,37 +2223,41 @@ You can also use natural language commands to control Claude Code CLI.`,
2133
2223
  return;
2134
2224
  this.notificationAdapter.sendTaskNotification({ ...notification, threadId }, openId);
2135
2225
  };
2136
- const emittedLocalImages = new Set();
2137
- const pendingLocalImageEmissions = new Set();
2226
+ const imageCwd = thread?.workingDirectory ?? executor.getCurrentWorkingDirectory();
2227
+ const imageGeneration = thread?.delegationWorkspaceGeneration ?? 0;
2228
+ const imageBackend = this.threadPool.getBackendKey(threadId);
2229
+ let imagePhase = 0;
2230
+ const imageEligible = () => {
2231
+ const phase = imagePhase;
2232
+ return () => phase === imagePhase && (!delegationScope
2233
+ || (!delegationScope.isClosed() && !delegationScope.hasPendingResults()));
2234
+ };
2235
+ imageDelivery = new ImageDelivery_1.ImageDelivery({
2236
+ readFile: filePath => (0, LocalImageDetector_1.readLocalImagePath)(filePath, imageCwd, this.directoryGuard),
2237
+ publish: image => {
2238
+ if (!this.sendImage(messageId, threadId, image))
2239
+ throw new Error('Image send failed');
2240
+ },
2241
+ notice: text => this.sendStreamChunk(messageId, threadId, text),
2242
+ isCurrent: () => {
2243
+ const current = this.threadManager.getThread(threadId);
2244
+ return !requestFinished && !this.isDestroyed && !this.abortOperations.has(threadId)
2245
+ && this.wsClient.isConnected() && this.imageDeliveries.get(threadId) === imageDelivery
2246
+ && this.wsClient.canSendTaskOutput?.(messageId) !== false
2247
+ && !!current && current.workingDirectory === imageCwd
2248
+ && (current.delegationWorkspaceGeneration ?? 0) === imageGeneration
2249
+ && this.threadPool.getBackendKey(threadId) === imageBackend;
2250
+ },
2251
+ });
2252
+ this.imageDeliveries.get(threadId)?.cancel();
2253
+ this.imageDeliveries.set(threadId, imageDelivery);
2138
2254
  const delegationControlTools = new Set();
2139
2255
  const toolImageStates = new Map();
2140
2256
  const webToolNames = new Set(['websearch', 'webfetch', 'web_search', 'web_fetch', 'search_web', 'read_url_content', 'fetch']);
2141
- let streamedOutput = '';
2142
- const emitLocalImages = async (text) => {
2143
- try {
2144
- const thread = this.threadManager.getThread(threadId);
2145
- if (typeof text !== 'string' || !text || !thread?.workingDirectory)
2146
- return;
2147
- const images = await (0, LocalImageDetector_1.readLocalImages)(text, thread.workingDirectory, (candidate, cwd) => (this.directoryGuard.isSafePath(candidate, cwd)));
2148
- for (const image of images) {
2149
- if (emittedLocalImages.has(image.path))
2150
- continue;
2151
- emittedLocalImages.add(image.path);
2152
- this.sendImage(messageId, threadId, {
2153
- type: 'image',
2154
- data: image.data,
2155
- mimeType: image.mimeType,
2156
- });
2157
- }
2158
- }
2159
- catch {
2160
- // Image forwarding is optional; never reject a task or log native content on failure.
2161
- console.warn('Failed to forward local images; continuing task output.');
2162
- }
2163
- };
2257
+ let streamedAny = false;
2258
+ let imageTextDropped = false;
2164
2259
  const queueLocalImages = (text) => {
2165
- const pending = emitLocalImages(text).finally(() => pendingLocalImageEmissions.delete(pending));
2166
- pendingLocalImageEmissions.add(pending);
2260
+ imageDelivery?.offerText(text, imageEligible());
2167
2261
  };
2168
2262
  const executeOptions = {
2169
2263
  onActivity: this.activityProgressSupported ? (snapshot) => activity.offer(snapshot) : undefined,
@@ -2174,8 +2268,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2174
2268
  onApprovalResolved: (requestId, status) => this.resolveApprovalCard(requestId, status),
2175
2269
  onTaskNotification,
2176
2270
  onStream: (chunk) => {
2177
- streamedOutput += chunk;
2178
- this.sendStreamChunk(messageId, threadId, chunk);
2271
+ streamedAny = true;
2272
+ if (this.sendStreamChunk(messageId, threadId, chunk))
2273
+ imageDelivery?.offerStream(chunk, imageEligible());
2274
+ else
2275
+ imageTextDropped = true;
2179
2276
  },
2180
2277
  onToolUse: (toolUse) => {
2181
2278
  if (delegationScope && /remote_cli_(list_backends|delegate|result|cancel|integrate)$/.test(toolUse.name)) {
@@ -2213,7 +2310,8 @@ You can also use natural language commands to control Claude Code CLI.`,
2213
2310
  },
2214
2311
  onRedactedThinking: () => this.sendRedactedThinking(messageId, threadId),
2215
2312
  onPlanMode: (planContent) => this.sendPlanMode(messageId, threadId, planContent),
2216
- onImage: (image) => this.sendImage(messageId, threadId, image),
2313
+ onImage: (image) => imageDelivery?.offerImage(image, imageEligible()),
2314
+ onImageFile: (filePath) => imageDelivery?.offerFile(filePath, imageEligible()),
2217
2315
  attachments,
2218
2316
  };
2219
2317
  const assertCoordinatorActive = () => {
@@ -2225,6 +2323,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2225
2323
  let mayRetry = true;
2226
2324
  const executeOnce = async (prompt, options, flushImages = true) => {
2227
2325
  assertCoordinatorActive();
2326
+ imagePhase++;
2327
+ streamedAny = false;
2328
+ imageTextDropped = false;
2329
+ imageDelivery?.resetStream();
2330
+ const imageEpoch = this.imageConnectionEpoch;
2228
2331
  const scope = delegationScope;
2229
2332
  let active = true;
2230
2333
  let finished = false;
@@ -2280,6 +2383,10 @@ You can also use natural language commands to control Claude Code CLI.`,
2280
2383
  options.onImage?.(image);
2281
2384
  else
2282
2385
  suppressed = true; },
2386
+ onImageFile: filePath => { if (canPublish())
2387
+ options.onImageFile?.(filePath);
2388
+ else
2389
+ suppressed = true; },
2283
2390
  onRedactedThinking: () => { if (canPublish())
2284
2391
  options.onRedactedThinking?.(); },
2285
2392
  onToolUse: tool => { if (active && !scope?.isClosed())
@@ -2292,7 +2399,19 @@ You can also use natural language commands to control Claude Code CLI.`,
2292
2399
  executionMetadata = launchMetadata;
2293
2400
  let capturedMetadata = false;
2294
2401
  try {
2295
- const result = await executor.execute(prompt, guardedOptions);
2402
+ if (goalRequest && (goalActivation?.cancelled || this.isDestroyed || this.abortOperations.has(threadId))) {
2403
+ throw new Error('Native goal activation was cancelled before it started.');
2404
+ }
2405
+ // No await between releasing preparation ownership and the adapter's
2406
+ // synchronous installation of native goal ownership.
2407
+ if (goalActivation && this.pendingGoalActivations.get(threadId) === goalActivation) {
2408
+ this.pendingGoalActivations.delete(threadId);
2409
+ }
2410
+ const result = goalRequest
2411
+ ? await executor.executeGoal(goalRequest, guardedOptions)
2412
+ : await executor.execute(prompt, guardedOptions);
2413
+ if (goalRequest && !streamedAny && result.output)
2414
+ guardedOptions.onStream?.(result.output);
2296
2415
  executionMetadata = (0, ExecutionMetadata_1.mergeReportedExecutionMetadata)(launchMetadata, (0, ExecutionMetadata_1.captureExecutionMetadata)(executor, backend));
2297
2416
  capturedMetadata = true;
2298
2417
  active = false;
@@ -2303,9 +2422,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2303
2422
  scope?.finishExecution(result.success);
2304
2423
  finished = true;
2305
2424
  if (flushImages) {
2306
- await Promise.all(pendingLocalImageEmissions);
2307
- const visibleResult = scope && (suppressed || scope.hasPendingResults()) ? '' : result.output ?? '';
2308
- await emitLocalImages(`${streamedOutput}\n${visibleResult}`);
2425
+ imageDelivery?.flushStream();
2426
+ const visibleResult = imageTextDropped || this.imageConnectionEpoch !== imageEpoch
2427
+ || (scope && (suppressed || scope.hasPendingResults())) ? '' : result.output ?? '';
2428
+ queueLocalImages(visibleResult);
2429
+ await imageDelivery?.drain();
2309
2430
  }
2310
2431
  assertCoordinatorActive();
2311
2432
  return result;
@@ -2319,7 +2440,8 @@ You can also use natural language commands to control Claude Code CLI.`,
2319
2440
  delegationBridge?.activate(undefined);
2320
2441
  if (!finished)
2321
2442
  scope?.finishExecution(false);
2322
- mayRetry = !scope || scope.getLaunchRevision() === launchRevision;
2443
+ // Goal activation mutates durable native state; never replay it as a model retry.
2444
+ mayRetry = !goalRequest && (!scope || scope.getLaunchRevision() === launchRevision);
2323
2445
  }
2324
2446
  };
2325
2447
  let prompt = content;
@@ -2514,7 +2636,7 @@ You can also use natural language commands to control Claude Code CLI.`,
2514
2636
  }
2515
2637
  sendStreamChunk(messageId, threadId, chunk) {
2516
2638
  try {
2517
- this.wsClient.send({
2639
+ return this.wsClient.send({
2518
2640
  type: 'stream',
2519
2641
  messageId,
2520
2642
  chunk,
@@ -2522,10 +2644,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2522
2644
  openId: this.getMessageOpenId(messageId),
2523
2645
  threadId,
2524
2646
  timestamp: Date.now(),
2525
- });
2647
+ }) !== false;
2526
2648
  }
2527
2649
  catch (error) {
2528
2650
  console.error('Failed to send stream chunk:', error);
2651
+ return false;
2529
2652
  }
2530
2653
  }
2531
2654
  sendToolUse(messageId, threadId, toolUse) {
@@ -2624,7 +2747,7 @@ You can also use natural language commands to control Claude Code CLI.`,
2624
2747
  }
2625
2748
  sendImage(messageId, threadId, image) {
2626
2749
  try {
2627
- this.wsClient.send({
2750
+ return this.wsClient.send({
2628
2751
  type: 'stream',
2629
2752
  messageId,
2630
2753
  streamType: 'image',
@@ -2632,10 +2755,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2632
2755
  openId: this.getMessageOpenId(messageId),
2633
2756
  threadId,
2634
2757
  timestamp: Date.now(),
2635
- });
2758
+ }) !== false;
2636
2759
  }
2637
- catch (error) {
2638
- console.error('Failed to send generated image:', error);
2760
+ catch {
2761
+ console.warn('Failed to send generated image.');
2762
+ return false;
2639
2763
  }
2640
2764
  }
2641
2765
  sendStructuredContent(messageId, threadId, structuredContent) {
@@ -2718,7 +2842,7 @@ You can also use natural language commands to control Claude Code CLI.`,
2718
2842
  return undefined;
2719
2843
  const projection = (entry) => ({ id: entry.id, backend: entry.backend,
2720
2844
  cwd: entry.workingDirectory, generation: entry.delegationWorkspaceGeneration, model: entry.model,
2721
- models: entry.models, efforts: entry.efforts, delegation: entry.delegation, delegationBackends: entry.delegationBackends });
2845
+ models: entry.models, efforts: entry.efforts, speeds: entry.speeds, delegation: entry.delegation, delegationBackends: entry.delegationBackends });
2722
2846
  const all = this.threadManager.listThreads().map(projection).sort((a, b) => a.id.localeCompare(b.id));
2723
2847
  const configuredModels = { ...thread.models };
2724
2848
  if (configuredModels.claude === undefined && thread.model)
@@ -2786,6 +2910,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2786
2910
  const config = this.executorConfiguration();
2787
2911
  const coordinator = this.threadPool.getBackendKey(threadId);
2788
2912
  const apply = async (executor) => {
2913
+ if (kind === 'speed') {
2914
+ if (!this.modelSpeedSettingsSupported || !executor.setSpeed)
2915
+ return { success: false, error: 'Native speed settings are unavailable.' };
2916
+ return executor.setSpeed(value);
2917
+ }
2789
2918
  if (kind === 'effort') {
2790
2919
  if (!executor.setEffort)
2791
2920
  return { success: false, error: 'This backend does not expose effort controls.' };
@@ -2822,7 +2951,7 @@ You can also use natural language commands to control Claude Code CLI.`,
2822
2951
  await this.threadManager.updateThread(threadId, { models: Object.keys(models).length ? models : undefined,
2823
2952
  ...(backend === 'claude' ? { model: value } : {}) });
2824
2953
  }
2825
- else {
2954
+ else if (kind === 'effort') {
2826
2955
  const efforts = { ...current.efforts };
2827
2956
  if (value === undefined)
2828
2957
  delete efforts[backend];
@@ -2830,6 +2959,14 @@ You can also use natural language commands to control Claude Code CLI.`,
2830
2959
  efforts[backend] = value;
2831
2960
  await this.threadManager.updateThread(threadId, { efforts: Object.keys(efforts).length ? efforts : undefined });
2832
2961
  }
2962
+ else {
2963
+ const speeds = { ...current.speeds };
2964
+ // Null records an explicit return to native configuration, so resumed
2965
+ // worker lanes also clear any previously sticky tier. Unset old threads
2966
+ // retain their original RPC shapes without additional settings reads.
2967
+ speeds[backend] = value ?? null;
2968
+ await this.threadManager.updateThread(threadId, { speeds: Object.keys(speeds).length ? speeds : undefined });
2969
+ }
2833
2970
  return result;
2834
2971
  }
2835
2972
  finally {
@@ -2997,6 +3134,9 @@ You can also use natural language commands to control Claude Code CLI.`,
2997
3134
  this.directoryBrowser.destroy();
2998
3135
  this.settingsService.destroy();
2999
3136
  this.isDestroyed = true;
3137
+ for (const delivery of this.imageDeliveries.values())
3138
+ delivery.cancel();
3139
+ this.imageDeliveries.clear();
3000
3140
  await this.fileInbox?.destroy();
3001
3141
  this.notificationAdapter.unregister();
3002
3142
  try {