@yu_robotics/remote-cli 1.6.53 → 1.6.66

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 (64) hide show
  1. package/README.md +18 -8
  2. package/dist/client/MessageHandler.d.ts +7 -0
  3. package/dist/client/MessageHandler.d.ts.map +1 -1
  4. package/dist/client/MessageHandler.js +96 -3
  5. package/dist/client/MessageHandler.js.map +1 -1
  6. package/dist/client/TaskRecovery.d.ts +7 -3
  7. package/dist/client/TaskRecovery.d.ts.map +1 -1
  8. package/dist/client/TaskRecovery.js +80 -27
  9. package/dist/client/TaskRecovery.js.map +1 -1
  10. package/dist/client/WebSocketClient.d.ts.map +1 -1
  11. package/dist/client/WebSocketClient.js +2 -10
  12. package/dist/client/WebSocketClient.js.map +1 -1
  13. package/dist/commands/start.d.ts +3 -11
  14. package/dist/commands/start.d.ts.map +1 -1
  15. package/dist/commands/start.js +22 -99
  16. package/dist/commands/start.js.map +1 -1
  17. package/dist/executor/ClaudePersistentExecutor.d.ts +34 -2
  18. package/dist/executor/ClaudePersistentExecutor.d.ts.map +1 -1
  19. package/dist/executor/ClaudePersistentExecutor.js +313 -9
  20. package/dist/executor/ClaudePersistentExecutor.js.map +1 -1
  21. package/dist/executor/CodexAppServerExecutor.d.ts +13 -0
  22. package/dist/executor/CodexAppServerExecutor.d.ts.map +1 -1
  23. package/dist/executor/CodexAppServerExecutor.js +208 -5
  24. package/dist/executor/CodexAppServerExecutor.js.map +1 -1
  25. package/dist/executor/CodexSandbox.d.ts +23 -0
  26. package/dist/executor/CodexSandbox.d.ts.map +1 -0
  27. package/dist/executor/CodexSandbox.js +211 -0
  28. package/dist/executor/CodexSandbox.js.map +1 -0
  29. package/dist/executor/IExecutor.d.ts +5 -1
  30. package/dist/executor/IExecutor.d.ts.map +1 -1
  31. package/dist/executor/claude/ClaudeFilePolicy.d.ts +7 -0
  32. package/dist/executor/claude/ClaudeFilePolicy.d.ts.map +1 -0
  33. package/dist/executor/claude/ClaudeFilePolicy.js +88 -0
  34. package/dist/executor/claude/ClaudeFilePolicy.js.map +1 -0
  35. package/dist/executor/claude/ClaudeSandbox.d.ts +32 -0
  36. package/dist/executor/claude/ClaudeSandbox.d.ts.map +1 -0
  37. package/dist/executor/claude/ClaudeSandbox.js +245 -0
  38. package/dist/executor/claude/ClaudeSandbox.js.map +1 -0
  39. package/dist/executor/claude/approvalMcpServer.d.ts +2 -0
  40. package/dist/executor/claude/approvalMcpServer.d.ts.map +1 -0
  41. package/dist/executor/claude/approvalMcpServer.js +159 -0
  42. package/dist/executor/claude/approvalMcpServer.js.map +1 -0
  43. package/dist/executor/claude/filePolicyHook.d.ts +2 -0
  44. package/dist/executor/claude/filePolicyHook.d.ts.map +1 -0
  45. package/dist/executor/claude/filePolicyHook.js +63 -0
  46. package/dist/executor/claude/filePolicyHook.js.map +1 -0
  47. package/dist/executor/index.d.ts.map +1 -1
  48. package/dist/executor/index.js +4 -3
  49. package/dist/executor/index.js.map +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/thread/ThreadManager.d.ts.map +1 -1
  53. package/dist/thread/ThreadManager.js +2 -0
  54. package/dist/thread/ThreadManager.js.map +1 -1
  55. package/dist/types/config.d.ts +32 -2
  56. package/dist/types/config.d.ts.map +1 -1
  57. package/dist/types/config.js.map +1 -1
  58. package/dist/types/index.d.ts +39 -0
  59. package/dist/types/index.d.ts.map +1 -1
  60. package/dist/update/AutomaticUpdater.d.ts +7 -1
  61. package/dist/update/AutomaticUpdater.d.ts.map +1 -1
  62. package/dist/update/AutomaticUpdater.js +25 -2
  63. package/dist/update/AutomaticUpdater.js.map +1 -1
  64. package/package.json +1 -1
