@yu_robotics/remote-cli 1.6.169 → 1.6.173

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 (49) hide show
  1. package/README.md +21 -2
  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 +3 -0
  7. package/dist/client/MessageHandler.d.ts.map +1 -1
  8. package/dist/client/MessageHandler.js +167 -43
  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 +8 -1
  17. package/dist/client/WebSocketClient.js.map +1 -1
  18. package/dist/executor/CodexAppServerClient.d.ts +1 -0
  19. package/dist/executor/CodexAppServerClient.d.ts.map +1 -1
  20. package/dist/executor/CodexAppServerClient.js +12 -4
  21. package/dist/executor/CodexAppServerClient.js.map +1 -1
  22. package/dist/executor/CodexAppServerExecutor.d.ts +28 -2
  23. package/dist/executor/CodexAppServerExecutor.d.ts.map +1 -1
  24. package/dist/executor/CodexAppServerExecutor.js +517 -30
  25. package/dist/executor/CodexAppServerExecutor.js.map +1 -1
  26. package/dist/executor/IExecutor.d.ts +24 -0
  27. package/dist/executor/IExecutor.d.ts.map +1 -1
  28. package/dist/executor/codex/CodexGoals.d.ts +6 -0
  29. package/dist/executor/codex/CodexGoals.d.ts.map +1 -0
  30. package/dist/executor/codex/CodexGoals.js +49 -0
  31. package/dist/executor/codex/CodexGoals.js.map +1 -0
  32. package/dist/executor/codex/CodexImages.d.ts +22 -0
  33. package/dist/executor/codex/CodexImages.d.ts.map +1 -0
  34. package/dist/executor/codex/CodexImages.js +201 -0
  35. package/dist/executor/codex/CodexImages.js.map +1 -0
  36. package/dist/maintenance/release-notes.json +1 -1
  37. package/dist/utils/ImageDelivery.d.ts +67 -0
  38. package/dist/utils/ImageDelivery.d.ts.map +1 -0
  39. package/dist/utils/ImageDelivery.js +270 -0
  40. package/dist/utils/ImageDelivery.js.map +1 -0
  41. package/dist/utils/ImageValidation.d.ts +11 -0
  42. package/dist/utils/ImageValidation.d.ts.map +1 -0
  43. package/dist/utils/ImageValidation.js +62 -0
  44. package/dist/utils/ImageValidation.js.map +1 -0
  45. package/dist/utils/LocalImageDetector.d.ts +12 -3
  46. package/dist/utils/LocalImageDetector.d.ts.map +1 -1
  47. package/dist/utils/LocalImageDetector.js +153 -55
  48. package/dist/utils/LocalImageDetector.js.map +1 -1
  49. package/package.json +1 -1
package/README.md CHANGED
@@ -20,8 +20,6 @@ Router 1.6.117 and newer explicitly label standalone background-task notificatio
20
20
 
21
21
  ## Public Activity
22
22
 
23
- From Router 1.6.169, reply-card thread lists start collapsed. Use **Show threads** to expand the list in the same card, or **Switch to this thread** to select the thread that produced the reply. Opening or hiding the list does not change the input target; a successful switch hides the list and confirms the new target without changing the reply's source label. No separate thread-management card is sent. The recursive element budget is 180 instead of 150, with room reserved for the expanded controls; table and size thresholds are unchanged. This presentation update requires the newer Router, not a new CLI protocol capability.
24
-
25
23
  From Router 1.6.158, completion pagination keeps the final body item (or complete worker group) with the completion note, optional model metadata and thread controls when they fit together. This avoids preventable footer-only continuations, including replies without model metadata. Indivisible oversized groups still split according to node, size and table limits; thread-button refreshes preserve the final body. This does not change streaming completion timing or the wire protocol.
26
24
 
27
25
  From CLI 1.6.153, Codex shell calls no longer lose their label when native command actions are absent or unknown. Native reads, searches, and directory listings remain preferred. Other calls use fixed operation labels such as `Run npm command` or `Run Git command` for simple allowlisted programs, and `Run shell command` for compound or unrecognized commands. These are deterministic operation labels, not model-authored purpose summaries: installed Codex 0.160.0 command items have no per-call description field. Arguments, full paths, output, and execution-wide reasoning are never copied into these fallback labels. Late native action metadata can enrich a pending row before its first result; repeated results retain one row. Existing description fields, heading limits, and card-node counts are unchanged; upgrade the CLI, with no new Router capability required.
@@ -80,6 +78,12 @@ The client is normally installed and run directly on the developer's machine rat
80
78
 
