Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9aef7c3

Browse files
committed
feat: enhance terminal auto execution policy with new labels and UI updates
1 parent a728f8b commit 9aef7c3

File tree

5 files changed

+120
-83
lines changed

5 files changed

+120
-83
lines changed

packages/ai-native/src/browser/mcp/tools/components/Terminal.tsx

Lines changed: 64 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import React, { memo, useCallback, useMemo, useState } from 'react';
1+
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
22

33
import { PreferenceService, useInjectable } from '@opensumi/ide-core-browser';
44
import { Button, Icon } from '@opensumi/ide-core-browser/lib/components';
5+
import { EnhancePopover } from '@opensumi/ide-core-browser/lib/components/ai-native/popover';
6+
import { Loading } from '@opensumi/ide-core-browser/lib/components/loading';
57
import { CommandService, localize } from '@opensumi/ide-core-common';
68
import { AINativeSettingSectionsId } from '@opensumi/ide-core-common/lib/settings';
79

@@ -37,16 +39,27 @@ function getResult(raw: string) {
3739
}
3840
}
3941

42+
const autoExecutionPolicyLabels: { [k in ETerminalAutoExecutionPolicy]: string } = {
43+
[ETerminalAutoExecutionPolicy.always]: 'ai.native.terminal.autorun.always',
44+
[ETerminalAutoExecutionPolicy.auto]: 'ai.native.terminal.autorun.auto',
45+
[ETerminalAutoExecutionPolicy.off]: 'ai.native.terminal.autorun.off',
46+
};
47+
48+
function getAutoExecutionPolicyLabels(k: ETerminalAutoExecutionPolicy) {
49+
return localize(autoExecutionPolicyLabels[k]);
50+
}
51+
4052
export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps) => {
4153
const { args, toolCallId } = props;
4254
const handler = useInjectable<RunCommandHandler>(RunCommandHandler);
4355
const preferenceService: PreferenceService = useInjectable(PreferenceService);
4456
const commandService = useInjectable<CommandService>(CommandService);
4557

46-
const [disabled, toggleDisabled] = useState(false);
47-
const [showPolicy, toggleShowPolicy] = useState(false);
58+
const [running, toggleRunning] = useState(false);
4859

49-
const terminalAutoExecution = preferenceService.get(AINativeSettingSectionsId.TerminalAutoRun);
60+
const terminalAutoExecution = preferenceService.get<ETerminalAutoExecutionPolicy>(
61+
AINativeSettingSectionsId.TerminalAutoRun,
62+
);
5063

5164
const needApproval = useMemo(() => {
5265
// 值为 off 或 auto 且 args.require_user_approval
@@ -60,6 +73,14 @@ export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps)
6073
return false;
6174
}, [props.args]);
6275

76+
useEffect(() => {
77+
if (props.state === 'result') {
78+
toggleRunning(false);
79+
} else if (!needApproval) {
80+
toggleRunning(true);
81+
}
82+
}, [props]);
83+
6384
const openCommandAutoExecutionConfig = useCallback(() => {
6485
commandService.executeCommand('workbench.action.openSettings', 'ai.native.terminal.autorun');
6586
}, []);
@@ -69,7 +90,7 @@ export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps)
6990
return;
7091
}
7192
handler.handleApproval(toolCallId, approval);
72-
toggleDisabled(true);
93+
toggleRunning(true);
7394
}, []);
7495