package/README.md CHANGED
@@ -5,15 +5,16 @@ Remote control Claude Code, AGY CLI, Codex CLI, OpenCode CLI, Kimi Code CLI, ZCo
5
5
  ## Features
6
6
 
7
7
  - **Remote Control**: Control your local development environment from anywhere via mobile
8
- - **Secure**: Directory whitelisting, command filtering, and device authentication
8
+ - **Access Controls**: Working-directory allowlist and user-device binding
9
9
  - **Mobile-Optimized**: Simplified commands and rich text formatting for Feishu
10
10
  - **Readable Code Changes**: Edit operations show collapsible, line-aware diff previews inside the existing progress card
11
11
  - **Multi-Backend Support**: Supports Claude Code, AGY CLI (Antigravity), Codex CLI (OpenAI), OpenCode CLI, Kimi Code CLI, ZCode, and Pi
12
- - **Image Input and Output**: Forward standalone images and mixed text-image Feishu posts to Claude Persistent, Codex App Server, OpenCode ACP, Kimi Code ACP, ZCode, and Pi backends, and forward Codex App Server generated images back through Router
12
+ - **Image Input and Output**: Forward Feishu images to supported backends and return Codex native image output. All backends can return local images through file paths or Markdown image links; files must be inside an allowed directory and no larger than 2 MiB. AGY does not accept image input.
13
13
  - **Multi-session (Threads)**: Create independent chat threads to parallelize tasks
14
14
  - **Remote Machine Management**: Control remote servers or Docker via SSH
15
15
  - **Persistent Process**: Long-running AI process with bidirectional streaming
16
- - **Non-Interactive Auto-Update**: Reconnect to a newer Router, wait for idle work, install its exact version, and exit for the process supervisor to restart
16
+ - **Automatic Updates**: Install the newer Router's exact version when idle. Manual clients keep running and use the update on their next start; supervised non-interactive clients exit for an immediate restart.
17
+ - **Task Recovery**: With a compatible Router, running tasks resume output on a usable surviving card or a new card with an output-gap notice. Recovery retries use bounded backoff; disconnected output is not buffered or replayed, even after recovery is paused.
17
18
 
18
19
  ## Prerequisites
19
20
 
@@ -63,6 +64,8 @@ remote-cli start
63
64
  Help me fix TypeScript errors in ~/projects/my-app
64
65
  ```
65
66
 
67
+ Manual starts automatically install updates when tasks and queues are idle, without exiting the running client. Updates take effect on the next start. Use `remote-cli service install` for automatic startup and supervised restarts after updates. If the Router rejects the running protocol, restart the manual client after installation to reconnect. See [Automatic Client Startup](../../README.md#automatic-client-startup) for update behavior and recovery.
68
+
66
69
  ## Commands
67
70
 
68
71
  | Command | Description |
@@ -98,12 +101,13 @@ Linux users upgrading from version 1.6.23 or earlier should run `remote-cli serv
98
101
  | `/compact` | Compress history to save tokens |
99
102
  | `/cd <dir>` | Change working directory for this thread |
100
103
  | `/model [name]` | List models for the active backend or set this thread's model |
101
- | `/effort [auto|level]` | Show or set Codex/AGY/OpenCode/Kimi reasoning effort |
104
+ | `/effort [auto|level]` | Show or set Codex/AGY/OpenCode/Kimi/ZCode/Pi reasoning effort |
105
+ | `/sandbox [on/off/read-only/default]` | Configure the current Codex or Claude Code thread sandbox, directory grants, and networking |
102
106
  | `/backend` | List backends and show the current thread's effective backend |
103
107
  | `/backend <index>` | Switch all threads and clear per-thread backend overrides |
104
108
  | `/backend <index> @` | Switch only the current thread |
105
109
  | `/backend default @` | Clear the current thread override and follow the global backend |
106
- | `/bind <码>` | Bind a new device |
110
+ | `/bind <code>` | Bind a new device |
107
111
  | `/unbind` | Unbind all devices |
108
112
  | `/device` | List and switch between bound devices |
109
113
 