81
79
  Claude tool-result content blocks are normalized to text; embedded media and unknown payloads are not scanned for local files. Local image detection and forwarding are best-effort: failures do not fail the task or terminate the CLI. Web-result excerpts remain excluded from local-file scanning.
82
80
 
81
+ CLI and Router 1.6.173 add direct forwarding for recognized successful Codex native image results, MCP image blocks, and dynamic-tool inline images. These images do not require a Markdown link. Native saved-file references use the same local directory policy as Markdown images. Structured extraction does not traverse arbitrary JSON path fields or publish image-view events, and remote URLs are not fetched. Ordinary tool-result text retains local-path detection; shell tools that return only text still need a local path or Markdown image link.
82
+
83
+ Each reply accepts up to 32 image candidates and displays up to 8 distinct images, with a 2 MiB decoded limit per image. Supported raster formats are checked from their bytes. Local files must pass canonical directory and regular-file checks; denied, missing, invalid, oversized, or timed-out images produce a bounded omission notice while text can still complete. Identical bytes are deduplicated within the reply, including native and Markdown copies; changed bytes at the same path can be displayed again. Reads and uploads have bounded concurrency and waiting times, and late results cannot be attached after cancellation or replacement.
84
+
85
+ Upgrade both CLI and Router for the complete fix: the CLI discovers and validates images, while the Router waits for admitted uploads before completing the card. The image wire format remains compatible with older image-capable peers, but an older Router retains its upload/finalization race. Delivery is best-effort with no image replay across disconnects; a model saying it sent an image is not a delivery acknowledgement.
86
+
83
87
  Sending local image files requires a Router with image forwarding support and
84
88
  the bot application's `im:resource` permission. Router 1.6.85 and newer render
85
89
  local and URL-based Markdown image references as captions; the uploaded image
@@ -433,6 +437,21 @@ With CLI 1.6.93 or newer, `/cd` to a different normalized directory clears saved
433
437
 
434
438
  Thread switch panels show `Reply from:` with the reply's full thread name and workspace. Under `Switch thread`, a check mark (`✓`) and primary button styling identify the destination of new top-level messages when the card is finalized or clicked. Selected buttons remain clickable. Switching updates only the clicked card; other historical cards retain their last displayed selection.
435
439
 
