@vybestack/llxprt-code 0.10.0 → 0.11.0-nightly.260728.ffaa8d5d7

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 (126) hide show
  1. package/package.json +13 -13
  2. package/src/commands/mcp/add.ts +8 -4
  3. package/src/config/cliArgParser.ts +2 -0
  4. package/src/config/config.ts +2 -0
  5. package/src/config/configBuilder.ts +3 -0
  6. package/src/config/configError.ts +23 -0
  7. package/src/config/extensions/extensionEnablement.ts +62 -1
  8. package/src/config/interactiveContext.ts +2 -1
  9. package/src/config/intermediateConfig.ts +4 -0
  10. package/src/config/keyBindings.ts +29 -3
  11. package/src/config/policy.ts +7 -1
  12. package/src/config/postConfigRuntime.ts +20 -14
  13. package/src/config/settings-schema/schema-ui.ts +9 -0
  14. package/src/config/settingsLoader.ts +21 -17
  15. package/src/config/settingsSchema.ts +2 -2
  16. package/src/config/trustedFolders.ts +275 -58
  17. package/src/config/yargsOptions.ts +7 -0
  18. package/src/generated/git-commit.json +1 -1
  19. package/src/nonInteractiveCli.ts +1 -0
  20. package/src/nonInteractiveCliSupport.ts +171 -51
  21. package/src/runtime/interactiveToolScheduler.ts +6 -6
  22. package/src/services/BuiltinCommandLoader.ts +33 -25
  23. package/src/services/FileCommandLoader.ts +34 -7
  24. package/src/test-utils/async.ts +20 -0
  25. package/src/test-utils/render.tsx +10 -1
  26. package/src/ui/App.tsx +17 -10
  27. package/src/ui/AppContainerRuntime.tsx +7 -9
  28. package/src/ui/cliUiRuntime.ts +28 -0
  29. package/src/ui/commands/authCommand.ts +240 -3
  30. package/src/ui/commands/compressCommand.ts +9 -1
  31. package/src/ui/commands/directoryCommand.tsx +1 -1
  32. package/src/ui/commands/logoutCommand.ts +2 -2
  33. package/src/ui/commands/permissionsCommand.ts +77 -20
  34. package/src/ui/commands/policiesCommand.ts +6 -1
  35. package/src/ui/commands/quotaCommand.ts +501 -0
  36. package/src/ui/commands/statsCommand.ts +2 -2
  37. package/src/ui/commands/statsQuota.ts +1 -1
  38. package/src/ui/commands/subagentCommand.ts +59 -27
  39. package/src/ui/components/AuthDialog.tsx +3 -3
  40. package/src/ui/components/Composer.tsx +1 -0
  41. package/src/ui/components/DialogManager.tsx +9 -2
  42. package/src/ui/components/FolderTrustDialog.tsx +71 -60
  43. package/src/ui/components/PermissionsModifyTrustDialog.tsx +211 -146
  44. package/src/ui/components/ProfileCreateWizard/constants.ts +13 -1
  45. package/src/ui/components/QueuedMessagesPanel.tsx +388 -0
  46. package/src/ui/components/inputPromptHooks.ts +2 -0
  47. package/src/ui/components/inputPromptKeyHandlers.ts +14 -0
  48. package/src/ui/components/inputPromptTypes.ts +6 -0
  49. package/src/ui/components/messages/AiMessage.tsx +24 -12
  50. package/src/ui/components/messages/ToolMessage.tsx +26 -40
  51. package/src/ui/containers/AppContainer/builders/buildUIActions.ts +3 -1
  52. package/src/ui/containers/AppContainer/builders/buildUIState.ts +5 -4
  53. package/src/ui/containers/AppContainer/hooks/useAppBootstrap.ts +3 -33
  54. package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +2 -14
  55. package/src/ui/containers/AppContainer/hooks/useAppInput.ts +31 -25
  56. package/src/ui/containers/AppContainer/hooks/useAppLayout.ts +90 -60
  57. package/src/ui/containers/AppContainer/hooks/useDisplayPreferences.ts +10 -0
  58. package/src/ui/containers/AppContainer/hooks/useInputHandling.ts +3 -17
  59. package/src/ui/containers/AppContainer/hooks/useKeybindings.ts +51 -38
  60. package/src/ui/containers/AppContainer/hooks/useSteer.ts +51 -0
  61. package/src/ui/contexts/KeypressContext.tsx +13 -2
  62. package/src/ui/contexts/ShellCommandDisplayContext.tsx +103 -0
  63. package/src/ui/contexts/TodoContext.tsx +0 -13
  64. package/src/ui/contexts/TodoProvider.tsx +1 -34
  65. package/src/ui/contexts/UIActionsContext.tsx +2 -1
  66. package/src/ui/contexts/UIStateContext.tsx +3 -2
  67. package/src/ui/hooks/agentStream/streamUtils.ts +0 -24
  68. package/src/ui/hooks/agentStream/thoughtState.ts +110 -30
  69. package/src/ui/hooks/agentStream/types.ts +1 -0
  70. package/src/ui/hooks/agentStream/useAgentEventStream.ts +8 -17
  71. package/src/ui/hooks/agentStream/useAgentStream.ts +2 -2
  72. package/src/ui/hooks/agentStream/useAgentStreamLifecycle.ts +4 -5
  73. package/src/ui/hooks/agentStream/useAgentStreamOrchestration.ts +12 -5
  74. package/src/ui/hooks/agentStream/useQueuedSubmissions.ts +99 -0
  75. package/src/ui/hooks/agentStream/useStreamEventHandlers.ts +4 -13
  76. package/src/ui/hooks/agentStream/useStreamState.ts +60 -41
  77. package/src/ui/hooks/agentStream/useSubmitQuery.ts +284 -59
  78. package/src/ui/hooks/slashCommandProcessorSupport.ts +4 -0
  79. package/src/ui/hooks/useAgentStream-test-helpers.ts +1 -0
  80. package/src/ui/hooks/useAutoAcceptIndicator.ts +13 -0
  81. package/src/ui/hooks/useFolderTrust.ts +118 -81
  82. package/src/ui/hooks/useGitBranchName.ts +79 -33
  83. package/src/ui/hooks/useIdeTrustListener.ts +5 -19
  84. package/src/ui/hooks/usePermissionsModifyTrust.ts +223 -85
  85. package/src/ui/hooks/useReactToolScheduler.ts +13 -17
  86. package/src/ui/hooks/useTodoPausePreserver.ts +0 -55
  87. package/src/ui/layouts/DefaultAppLayout.tsx +2 -0
  88. package/src/ui/layouts/DefaultAppLayoutHelpers.tsx +37 -162
  89. package/src/ui/layouts/InlineContent.tsx +198 -0
  90. package/src/ui/trustDialogHelpers.ts +190 -0
  91. package/src/utils/cleanup-state.ts +115 -0
  92. package/src/utils/cleanup.ts +32 -34
  93. package/src/utils/sandbox-containers.ts +183 -53
  94. package/src/utils/sandbox-exec.ts +9 -3
  95. package/src/zed-integration/acp-terminal-shell-host.ts +112 -0
  96. package/src/zed-integration/acp-types.ts +35 -0
  97. package/src/zed-integration/fileSystemService.ts +1 -1
  98. package/src/zed-integration/runZedIntegration.ts +142 -0
  99. package/src/zed-integration/zed-agent-event-handler.ts +179 -0
  100. package/src/zed-integration/zed-agent-setup.ts +43 -0
  101. package/src/zed-integration/zed-command-registry.ts +167 -0
  102. package/src/zed-integration/zed-config-options.ts +280 -0
  103. package/src/zed-integration/zed-content-utils.ts +2 -2
  104. package/src/zed-integration/zed-helpers.ts +132 -0
  105. package/src/zed-integration/zed-initialize.ts +64 -0
  106. package/src/zed-integration/zed-plan-update.ts +20 -0
  107. package/src/zed-integration/zed-prompt-command.ts +77 -0
  108. package/src/zed-integration/zed-session-config.ts +119 -0
  109. package/src/zed-integration/zed-session-errors.ts +337 -0
  110. package/src/zed-integration/zed-session-events.ts +216 -0
  111. package/src/zed-integration/zed-session-info.ts +255 -0
  112. package/src/zed-integration/zed-session-lifecycle.ts +186 -0
  113. package/src/zed-integration/zed-session-listing.ts +153 -0
  114. package/src/zed-integration/zed-session-loader.ts +193 -0
  115. package/src/zed-integration/zed-session-pagination.ts +240 -0
  116. package/src/zed-integration/zed-session-replay.ts +564 -0
  117. package/src/zed-integration/zed-stream-batcher.ts +236 -0
  118. package/src/zed-integration/zed-terminal-manager.ts +416 -0
  119. package/src/zed-integration/zed-terminal-setup.ts +72 -0
  120. package/src/zed-integration/zed-test-helpers.ts +349 -4
  121. package/src/zed-integration/zed-tool-handler.ts +47 -5
  122. package/src/zed-integration/zedIntegration.ts +538 -587
  123. package/src/services/todo-continuation/todoContinuationService.ts +0 -610
  124. package/src/ui/containers/AppContainer/hooks/useIdeRestartHotkey.ts +0 -33
  125. package/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts +0 -160
  126. package/src/ui/hooks/useTodoContinuation.ts +0 -299