@@ -153,14 +157,20 @@ When diagnosing a task, check `/status`, then `/context`, then `/queue`. Use `/a
153
157
 
154
158
  ## Security
155
159
 
156
- - **Directory whitelisting**: Only explicitly allowed directories are accessible
157
- - **Command filtering**: Dangerous commands are automatically blocked
160
+ Codex permission requests use interactive cards when both CLI and Router support them. Choose Allow, Deny, or (for explicit directory grants) Allow and remember directory. Buttons stay bound to the original request and are invalidated when it finishes. Pending approvals get new cards after reconnecting; older routers and card delivery failures retain text replies.
161
+
162
+ Codex supports an opt-in native sandbox through `/sandbox on`, `/sandbox read-only`, and `/sandbox off`. Use `/sandbox allow <directory>` to save extra writable directories for the current thread, `remove <directory>` to revoke them, and `network on|off` to control networking. The workspace-write default allows cross-project reads, networking, and writes to the current project, dedicated temporary/download directories, and common package caches. Grants survive conversation resets and backend switches. See [Codex sandbox configuration](../../README.md#optional-codex-sandbox) for boundaries, approval choices, and configuration defaults. Other backends retain their existing behavior.
163
+
164
+ Claude Code also supports opt-in `/sandbox` policies. Workspace-write mode automatically permits ordinary file edits inside the workspace and explicitly authorized writable directories through a process-scoped native hook. Real file destinations are checked, and native deny/ask rules remain effective. Outside writes, protected files, and all read-only-mode file modifications still require approval. Startup verifies the hook before sending a task and fails if native hook restrictions prevent it from running. Missing Linux dependencies (`bubblewrap` and `socat` on `PATH`) stop execution instead of falling back to full access. Permission cards use text replies (`yes` / `no`, or a request ID when several approvals are pending) if cards are unavailable. Pending approvals expire when the task ends or the process stops. Sandboxed commands can access network domains without per-domain approval by default; `/sandbox network on` restores this behavior while preserving filesystem isolation, native domain deny rules, and administrator-managed restrictions. Previously saved network-off settings remain disabled until explicitly changed. `/sandbox network off` overrides saved domain allowlists for sandboxed commands; it does not block Claude API traffic or in-process web tools. See [Claude sandbox configuration](../../README.md#optional-claude-code-sandbox).
165
+
166
+ - **Directory whitelisting**: Controls which working directory a thread can select; it is not a process sandbox
167
+ - **Backend permissions**: AI processes inherit the operating-system user's permissions. remote-cli does not install a global Claude security hook or automatically filter backend shell commands.
158
168
  - **Device authentication**: Each device has a unique hardware-based ID
159
169
  - **Binding codes**: Expire after 5 minutes
160
170
 
161
171
  ## Documentation
162
172
 
163
- For full documentation including router server deployment, see the [project README](https://github.com/xiaoyu/remote-cli#readme).
173
+ For full documentation including router server deployment, see the [project README](https://github.com/Yu-Robotics/remote-cli#readme).
164
174
 
165
175
  ## License
166
176
 
@@ -36,6 +36,8 @@ export declare class MessageHandler {
36
36
  private readonly activeThreadOperations;
37
37
  private readonly abortOperations;
38
38
  private automaticUpdateInProgress;
39
+ private approvalCardsSupported;
40
+ private readonly pendingApprovalCards;
39
41
  constructor(wsClient: WebSocketClient, threadPool: ThreadExecutorPool, threadManager: ThreadManager, directoryGuard: DirectoryGuard, config: ConfigManager);
40
42
  /**
41
43
  * Handle message (supports new IncomingMessage format)
@@ -153,6 +155,11 @@ export declare class MessageHandler {
153
155
  private isUnavailableCodexModelError;
154
156
  private getMessageOpenId;
155
157
  private trackTask;
158
+ private forwardApproval;
159
+ private sendApprovalMessage;
160
+ private showApprovalFallback;
161
+ private resolveApprovalCard;
162
+ private handleApprovalResponse;
156
163
  private sendStreamChunk;
157
164
  private sendToolUse;
158
165
  private sendToolResult;
@@ -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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"}
@@ -87,6 +87,8 @@ class MessageHandler {
87
87
  activeThreadOperations = new Map();
88
88
  abortOperations = new Map();
89
89
  automaticUpdateInProgress = false;
90
+ approvalCardsSupported = false;
91
+ pendingApprovalCards = new Map();
90
92
  constructor(wsClient, threadPool, threadManager, directoryGuard, config) {
91
93
  this.wsClient = wsClient;
92
94
  this.threadPool = threadPool;
@@ -121,7 +123,24 @@ class MessageHandler {
121
123
  return;
122
124
  case 'heartbeat':
123
125
  return;
124
- case 'binding_confirm':
126
+ case 'binding_confirm': {
127
+ const data = message.data;
128
+ if (data?.success !== true)
129
+ return;
130
+ this.approvalCardsSupported = data.capabilities?.approvalCards === true;
131
+ for (const pending of this.pendingApprovalCards.values()) {
132
+ if (this.approvalCardsSupported)
133
+ this.sendApprovalMessage(pending.request);
134
+ else
135
+ this.showApprovalFallback(pending.request.messageId);
136
+ }
137
+ return;
138
+ }
139
+ case 'approval_response':
140
+ this.handleApprovalResponse(message);
141
+ return;
142
+ case 'approval_unavailable':
143
+ this.showApprovalFallback(message.messageId);
125
144
  return;
126
145
  default:
127
146
  this.sendResponse(message.messageId, undefined, {
@@ -233,7 +252,9 @@ class MessageHandler {
233
252
  this.sendResponse(messageId, resolvedThreadId, {
234
253
  success: sent,
235
254
  output: sent ? `✅ Sent: "${input}"` : undefined,
236
- error: sent ? undefined : '❌ Failed to send input - executor is no longer waiting',
255
+ error: sent ? undefined : ex.isWaitingInput()
256
+ ? '❌ Input was not accepted. Check the pending request and reply again.'
257
+ : '❌ Failed to send input - executor is no longer waiting',
237
258
  });
238
259
  }
239
260
  else {
@@ -286,7 +307,7 @@ class MessageHandler {
286
307
  }
287
308
  return;
288
309
  }
289
- const queueSensitiveCommand = /^\/(?:clear|new|compact|cd|model|effort)(?:\s|$)/.test(content?.trim() ?? '')
310
+ const queueSensitiveCommand = /^\/(?:clear|new|compact|cd|model|effort|sandbox)(?:\s|$)/.test(content?.trim() ?? '')
290
311
  || /^\/thread\s+delete(?:\s|$)/.test(content?.trim() ?? '');
291
312
  if (queueSensitiveCommand && this.hasThreadQueueState(resolvedThreadId)) {
292
313
  this.sendResponse(messageId, resolvedThreadId, {
@@ -432,6 +453,18 @@ class MessageHandler {
432
453
  */
433
454
  async handleBuiltInCommand(messageId, threadId, content, executor) {
434
455
  const trimmed = content.trim();
456
+ if (trimmed === '/sandbox' || trimmed.startsWith('/sandbox ')) {
457
+ if (!('getSandboxStatus' in executor) || !('configureSandbox' in executor)) {
458
+ this.sendResponse(messageId, threadId, { success: false, error: '/sandbox is currently supported by the Codex and Claude Code backends.' });
459
+ return true;
460
+ }
461
+ const control = executor;
462
+ const result = trimmed === '/sandbox'
463
+ ? { success: true, output: control.getSandboxStatus() }
464
+ : await control.configureSandbox(trimmed.slice('/sandbox'.length).trim());
465
+ this.sendResponse(messageId, threadId, result);
466
+ return true;
467
+ }
435
468
  if (trimmed === '/status') {
436
469
  const cwd = executor.getCurrentWorkingDirectory();
437
470
  const allowedDirs = this.directoryGuard.getAllowedDirectories();
@@ -521,6 +554,7 @@ Use /compact to reduce conversation context or /clear to start a fresh context.`
521
554
  - /cd <directory> - Change working directory for this thread
