@yu_robotics/remote-cli 1.6.25 → 1.6.30

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 (48) hide show
  1. package/README.md +9 -8
  2. package/dist/client/MessageHandler.d.ts +4 -1
  3. package/dist/client/MessageHandler.d.ts.map +1 -1
  4. package/dist/client/MessageHandler.js +70 -22
  5. package/dist/client/MessageHandler.js.map +1 -1
  6. package/dist/client/WebSocketClient.js +1 -1
  7. package/dist/client/WebSocketClient.js.map +1 -1
  8. package/dist/commands/start.d.ts +1 -4
  9. package/dist/commands/start.d.ts.map +1 -1
  10. package/dist/commands/start.js +29 -19
  11. package/dist/commands/start.js.map +1 -1
  12. package/dist/executor/KimiExecutor.d.ts +18 -0
  13. package/dist/executor/KimiExecutor.d.ts.map +1 -0
  14. package/dist/executor/KimiExecutor.js +28 -0
  15. package/dist/executor/KimiExecutor.js.map +1 -0
  16. package/dist/executor/OpenCodeExecutor.d.ts +80 -0
  17. package/dist/executor/OpenCodeExecutor.d.ts.map +1 -0
  18. package/dist/executor/OpenCodeExecutor.js +513 -0
  19. package/dist/executor/OpenCodeExecutor.js.map +1 -0
  20. package/dist/executor/acp/AcpClient.d.ts +47 -40
  21. package/dist/executor/acp/AcpClient.d.ts.map +1 -1
  22. package/dist/executor/acp/AcpClient.js +113 -189
  23. package/dist/executor/acp/AcpClient.js.map +1 -1
  24. package/dist/executor/acp/AcpTypes.d.ts +27 -68
  25. package/dist/executor/acp/AcpTypes.d.ts.map +1 -1
  26. package/dist/executor/acp/AcpTypes.js +0 -9
  27. package/dist/executor/acp/AcpTypes.js.map +1 -1
  28. package/dist/executor/index.d.ts +4 -6
  29. package/dist/executor/index.d.ts.map +1 -1
  30. package/dist/executor/index.js +32 -27
  31. package/dist/executor/index.js.map +1 -1
  32. package/dist/thread/ThreadExecutorPool.js +1 -1
  33. package/dist/thread/ThreadExecutorPool.js.map +1 -1
  34. package/dist/thread/types.d.ts +1 -1
  35. package/dist/thread/types.d.ts.map +1 -1
  36. package/dist/types/config.d.ts +19 -13
  37. package/dist/types/config.d.ts.map +1 -1
  38. package/dist/types/config.js +6 -2
  39. package/dist/types/config.js.map +1 -1
  40. package/dist/update/AutomaticUpdater.d.ts +26 -0
  41. package/dist/update/AutomaticUpdater.d.ts.map +1 -0
  42. package/dist/update/AutomaticUpdater.js +109 -0
  43. package/dist/update/AutomaticUpdater.js.map +1 -0
  44. package/dist/utils/version.d.ts +3 -0
  45. package/dist/utils/version.d.ts.map +1 -0
  46. package/dist/utils/version.js +20 -0
  47. package/dist/utils/version.js.map +1 -0
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @yu_robotics/remote-cli
2
2
 