package/src/ui/App.tsx CHANGED
@@ -25,6 +25,7 @@ import { RuntimeContextProvider } from './contexts/RuntimeContext.js';
25
25
  import { OverflowProvider } from './contexts/OverflowContext.js';
26
26
  import { AppDispatchProvider } from './contexts/AppDispatchContext.js';
27
27
  import { ScrollProvider } from './contexts/ScrollProvider.js';
28
+ import { ShellCommandDisplayProvider } from './contexts/ShellCommandDisplayContext.js';
28
29
  import { inkRenderOptions } from './inkRenderOptions.js';
29
30
  import { isMouseEventsEnabled } from './mouseEventsEnabled.js';
30
31
  import { appReducer, initialAppState } from './reducers/appReducer.js';
@@ -79,19 +80,25 @@ export const AppWrapper = (props: AppProps) => {
79
80
  <ScrollProvider>
80
81
  <SessionStatsProvider>
81
82
  <VimModeProvider settings={props.settings}>
82
- <ToolCallProvider
83
- sessionId={props.uiRuntime.session.getSessionId()}
83
+ <ShellCommandDisplayProvider
84
+ alwaysDisplayFullShellCommand={
85
+ props.settings.merged.ui.alwaysDisplayFullShellCommand ?? true
86
+ }
84
87
  >
85
- <TodoProvider
88
+ <ToolCallProvider
86
89
  sessionId={props.uiRuntime.session.getSessionId()}
87
90
  >
88
- <RuntimeContextProvider agent={props.agent}>
89
- <OverflowProvider>
90
- <AppWithState {...props} />
91
- </OverflowProvider>
92
- </RuntimeContextProvider>
93
- </TodoProvider>
94
- </ToolCallProvider>
91
+ <TodoProvider
92
+ sessionId={props.uiRuntime.session.getSessionId()}
93
+ >
94
+ <RuntimeContextProvider agent={props.agent}>
95
+ <OverflowProvider>
96
+ <AppWithState {...props} />
97
+ </OverflowProvider>
98
+ </RuntimeContextProvider>
99
+ </TodoProvider>
100
+ </ToolCallProvider>
101
+ </ShellCommandDisplayProvider>
95
102
  </VimModeProvider>
96
103
  </SessionStatsProvider>
97
104
  </ScrollProvider>
@@ -89,10 +89,6 @@ function buildInputParams(
89
89
  loadHistory: bootstrap.loadHistory,
90
90
  todos: bootstrap.todos,
91
91
  updateTodos: bootstrap.updateTodos,
92
- todoPauseController: bootstrap.todoPauseController,
93
- todoContinuationRef: bootstrap.todoContinuationRef,
94
- hadToolCallsRef: bootstrap.hadToolCallsRef,
95
- registerTodoPause: bootstrap.registerTodoPause,
96
92
  recordingIntegrationRef: bootstrap.recordingIntegrationRef,
97
93
  recordingSwapCallbacks: bootstrap.recordingSwapCallbacks,
98
94
  recordingIntegration: bootstrap.recordingIntegration,
@@ -146,10 +142,7 @@ function buildLayoutParams(
146
142
  ): AppLayoutParams {
147
143
  return {
148
144
  uiRuntime: bootstrap.uiRuntime,
149
- agent: bootstrap.agent,
150
145
  settings: bootstrap.settings,
151
- todoContinuationRef: bootstrap.todoContinuationRef,
152
- hadToolCallsRef: bootstrap.hadToolCallsRef,
153
146
  runtimeMessageBus: bootstrap.runtimeMessageBus,
154
147
  consoleMessages: bootstrap.consoleMessages,
155
148
  clearConsoleMessagesState: bootstrap.clearConsoleMessagesState,
@@ -167,6 +160,9 @@ function buildLayoutParams(
167
160
  setRenderMarkdown: dialogs.setRenderMarkdown,
168
161
  isTodoPanelCollapsed: dialogs.isTodoPanelCollapsed,
169
162
  setIsTodoPanelCollapsed: dialogs.setIsTodoPanelCollapsed,
163
+ isQueuedMessagesPanelCollapsed: dialogs.isQueuedMessagesPanelCollapsed,
164
+ setIsQueuedMessagesPanelCollapsed:
165
+ dialogs.setIsQueuedMessagesPanelCollapsed,
170
166
  setFooterHeight: dialogs.setFooterHeight,
171
167
  footerHeight: dialogs.footerHeight,
172
168
  copyModeEnabled: dialogs.copyModeEnabled,
@@ -197,6 +193,7 @@ function buildLayoutParams(
197
193
  handleSlashCommand: input.handleSlashCommand,
198
194
  inputHistoryStore: input.inputHistoryStore,
199
195
  handleUserInputSubmit: input.handleUserInputSubmit,
196
+ handleSteer: input.handleSteer,
200
197
  interactiveRuntimeReady: input.interactiveRuntimeReady,
201
198
  vimModeEnabled: input.vimModeEnabled,
202
199
  terminalHeight: input.terminalHeight,
@@ -279,12 +276,13 @@ function buildUIStateParamsExtra(r: HookResults) {
279
276
  ctrlCPressedOnce: i.ctrlCPressedOnce,
280
277
  ctrlDPressedOnce: i.ctrlDPressedOnce,
281
278
  showEscapePrompt: d.showEscapePrompt,
282
- showIdeRestartPrompt: d.showIdeRestartPrompt,
283
279
  quittingMessages: i.quittingMessages,
284
280
  constrainHeight: d.constrainHeight,
285
281
  showErrorDetails: d.showErrorDetails,
286
282
  showToolDescriptions: d.showToolDescriptions,
287
283
  isTodoPanelCollapsed: d.isTodoPanelCollapsed,
284
+ isQueuedMessagesPanelCollapsed: d.isQueuedMessagesPanelCollapsed,
285
+ queuedSubmissions: i.queuedSubmissions,
288
286
  isNarrow: b.isNarrow,
289
287
  vimModeEnabled: i.vimModeEnabled,
290
288
  vimMode: i.vimMode,
@@ -314,7 +312,6 @@ function buildUIStateParamsExtra(r: HookResults) {
314
312
  commandContext: i.commandContext,
315
313
  shouldShowIdePrompt: b.shouldShowIdePrompt === true,
316
314
  currentIDE: b.currentIDE,
317
- isRestarting: d.isRestarting,
318
315
  isTrustedFolder: b.uiRuntime.app.isTrustedFolder(),
319
316
  isWelcomeDialogOpen: d.isWelcomeDialogOpen,
320
317
  welcomeState: d.welcomeState,
@@ -343,6 +340,7 @@ function buildUIActionsParams(r: HookResults) {
343
340
  loadHistory: b.loadHistory,
344
341
  refreshStatic: d.refreshStatic,
345
342
  handleUserInputSubmit: i.handleUserInputSubmit,
343
+ handleSteer: i.handleSteer,
346
344
  handleClearScreen: l.handleClearScreen,
347
345
  openThemeDialog: d.openThemeDialog,
348
346
  handleThemeSelect: d.handleThemeSelect,
@@ -44,6 +44,8 @@ import type {
44
44
  LspConfig,
45
45
  LspServiceClient,
46
46
  } from '@vybestack/llxprt-code-ide-integration';
47
+ import type { EventEmitter } from 'node:events';
48
+ import { AppEvent, appEvents, type AppEvents } from '../utils/events.js';
47
49
 
48
50
  export interface RefreshMemoryResult {
49
51
  memoryContent: string;
@@ -373,6 +375,15 @@ export interface McpDiscoveryRuntime {
373
375
  getMcpServers(): Record<string, MCPServerConfig> | undefined;
374
376
  }
375
377
 
378
+ /** App-event capability for MCP discovery changes owned by this session. */
379
+ export interface AppEventRuntime {
380
+ onMcpClientUpdate(listener: () => void): () => void;
381
+ }
382
+
383
+ export interface AppEventSource {
384
+ getExtensionEvents(): EventEmitter<AppEvents> | EventEmitter | undefined;
385
+ }
386
+
376
387
  /**
377
388
  * Approval/policy capability for tools dialog and approval-mode display.
378
389
  */
@@ -403,6 +414,7 @@ export interface AppStateRuntime {
403
414
  getDebugMode(): boolean;
404
415
  isRestrictiveSandbox(): boolean;
405
416
  isTrustedFolder(): boolean;
417
+ setTrustedFolderLive(trusted: boolean): Promise<void>;
406
418
  getFolderTrust(): boolean;
407
419
  getQuestion(): string | undefined;
408
420
  getConversationLogPath(): string;
@@ -433,6 +445,7 @@ export interface StreamRuntime {
433
445
  settings: SettingsTelemetryState;
434
446
  scheduler: SchedulerRuntime;
435
447
  asyncTasks: AsyncTaskRuntime;
448
+ events: AppEventRuntime;
436
449
  bucketFailover: BucketFailoverRuntime;
437
450
  checkpoint: CheckpointRuntime;
438
451
  sessionLimits: SessionLimitsRuntime;
@@ -473,6 +486,7 @@ export interface StreamRuntimeBareSource
473
486
  ToolRuntime,
474
487
  SchedulerRuntime,
475
488
  AsyncTaskRuntime,
489
+ AppEventSource,
476
490
  BucketFailoverRuntime,
477
491
  CheckpointRuntime,
478
492
  SessionLimitsRuntime,
@@ -651,6 +665,18 @@ function buildAsyncTaskRuntime(
651
665
  };
652
666
  }
653
667
 
668
+ function buildAppEventRuntime(
669
+ source: StreamRuntimeBareSource,
670
+ ): AppEventRuntime {
671
+ return {
672
+ onMcpClientUpdate: (listener) => {
673
+ const events = source.getExtensionEvents() ?? appEvents;
674
+ events.on(AppEvent.McpClientUpdate, listener);
675
+ return () => events.off(AppEvent.McpClientUpdate, listener);
676
+ },
677
+ };
678
+ }
679
+
654
680
  /**
655
681
  * Helper to build a {@link StreamRuntime} from a runtime source at the
656
682
  * composition edge. Each field is a concrete focused adapter so the nested
@@ -672,6 +698,7 @@ function buildStreamRuntimeFromSource(
672
698
  settings: buildSettingsRuntime(source),
673
699
  scheduler: buildSchedulerRuntime(source),
674
700
  asyncTasks: buildAsyncTaskRuntime(source),
701
+ events: buildAppEventRuntime(source),
675
702
  bucketFailover: {
676
703
  getBucketFailoverHandler: () => source.getBucketFailoverHandler(),
677
704
  },
@@ -712,6 +739,7 @@ export function buildUiRuntimeFromSource(
712
739
  getDebugMode: () => source.getDebugMode(),
713
740
  isRestrictiveSandbox: () => source.isRestrictiveSandbox(),
714
741
  isTrustedFolder: () => source.isTrustedFolder(),
742
+ setTrustedFolderLive: (trusted) => source.setTrustedFolderLive(trusted),
715
743
  getFolderTrust: () => source.getFolderTrust(),
716
744
  getQuestion: () => source.getQuestion(),
717
745
  getConversationLogPath: () => source.getConversationLogPath(),
@@ -18,6 +18,11 @@ import type {
18
18
  import { CommandKind } from './types.js';
19
19
  import type { OAuthManager } from '@vybestack/llxprt-code-providers/auth.js';
20
20
  import { DebugLogger } from '@vybestack/llxprt-code-telemetry';
21
+ import {
22
+ validateProfileDirectory,
23
+ discoverBrowserProfiles,
24
+ type DiscoveredBrowserProfile,
25
+ } from '@vybestack/llxprt-code-core';
21
26
  import { getRuntimeApi } from '../contexts/RuntimeContext.js';
22
27
  import {
23
28
  type CommandArgumentSchema,
@@ -26,6 +31,7 @@ import {
26
31
  import { withFuzzyFilter } from '../utils/fuzzyFilter.js';
27
32
 
28
33
  const logger = new DebugLogger('llxprt:ui:auth-command');
34
+ const BROWSER_PROFILE_ASSOCIATION_BROWSER = 'chrome' as const;
29
35
 
30
36
  /**
31
37
  * Get the OAuth manager instance
@@ -158,6 +164,38 @@ const authCommandSchema: CommandArgumentSchema = [
158
164
  },
159
165
  ],
160
166
  },
167
+ {
168
+ kind: 'literal',
169
+ value: 'create',
170
+ description: 'Discover browser profiles for bucket association setup',
171
+ next: [
172
+ {
173
+ kind: 'value',
174
+ name: 'bucket',
175
+ description: 'Bucket name (defaults to default)',
176
+ },
177
+ ],
178
+ },
179
+ {
180
+ kind: 'literal',
181
+ value: 'profile',
182
+ description: 'Associate or clear a browser profile for a bucket',
183
+ next: [
184
+ {
185
+ kind: 'value',
186
+ name: 'bucket',
187
+ description: 'Bucket name',
188
+ completer: bucketCompleter,
189
+ next: [
190
+ {
191
+ kind: 'value',
192
+ name: 'selector',
193
+ description: 'Profile number, directory name, or --clear',
194
+ },
195
+ ],
196
+ },
197
+ ],
198
+ },
161
199
  {
162
200
  kind: 'literal',
163
201
  value: 'enable',
@@ -219,6 +257,46 @@ function formatBucketStatusLine(bucket: BucketStatus): string {
219
257
  return `${marker}- ${bucket.bucket} (${activeStr}expires: ${expiryDate.toLocaleString()})`;
220
258
  }
221
259
 
260
+ /**
261
+ * Resolve a profile selector into a directory name (and display name when the
262
+ * selector is a number matching a discovered profile).
263
+ *
264
+ * - A 1-based number resolves against the currently discovered Chrome profiles.
265
+ * - Any other value is treated as a literal profile directory name and must
266
+ * pass the strict allowlist validation (security: prevents command injection).
267
+ *
268
+ * Returns undefined when the selector cannot be resolved safely.
269
+ */
270
+ function resolveProfileSelector(selector: string):
271
+ | {
272
+ profileDirectory: string;
273
+ displayName?: string;
274
+ }
275
+ | undefined {
276
+ const numericMatch = /^(\d+)$/.exec(selector);
277
+ if (numericMatch) {
278
+ const index = Number.parseInt(numericMatch[1], 10) - 1;
279
+ const profiles: DiscoveredBrowserProfile[] = discoverBrowserProfiles(
280
+ BROWSER_PROFILE_ASSOCIATION_BROWSER,
281
+ );
282
+ if (index < 0 || index >= profiles.length) {
283
+ return undefined;
284
+ }
285
+ const profile = profiles[index];
286
+ return {
287
+ profileDirectory: profile.directoryName,
288
+ displayName: profile.displayName,
289
+ };
290
+ }
291
+
292
+ try {
293
+ validateProfileDirectory(selector);
294
+ } catch {
295
+ return undefined;
296
+ }
297
+ return { profileDirectory: selector };
298
+ }
299
+
222
300
  export class AuthCommandExecutor {
223
301
  constructor(private oauthManager: OAuthManager) {}
224
302
 
@@ -244,6 +322,21 @@ export class AuthCommandExecutor {
244
322
  const action = parts.length > 1 ? rawAction.toLowerCase() : undefined;
245
323
  const provider = rawProvider.toLowerCase();
246
324
 
325
+ // `/auth anthropic` is not an OAuth identity (issue #2274): the Claude.ai
326
+ // subscription OAuth flow lives under `claudecode`, and Anthropic API keys
327
+ // are configured via `/provider anthropic` + `/key`/`/keyfile`. Redirect
328
+ // users instead of touching OAuth manager state.
329
+ if (provider === 'anthropic') {
330
+ return {
331
+ type: 'message',
332
+ messageType: 'info',
333
+ content:
334
+ 'Anthropic API keys and Claude Code subscription OAuth are now separate.\n' +
335
+ 'For Claude.ai subscription OAuth, run: /auth claudecode\n' +
336
+ 'For Anthropic API-key access, run: /provider anthropic, then /key or /keyfile',
337
+ };
338
+ }
339
+
247
340
  // Check if provider is supported before processing actions
248
341
  const supportedProviders = this.oauthManager.getSupportedProviders();
249
342
  if (!supportedProviders.includes(provider)) {
@@ -279,16 +372,26 @@ export class AuthCommandExecutor {
279
372
  return this.switchBucket(provider, param);
280
373
  }
281
374
 
282
- // Lines 15-17: Handle logout action (NEW) @pseudocode lines 15-17
375
+ // Handle create action: discover browser profiles for a new bucket
376
+ if (action === 'create') {
377
+ const bucket = param && param.length > 0 ? param : 'default';
378
+ return this.discoverBrowserProfilesForBucket(provider, bucket);
379
+ }
380
+
381
+ // Handle profile action: associate/clear a browser profile for a bucket
382
+ if (action === 'profile') {
383
+ const selector = parts[3];
384
+ return this.manageBrowserProfile(provider, param, selector);
385
+ }
386
+
283
387
  if (action === 'logout' || action === 'signout') {
284
388
  return this.logoutWithBucket(provider, param);
285
389
  }
286
390
 
287
- // Lines 19-24: Invalid action @pseudocode lines 19-24
288
391
  return {
289
392
  type: 'message',
290
393
  messageType: 'error',
291
- content: `Invalid action: ${action}. Use enable, disable, login, logout, status, or switch`,
394
+ content: `Invalid action: ${action}. Use create, disable, enable, login, logout, profile, status, or switch`,
292
395
  };
293
396
  }
294
397
 
@@ -480,6 +583,7 @@ export class AuthCommandExecutor {
480
583
  // Check if --all flag specified
481
584
  if (bucketOrFlag === '--all') {
482
585
  await this.oauthManager.logoutAllBuckets(provider);
586
+ this.clearProviderCache(provider);
483
587
  return {
484
588
  type: 'message',
485
589
  messageType: 'info',
@@ -639,6 +743,139 @@ export class AuthCommandExecutor {
639
743
  };
640
744
  }
641
745
  }
746
+ /**
747
+ * Discover available Chrome browser profiles so the user can associate
748
+ * one with a bucket. The association controls which browser/profile opens
749
+ * during the OAuth flow for that bucket.
750
+ */
751
+ private async discoverBrowserProfilesForBucket(
752
+ provider: string,
753
+ bucket: string,
754
+ ): Promise<MessageActionReturn> {
755
+ try {
756
+ const profiles = discoverBrowserProfiles(
757
+ BROWSER_PROFILE_ASSOCIATION_BROWSER,
758
+ );
759
+
760
+ if (profiles.length === 0) {
761
+ return {
762
+ type: 'message',
763
+ messageType: 'info',
764
+ content:
765
+ `No Chrome profiles were detected for ${provider} bucket "${bucket}".\n` +
766
+ `You can associate a profile manually with:\n` +
767
+ ` /auth ${provider} profile ${bucket} <profile-directory>`,
768
+ };
769
+ }
770
+
771
+ const lines: string[] = [
772
+ `Discovered Chrome profiles for ${provider} bucket "${bucket}":`,
773
+ '',
774
+ ...profiles.map(
775
+ (profile, index) =>
776
+ ` ${index + 1}: ${profile.displayName} ` +
777
+ `(profile: ${profile.directoryName})`,
778
+ ),
779
+ '',
780
+ `To associate a profile, run:`,
781
+ ` /auth ${provider} profile ${bucket} <number-or-directory>`,
782
+ `Then authenticate with:`,
783
+ ` /auth ${provider} login ${bucket}`,
784
+ ];
785
+
786
+ return {
787
+ type: 'message',
788
+ messageType: 'info',
789
+ content: lines.join('\n'),
790
+ };
791
+ } catch (error) {
792
+ const errorMessage =
793
+ error instanceof Error ? error.message : String(error);
794
+ return {
795
+ type: 'message',
796
+ messageType: 'error',
797
+ content: `Failed to discover browser profiles for ${provider}: ${errorMessage}`,
798
+ };
799
+ }
800
+ }
801
+
802
+ /**
803
+ * Associate (or clear) a browser profile for a provider+bucket.
804
+ * The selector may be a 1-based number from the discovered list, a literal
805
+ * profile directory name, or "--clear" to remove the association.
806
+ */
807
+ private async manageBrowserProfile(
808
+ provider: string,
809
+ bucket: string | undefined,
810
+ selector: string | undefined,
811
+ ): Promise<MessageActionReturn> {
812
+ try {
813
+ if (!bucket) {
814
+ return {
815
+ type: 'message',
816
+ messageType: 'error',
817
+ content:
818
+ 'Bucket name required for profile command. Usage: /auth <provider> profile <bucket> <number|directory|--clear>',
819
+ };
820
+ }
821
+
822
+ if (!selector) {
823
+ return {
824
+ type: 'message',
825
+ messageType: 'error',
826
+ content: `A selector is required. Usage: /auth ${provider} profile ${bucket} <number|directory|--clear>`,
827
+ };
828
+ }
829
+
830
+ if (selector === '--clear') {
831
+ this.oauthManager.clearBrowserProfileAssociation(provider, bucket);
832
+ return {
833
+ type: 'message',
834
+ messageType: 'info',
835
+ content: `Cleared browser profile association for ${provider} bucket "${bucket}"`,
836
+ };
837
+ }
838
+
839
+ const resolved = resolveProfileSelector(selector);
840
+
841
+ if (resolved === undefined) {
842
+ return {
843
+ type: 'message',
844
+ messageType: 'error',
845
+ content:
846
+ `Invalid profile selector: "${selector}". ` +
847
+ `Use a number from the discovered list or a profile directory name. ` +
848
+ `Path separators, control characters, and traversal sequences (..) are not allowed.`,
849
+ };
850
+ }
851
+
852
+ this.oauthManager.setBrowserProfileAssociation(provider, bucket, {
853
+ browser: BROWSER_PROFILE_ASSOCIATION_BROWSER,
854
+ profileDirectory: resolved.profileDirectory,
855
+ ...(resolved.displayName !== undefined
856
+ ? { displayName: resolved.displayName }
857
+ : {}),
858
+ });
859
+
860
+ const label =
861
+ resolved.displayName !== undefined
862
+ ? `${resolved.displayName} (${resolved.profileDirectory})`
863
+ : resolved.profileDirectory;
864
+ return {
865
+ type: 'message',
866
+ messageType: 'info',
867
+ content: `Associated ${provider} bucket "${bucket}" with Chrome profile: ${label}`,
868
+ };
869
+ } catch (error) {
870
+ const errorMessage =
871
+ error instanceof Error ? error.message : String(error);
872
+ return {
873
+ type: 'message',
874
+ messageType: 'error',
875
+ content: `Failed to set browser profile for ${provider}: ${errorMessage}`,
876
+ };
877
+ }
878
+ }
642
879
 
643
880
  async getAuthStatus(): Promise<string[]> {
644
881
  try {
@@ -27,6 +27,9 @@ function resolveCompressionStatus(
27
27
  return CompressionStatus.COMPRESSION_FAILED;
28
28
  case PerformCompressionResult.SKIPPED_EMPTY:
29
29
  return CompressionStatus.NOOP;
30
+ case PerformCompressionResult.NOOP:
31
+ // Structural no-op: history unchanged, no mutation occurred.
32
+ return CompressionStatus.NOOP;
30
33
  case PerformCompressionResult.COMPRESSED:
31
34
  if (newTokenCount < originalTokenCount) {
32
35
  return CompressionStatus.COMPRESSED;
@@ -44,13 +47,18 @@ function resolveCompressionStatus(
44
47
  }
45
48
 
46
49
  function resolveAgentCompressionStatus(
47
- status: 'compressed' | 'skipped' | 'failed',
50
+ status: 'compressed' | 'skipped' | 'failed' | 'noop',
48
51
  originalTokenCount: number | undefined,
49
52
  newTokenCount: number | undefined,
50
53
  ): CompressionStatus {
51
54
  if (status === 'failed') {
52
55
  return CompressionStatus.COMPRESSION_FAILED;
53
56
  }
57
+ // Structural no-op: history was unchanged by a deterministic strategy guard.
58
+ // Distinct from 'skipped' (cooldown/empty) per issue #2602.
59
+ if (status === 'noop') {
60
+ return CompressionStatus.NOOP;
61
+ }
54
62
  // The agent's CompressionResult collapses both SKIPPED_COOLDOWN (treated as
55
63
  // COMPRESSION_FAILED by the legacy path) and SKIPPED_EMPTY (treated as NOOP)
56
64
  // into a single 'skipped' status. This divergence is tracked debt — see #1595
@@ -41,7 +41,7 @@ function addDirectoriesToWorkspace(
41
41
 
42
42
  if (trustedFolders) {
43
43
  const isTrusted = trustedFolders.isPathTrusted(expandedPath);
44
- if (isTrusted === false) {
44
+ if (isTrusted !== true) {
45
45
  errors.push(
46
46
  `Directory '${pathToAdd.trim()}' is not trusted. Use the '/permissions' command to change the trust level.`,
47
47
  );
@@ -86,7 +86,7 @@ async function logoutViaRuntimeApi(
86
86
  export const logoutCommand: SlashCommand = {
87
87
  name: 'logout',
88
88
  description:
89
- 'logout from OAuth authentication for a provider (gemini, anthropic, codex)',
89
+ 'logout from OAuth authentication for a provider (gemini, claudecode, codex)',
90
90
  kind: CommandKind.BUILT_IN,
91
91
  action: async (
92
92
  context: CommandContext,
@@ -98,7 +98,7 @@ export const logoutCommand: SlashCommand = {
98
98
  type: 'message',
99
99
  messageType: 'error',
100
100
  content:
101
- 'Please specify a provider to logout from: /logout <provider>\nSupported providers: gemini, anthropic, codex',
101
+ 'Please specify a provider to logout from: /logout <provider>\nSupported providers: gemini, claudecode, codex',
102
102
  };
103
103
  }
104
104
  const agent = context.services.agent;