@theia/ai-ide 1.74.0-next.9 → 1.74.0
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/lib/browser/ai-allow-all-mode-chat-banner.d.ts +68 -0
- package/lib/browser/ai-allow-all-mode-chat-banner.d.ts.map +1 -0
- package/lib/browser/ai-allow-all-mode-chat-banner.js +184 -0
- package/lib/browser/ai-allow-all-mode-chat-banner.js.map +1 -0
- package/lib/browser/ai-allow-all-mode-chat-banner.spec.d.ts +2 -0
- package/lib/browser/ai-allow-all-mode-chat-banner.spec.d.ts.map +1 -0
- package/lib/browser/ai-allow-all-mode-chat-banner.spec.js +170 -0
- package/lib/browser/ai-allow-all-mode-chat-banner.spec.js.map +1 -0
- package/lib/browser/ai-configuration/tools-configuration-widget.d.ts +3 -4
- package/lib/browser/ai-configuration/tools-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/tools-configuration-widget.js +17 -10
- package/lib/browser/ai-configuration/tools-configuration-widget.js.map +1 -1
- package/lib/browser/ai-first-perspective-contribution.d.ts +6 -0
- package/lib/browser/ai-first-perspective-contribution.d.ts.map +1 -0
- package/lib/browser/ai-first-perspective-contribution.js +50 -0
- package/lib/browser/ai-first-perspective-contribution.js.map +1 -0
- package/lib/browser/ai-sessions-view-contribution.d.ts +13 -0
- package/lib/browser/ai-sessions-view-contribution.d.ts.map +1 -0
- package/lib/browser/ai-sessions-view-contribution.js +69 -0
- package/lib/browser/ai-sessions-view-contribution.js.map +1 -0
- package/lib/browser/ai-sessions-widget.d.ts +27 -0
- package/lib/browser/ai-sessions-widget.d.ts.map +1 -0
- package/lib/browser/ai-sessions-widget.js +131 -0
- package/lib/browser/ai-sessions-widget.js.map +1 -0
- package/lib/browser/chat-session-item-action-contribution.js +2 -2
- package/lib/browser/chat-session-item-action-contribution.js.map +1 -1
- package/lib/browser/chat-session-item.d.ts +15 -0
- package/lib/browser/chat-session-item.d.ts.map +1 -1
- package/lib/browser/chat-session-item.js +39 -18
- package/lib/browser/chat-session-item.js.map +1 -1
- package/lib/browser/chat-session-list-components.d.ts +38 -0
- package/lib/browser/chat-session-list-components.d.ts.map +1 -0
- package/lib/browser/chat-session-list-components.js +87 -0
- package/lib/browser/chat-session-list-components.js.map +1 -0
- package/lib/browser/chat-session-list-service.d.ts +34 -0
- package/lib/browser/chat-session-list-service.d.ts.map +1 -0
- package/lib/browser/chat-session-list-service.js +251 -0
- package/lib/browser/chat-session-list-service.js.map +1 -0
- package/lib/browser/chat-session-list-service.spec.d.ts +2 -0
- package/lib/browser/chat-session-list-service.spec.d.ts.map +1 -0
- package/lib/browser/{chat-sessions-welcome-message-provider.spec.js → chat-session-list-service.spec.js} +133 -39
- package/lib/browser/chat-session-list-service.spec.js.map +1 -0
- package/lib/browser/chat-session-tooltip.d.ts +2 -2
- package/lib/browser/chat-session-tooltip.d.ts.map +1 -1
- package/lib/browser/chat-session-tooltip.js +33 -10
- package/lib/browser/chat-session-tooltip.js.map +1 -1
- package/lib/browser/chat-sessions-welcome-message-provider.d.ts +9 -35
- package/lib/browser/chat-sessions-welcome-message-provider.d.ts.map +1 -1
- package/lib/browser/chat-sessions-welcome-message-provider.js +51 -205
- package/lib/browser/chat-sessions-welcome-message-provider.js.map +1 -1
- package/lib/browser/context-file-validation-service-impl.spec.js +2 -2
- package/lib/browser/context-file-validation-service-impl.spec.js.map +1 -1
- package/lib/browser/frontend-module.d.ts.map +1 -1
- package/lib/browser/frontend-module.js +20 -0
- package/lib/browser/frontend-module.js.map +1 -1
- package/lib/browser/workspace-functions.d.ts +5 -6
- package/lib/browser/workspace-functions.d.ts.map +1 -1
- package/lib/browser/workspace-functions.js +24 -9
- package/lib/browser/workspace-functions.js.map +1 -1
- package/lib/browser/workspace-functions.spec.js +29 -29
- package/lib/browser/workspace-functions.spec.js.map +1 -1
- package/lib/browser/workspace-launch-provider.spec.js +3 -3
- package/lib/browser/workspace-launch-provider.spec.js.map +1 -1
- package/lib/browser/workspace-search-provider.d.ts.map +1 -1
- package/lib/browser/workspace-search-provider.js +28 -20
- package/lib/browser/workspace-search-provider.js.map +1 -1
- package/lib/browser/workspace-search-provider.spec.js +68 -0
- package/lib/browser/workspace-search-provider.spec.js.map +1 -1
- package/lib/browser/workspace-task-provider.spec.js +4 -4
- package/lib/browser/workspace-task-provider.spec.js.map +1 -1
- package/package.json +25 -25
- package/src/browser/ai-allow-all-mode-chat-banner.spec.ts +205 -0
- package/src/browser/ai-allow-all-mode-chat-banner.tsx +217 -0
- package/src/browser/ai-configuration/tools-configuration-widget.tsx +20 -13
- package/src/browser/ai-first-perspective-contribution.ts +48 -0
- package/src/browser/ai-sessions-view-contribution.ts +67 -0
- package/src/browser/ai-sessions-widget.tsx +151 -0
- package/src/browser/chat-session-item-action-contribution.ts +2 -2
- package/src/browser/chat-session-item.tsx +82 -27
- package/src/browser/chat-session-list-components.tsx +136 -0
- package/src/browser/{chat-sessions-welcome-message-provider.spec.ts → chat-session-list-service.spec.ts} +136 -30
- package/src/browser/chat-session-list-service.ts +261 -0
- package/src/browser/chat-session-tooltip.ts +31 -11
- package/src/browser/chat-sessions-welcome-message-provider.tsx +78 -268
- package/src/browser/context-file-validation-service-impl.spec.ts +3 -3
- package/src/browser/frontend-module.ts +23 -0
- package/src/browser/style/index.css +150 -3
- package/src/browser/workspace-functions.spec.ts +36 -37
- package/src/browser/workspace-functions.ts +8 -9
- package/src/browser/workspace-launch-provider.spec.ts +5 -5
- package/src/browser/workspace-search-provider.spec.ts +101 -0
- package/src/browser/workspace-search-provider.ts +34 -23
- package/src/browser/workspace-task-provider.spec.ts +6 -7
- package/lib/browser/chat-sessions-welcome-message-provider.spec.d.ts +0 -2
- package/lib/browser/chat-sessions-welcome-message-provider.spec.d.ts.map +0 -1
- package/lib/browser/chat-sessions-welcome-message-provider.spec.js.map +0 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as React from '@theia/core/shared/react';
|
|
18
|
+
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
19
|
+
import { codicon } from '@theia/core/lib/browser';
|
|
20
|
+
import { DisposableCollection, Emitter, Event } from '@theia/core';
|
|
21
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
22
|
+
import { PreferenceService } from '@theia/core/lib/common';
|
|
23
|
+
import { ChatBannerProvider } from '@theia/ai-chat-ui/lib/browser/chat-banner-provider';
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_TOOL_CONFIRMATION_PREFERENCE,
|
|
26
|
+
TOOL_CONFIRMATION_PREFERENCE,
|
|
27
|
+
ToolConfirmationMode
|
|
28
|
+
} from '@theia/ai-chat/lib/common/chat-tool-preferences';
|
|
29
|
+
|
|
30
|
+
interface AiSessionOverride {
|
|
31
|
+
/** Human-readable label, e.g. "Default tool confirmation: always_allow". */
|
|
32
|
+
label: string;
|
|
33
|
+
/** Whether this override bypasses AI tool confirmations globally. */
|
|
34
|
+
bypass: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Persistent strip rendered above the chat content whenever an AI tool confirmation
|
|
39
|
+
* preference (`ai-features.chat.defaultToolConfirmation` or `ai-features.chat.toolConfirmation`)
|
|
40
|
+
* is active in the session scope (typically set via `--session-preference`). When the
|
|
41
|
+
* global default is forced to "Allow All" the strip is shown in error styling as the
|
|
42
|
+
* Theia AI Allow-All Mode banner; per-tool confirmation overrides use warning styling.
|
|
43
|
+
*
|
|
44
|
+
* Other AI-namespaced session overrides (e.g. forced-on AI features, default chat agent)
|
|
45
|
+
* are reported by the generic Session Preferences status bar item and intentionally not
|
|
46
|
+
* duplicated here.
|
|
47
|
+
*/
|
|
48
|
+
@injectable()
|
|
49
|
+
export class AiAllowAllModeChatBanner implements ChatBannerProvider {
|
|
50
|
+
|
|
51
|
+
readonly priority = 1000;
|
|
52
|
+
|
|
53
|
+
@inject(PreferenceService)
|
|
54
|
+
protected readonly preferenceService: PreferenceService;
|
|
55
|
+
|
|
56
|
+
protected readonly toDispose = new DisposableCollection();
|
|
57
|
+
protected readonly onDidChangeEmitter = new Emitter<void>();
|
|
58
|
+
readonly onDidChange: Event<void> = this.onDidChangeEmitter.event;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Only tool confirmation preferences are watched here. Other AI session overrides
|
|
62
|
+
* (enable AI, agent mode, default chat agent, etc.) are surfaced in the generic
|
|
63
|
+
* Session Preferences status bar item to keep the banner from duplicating the list.
|
|
64
|
+
*/
|
|
65
|
+
protected readonly watchedKeys: ReadonlyArray<string> = [
|
|
66
|
+
DEFAULT_TOOL_CONFIRMATION_PREFERENCE,
|
|
67
|
+
TOOL_CONFIRMATION_PREFERENCE
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Process-lifetime dismissal flag. Reset on the next launch so the user is
|
|
72
|
+
* always reminded once per process. Also automatically cleared when the override
|
|
73
|
+
* set changes in any way after a dismissal (see {@link init}), so a previously
|
|
74
|
+
* dismissed strip cannot silently hide new or escalated overrides.
|
|
75
|
+
*/
|
|
76
|
+
protected dismissed = false;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Signature of the override set at the moment the user dismissed the strip.
|
|
80
|
+
* `undefined` while the strip is not dismissed.
|
|
81
|
+
*/
|
|
82
|
+
protected dismissedSignature: string | undefined;
|
|
83
|
+
|
|
84
|
+
@postConstruct()
|
|
85
|
+
protected init(): void {
|
|
86
|
+
const watched = new Set(this.watchedKeys);
|
|
87
|
+
this.toDispose.push(this.preferenceService.onPreferenceChanged(e => {
|
|
88
|
+
if (!watched.has(e.preferenceName)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (this.dismissed && this.overrideSignature() !== this.dismissedSignature) {
|
|
92
|
+
// The override set has changed since the user dismissed the strip
|
|
93
|
+
// (new override added, existing value changed, escalation into a bypass, ...).
|
|
94
|
+
// Re-surface the strip so the change cannot hide behind the earlier dismissal.
|
|
95
|
+
this.dismissed = false;
|
|
96
|
+
this.dismissedSignature = undefined;
|
|
97
|
+
}
|
|
98
|
+
this.onDidChangeEmitter.fire();
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Stable snapshot of the watched preferences' current session values. Used to
|
|
104
|
+
* detect changes to the override set for the dismissal-reset logic; not for
|
|
105
|
+
* anything user-visible so the format is intentionally simple.
|
|
106
|
+
*/
|
|
107
|
+
protected overrideSignature(): string {
|
|
108
|
+
return this.watchedKeys
|
|
109
|
+
.map(key => `${key}=${JSON.stringify(this.preferenceService.inspect(key)?.sessionValue ?? undefined)}`)
|
|
110
|
+
.join('|');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
renderBanner(): React.ReactNode | undefined {
|
|
114
|
+
if (this.dismissed) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
const overrides = this.collectOverrides();
|
|
118
|
+
if (overrides.length === 0) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const isAllowAll = overrides.some(override => override.bypass);
|
|
122
|
+
const className = `theia-ai-allow-all-mode-strip ${isAllowAll ? 'is-allow-all' : 'is-overrides'}`;
|
|
123
|
+
const title = isAllowAll
|
|
124
|
+
? nls.localize('theia/ai/ide/allowAllMode/title', 'Theia AI Allow-All Mode')
|
|
125
|
+
: nls.localize('theia/ai/ide/toolConfirmationOverrides/title', 'AI Tool Confirmation Overrides');
|
|
126
|
+
const tooltipLines = [
|
|
127
|
+
isAllowAll
|
|
128
|
+
? nls.localize('theia/ai/ide/allowAllMode/stripTooltip',
|
|
129
|
+
'AI tool calls run without confirmation in this session. Only enable in environments you trust.')
|
|
130
|
+
: nls.localize('theia/ai/ide/toolConfirmationOverrides/stripTooltip',
|
|
131
|
+
'One or more AI tools are auto-approved for this session via command-line flags.'),
|
|
132
|
+
'',
|
|
133
|
+
...overrides.map(override => `• ${override.label}`),
|
|
134
|
+
'',
|
|
135
|
+
nls.localize('theia/ai/ide/allowAllMode/stripTooltipFooter',
|
|
136
|
+
'Set via --session-preference. Restart without the flag to restore your saved settings.')
|
|
137
|
+
];
|
|
138
|
+
return <div
|
|
139
|
+
className={className}
|
|
140
|
+
title={tooltipLines.join('\n')}
|
|
141
|
+
role='status'
|
|
142
|
+
aria-live='polite'>
|
|
143
|
+
<span className={`theia-ai-allow-all-mode-strip-icon ${codicon('warning')}`}></span>
|
|
144
|
+
<span className='theia-ai-allow-all-mode-strip-title'>{title}</span>
|
|
145
|
+
<button
|
|
146
|
+
type='button'
|
|
147
|
+
className={`theia-ai-allow-all-mode-strip-dismiss ${codicon('close')}`}
|
|
148
|
+
title={nls.localizeByDefault('Hide for this session')}
|
|
149
|
+
aria-label={nls.localizeByDefault('Hide for this session')}
|
|
150
|
+
onClick={this.handleDismiss}
|
|
151
|
+
/>
|
|
152
|
+
</div>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** AI-relevant preferences currently coming from the session scope. */
|
|
156
|
+
protected collectOverrides(): AiSessionOverride[] {
|
|
157
|
+
const overrides: AiSessionOverride[] = [];
|
|
158
|
+
for (const key of this.watchedKeys) {
|
|
159
|
+
const sessionValue = this.preferenceService.inspect(key)?.sessionValue;
|
|
160
|
+
if (sessionValue === undefined) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
overrides.push({
|
|
164
|
+
label: this.describe(key, sessionValue),
|
|
165
|
+
bypass: this.isBypassValue(key, sessionValue)
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return overrides;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Returns `true` only when the global default is forced to Allow-All. Per-tool
|
|
173
|
+
* `always_allow` entries via `TOOL_CONFIRMATION_PREFERENCE` are scoped exceptions
|
|
174
|
+
* and remain in the banner as informational (warning) entries rather than escalating
|
|
175
|
+
* the whole strip to the red "Allow-All Mode" treatment.
|
|
176
|
+
*/
|
|
177
|
+
protected isBypassValue(key: string, value: unknown): boolean {
|
|
178
|
+
if (key === DEFAULT_TOOL_CONFIRMATION_PREFERENCE) {
|
|
179
|
+
return value === ToolConfirmationMode.ALWAYS_ALLOW;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
protected describe(key: string, value: unknown): string {
|
|
185
|
+
switch (key) {
|
|
186
|
+
case DEFAULT_TOOL_CONFIRMATION_PREFERENCE:
|
|
187
|
+
return nls.localize('theia/ai/ide/sessionOverride/defaultToolConfirmation',
|
|
188
|
+
'Default tool confirmation: {0}', String(value));
|
|
189
|
+
case TOOL_CONFIRMATION_PREFERENCE: {
|
|
190
|
+
if (value && typeof value === 'object') {
|
|
191
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
192
|
+
.map(([tool, mode]) => `${tool}=${mode}`)
|
|
193
|
+
.join(', ');
|
|
194
|
+
return nls.localize('theia/ai/ide/sessionOverride/toolConfirmation',
|
|
195
|
+
'Per-tool confirmation overrides: {0}', entries);
|
|
196
|
+
}
|
|
197
|
+
return nls.localize('theia/ai/ide/sessionOverride/toolConfirmationGeneric',
|
|
198
|
+
'Per-tool confirmation overrides set');
|
|
199
|
+
}
|
|
200
|
+
default:
|
|
201
|
+
return `${key} = ${JSON.stringify(value)}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
protected handleDismiss = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
this.dismissed = true;
|
|
208
|
+
// Snapshot the signature so a subsequent change to the override set is detected
|
|
209
|
+
// in the preference-change listener and un-dismisses the strip automatically.
|
|
210
|
+
this.dismissedSignature = this.overrideSignature();
|
|
211
|
+
this.onDidChangeEmitter.fire();
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
dispose(): void {
|
|
215
|
+
this.toDispose.dispose();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
import { ConfirmDialog } from '@theia/core/lib/browser';
|
|
18
18
|
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
19
19
|
import * as React from '@theia/core/shared/react';
|
|
20
|
-
import { ToolInvocationRegistry, ToolRequest } from '@theia/ai-core';
|
|
21
|
-
import { nls
|
|
20
|
+
import { AiConfigurationService, ToolInvocationRegistry, ToolRequest } from '@theia/ai-core';
|
|
21
|
+
import { nls } from '@theia/core';
|
|
22
22
|
import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
|
|
23
23
|
import { ShellCommandPermissionService } from '@theia/ai-terminal/lib/browser/shell-command-permission-service';
|
|
24
24
|
import {
|
|
@@ -47,8 +47,8 @@ export class AIToolsConfigurationWidget extends AITableConfigurationWidget<ToolI
|
|
|
47
47
|
@inject(ToolConfirmationManager)
|
|
48
48
|
protected readonly confirmationManager: ToolConfirmationManager;
|
|
49
49
|
|
|
50
|
-
@inject(
|
|
51
|
-
protected readonly
|
|
50
|
+
@inject(AiConfigurationService)
|
|
51
|
+
protected readonly aiConfigurationService: AiConfigurationService;
|
|
52
52
|
|
|
53
53
|
@inject(ToolInvocationRegistry)
|
|
54
54
|
protected readonly toolInvocationRegistry: ToolInvocationRegistry;
|
|
@@ -74,18 +74,18 @@ export class AIToolsConfigurationWidget extends AITableConfigurationWidget<ToolI
|
|
|
74
74
|
|
|
75
75
|
this.loadData().then(() => this.update());
|
|
76
76
|
this.toDispose.pushAll([
|
|
77
|
-
this.
|
|
78
|
-
if (e.
|
|
79
|
-
|| e.
|
|
77
|
+
this.aiConfigurationService.onDidChange(async e => {
|
|
78
|
+
if (e.affectsPreference(TOOL_CONFIRMATION_PREFERENCE)
|
|
79
|
+
|| e.affectsPreference(DEFAULT_TOOL_CONFIRMATION_PREFERENCE)) {
|
|
80
80
|
this.defaultState = await this.loadDefaultConfirmation();
|
|
81
81
|
this.toolConfirmationModes = await this.loadToolConfigurationModes();
|
|
82
82
|
this.update();
|
|
83
83
|
}
|
|
84
|
-
if (e.
|
|
84
|
+
if (e.affectsPreference(SHELL_COMMAND_ALLOWLIST_PREFERENCE)) {
|
|
85
85
|
this.allowlistPatterns = this.shellCommandPermissionService.getAllowlistPatterns();
|
|
86
86
|
this.update();
|
|
87
87
|
}
|
|
88
|
-
if (e.
|
|
88
|
+
if (e.affectsPreference(SHELL_COMMAND_DENYLIST_PREFERENCE)) {
|
|
89
89
|
this.denylistPatterns = this.shellCommandPermissionService.getDenylistPatterns();
|
|
90
90
|
this.update();
|
|
91
91
|
}
|
|
@@ -98,6 +98,7 @@ export class AIToolsConfigurationWidget extends AITableConfigurationWidget<ToolI
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
protected async loadData(): Promise<void> {
|
|
101
|
+
await this.aiConfigurationService.ready;
|
|
101
102
|
await this.loadItems();
|
|
102
103
|
this.defaultState = await this.loadDefaultConfirmation();
|
|
103
104
|
this.toolConfirmationModes = await this.loadToolConfigurationModes();
|
|
@@ -374,7 +375,8 @@ export class AIToolsConfigurationWidget extends AITableConfigurationWidget<ToolI
|
|
|
374
375
|
}
|
|
375
376
|
|
|
376
377
|
protected handleRemoveAllowlistPattern(pattern: string): void {
|
|
377
|
-
this.shellCommandPermissionService.removeAllowlistPattern(pattern)
|
|
378
|
+
this.shellCommandPermissionService.removeAllowlistPattern(pattern)
|
|
379
|
+
.catch(error => console.error('Failed to remove allowlist pattern:', error));
|
|
378
380
|
}
|
|
379
381
|
|
|
380
382
|
protected handleAddDenylistPattern(): void {
|
|
@@ -388,12 +390,13 @@ export class AIToolsConfigurationWidget extends AITableConfigurationWidget<ToolI
|
|
|
388
390
|
}
|
|
389
391
|
|
|
390
392
|
protected handleRemoveDenylistPattern(pattern: string): void {
|
|
391
|
-
this.shellCommandPermissionService.removeDenylistPattern(pattern)
|
|
393
|
+
this.shellCommandPermissionService.removeDenylistPattern(pattern)
|
|
394
|
+
.catch(error => console.error('Failed to remove denylist pattern:', error));
|
|
392
395
|
}
|
|
393
396
|
|
|
394
397
|
protected handleAddPatternToList(
|
|
395
398
|
inputRef: React.RefObject<HTMLInputElement | null>,
|
|
396
|
-
addFn: (pattern: string) => void
|
|
399
|
+
addFn: (pattern: string) => Promise<void>,
|
|
397
400
|
getFn: () => string[],
|
|
398
401
|
setPatterns: (patterns: string[]) => void,
|
|
399
402
|
setError: (error: string | undefined) => void
|
|
@@ -409,7 +412,11 @@ export class AIToolsConfigurationWidget extends AITableConfigurationWidget<ToolI
|
|
|
409
412
|
}
|
|
410
413
|
|
|
411
414
|
try {
|
|
412
|
-
|
|
415
|
+
// Validation throws synchronously; a failed write rejects the returned promise.
|
|
416
|
+
addFn(trimmed).catch(error => {
|
|
417
|
+
setError(error instanceof Error ? error.message : 'Failed to save pattern');
|
|
418
|
+
this.update();
|
|
419
|
+
});
|
|
413
420
|
input.value = '';
|
|
414
421
|
setError(undefined);
|
|
415
422
|
setPatterns(getFn());
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { injectable } from '@theia/core/shared/inversify';
|
|
18
|
+
import { nls } from '@theia/core';
|
|
19
|
+
import { PerspectiveContribution, PerspectiveChromeOptions, PerspectiveService } from '@theia/core/lib/browser/perspective-service';
|
|
20
|
+
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
|
21
|
+
import { EXPLORER_VIEW_CONTAINER_ID } from '@theia/navigator/lib/browser';
|
|
22
|
+
import { SCM_VIEW_CONTAINER_ID } from '@theia/scm/lib/browser/scm-contribution';
|
|
23
|
+
import { ChatViewWidget } from '@theia/ai-chat-ui/lib/browser/chat-view-widget';
|
|
24
|
+
import { AISessionsWidget } from './ai-sessions-widget';
|
|
25
|
+
|
|
26
|
+
export const AI_FIRST_PERSPECTIVE_ID = 'ai-first';
|
|
27
|
+
|
|
28
|
+
@injectable()
|
|
29
|
+
export class AIFirstPerspectiveContribution implements PerspectiveContribution {
|
|
30
|
+
|
|
31
|
+
registerPerspectives(service: PerspectiveService): void {
|
|
32
|
+
const chromeOptions: PerspectiveChromeOptions = {
|
|
33
|
+
collapseAreas: ['bottom']
|
|
34
|
+
};
|
|
35
|
+
service.registerPerspective({
|
|
36
|
+
id: AI_FIRST_PERSPECTIVE_ID,
|
|
37
|
+
label: nls.localize('theia/ai-ide/perspective/aiFirst', 'AI First'),
|
|
38
|
+
viewPlacements: new Map<string, ApplicationShell.Area>([
|
|
39
|
+
[ChatViewWidget.ID, 'main'],
|
|
40
|
+
[EXPLORER_VIEW_CONTAINER_ID, 'right'],
|
|
41
|
+
[SCM_VIEW_CONTAINER_ID, 'right'],
|
|
42
|
+
[AISessionsWidget.ID, 'left']
|
|
43
|
+
]),
|
|
44
|
+
primaryViews: { right: EXPLORER_VIEW_CONTAINER_ID },
|
|
45
|
+
chromeOptions
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { AI_CHAT_HOME, ChatCommands } from '@theia/ai-chat-ui/lib/browser/chat-view-commands';
|
|
18
|
+
import { AIViewContribution, ENABLE_AI_CONTEXT_KEY } from '@theia/ai-core/lib/browser';
|
|
19
|
+
import { Command, CommandRegistry, nls } from '@theia/core';
|
|
20
|
+
import { codicon } from '@theia/core/lib/browser';
|
|
21
|
+
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
|
22
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
23
|
+
import { AISessionsWidget } from './ai-sessions-widget';
|
|
24
|
+
|
|
25
|
+
export const AI_SESSIONS_TOGGLE_COMMAND_ID = 'aiSessions:toggle';
|
|
26
|
+
|
|
27
|
+
export const AI_SESSIONS_NEW_SESSION = Command.toLocalizedCommand({
|
|
28
|
+
id: 'aiSessions:newSession',
|
|
29
|
+
iconClass: codicon('add'),
|
|
30
|
+
category: ChatCommands.CHAT_CATEGORY,
|
|
31
|
+
label: 'New Chat'
|
|
32
|
+
}, 'theia/ai-ide/newChat', ChatCommands.CHAT_CATEGORY_KEY);
|
|
33
|
+
|
|
34
|
+
@injectable()
|
|
35
|
+
export class AISessionsViewContribution extends AIViewContribution<AISessionsWidget> implements TabBarToolbarContribution {
|
|
36
|
+
|
|
37
|
+
@inject(CommandRegistry)
|
|
38
|
+
protected readonly commandRegistry: CommandRegistry;
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
super({
|
|
42
|
+
widgetId: AISessionsWidget.ID,
|
|
43
|
+
widgetName: AISessionsWidget.LABEL,
|
|
44
|
+
defaultWidgetOptions: {
|
|
45
|
+
area: 'left'
|
|
46
|
+
},
|
|
47
|
+
toggleCommandId: AI_SESSIONS_TOGGLE_COMMAND_ID
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override registerCommands(commands: CommandRegistry): void {
|
|
52
|
+
super.registerCommands(commands);
|
|
53
|
+
commands.registerCommand(AI_SESSIONS_NEW_SESSION, this.commandHandlerFactory({
|
|
54
|
+
execute: () => this.commandRegistry.executeCommand(AI_CHAT_HOME.id)
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
|
59
|
+
registry.registerItem({
|
|
60
|
+
id: AI_SESSIONS_NEW_SESSION.id,
|
|
61
|
+
command: AI_SESSIONS_NEW_SESSION.id,
|
|
62
|
+
tooltip: nls.localizeByDefault('New Chat'),
|
|
63
|
+
isVisible: widget => this.activationService.isActive && widget instanceof AISessionsWidget,
|
|
64
|
+
when: ENABLE_AI_CONTEXT_KEY
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ChatAgentService, ChatService, ChatSessionMetadata } from '@theia/ai-chat';
|
|
18
|
+
import { AI_CHAT_OPEN_SESSION } from '@theia/ai-chat-ui/lib/browser/chat-view-commands';
|
|
19
|
+
import { formatTimeAgo } from '@theia/ai-chat-ui/lib/browser/chat-date-utils';
|
|
20
|
+
import { ChatSessionItemAction, ChatSessionItemActionContribution } from './chat-session-item-action-contribution';
|
|
21
|
+
import { ChatSessionListService } from './chat-session-list-service';
|
|
22
|
+
import { SessionRow, SessionsList } from './chat-session-list-components';
|
|
23
|
+
import { ChatSessionItem } from './chat-session-item';
|
|
24
|
+
import { CommandRegistry, ContributionProvider, nls } from '@theia/core';
|
|
25
|
+
import { codicon, HoverService, ReactWidget } from '@theia/core/lib/browser';
|
|
26
|
+
import { MarkdownRenderer, MarkdownRendererFactory } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
|
|
27
|
+
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
|
|
28
|
+
import * as React from '@theia/core/shared/react';
|
|
29
|
+
|
|
30
|
+
@injectable()
|
|
31
|
+
export class AISessionsWidget extends ReactWidget {
|
|
32
|
+
|
|
33
|
+
static readonly ID = 'ai-sessions-widget';
|
|
34
|
+
static readonly LABEL = nls.localize('theia/ai-ide/sessionsView', 'AI Sessions');
|
|
35
|
+
|
|
36
|
+
@inject(ChatSessionListService)
|
|
37
|
+
protected readonly sessionListService: ChatSessionListService;
|
|
38
|
+
|
|
39
|
+
@inject(ChatService)
|
|
40
|
+
protected readonly chatService: ChatService;
|
|
41
|
+
|
|
42
|
+
@inject(ChatAgentService)
|
|
43
|
+
protected readonly chatAgentService: ChatAgentService;
|
|
44
|
+
|
|
45
|
+
@inject(CommandRegistry)
|
|
46
|
+
protected readonly commandRegistry: CommandRegistry;
|
|
47
|
+
|
|
48
|
+
@inject(HoverService)
|
|
49
|
+
protected readonly hoverService: HoverService;
|
|
50
|
+
|
|
51
|
+
@inject(MarkdownRendererFactory)
|
|
52
|
+
protected readonly markdownRendererFactory: MarkdownRendererFactory;
|
|
53
|
+
|
|
54
|
+
@inject(ContributionProvider) @named(ChatSessionItemActionContribution)
|
|
55
|
+
protected readonly chatSessionItemActionContributions: ContributionProvider<ChatSessionItemActionContribution>;
|
|
56
|
+
|
|
57
|
+
protected _markdownRenderer: MarkdownRenderer | undefined;
|
|
58
|
+
protected get markdownRenderer(): MarkdownRenderer {
|
|
59
|
+
if (!this._markdownRenderer) {
|
|
60
|
+
this._markdownRenderer = this.markdownRendererFactory();
|
|
61
|
+
}
|
|
62
|
+
return this._markdownRenderer;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@postConstruct()
|
|
66
|
+
protected init(): void {
|
|
67
|
+
this.id = AISessionsWidget.ID;
|
|
68
|
+
this.title.label = AISessionsWidget.LABEL;
|
|
69
|
+
this.title.caption = AISessionsWidget.LABEL;
|
|
70
|
+
this.title.closable = true;
|
|
71
|
+
this.title.iconClass = codicon('history');
|
|
72
|
+
this.addClass('ai-sessions-view');
|
|
73
|
+
|
|
74
|
+
this.toDispose.pushAll([
|
|
75
|
+
this.sessionListService.onStateChanged(() => this.update()),
|
|
76
|
+
this.sessionListService.onUnreadChanged(() => this.update())
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
this.update();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected render(): React.ReactNode {
|
|
83
|
+
const sections = this.sessionListService.getSections();
|
|
84
|
+
const total = sections.active.length + sections.restored.length;
|
|
85
|
+
|
|
86
|
+
if (total === 0) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="ai-sessions-view-empty">
|
|
89
|
+
<span className={codicon('comment-discussion')} />
|
|
90
|
+
<p>{nls.localize('theia/ai-ide/noSessions', 'No chat sessions yet.')}</p>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const rows = this.sessionListService.buildRows(sections);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className="ai-sessions-view-content">
|
|
99
|
+
<SessionsList
|
|
100
|
+
rows={rows}
|
|
101
|
+
renderRow={this.renderSessionRow}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected getSessionActions(session: ChatSessionMetadata): ChatSessionItemAction[] {
|
|
108
|
+
return this.chatSessionItemActionContributions
|
|
109
|
+
.getContributions()
|
|
110
|
+
.flatMap(c => c.getActions(session))
|
|
111
|
+
.filter(action => this.commandRegistry.isEnabled(action.commandId, session))
|
|
112
|
+
.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected renderSessionRow = (row: SessionRow): React.ReactNode => this.renderSessionRowAtDepth(row, 0);
|
|
116
|
+
|
|
117
|
+
protected renderSessionRowAtDepth(row: SessionRow, depth: number): React.ReactNode {
|
|
118
|
+
const hasChildSessions = row.childSessions.length > 0;
|
|
119
|
+
const isExpanded = hasChildSessions && this.sessionListService.isExpanded(row.session.sessionId);
|
|
120
|
+
const descendantNeedsAttention = this.sessionListService.descendantRequiresAction(row);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<React.Fragment key={row.session.sessionId}>
|
|
124
|
+
<ChatSessionItem
|
|
125
|
+
session={row.session}
|
|
126
|
+
isRestored={row.isRestored}
|
|
127
|
+
chatService={this.chatService}
|
|
128
|
+
chatAgentService={this.chatAgentService}
|
|
129
|
+
hoverService={this.hoverService}
|
|
130
|
+
markdownRenderer={this.markdownRenderer}
|
|
131
|
+
unreadState={this.sessionListService}
|
|
132
|
+
onClick={async () => {
|
|
133
|
+
await this.commandRegistry.executeCommand(AI_CHAT_OPEN_SESSION.id, row.session.sessionId);
|
|
134
|
+
}}
|
|
135
|
+
actions={this.getSessionActions(row.session)}
|
|
136
|
+
onAction={(action: ChatSessionItemAction, s: ChatSessionMetadata) => {
|
|
137
|
+
this.commandRegistry.executeCommand(action.commandId, s);
|
|
138
|
+
}}
|
|
139
|
+
formatTimeAgo={date => formatTimeAgo(date)}
|
|
140
|
+
hasChildSessions={hasChildSessions}
|
|
141
|
+
isChildSession={depth > 0}
|
|
142
|
+
depth={depth}
|
|
143
|
+
isExpanded={isExpanded}
|
|
144
|
+
descendantNeedsAttention={descendantNeedsAttention}
|
|
145
|
+
onToggleExpand={hasChildSessions ? () => this.sessionListService.toggleExpand(row.session.sessionId) : undefined}
|
|
146
|
+
/>
|
|
147
|
+
{isExpanded && row.childSessions.map(child => this.renderSessionRowAtDepth(child, depth + 1))}
|
|
148
|
+
</React.Fragment>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -58,13 +58,13 @@ export class DefaultChatSessionItemActionContribution implements ChatSessionItem
|
|
|
58
58
|
{
|
|
59
59
|
commandId: ChatCommands.AI_CHAT_RENAME_SESSION.id,
|
|
60
60
|
iconClass: codicon('edit'),
|
|
61
|
-
tooltip: nls.localizeByDefault('Rename
|
|
61
|
+
tooltip: nls.localizeByDefault('Rename chat'),
|
|
62
62
|
priority: 0,
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
commandId: ChatCommands.AI_CHAT_DELETE_SESSION.id,
|
|
66
66
|
iconClass: codicon('remove-close'),
|
|
67
|
-
tooltip: nls.
|
|
67
|
+
tooltip: nls.localizeByDefault('Delete Chat'),
|
|
68
68
|
priority: 10,
|
|
69
69
|
},
|
|
70
70
|
];
|