522
555
  - /model [name] - Show models for the active backend, or set this thread's model
523
556
  - /effort [auto|level] - Show or set effort for Codex/AGY/OpenCode/Kimi/ZCode/Pi (Claude Code is unsupported)
557
+ - /sandbox [on|off|read-only|default|allow <directory>|remove <directory>|network on|off] - Configure this thread's sandbox (Codex, Claude Code)
524
558
  - /backend - List backends and show the current thread's effective backend
525
559
  - /backend <index> - Switch all threads and clear per-thread backend overrides
526
560
  - /backend <index> @ - Switch only the current thread
@@ -1554,6 +1588,8 @@ You can also use natural language commands to control Claude Code CLI.`,
1554
1588
  pendingLocalImageEmissions.add(pending);
1555
1589
  };
1556
1590
  const executeOptions = {
1591
+ onApprovalRequest: (approval) => this.forwardApproval(approval, executor, messageId, threadId),
1592
+ onApprovalResolved: (requestId, status) => this.resolveApprovalCard(requestId, status),
1557
1593
  onTaskNotification,
1558
1594
  onStream: (chunk) => {
1559
1595
  streamedOutput += chunk;
@@ -1646,6 +1682,62 @@ You can also use natural language commands to control Claude Code CLI.`,
1646
1682
  backend: this.threadPool.getBackendKey(threadId),