440
+ From Router 1.6.171, completed cards keep the thread buttons and **+ New** inside a native Feishu **Switch thread** folding panel, collapsed by default. Click its header to expand or collapse the list without sending a command or changing the input target. The reply-source caption remains visible. Selecting a thread uses the existing routing behavior. Hidden buttons still count toward card limits; unrelated full-card updates can reset the panel to its default collapsed state. This uses the existing Card 2.0 client requirement (Feishu 7.20 or newer).
441
+
442
+ From CLI 1.6.172, Codex threads support native goals through `/goal` (status), `/goal <objective>` (start), `/goal pause`, `/goal resume`, and `/goal clear`. Start or resume only when the thread and queue are idle, with managed delegation explicitly disabled using `/delegation off`. This initial restriction does not disable Codex's native subagents. Optional `--budget <tokens>` on start or resume sets an explicit positive token budget; omitting it does not invent a budget. For example:
443
+
444
+ ```text
445
+ /delegation off
446
+ /goal --budget 40000 Fix the failing tests and verify the public API stays compatible
447
+ /goal
448
+ /goal pause
449
+ /goal resume
450
+ /goal clear
451
+ ```
452
+
453
+ Codex owns the persisted goal and automatic continuations; remote-cli keeps the execution card, approvals, and busy-state reservation across native turns. Status, pause, clear, and `/abort` remain available during work. Pause and clear stop current goal work; `/abort` pauses the goal, while clear removes the goal without clearing the conversation. After process loss, inspect with `/goal` and explicitly recover with `/goal resume`; status queries do not load or start the goal. Send attachments in an ordinary message before starting a goal. Use a Codex build exposing the native thread-goal APIs (verified with 0.163.0); unavailable APIs or disabled goals report an error. Other backends reject `/goal`. See [the native goal API](https://learn.chatgpt.com/docs/app-server#manage-a-thread-goal).
454
+
436
455
  With a CLI and Router that support queue-start notifications, confirming a message keeps a static queue receipt. When the task actually starts, a new execution card appears at the bottom of the chat with its thread, working directory, task preview, and remaining queue count. Progress and results stay on that new card, and the old receipt points to it. Older clients retain the waiting-card behavior. Queued work remains in memory; this does not make queues persistent across service restarts.
437
456
 
438
457
  When a thread is busy, ordinary messages require confirmation before they enter its queue. Use `/queue` to inspect pending work, `/queue clear` to discard queued messages, and `/abort` to stop the active task and clear that thread's queue. A message received during abort cleanup waits and starts after the backend is safe to reuse. A backend failure in the active task still advances to the first confirmed queued message; a failure in a task taken from the queue pauses the remaining queue. Context-changing commands such as `/model`, `/effort`, `/cd`, `/compact`, `/clear`, and its `/new` alias are handled separately rather than queued as ordinary messages. Feishu thread buttons show automatic names such as `thread-2` as their sequence number, such as `2`; routing continues to use the full internal name. Continuation cards repeat the thread and working-directory header of the original card.
@@ -0,0 +1,15 @@
1
+ import type { ExecutorGoal, ExecutorGoalRunRequest } from '../executor/IExecutor';
2
+ export declare const GOAL_USAGE: string;
3
+ export type GoalCommand = {
4
+ kind: 'show' | 'pause' | 'clear' | 'help';
5
+ } | {
6
+ kind: 'run';
7
+ request: ExecutorGoalRunRequest;
8
+ } | {
9
+ kind: 'invalid';
10
+ error: string;
11
+ };
12
+ /** Parse before approval/input routing so control commands never answer a tool prompt. */
13
+ export declare function parseGoalCommand(content: string): GoalCommand | undefined;
14
+ export declare function formatGoal(goal: ExecutorGoal | null): string;
15
+ //# sourceMappingURL=GoalCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoalCommand.d.ts","sourceRoot":"","sources":["../../src/client/GoalCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAElF,eAAO,MAAM,UAAU,QAMX,CAAC;AAEb,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,sBAAsB,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CA6BzE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,CAU5D"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GOAL_USAGE = void 0;
4
+ exports.parseGoalCommand = parseGoalCommand;
5
+ exports.formatGoal = formatGoal;
6
+ exports.GOAL_USAGE = [
7
+ '/goal - Show the current Codex goal',
8
+ '/goal [--budget <tokens>] <objective> - Start a native goal',
9
+ '/goal pause - Pause the goal and stop its current work',
10
+ '/goal resume [--budget <tokens>] - Resume the existing goal',
11
+ '/goal clear - Remove the goal and stop its current work',
12
+ ].join('\n');
13
+ /** Parse before approval/input routing so control commands never answer a tool prompt. */
14
+ function parseGoalCommand(content) {
15
+ const match = /^\/goal(?:\s+([\s\S]*))?$/.exec(content.trim());
16
+ if (!match)
17
+ return undefined;
18
+ const argument = (match[1] ?? '').trim();
19
+ if (!argument || argument === 'status')
20
+ return { kind: 'show' };
21
+ if (argument === 'pause' || argument === 'clear' || argument === 'help')
22
+ return { kind: argument };
23
+ const resume = /^resume(?:\s|$)/.test(argument);
24
+ let objective = resume ? argument.slice('resume'.length).trim() : argument;
25
+ let tokenBudget;
26
+ if (/^--budget(?:\s|$)/.test(objective)) {
27
+ const budget = /^--budget\s+([0-9]+)(?:\s+([\s\S]*))?$/.exec(objective);
28
+ if (!budget || !Number.isSafeInteger(Number(budget[1])) || Number(budget[1]) <= 0) {
29
+ return { kind: 'invalid', error: 'Goal token budget must be a positive safe integer.\n' + exports.GOAL_USAGE };
30
+ }
31
+ tokenBudget = Number(budget[1]);
32
+ objective = (budget[2] ?? '').trim();
33
+ }
34
+ if (resume) {
35
+ if (objective)
36
+ return { kind: 'invalid', error: 'Resume does not accept a new objective.\n' + exports.GOAL_USAGE };
37
+ return { kind: 'run', request: tokenBudget === undefined ? {} : { tokenBudget } };
38
+ }
39
+ if (!objective || objective.startsWith('--') || /^(?:pause|clear|status|help)(?:\s|$)/.test(objective)) {
40
+ return { kind: 'invalid', error: 'Invalid goal command.\n' + exports.GOAL_USAGE };
41
+ }
42
+ if (Array.from(objective).length > 4000) {
43
+ return { kind: 'invalid', error: 'Goal objectives must contain at most 4,000 characters.' };
44
+ }
45
+ return { kind: 'run', request: { objective, ...(tokenBudget === undefined ? {} : { tokenBudget }) } };
46
+ }
47
+ function formatGoal(goal) {
48
+ if (!goal)
49
+ return 'No Codex goal is set for this thread.\n\n' + exports.GOAL_USAGE;
50
+ return [
51
+ 'Codex goal',
52
+ `Objective: ${goal.objective}`,
53
+ `Status: ${goal.status}`,
54
+ `Tokens used: ${goal.tokensUsed}${goal.tokenBudget === null ? ' (no explicit budget)' : ` / ${goal.tokenBudget}`}`,
55
+ `Active time: ${goal.timeUsedSeconds}s`,
56
+ '', exports.GOAL_USAGE,
57
+ ].join('\n');
58
+ }
59
+ //# sourceMappingURL=GoalCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoalCommand.js","sourceRoot":"","sources":["../../src/client/GoalCommand.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,7 @@ export declare class MessageHandler {
34
34
  private readonly sessionControl;
35
35
  private readonly activeThreadOperations;
36
36
  private readonly abortOperations;
37
+ private readonly pendingGoalActivations;
37
38
  private automaticUpdateInProgress;
38
39
  private approvalCardsSupported;
39
40
  private streamingContextSupported;
@@ -53,6 +54,8 @@ export declare class MessageHandler {
53
54
  private readonly delegation;
54
55
  private readonly delegationBridges;
55
56
  private fileInbox?;
57
+ private readonly imageDeliveries;
58
+ private imageConnectionEpoch;
56
59
  constructor(wsClient: WebSocketClient, threadPool: ThreadExecutorPool, threadManager: ThreadManager, directoryGuard: DirectoryGuard, config: ConfigManager);
57
60
  reconcilePendingDelegatedWorkers(): Promise<void>;
58
61
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"MessageHandler.d.ts","sourceRoot":"","sources":["../../src/client/MessageHandler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MessageHandler.d.ts","sourceRoot":"","sources":["../../src/client/MessageHandler.ts"],"names":[],"mappings":"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"}
@@ -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;
@@ -186,6 +189,8 @@ class MessageHandler {
186
189
  delegation;
187
190
  delegationBridges = new Map();
188
191
  fileInbox;
192
+ imageDeliveries = new Map();
193
+ imageConnectionEpoch = 0;
189
194
  constructor(wsClient, threadPool, threadManager, directoryGuard, config) {
190
195
  this.wsClient = wsClient;
191
196
  this.threadPool = threadPool;
@@ -193,8 +198,11 @@ class MessageHandler {
193
198
  this.directoryGuard = directoryGuard;
194
199
  this.config = config;
195
200
  this.wsClient.onClose?.(() => {
201
+ this.imageConnectionEpoch++;
196
202
  this.routerVersion = undefined;
197
203
  this.fileInbox?.disconnect();
204
+ for (const delivery of this.imageDeliveries.values())
205
+ delivery.invalidatePending();
198
206
  });
199
207
  this.delegation = new DelegationManager_1.DelegationManager(directoryGuard);
200
208
  this.settingsCatalogReader = new SettingsCatalog_1.SettingsCatalogReader(directoryGuard);
@@ -525,6 +533,53 @@ class MessageHandler {
525
533
  }
526
534
  const executor = this.threadPool.getExecutor(resolvedThreadId);
527
535
  this.sendStreamContext(messageId, resolvedThreadId, executor);
536
+ const goalCommand = (0, GoalCommand_1.parseGoalCommand)(settingsCommand);
537
+ let goalRequest;
538
+ if (goalCommand) {
539
+ if (goalCommand.kind === 'invalid' || goalCommand.kind === 'help') {
540
+ this.sendResponse(messageId, resolvedThreadId, goalCommand.kind === 'invalid'
541
+ ? { success: false, error: goalCommand.error } : { success: true, output: GoalCommand_1.GOAL_USAGE });
542
+ return;
543
+ }
544
+ if (this.threadPool.getBackendKey(resolvedThreadId) !== 'codex'
545
+ || !executor.getGoal || !executor.executeGoal || !executor.pauseGoal || !executor.clearGoal) {
546
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
547
+ error: '/goal requires the Codex app-server backend and an executor version with native goal support.' });
548
+ return;
549
+ }
550
+ if (attachments?.length || message.fileIds?.length) {
551
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
552
+ error: 'Send attachments in a normal message before starting a goal. No goal was changed.' });
553
+ return;
554
+ }
555
+ if (goalCommand.kind !== 'run') {
556
+ try {
557
+ const pending = this.pendingGoalActivations.get(resolvedThreadId);
558
+ if (pending && goalCommand.kind !== 'show')
559
+ pending.cancelled = true;
560
+ const result = goalCommand.kind === 'show' ? { success: true, output: (0, GoalCommand_1.formatGoal)(await executor.getGoal()) }
561
+ : goalCommand.kind === 'pause' ? await executor.pauseGoal() : await executor.clearGoal();
562
+ this.sendResponse(messageId, resolvedThreadId, { ...result, threads: this.threadPool.getSummaries() });
563
+ }
564
+ catch (error) {
565
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
566
+ error: `Goal control failed: ${error instanceof Error ? error.message : String(error)}` });
567
+ }
568
+ return;
569
+ }
570
+ if ((0, DelegationSettings_1.isDelegationEnabled)(thread) || this.delegation.hasActiveTasks(resolvedThreadId)) {
571
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
572
+ error: 'Native goals require managed delegation to be off. Wait for workers to finish and send /delegation off in this thread, then retry /goal.' });
573
+ return;
574
+ }
575
+ if (this.threadPool.isThreadBusy(resolvedThreadId) || executor.isBusy?.()
576
+ || this.hasThreadQueueState(resolvedThreadId) || this.abortOperations.has(resolvedThreadId)) {
577
+ this.sendResponse(messageId, resolvedThreadId, { success: false,
578
+ 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.' });
579
+ return;
580
+ }
581
+ goalRequest = goalCommand.request;
582
+ }
528
583
  // Handle /abort for this specific thread (bypasses busy check)
529
584
  if (content?.trim() === '/abort') {
530
585
  await this.handleAbortCommand(messageId, resolvedThreadId, executor);
@@ -594,7 +649,7 @@ class MessageHandler {
594
649
  return;
595
650
  }
596
651
  const inputExecutor = this.delegation.waitingExecutor(resolvedThreadId) ?? executor;
597
- if ('isWaitingInput' in inputExecutor && typeof inputExecutor.isWaitingInput === 'function') {
652
+ if (!goalRequest && 'isWaitingInput' in inputExecutor && typeof inputExecutor.isWaitingInput === 'function') {
598
653
  const ex = inputExecutor;
599
654
  if (ex.isWaitingInput()) {
600
655
  const input = content?.trim();
@@ -682,6 +737,9 @@ class MessageHandler {
682
737
  });
683
738
  return;
684
739
  }
740
+ const goalActivation = goalRequest ? { cancelled: false } : undefined;
741
+ if (goalActivation)
742
+ this.pendingGoalActivations.set(resolvedThreadId, goalActivation);
685
743
  const operationToken = this.beginThreadOperation(resolvedThreadId);
686
744
  try {
687
745
  this.trackTask(messageId, resolvedThreadId, content || '');
@@ -716,6 +774,10 @@ class MessageHandler {
716
774
  await this.executePendingReplace(messageId, resolvedThreadId, pendingKey, content);
717
775
  return;
718
776
  }
777
+ if (goalRequest) {
778
+ await this.executeCommand(messageId, resolvedThreadId, content, executor, undefined, false, goalRequest, goalActivation);
779
+ return;
780
+ }
719
781
  const builtInResult = await this.handleBuiltInCommand(messageId, resolvedThreadId, content, executor);
720
782
  if (builtInResult)
721
783
  return;
@@ -736,6 +798,9 @@ class MessageHandler {
736
798
  });
737
799
  }
738
800
  finally {
801
+ if (goalActivation && this.pendingGoalActivations.get(resolvedThreadId) === goalActivation) {
802
+ this.pendingGoalActivations.delete(resolvedThreadId);
803
+ }
739
804
  if (this.finishThreadOperation(resolvedThreadId, operationToken) && !this.pausedQueues.has(resolvedThreadId)) {
740
805
  void this.startNextQueuedCommand(resolvedThreadId);
741
806
  }
@@ -746,9 +811,20 @@ class MessageHandler {
746
811
  */
747
812
  async handleAbortCommand(messageId, threadId, executor) {
748
813
  this.fileInbox?.cancelThread(threadId);
814
+ this.imageDeliveries.get(threadId)?.cancel();
815
+ const pendingGoal = this.pendingGoalActivations.get(threadId);
816
+ if (pendingGoal)
817
+ pendingGoal.cancelled = true;
749
818
  const existingAbort = this.abortOperations.get(threadId);
750
819
  if (existingAbort) {
751
820
  await existingAbort;
821
+ if (executor.isGoalRunning?.()) {
822
+ this.sendResponse(messageId, threadId, {
823
+ success: false,
824
+ error: 'Native goal stop could not be confirmed. This thread remains busy; retry /goal pause or /abort.',
825
+ });
826
+ return;
827
+ }
752
828
  this.sendResponse(messageId, threadId, {
753
829
  success: true,
754
830
  output: 'ℹ️ The abort operation has already completed.',
@@ -763,6 +839,9 @@ class MessageHandler {
763
839
  if ((0, DelegationSettings_1.isDelegationEnabled)(this.threadManager.getThread(threadId)))
764
840
  await this.delegation.cancelThread(threadId);
765
841
  aborted = await executor.abort();
842
+ if (!aborted && executor.isGoalRunning?.()) {
843
+ throw new Error('Native goal stop could not be confirmed. This thread remains busy; retry /goal pause or /abort.');
844
+ }
766
845
  if (activeOperation)
767
846
  await activeOperation.done;
768
847
  })();
@@ -1051,6 +1130,7 @@ Use /compact to reduce conversation context or /clear to start a fresh context.`
1051
1130
  - /cd <directory> - Change working directory for this thread
1052
1131
  - /model [name] - Open model settings, or set the active backend model with a text argument
1053
1132
  - /effort [auto|level] - Open effort settings, or set a native supported level with a text argument
1133
+ - /goal [objective|pause|resume|clear] - Manage a native Codex goal; optional --budget <tokens> on start or resume
1054
1134
  - /sandbox [on|off|read-only|default|allow <directory>|remove <directory>|network on|off] - Configure this thread's sandbox (Codex, Claude Code)
1055
1135
  - /delegation [on|off|reset [backend]] - Delegate tasks between installed agent backends; reset saved worker context when needed
1056
1136
  - /backend - Open backend settings (Current thread by default; Confirm applies)
@@ -2010,12 +2090,13 @@ You can also use natural language commands to control Claude Code CLI.`,
2010
2090
  /**
2011
2091
  * Execute AI command on a specific executor
2012
2092
  */
2013
- async executeCommand(messageId, threadId, content, executor, attachments, fromQueue = false) {
2093
+ async executeCommand(messageId, threadId, content, executor, attachments, fromQueue = false, goalRequest, goalActivation) {
2014
2094
  this.sendStreamContext(messageId, threadId, executor);
2015
2095
  let delegationScope;
2016
2096
  let delegationBridge;
2017
2097
  let executionMetadata;
2018
2098
  let requestFinished = false;
2099
+ let imageDelivery;
2019
2100
  let executionActive = false;
2020
2101
  let lastProgramState;
2021
2102
  const activity = new ActivityProgress_1.ActivityProgress(snapshot => {
@@ -2040,6 +2121,9 @@ You can also use natural language commands to control Claude Code CLI.`,
2040
2121
  lastProgramState = text;
2041
2122
  };
2042
2123
  const complete = async (success, error) => {
2124
+ await imageDelivery?.finish();
2125
+ if (this.imageDeliveries.get(threadId) === imageDelivery)
2126
+ this.imageDeliveries.delete(threadId);
2043
2127
  requestFinished = true;
2044
2128
  activity.dispose();
2045
2129
  delegationBridge?.activate(undefined);
@@ -2079,6 +2163,9 @@ You can also use natural language commands to control Claude Code CLI.`,
2079
2163
  }
2080
2164
  const openId = this.getMessageOpenId(messageId);
2081
2165
  const thread = this.threadManager.getThread(threadId);
2166
+ if (goalRequest && thread && (0, DelegationSettings_1.isDelegationEnabled)(thread)) {
2167
+ throw new Error('Turn managed delegation off before starting a native goal.');
2168
+ }
2082
2169
  if (thread && (0, DelegationSettings_1.isDelegationEnabled)(thread) && executor.configureDelegation) {
2083
2170
  if (this.delegation.blocksWorkspace(executor.getCurrentWorkingDirectory(), threadId)) {
2084
2171
  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 +2220,41 @@ You can also use natural language commands to control Claude Code CLI.`,
2133
2220
  return;
2134
2221
  this.notificationAdapter.sendTaskNotification({ ...notification, threadId }, openId);
2135
2222
  };
2136
- const emittedLocalImages = new Set();
2137
- const pendingLocalImageEmissions = new Set();
2223
+ const imageCwd = thread?.workingDirectory ?? executor.getCurrentWorkingDirectory();
2224
+ const imageGeneration = thread?.delegationWorkspaceGeneration ?? 0;
2225
+ const imageBackend = this.threadPool.getBackendKey(threadId);
2226
+ let imagePhase = 0;
2227
+ const imageEligible = () => {
2228
+ const phase = imagePhase;
2229
+ return () => phase === imagePhase && (!delegationScope
2230
+ || (!delegationScope.isClosed() && !delegationScope.hasPendingResults()));
2231
+ };
2232
+ imageDelivery = new ImageDelivery_1.ImageDelivery({
2233
+ readFile: filePath => (0, LocalImageDetector_1.readLocalImagePath)(filePath, imageCwd, this.directoryGuard),
2234
+ publish: image => {
2235
+ if (!this.sendImage(messageId, threadId, image))
2236
+ throw new Error('Image send failed');
2237
+ },
2238
+ notice: text => this.sendStreamChunk(messageId, threadId, text),
2239
+ isCurrent: () => {
2240
+ const current = this.threadManager.getThread(threadId);
2241
+ return !requestFinished && !this.isDestroyed && !this.abortOperations.has(threadId)
2242
+ && this.wsClient.isConnected() && this.imageDeliveries.get(threadId) === imageDelivery
2243
+ && this.wsClient.canSendTaskOutput?.(messageId) !== false
2244
+ && !!current && current.workingDirectory === imageCwd
2245
+ && (current.delegationWorkspaceGeneration ?? 0) === imageGeneration
2246
+ && this.threadPool.getBackendKey(threadId) === imageBackend;
2247
+ },
2248
+ });
2249
+ this.imageDeliveries.get(threadId)?.cancel();
2250
+ this.imageDeliveries.set(threadId, imageDelivery);
2138
2251
  const delegationControlTools = new Set();
2139
2252
  const toolImageStates = new Map();
2140
2253
  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
- };
2254
+ let streamedAny = false;
2255
+ let imageTextDropped = false;
2164
2256
  const queueLocalImages = (text) => {
2165
- const pending = emitLocalImages(text).finally(() => pendingLocalImageEmissions.delete(pending));
2166
- pendingLocalImageEmissions.add(pending);
2257
+ imageDelivery?.offerText(text, imageEligible());
2167
2258
  };
2168
2259
  const executeOptions = {
2169
2260
  onActivity: this.activityProgressSupported ? (snapshot) => activity.offer(snapshot) : undefined,
@@ -2174,8 +2265,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2174
2265
  onApprovalResolved: (requestId, status) => this.resolveApprovalCard(requestId, status),
2175
2266
  onTaskNotification,
2176
2267
  onStream: (chunk) => {
2177
- streamedOutput += chunk;
2178
- this.sendStreamChunk(messageId, threadId, chunk);
2268
+ streamedAny = true;
2269
+ if (this.sendStreamChunk(messageId, threadId, chunk))
2270
+ imageDelivery?.offerStream(chunk, imageEligible());
2271
+ else
2272
+ imageTextDropped = true;
2179
2273
  },
2180
2274
  onToolUse: (toolUse) => {
2181
2275
  if (delegationScope && /remote_cli_(list_backends|delegate|result|cancel|integrate)$/.test(toolUse.name)) {
@@ -2213,7 +2307,8 @@ You can also use natural language commands to control Claude Code CLI.`,
2213
2307
  },
2214
2308
  onRedactedThinking: () => this.sendRedactedThinking(messageId, threadId),
2215
2309
  onPlanMode: (planContent) => this.sendPlanMode(messageId, threadId, planContent),
2216
- onImage: (image) => this.sendImage(messageId, threadId, image),
2310
+ onImage: (image) => imageDelivery?.offerImage(image, imageEligible()),
2311
+ onImageFile: (filePath) => imageDelivery?.offerFile(filePath, imageEligible()),
2217
2312
  attachments,
2218
2313
  };
2219
2314
  const assertCoordinatorActive = () => {
@@ -2225,6 +2320,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2225
2320
  let mayRetry = true;
2226
2321
  const executeOnce = async (prompt, options, flushImages = true) => {
2227
2322
  assertCoordinatorActive();
2323
+ imagePhase++;
2324
+ streamedAny = false;
2325
+ imageTextDropped = false;
2326
+ imageDelivery?.resetStream();
2327
+ const imageEpoch = this.imageConnectionEpoch;
2228
2328
  const scope = delegationScope;
2229
2329
  let active = true;
2230
2330
  let finished = false;
@@ -2280,6 +2380,10 @@ You can also use natural language commands to control Claude Code CLI.`,
2280
2380
  options.onImage?.(image);
2281
2381
  else
2282
2382
  suppressed = true; },
2383
+ onImageFile: filePath => { if (canPublish())
2384
+ options.onImageFile?.(filePath);
2385
+ else
2386
+ suppressed = true; },
2283
2387
  onRedactedThinking: () => { if (canPublish())
2284
2388
  options.onRedactedThinking?.(); },
2285
2389
  onToolUse: tool => { if (active && !scope?.isClosed())
@@ -2292,7 +2396,19 @@ You can also use natural language commands to control Claude Code CLI.`,
2292
2396
  executionMetadata = launchMetadata;
2293
2397
  let capturedMetadata = false;
2294
2398
  try {
2295
- const result = await executor.execute(prompt, guardedOptions);
2399
+ if (goalRequest && (goalActivation?.cancelled || this.isDestroyed || this.abortOperations.has(threadId))) {
2400
+ throw new Error('Native goal activation was cancelled before it started.');
2401
+ }
2402
+ // No await between releasing preparation ownership and the adapter's
2403
+ // synchronous installation of native goal ownership.
2404
+ if (goalActivation && this.pendingGoalActivations.get(threadId) === goalActivation) {
2405
+ this.pendingGoalActivations.delete(threadId);
2406
+ }
2407
+ const result = goalRequest
2408
+ ? await executor.executeGoal(goalRequest, guardedOptions)
2409
+ : await executor.execute(prompt, guardedOptions);
2410
+ if (goalRequest && !streamedAny && result.output)
2411
+ guardedOptions.onStream?.(result.output);
2296
2412
  executionMetadata = (0, ExecutionMetadata_1.mergeReportedExecutionMetadata)(launchMetadata, (0, ExecutionMetadata_1.captureExecutionMetadata)(executor, backend));
2297
2413
  capturedMetadata = true;
2298
2414
  active = false;
@@ -2303,9 +2419,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2303
2419
  scope?.finishExecution(result.success);
2304
2420
  finished = true;
2305
2421
  if (flushImages) {
2306
- await Promise.all(pendingLocalImageEmissions);
2307
- const visibleResult = scope && (suppressed || scope.hasPendingResults()) ? '' : result.output ?? '';
2308
- await emitLocalImages(`${streamedOutput}\n${visibleResult}`);
2422
+ imageDelivery?.flushStream();
2423
+ const visibleResult = imageTextDropped || this.imageConnectionEpoch !== imageEpoch
2424
+ || (scope && (suppressed || scope.hasPendingResults())) ? '' : result.output ?? '';
2425
+ queueLocalImages(visibleResult);
2426
+ await imageDelivery?.drain();
2309
2427
  }
2310
2428
  assertCoordinatorActive();
2311
2429
  return result;
@@ -2319,7 +2437,8 @@ You can also use natural language commands to control Claude Code CLI.`,
2319
2437
  delegationBridge?.activate(undefined);
2320
2438
  if (!finished)
2321
2439
  scope?.finishExecution(false);
2322
- mayRetry = !scope || scope.getLaunchRevision() === launchRevision;
2440
+ // Goal activation mutates durable native state; never replay it as a model retry.
2441
+ mayRetry = !goalRequest && (!scope || scope.getLaunchRevision() === launchRevision);
2323
2442
  }
2324
2443
  };
2325
2444
  let prompt = content;
@@ -2514,7 +2633,7 @@ You can also use natural language commands to control Claude Code CLI.`,
2514
2633
  }
2515
2634
  sendStreamChunk(messageId, threadId, chunk) {
2516
2635
  try {
2517
- this.wsClient.send({
2636
+ return this.wsClient.send({
2518
2637
  type: 'stream',
2519
2638
  messageId,
2520
2639
  chunk,
@@ -2522,10 +2641,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2522
2641
  openId: this.getMessageOpenId(messageId),
2523
2642
  threadId,
2524
2643
  timestamp: Date.now(),
2525
- });
2644
+ }) !== false;
2526
2645
  }
2527
2646
  catch (error) {
2528
2647
  console.error('Failed to send stream chunk:', error);
2648
+ return false;
2529
2649
  }
2530
2650
  }
2531
2651
  sendToolUse(messageId, threadId, toolUse) {
@@ -2624,7 +2744,7 @@ You can also use natural language commands to control Claude Code CLI.`,
2624
2744
  }
2625
2745
  sendImage(messageId, threadId, image) {
2626
2746
  try {
2627
- this.wsClient.send({
2747
+ return this.wsClient.send({
2628
2748
  type: 'stream',
2629
2749
  messageId,
2630
2750
  streamType: 'image',
@@ -2632,10 +2752,11 @@ You can also use natural language commands to control Claude Code CLI.`,
2632
2752
  openId: this.getMessageOpenId(messageId),
2633
2753
  threadId,
2634
2754
  timestamp: Date.now(),
2635
- });
2755
+ }) !== false;
2636
2756
  }
2637
- catch (error) {
2638
- console.error('Failed to send generated image:', error);
2757
+ catch {
2758
+ console.warn('Failed to send generated image.');
2759
+ return false;
2639
2760
  }
2640
2761
  }
2641
2762
  sendStructuredContent(messageId, threadId, structuredContent) {
@@ -2997,6 +3118,9 @@ You can also use natural language commands to control Claude Code CLI.`,
2997
3118
  this.directoryBrowser.destroy();
2998
3119
  this.settingsService.destroy();
2999
3120
  this.isDestroyed = true;
3121
+ for (const delivery of this.imageDeliveries.values())
3122
+ delivery.cancel();
3123
+ this.imageDeliveries.clear();
3000
3124
  await this.fileInbox?.destroy();
3001
3125
  this.notificationAdapter.unregister();
3002
3126
  try {