3
- Remote control your [Claude Code](https://claude.ai/code) CLI from anywhere using your mobile phone through Feishu (Lark) messaging.
3
+ Remote control Claude Code, AGY CLI, Codex CLI, OpenCode CLI, or Kimi Code CLI from anywhere using your mobile phone through Feishu (Lark) messaging.
4
4
 
5
5
  ## Features
6
6
 
@@ -8,16 +8,17 @@ Remote control your [Claude Code](https://claude.ai/code) CLI from anywhere usin
8
8
  - **Secure**: Directory whitelisting, command filtering, and device authentication
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
- - **Multi-Backend Support**: Supports Claude Code and Gemini CLI
12
- - **Image Input and Output**: Forward standalone images and mixed text-image Feishu posts to Claude Persistent and Codex App Server backends, and forward Codex App Server generated images back through Router
11
+ - **Multi-Backend Support**: Supports Claude Code, AGY CLI (Antigravity), Codex CLI (OpenAI), OpenCode CLI, and Kimi Code CLI
12
+ - **Image Input and Output**: Forward standalone images and mixed text-image Feishu posts to Claude Persistent, Codex App Server, OpenCode ACP, and Kimi Code ACP backends, and forward Codex App Server generated images back through Router
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
17
 
17
18
  ## Prerequisites
18
19
 
19
20
  - **Node.js** >= 18.0.0
20
- - **Claude Code CLI** installed and configured
21
+ - **Claude Code CLI**, **AGY CLI**, **Codex CLI**, **OpenCode CLI**, or **Kimi Code CLI** installed and configured
21
22
  - Access to a Feishu (Lark) bot connected to a [remote-cli-router](https://www.npmjs.com/package/@yu_robotics/remote-cli-router) server
22
23
 
23
24
  The client is normally installed and run directly on the developer's machine rather than in Docker, so it can access local project directories and the installed AI CLI binaries. Docker is recommended for the shared Router server instead.
@@ -96,7 +97,7 @@ Linux users upgrading from version 1.6.23 or earlier should run `remote-cli serv
96
97
  | `/compact` | Compress history to save tokens |
97
98
  | `/cd <dir>` | Change working directory for this thread |
98
99
  | `/model [name]` | List models for the active backend or set this thread's model |
99
- | `/effort [auto|low|medium|high]` | Show or set Codex/AGY reasoning effort |
100
+ | `/effort [auto|level]` | Show or set Codex/AGY/OpenCode/Kimi reasoning effort |
100
101
  | `/backend` | List backends and show the current thread's effective backend |
101
102
  | `/backend <index>` | Switch all threads and clear per-thread backend overrides |
102
103
  | `/backend <index> @` | Switch only the current thread |
@@ -118,7 +119,7 @@ Linux users upgrading from version 1.6.23 or earlier should run `remote-cli serv
118
119
 
119
120
  ### AI CLI Commands Passthrough
120
121
 
121
- All commands/skills supported by local Claude Code or Gemini CLI are passed through directly where the backend protocol supports them. The built-in `/status`, `/context`, and `/skills` commands provide a consistent remote view across backends.
122
+ Backend-specific commands are forwarded only where the active backend supports them. The built-in `/status`, `/context`, and `/skills` commands provide a consistent remote view across backends.
122
123
  - `/commit` - Commit code changes
123
124
  - `/review` - Code review
124
125
  - `/test` - Run tests
@@ -128,11 +129,11 @@ All commands/skills supported by local Claude Code or Gemini CLI are passed thro
128
129
 
129
130
  Use one thread per project or task so each thread keeps its own working directory, backend session, model, and queue. Use `/backend <index>` for a global switch, or `/backend <index> @` to override only the current thread. `/backend default @` removes that override.
130
131
 
131
- 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. Context-changing commands such as `/model`, `/effort`, `/cd`, `/compact`, and `/clear` are handled separately rather than queued as ordinary messages.
132
+ 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`, and `/clear` are handled separately rather than queued as ordinary messages.
132
133
 
133
134
  ## Expert Usage
134
135
 
135
- For a shared deployment, run the Router with Docker Compose on an internal server and run one CLI client on each developer machine. Keep project directories and Claude Code, AGY, or Codex credentials on the client machine; the Router persists its configuration and bindings in `./router-data`. On Linux, create that directory as the deployment user and set `ROUTER_UID` and `ROUTER_GID` in `.env` to `id -u` and `id -g` so the container can write to the bind mount.
136
+ For a shared deployment, run the Router with Docker Compose on an internal server and run one CLI client on each developer machine. Keep project directories and Claude Code, AGY, Codex, OpenCode, or Kimi Code credentials on the client machine; the Router persists its configuration and bindings in `./router-data`. On Linux, create that directory as the deployment user and set `ROUTER_UID` and `ROUTER_GID` in `.env` to `id -u` and `id -g` so the container can write to the bind mount.
136
137
 
137
138
  To update a Router deployment without losing bindings:
138
139
 
@@ -35,11 +35,14 @@ export declare class MessageHandler {
35
35
  private readonly messageOpenIds;
36
36
  private readonly activeThreadOperations;
37
37
  private readonly abortOperations;
38
+ private automaticUpdateInProgress;
38
39
  constructor(wsClient: WebSocketClient, threadPool: ThreadExecutorPool, threadManager: ThreadManager, directoryGuard: DirectoryGuard, config: ConfigManager);
39
40
  /**
40
41
  * Handle message (supports new IncomingMessage format)
41
42
  */
42
43
  handleMessage(message: Message | IncomingMessage): Promise<void>;
44
+ tryBeginAutomaticUpdate(): boolean;
45
+ endAutomaticUpdate(): void;
43
46
  /**
44
47
  * Handle command message — route to the correct thread executor.
45
48
  */
@@ -73,6 +76,7 @@ export declare class MessageHandler {
73
76
  * - claude: `claude --print /model` prints current + available aliases
74
77
  * - agy: `agy models` prints "slug<TAB>Display Name" lines
75
78
  * - codex app-server: `model/list` returns the authenticated catalog
79
+ * - opencode ACP: session config options expose enabled models
76
80
  * - codex exec fallback: listing is unavailable
77
81
  */
78
82
  private handleModelList;
@@ -117,7 +121,6 @@ export declare class MessageHandler {
117
121
  private listLocalSkills;
118
122
  /**
119
123
  * Which backend slash commands should be forwarded to for a thread.
120
- * 'auto'/claude-* always resolve to Claude; legacy 'gemini' means AGY.
121
124
  */
122
125
  private resolveSlashBackend;
123
126
  /**
@@ -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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"}
@@ -34,6 +34,7 @@ class MessageHandler {
34
34
  messageOpenIds = new Map();
35
35
  activeThreadOperations = new Map();
36
36
  abortOperations = new Map();
37
+ automaticUpdateInProgress = false;
37
38
  constructor(wsClient, threadPool, threadManager, directoryGuard, config) {
38
39
  this.wsClient = wsClient;
39
40
  this.threadPool = threadPool;
@@ -77,6 +78,25 @@ class MessageHandler {
77
78
  });
78
79
  }
79
80
  }
81
+ tryBeginAutomaticUpdate() {
82
+ this.removeExpiredQueueConfirmations();
83
+ const hasQueuedCommands = Array.from(this.threadQueues.values()).some((queue) => queue.length > 0);
84
+ const hasRunningThread = this.threadPool.getSummaries().some((thread) => thread.status === 'running');
85
+ if (this.automaticUpdateInProgress
86
+ || hasRunningThread
87
+ || hasQueuedCommands
88
+ || this.pendingQueueConfirmations.size > 0
89
+ || this.pendingReplaces.size > 0
90
+ || this.activeThreadOperations.size > 0
91
+ || this.abortOperations.size > 0) {
92
+ return false;
93
+ }
94
+ this.automaticUpdateInProgress = true;
95
+ return true;
96
+ }
97
+ endAutomaticUpdate() {
98
+ this.automaticUpdateInProgress = false;
99
+ }
80
100
  /**
81
101
  * Handle command message — route to the correct thread executor.
82
102
  */
@@ -97,6 +117,13 @@ class MessageHandler {
97
117
  return;
98
118
  }
99
119
  const resolvedThreadId = thread.id;
120
+ if (this.automaticUpdateInProgress) {
121
+ this.sendResponse(messageId, resolvedThreadId, {
122
+ success: false,
123
+ error: 'The client is installing an automatic update. Please retry after it reconnects.',
124
+ });
125
+ return;
126
+ }
100
127
  const executor = this.threadPool.getExecutor(resolvedThreadId);
101
128
  // Handle /abort for this specific thread (bypasses busy check)
102
129
  if (content?.trim() === '/abort') {
@@ -228,10 +255,7 @@ class MessageHandler {
228
255
  }
229
256
  const expandedContent = this.expandCommandShortcuts(content);
230
257
  const processedContent = (0, FileReadDetector_1.processFileReadContent)(expandedContent);
231
- const success = await this.executeCommand(messageId, resolvedThreadId, processedContent, executor, attachments);
232
- if (!success && this.threadQueues.has(resolvedThreadId)) {
233
- this.pausedQueues.add(resolvedThreadId);
234
- }
258
+ await this.executeCommand(messageId, resolvedThreadId, processedContent, executor, attachments);
235
259
  }
236
260
  catch (error) {
237
261
  this.threadPool.setThreadError(resolvedThreadId, true);
@@ -389,7 +413,7 @@ Use /compact to reduce conversation context or /clear to start a fresh context.`
389
413
  }
390
414
  if (trimmed === '/skills') {
391
415
  const backend = this.threadPool.getBackendKey(threadId);
392
- if (backend === 'claude' || backend === 'agy') {
416
+ if (backend === 'claude' || backend === 'agy' || backend === 'opencode' || backend === 'kimi') {
393
417
  await this.executeSlashCommand(messageId, threadId, trimmed, executor);
394
418
  }
395
419
  else {
@@ -417,7 +441,7 @@ Use /compact to reduce conversation context or /clear to start a fresh context.`
417
441
  - /compact - Compress conversation history to reduce context size
418
442
  - /cd <directory> - Change working directory for this thread
419
443
  - /model [name] - Show models for the active backend, or set this thread's model
420
- - /effort [auto|low|medium|high] - Show or set effort for Codex/AGY (Claude Code is unsupported)
444
+ - /effort [auto|level] - Show or set effort for Codex/AGY/OpenCode/Kimi (Claude Code is unsupported)
421
445
  - /backend - List backends and show the current thread's effective backend
422
446
  - /backend <index> - Switch all threads and clear per-thread backend overrides
423
447
  - /backend <index> @ - Switch only the current thread
@@ -506,7 +530,7 @@ You can also use natural language commands to control Claude Code CLI.`,
506
530
  if (trimmed === '/effort' || trimmed.startsWith('/effort ')) {
507
531
  const executorConfig = this.config.get('executor') ?? { type: 'auto' };
508
532
  const key = this.threadPool.getBackendKey(threadId);
509
- if (key !== 'codex' && key !== 'agy') {
533
+ if (key !== 'codex' && key !== 'agy' && key !== 'opencode' && key !== 'kimi') {
510
534
  this.sendResponse(messageId, threadId, {
511
535
  success: false,
512
536
  error: '/effort is not supported yet for Claude Code.',
@@ -522,13 +546,17 @@ You can also use natural language commands to control Claude Code CLI.`,
522
546
  output: [
523
547
  `Current reasoning effort: ${thread?.efforts?.agy ?? 'auto'}`,
524
548
  'Supported levels: low, medium, high',
525
- 'Set with: /effort <auto|low|medium|high>',
549
+ 'Set with: /effort <auto|level>',
526
550
  ].join('\n'),
527
551
  });
528
552
  return true;
529
553
  }
530
- const configuredModel = thread?.models?.codex ?? executorConfig.codex?.model;
531
- const lines = [`Current reasoning effort: ${thread?.efforts?.codex ?? 'auto'}`];
554
+ const configuredModel = key === 'codex'
555
+ ? thread?.models?.codex ?? executorConfig.codex?.model
556
+ : key === 'opencode'
557
+ ? thread?.models?.opencode ?? executorConfig.opencode?.model
558
+ : thread?.models?.kimi ?? executorConfig.kimi?.model;
559
+ const lines = [`Current reasoning effort: ${thread?.efforts?.[key] ?? 'auto'}`];
532
560
  if ('listModels' in executor && typeof executor.listModels === 'function') {
533
561
  try {
534
562
  const models = await executor.listModels();
@@ -542,7 +570,8 @@ You can also use natural language commands to control Claude Code CLI.`,
542
570
  }
543
571
  }
544
572
  catch (error) {
545
- lines.push(`Could not fetch Codex effort metadata: ${error instanceof Error ? error.message : String(error)}`);
573
+ const label = key === 'opencode' ? 'OpenCode' : key === 'kimi' ? 'Kimi Code' : 'Codex';
574
+ lines.push(`Could not fetch ${label} effort metadata: ${error instanceof Error ? error.message : String(error)}`);
546
575
  }
547
576
  }
548
577
  lines.push('Set with: /effort <auto|level>');
@@ -552,7 +581,7 @@ You can also use natural language commands to control Claude Code CLI.`,
552
581
  if (!('setEffort' in executor && typeof executor.setEffort === 'function')) {
553
582
  this.sendResponse(messageId, threadId, {
554
583
  success: false,
555
- error: `/effort is not supported by this ${key === 'agy' ? 'AGY' : 'Codex'} executor version.`,
584
+ error: `/effort is not supported by this ${key === 'agy' ? 'AGY' : key === 'opencode' ? 'OpenCode' : key === 'kimi' ? 'Kimi Code' : 'Codex'} executor version.`,
556
585
  });
557
586
  return true;
558
587
  }
@@ -801,6 +830,7 @@ You can also use natural language commands to control Claude Code CLI.`,
801
830
  * - claude: `claude --print /model` prints current + available aliases
802
831
  * - agy: `agy models` prints "slug<TAB>Display Name" lines
803
832
  * - codex app-server: `model/list` returns the authenticated catalog
833
+ * - opencode ACP: session config options expose enabled models
804
834
  * - codex exec fallback: listing is unavailable
805
835
  */
806
836
  async handleModelList(messageId, threadId) {
@@ -808,12 +838,16 @@ You can also use natural language commands to control Claude Code CLI.`,
808
838
  const key = this.threadPool.getBackendKey(threadId);
809
839
  const thread = this.threadManager.getThread(threadId);
810
840
  const current = thread?.models?.[key] ?? (key === 'claude' ? thread?.model : undefined);
811
- const backendLabel = key === 'claude' ? 'Claude Code' : key === 'agy' ? 'AGY CLI' : 'Codex CLI';
841
+ const backendLabel = key === 'claude' ? 'Claude Code'
842
+ : key === 'agy' ? 'AGY CLI'
843
+ : key === 'opencode' ? 'OpenCode CLI'
844
+ : key === 'kimi' ? 'Kimi Code CLI'
845
+ : 'Codex CLI';
812
846
  const lines = [
813
847
  `🎯 Backend: ${backendLabel}`,
814
848
  `Current model: ${current ?? 'backend default'}`,
815
849
  ];
816
- if (key === 'codex') {
850
+ if (key === 'codex' || key === 'opencode' || key === 'kimi') {
817
851
  const executor = this.threadPool.getExecutor(threadId);
818
852
  if ('listModels' in executor && typeof executor.listModels === 'function') {
819
853
  try {
@@ -826,15 +860,15 @@ You can also use natural language commands to control Claude Code CLI.`,
826
860
  }
827
861
  }
828
862
  else {
829
- lines.push('', '⚠️ Codex returned an empty model list.');
863
+ lines.push('', `⚠️ ${backendLabel} returned an empty model list.`);
830
864
  }
831
865
  }
832
866
  catch (error) {
833
- lines.push('', `⚠️ Could not fetch the model list from Codex: ${error instanceof Error ? error.message : String(error)}`);
867
+ lines.push('', `⚠️ Could not fetch the model list from ${backendLabel}: ${error instanceof Error ? error.message : String(error)}`);
834
868
  }
835
869
  }
836
870
  else {
837
- lines.push('', 'Model listing is unavailable in the Codex exec fallback transport.');
871
+ lines.push('', `Model listing is unavailable in this ${backendLabel} transport.`);
838
872
  }
839
873
  }
840
874
  else {
@@ -1256,7 +1290,6 @@ You can also use natural language commands to control Claude Code CLI.`,
1256
1290
  }
1257
1291
  /**
1258
1292
  * Which backend slash commands should be forwarded to for a thread.
1259
- * 'auto'/claude-* always resolve to Claude; legacy 'gemini' means AGY.
1260
1293
  */
1261
1294
  resolveSlashBackend(threadId) {
1262
1295
  return this.threadPool.getBackendKey(threadId);
@@ -1274,6 +1307,19 @@ You can also use natural language commands to control Claude Code CLI.`,
1274
1307
  */
1275
1308
  async executeSlashCommand(messageId, threadId, command, executor) {
1276
1309
  const backend = this.resolveSlashBackend(threadId);
1310
+ if (backend === 'opencode' || backend === 'kimi') {
1311
+ const result = await executor.execute(command, {
1312
+ onStream: (chunk) => this.sendStreamChunk(messageId, threadId, chunk),
1313
+ onToolUse: (toolUse) => this.sendToolUse(messageId, threadId, toolUse),
1314
+ onToolResult: (toolResult) => this.sendToolResult(messageId, threadId, toolResult),
1315
+ onPlanMode: (plan) => this.sendPlanMode(messageId, threadId, plan),
1316
+ onImage: (image) => this.sendImage(messageId, threadId, image),
1317
+ });
1318
+ this.sendResponse(messageId, threadId, result.success
1319
+ ? { success: true, output: result.output || '✅ Command executed successfully' }
1320
+ : { success: false, error: result.error || `${backend === 'kimi' ? 'Kimi Code' : 'OpenCode'} command failed` });
1321
+ return;
1322
+ }
1277
1323
  if (backend === 'codex') {
1278
1324
  this.sendResponse(messageId, threadId, {
1279
1325
  success: false,
@@ -1586,14 +1632,18 @@ You can also use natural language commands to control Claude Code CLI.`,
1586
1632
  });
1587
1633
  }
1588
1634
  async detectBackends() {
1589
- const [claudeInstalled, codexInstalled, agyInstalled] = await Promise.all([
1635
+ const [claudeInstalled, codexInstalled, openCodeInstalled, kimiInstalled, agyInstalled] = await Promise.all([
1590
1636
  this.checkCommand('claude', ['--version']),
1591
1637
  this.checkCommand('codex', ['--version']),
1638
+ this.checkCommand('opencode', ['--version']),
1639
+ this.checkCommand('kimi', ['--version']),
1592
1640
  this.checkCommand('agy', ['--version']),
1593
1641
  ]);
1594
1642
  return [
1595
1643
  { id: 'auto', label: 'Claude Code', installed: claudeInstalled },
1596
1644
  { id: 'codex', label: 'Codex CLI (OpenAI)', installed: codexInstalled },
1645
+ { id: 'opencode', label: 'OpenCode CLI', installed: openCodeInstalled },
1646
+ { id: 'kimi', label: 'Kimi Code CLI', installed: kimiInstalled },
1597
1647
  { id: 'agy', label: 'AGY CLI (Antigravity)', installed: agyInstalled },
1598
1648
  ];
1599
1649
  }
@@ -1616,9 +1666,7 @@ You can also use natural language commands to control Claude Code CLI.`,
1616
1666
  const lines = installed.map((b, i) => {
1617
1667
  const isClaudeActive = b.id === 'auto' &&
1618
1668
  (currentType === 'auto' || currentType === 'claude-persistent' || currentType === 'claude-spawn');
1619
- // Legacy configs may still say 'gemini' — that slot now means AGY.
1620
- const isAgyActive = b.id === 'agy' &&
1621
- (currentType === 'agy' || currentType === 'gemini');
1669
+ const isAgyActive = b.id === 'agy' && currentType === 'agy';
1622
1670
  const active = b.id === currentType || isClaudeActive || isAgyActive ? ' ★ (active)' : '';
1623
1671
  return `${i + 1}. ${b.label}${active}`;
1624
1672
  });