1647
1683
  cwd: this.threadPool.getExecutor(threadId).getCurrentWorkingDirectory(), preview: content });
1648
1684
  }
1685
+ forwardApproval(approval, executor, taskMessageId, threadId) {
1686
+ const openId = this.getMessageOpenId(taskMessageId);
1687
+ if (!openId)
1688
+ return false;
1689
+ const request = { type: 'approval_request', messageId: approval.requestId,
1690
+ taskMessageId, openId, threadId, threadName: this.threadManager.getThread(threadId)?.name ?? threadId,
1691
+ cwd: executor.getCurrentWorkingDirectory(), approval, timestamp: Date.now() };
1692
+ this.pendingApprovalCards.set(approval.requestId, { request, executor });
1693
+ if (this.approvalCardsSupported)
1694
+ this.sendApprovalMessage(request);
1695
+ return this.approvalCardsSupported;
1696
+ }
1697
+ sendApprovalMessage(message) {
1698
+ try {
1699
+ this.wsClient.send(message);
1700
+ }
1701
+ catch { /* Pending approvals are resent after registration. */ }
1702
+ }
1703
+ showApprovalFallback(requestId) {
1704
+ const pending = this.pendingApprovalCards.get(requestId);
1705
+ if (!pending)
1706
+ return;
1707
+ const { request } = pending;
1708
+ this.sendStreamChunk(request.taskMessageId, request.threadId, `\nApproval required: ${request.approval.description}\nReply yes, no${request.approval.canRemember ? ', or remember to save these directories' : ''}.\n`);
1709
+ }
1710
+ resolveApprovalCard(requestId, status) {
1711
+ const pending = this.pendingApprovalCards.get(requestId);
1712
+ if (!pending)
1713
+ return;
1714
+ this.pendingApprovalCards.delete(requestId);
1715
+ if (this.approvalCardsSupported)
1716
+ this.sendApprovalMessage({ type: 'approval_resolved', messageId: requestId,
1717
+ openId: pending.request.openId, threadId: pending.request.threadId, status, timestamp: Date.now() });
1718
+ }
1719
+ handleApprovalResponse(message) {
1720
+ const pending = this.pendingApprovalCards.get(message.messageId);
1721
+ if (!pending || pending.request.threadId !== message.threadId || pending.request.openId !== message.openId
1722
+ || pending.request.taskMessageId !== message.taskMessageId) {
1723
+ this.sendApprovalMessage({ type: 'approval_resolved', messageId: message.messageId,
1724
+ openId: message.openId, threadId: message.threadId, status: 'expired', timestamp: Date.now() });
1725
+ return;
1726
+ }
1727
+ let accepted = false;
1728
+ try {
1729
+ accepted = pending.executor.respondToApproval?.(message.messageId, message.action) === true;
1730
+ }
1731
+ catch { /* Report failure without approving another request. */ }
1732
+ if (accepted) {
1733
+ this.resolveApprovalCard(message.messageId, message.action === 'remember' ? 'remembered' : message.action === 'deny' ? 'denied' : 'approved');
1734
+ }
1735
+ else if (this.pendingApprovalCards.has(message.messageId)) {
1736
+ this.sendApprovalMessage({ type: 'approval_resolved', messageId: message.messageId,
1737
+ openId: message.openId, threadId: message.threadId, status: 'pending',
1738
+ error: 'Approval could not be applied. Check the task output or choose another action.', timestamp: Date.now() });
1739
+ }
1740
+ }
1649
1741
  sendStreamChunk(messageId, threadId, chunk) {
1650
1742
  try {
1651
1743
  this.wsClient.send({
@@ -1922,6 +2014,7 @@ You can also use natural language commands to control Claude Code CLI.`,
1922
2014
  this.notificationAdapter.unregister();
1923
2015
  try {
1924
2016
  await this.threadPool.destroyAll({ deleteData: false });
2017
+ this.pendingApprovalCards.clear();
1925
2018
  }
1926
2019
  catch (err) {
1927
2020
  console.error('Error destroying thread executors:', err);