7596
const output = useMemo(() => {
@@ -81,68 +102,51 @@ export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps)
81102

82103
return (
83104
<div className={styles.run_cmd_tool}>
84-
{props.state === 'result' && (
85-
<div>
86-
{args && (
87-
<>
88-
<div className={styles.command_title}>
89-
<Icon icon='terminal' />
90-
<span>{localize('ai.native.mcp.terminal.command')}:</span>
91-
</div>
92-
<p className={styles.command_content}>
93-
<code>$ {args.command}</code>
94-
</p>
95-
</>
96-
)}
97-
{output ? (
98-
<div className={styles.command_content}>
99-
<Icon icon='output' />
100-
<code dangerouslySetInnerHTML={{ __html: computeAnsiLogString(output.text || '') }} />
101-
{needApproval && (
102-
<div className={styles.auto_execution_policy}>
103-
<span className={styles.auto_execution_policy_title} onClick={() => toggleShowPolicy(!showPolicy)}>
104-
{showPolicy ? (
105-
<Icon iconClass='codicon codicon-chevron-down' />
106-
) : (
107-
<Icon iconClass='codicon codicon-chevron-right' />
108-
)}
109-
{localize('ai.native.terminal.autorun.denied')}
110-
</span>
111-
{showPolicy && (
112-
<>
113-
<span>{localize('ai.native.terminal.autorun.question')}</span>
114-
<span className={styles.auto_execution_policy_conf} onClick={openCommandAutoExecutionConfig}>
115-
{localize('ai.native.terminal.autorun.command')}
116-
</span>
117-
</>
118-
)}
119-
</div>
120-
)}
121-
</div>
122-
) : (
123-
''
124-
)}
105+
<div>
106+
<div className={styles.command_title}>
107+
<Icon icon='terminal' />
108+
<span>
109+
{needApproval
110+
? localize('ai.native.mcp.terminal.allow-question')
111+
: localize('ai.native.mcp.terminal.command')}
112+
</span>
125113
</div>
126-
)}
127-
128-
{props.state === 'complete' && needApproval && args && (
129-
<div>
130-
<div className={styles.command_title}>
131-
<Icon icon='terminal' />
132-
<span>{localize('ai.native.mcp.terminal.allow-question')}</span>
133-
</div>
134-
<p className={styles.command_content}>
135-
<code>$ {args.command}</code>
136-
</p>
137-
<p className={styles.comand_description}>{args.explanation}</p>
114+
<p className={styles.command_content}>
115+
<code>$ {args?.command}</code>
116+
</p>
117+
<p className={styles.comand_description}>{args?.explanation}</p>
118+
{props.state === 'complete' && needApproval && args && !running && (
138119
<div className={styles.cmmand_footer}>
139-
<Button type='link' size='small' disabled={disabled} onClick={() => handleClick(true)}>
120+
<Button type='link' size='small' onClick={() => handleClick(true)}>
140121
{localize('ai.native.mcp.terminal.allow')}
141122
</Button>
142-
<Button type='link' size='small' disabled={disabled} onClick={() => handleClick(false)}>
123+
<Button type='link' size='small' onClick={() => handleClick(false)}>
143124
{localize('ai.native.mcp.terminal.deny')}
144125
</Button>
145126
</div>
127+
)}
128+
{props.state === 'result' && output && (
129+
<>
130+
<div className={styles.command_content}>
131+
<Icon icon='output' />
132+
<code dangerouslySetInnerHTML={{ __html: computeAnsiLogString(output.text || '') }} />
133+
</div>
134+
135+
<div className={styles.auto_execution_policy}>
136+
<span className={styles.auto_execution_policy_label}>
137+
{getAutoExecutionPolicyLabels(terminalAutoExecution || ETerminalAutoExecutionPolicy.auto)}
138+
</span>
139+
<EnhancePopover id='policy-config-popover' title={localize('ai.native.terminal.autorun.command')}>
140+
<Icon size='small' iconClass='codicon codicon-settings-gear' onClick={openCommandAutoExecutionConfig} />
141+
</EnhancePopover>
142+
</div>
143+
</>
144+
)}
145+
</div>
146+
{running && (
147+
<div className={styles.running}>
148+
<Loading />
149+
<span>{localize('ai.native.terminal.autorun.running')}</span>
146150
</div>
147151
)}
148152
</div>

packages/ai-native/src/browser/mcp/tools/components/index.module.less

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@
159159
border-radius: 4px;
160160
margin: 10px 0px;
161161

162+
.running {
163+
display: flex;
164+
align-items: center;
165+
justify-content: flex-end;
166+
font-size: 11px;
167+
color: var(--descriptionForeground);
168+
}
169+
162170
.command_title {
163171
display: flex;
164172
align-items: center;
@@ -168,6 +176,26 @@
168176
}
169177
}
170178

179+
.auto_execution_policy {
180+
font-size: 11px;
181+
display: flex;
182+
justify-content: flex-end;
183+
align-items: center;
184+
color: var(--descriptionForeground);
185+
186+
:global(.kt-popover-trigger) {
187+
display: flex;
188+
}
189+
190+
:global(.kt-icon) {
191+
font-size: 13px;
192+
}
193+
194+
.auto_execution_policy_label {
195+
margin-right: 5px;
196+
}
197+
}
198+
171199
.command_content {
172200
max-height: 200px;
173201
overflow-y: auto;
@@ -180,27 +208,6 @@
180208
border-radius: 4px;
181209
overflow: auto;
182210

183-
.auto_execution_policy {
184-
font-size: 11px;
185-
display: flex;
186-
flex-direction: column;
187-
align-items: end;
188-
color: var(--descriptionForeground);
189-
190-
.auto_execution_policy_title {
191-
display: flex;
192-
cursor: pointer;
193-
align-items: center;
194-
}
195-
196-
.auto_execution_policy_conf {
197-
cursor: pointer;
198-
&:hover {
199-
color: var(--kt-linkButton-hoverForeground);
200-
}
201-
}
202-
}
203-
204211
code {
205212
font-size: 11px;
206213
white-space: pre;
@@ -215,5 +222,6 @@
215222
.cmmand_footer {
216223
display: flex;
217224
justify-content: flex-end;
225+
font-size: 12px;
218226
}
219227
}

packages/ai-native/src/browser/mcp/tools/handlers/RunCommand.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import z from 'zod';
22

33
import { Autowired, Injectable } from '@opensumi/di';
4-
import { AppConfig } from '@opensumi/ide-core-browser';
4+
import { AINativeSettingSectionsId, AppConfig, PreferenceService } from '@opensumi/ide-core-browser';
55
import { ITerminalController, ITerminalGroupViewService } from '@opensumi/ide-terminal-next';
66
import { Deferred } from '@opensumi/ide-utils/lib/promises';
77

8+
import { ETerminalAutoExecutionPolicy } from '../../../preferences/schema';
89
import { MCPLogger } from '../../../types';
910

1011
const color = {
@@ -36,6 +37,9 @@ export class RunCommandHandler {
3637
@Autowired(ITerminalGroupViewService)
3738
protected readonly terminalView: ITerminalGroupViewService;
3839

40+
@Autowired(PreferenceService)
41+
protected readonly preferenceService: PreferenceService;
42+
3943
private approvalDeferredMap = new Map<string, Deferred<boolean>>();
4044

4145
private terminalId = 0;
@@ -48,9 +52,21 @@ export class RunCommandHandler {
4852
};
4953
}
5054

55+
private isAlwaysApproval(requireApproval: boolean) {
56+
const terminalAutoExecution = this.preferenceService.get(AINativeSettingSectionsId.TerminalAutoRun);
57+
if (
58+
terminalAutoExecution === ETerminalAutoExecutionPolicy.off ||
59+
(terminalAutoExecution === ETerminalAutoExecutionPolicy.auto && requireApproval)
60+
) {
61+
return true;
62+
}
63+
64+
return false;
65+
}
66+
5167
async handler(args: z.infer<typeof inputSchema> & { toolCallId: string }, logger: MCPLogger) {
5268
logger.appendLine(`Executing command: ${args.command}`);
53-
if (args.require_user_approval) {
69+
if (this.isAlwaysApproval(args.require_user_approval)) {
5470
const def = new Deferred<boolean>();
5571
this.approvalDeferredMap.set(args.toolCallId, def);
5672
const approval = await def.promise;

packages/i18n/src/common/en-US.lang.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,11 @@ export const localizationBundle = {
16121612
'ai.native.terminal.autorun.question': 'Want to run this automatically in the future?',
16131613
'ai.native.terminal.autorun.command': 'Change your command auto execution policy.',
16141614

1615+
'ai.native.terminal.autorun.always': 'Always auto rxecution',
1616+
'ai.native.terminal.autorun.auto': 'Auto Execution by Model',
1617+
'ai.native.terminal.autorun.off': 'Auto Execution disabled',
1618+
'ai.native.terminal.autorun.running': 'Command execution...',
1619+
16151620
// MCP Server Configuration
16161621
'ai.native.mcp.name': 'Name:',
16171622
'ai.native.mcp.command': 'Command:',

packages/i18n/src/common/zh-CN.lang.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,10 @@ export const localizationBundle = {
13741374
'ai.native.terminal.autorun.denied': '默认情况下拒绝自动运行',
13751375
'ai.native.terminal.autorun.question': '希望自动运行?',
13761376
'ai.native.terminal.autorun.command': '点击更改命令自动执行策略.',
1377+
'ai.native.terminal.autorun.always': '始终允许',
1378+
'ai.native.terminal.autorun.auto': '模型自动执行',
1379+
'ai.native.terminal.autorun.off': '禁止自动执行',
1380+
'ai.native.terminal.autorun.running': '命令执行中...',
13771381

13781382
// MCP Server Configuration
13791383
'ai.native.mcp.name': '名称:',

0 commit comments

Comments
 (0)