@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.
- package/package.json +13 -13
- package/src/commands/mcp/add.ts +8 -4
- package/src/config/cliArgParser.ts +2 -0
- package/src/config/config.ts +2 -0
- package/src/config/configBuilder.ts +3 -0
- package/src/config/configError.ts +23 -0
- package/src/config/extensions/extensionEnablement.ts +62 -1
- package/src/config/interactiveContext.ts +2 -1
- package/src/config/intermediateConfig.ts +4 -0
- package/src/config/keyBindings.ts +29 -3
- package/src/config/policy.ts +7 -1
- package/src/config/postConfigRuntime.ts +20 -14
- package/src/config/settings-schema/schema-ui.ts +9 -0
- package/src/config/settingsLoader.ts +21 -17
- package/src/config/settingsSchema.ts +2 -2
- package/src/config/trustedFolders.ts +275 -58
- package/src/config/yargsOptions.ts +7 -0
- package/src/generated/git-commit.json +1 -1
- package/src/nonInteractiveCli.ts +1 -0
- package/src/nonInteractiveCliSupport.ts +171 -51
- package/src/runtime/interactiveToolScheduler.ts +6 -6
- package/src/services/BuiltinCommandLoader.ts +33 -25
- package/src/services/FileCommandLoader.ts +34 -7
- package/src/test-utils/async.ts +20 -0
- package/src/test-utils/render.tsx +10 -1
- package/src/ui/App.tsx +17 -10
- package/src/ui/AppContainerRuntime.tsx +7 -9
- package/src/ui/cliUiRuntime.ts +28 -0
- package/src/ui/commands/authCommand.ts +240 -3
- package/src/ui/commands/compressCommand.ts +9 -1
- package/src/ui/commands/directoryCommand.tsx +1 -1
- package/src/ui/commands/logoutCommand.ts +2 -2
- package/src/ui/commands/permissionsCommand.ts +77 -20
- package/src/ui/commands/policiesCommand.ts +6 -1
- package/src/ui/commands/quotaCommand.ts +501 -0
- package/src/ui/commands/statsCommand.ts +2 -2
- package/src/ui/commands/statsQuota.ts +1 -1
- package/src/ui/commands/subagentCommand.ts +59 -27
- package/src/ui/components/AuthDialog.tsx +3 -3
- package/src/ui/components/Composer.tsx +1 -0
- package/src/ui/components/DialogManager.tsx +9 -2
- package/src/ui/components/FolderTrustDialog.tsx +71 -60
- package/src/ui/components/PermissionsModifyTrustDialog.tsx +211 -146
- package/src/ui/components/ProfileCreateWizard/constants.ts +13 -1
- package/src/ui/components/QueuedMessagesPanel.tsx +388 -0
- package/src/ui/components/inputPromptHooks.ts +2 -0
- package/src/ui/components/inputPromptKeyHandlers.ts +14 -0
- package/src/ui/components/inputPromptTypes.ts +6 -0
- package/src/ui/components/messages/AiMessage.tsx +24 -12
- package/src/ui/components/messages/ToolMessage.tsx +26 -40
- package/src/ui/containers/AppContainer/builders/buildUIActions.ts +3 -1
- package/src/ui/containers/AppContainer/builders/buildUIState.ts +5 -4
- package/src/ui/containers/AppContainer/hooks/useAppBootstrap.ts +3 -33
- package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +2 -14
- package/src/ui/containers/AppContainer/hooks/useAppInput.ts +31 -25
- package/src/ui/containers/AppContainer/hooks/useAppLayout.ts +90 -60
- package/src/ui/containers/AppContainer/hooks/useDisplayPreferences.ts +10 -0
- package/src/ui/containers/AppContainer/hooks/useInputHandling.ts +3 -17
- package/src/ui/containers/AppContainer/hooks/useKeybindings.ts +51 -38
- package/src/ui/containers/AppContainer/hooks/useSteer.ts +51 -0
- package/src/ui/contexts/KeypressContext.tsx +13 -2
- package/src/ui/contexts/ShellCommandDisplayContext.tsx +103 -0
- package/src/ui/contexts/TodoContext.tsx +0 -13
- package/src/ui/contexts/TodoProvider.tsx +1 -34
- package/src/ui/contexts/UIActionsContext.tsx +2 -1
- package/src/ui/contexts/UIStateContext.tsx +3 -2
- package/src/ui/hooks/agentStream/streamUtils.ts +0 -24
- package/src/ui/hooks/agentStream/thoughtState.ts +110 -30
- package/src/ui/hooks/agentStream/types.ts +1 -0
- package/src/ui/hooks/agentStream/useAgentEventStream.ts +8 -17
- package/src/ui/hooks/agentStream/useAgentStream.ts +2 -2
- package/src/ui/hooks/agentStream/useAgentStreamLifecycle.ts +4 -5
- package/src/ui/hooks/agentStream/useAgentStreamOrchestration.ts +12 -5
- package/src/ui/hooks/agentStream/useQueuedSubmissions.ts +99 -0
- package/src/ui/hooks/agentStream/useStreamEventHandlers.ts +4 -13
- package/src/ui/hooks/agentStream/useStreamState.ts +60 -41
- package/src/ui/hooks/agentStream/useSubmitQuery.ts +284 -59
- package/src/ui/hooks/slashCommandProcessorSupport.ts +4 -0
- package/src/ui/hooks/useAgentStream-test-helpers.ts +1 -0
- package/src/ui/hooks/useAutoAcceptIndicator.ts +13 -0
- package/src/ui/hooks/useFolderTrust.ts +118 -81
- package/src/ui/hooks/useGitBranchName.ts +79 -33
- package/src/ui/hooks/useIdeTrustListener.ts +5 -19
- package/src/ui/hooks/usePermissionsModifyTrust.ts +223 -85
- package/src/ui/hooks/useReactToolScheduler.ts +13 -17
- package/src/ui/hooks/useTodoPausePreserver.ts +0 -55
- package/src/ui/layouts/DefaultAppLayout.tsx +2 -0
- package/src/ui/layouts/DefaultAppLayoutHelpers.tsx +37 -162
- package/src/ui/layouts/InlineContent.tsx +198 -0
- package/src/ui/trustDialogHelpers.ts +190 -0
- package/src/utils/cleanup-state.ts +115 -0
- package/src/utils/cleanup.ts +32 -34
- package/src/utils/sandbox-containers.ts +183 -53
- package/src/utils/sandbox-exec.ts +9 -3
- package/src/zed-integration/acp-terminal-shell-host.ts +112 -0
- package/src/zed-integration/acp-types.ts +35 -0
- package/src/zed-integration/fileSystemService.ts +1 -1
- package/src/zed-integration/runZedIntegration.ts +142 -0
- package/src/zed-integration/zed-agent-event-handler.ts +179 -0
- package/src/zed-integration/zed-agent-setup.ts +43 -0
- package/src/zed-integration/zed-command-registry.ts +167 -0
- package/src/zed-integration/zed-config-options.ts +280 -0
- package/src/zed-integration/zed-content-utils.ts +2 -2
- package/src/zed-integration/zed-helpers.ts +132 -0
- package/src/zed-integration/zed-initialize.ts +64 -0
- package/src/zed-integration/zed-plan-update.ts +20 -0
- package/src/zed-integration/zed-prompt-command.ts +77 -0
- package/src/zed-integration/zed-session-config.ts +119 -0
- package/src/zed-integration/zed-session-errors.ts +337 -0
- package/src/zed-integration/zed-session-events.ts +216 -0
- package/src/zed-integration/zed-session-info.ts +255 -0
- package/src/zed-integration/zed-session-lifecycle.ts +186 -0
- package/src/zed-integration/zed-session-listing.ts +153 -0
- package/src/zed-integration/zed-session-loader.ts +193 -0
- package/src/zed-integration/zed-session-pagination.ts +240 -0
- package/src/zed-integration/zed-session-replay.ts +564 -0
- package/src/zed-integration/zed-stream-batcher.ts +236 -0
- package/src/zed-integration/zed-terminal-manager.ts +416 -0
- package/src/zed-integration/zed-terminal-setup.ts +72 -0
- package/src/zed-integration/zed-test-helpers.ts +349 -4
- package/src/zed-integration/zed-tool-handler.ts +47 -5
- package/src/zed-integration/zedIntegration.ts +538 -587
- package/src/services/todo-continuation/todoContinuationService.ts +0 -610
- package/src/ui/containers/AppContainer/hooks/useIdeRestartHotkey.ts +0 -33
- package/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts +0 -160
- 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
|
-
<
|
|
83
|
-
|
|
83
|
+
<ShellCommandDisplayProvider
|
|
84
|
+
alwaysDisplayFullShellCommand={
|
|
85
|
+
props.settings.merged.ui.alwaysDisplayFullShellCommand ?? true
|
|
86
|
+
}
|
|
84
87
|
>
|
|
85
|
-
<
|
|
88
|
+
<ToolCallProvider
|
|
86
89
|
sessionId={props.uiRuntime.session.getSessionId()}
|
|
87
90
|
>
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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,
|
package/src/ui/cliUiRuntime.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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;
|