diff --git a/docs/features/assistant-workbar/runtime-native-client-proposal.md b/docs/features/assistant-workbar/runtime-native-client-proposal.md
index 638698c9..5ecaf003 100644
--- a/docs/features/assistant-workbar/runtime-native-client-proposal.md
+++ b/docs/features/assistant-workbar/runtime-native-client-proposal.md
@@ -18,7 +18,7 @@ Status: Local shortcut/coordinator paths and configured-model responses verified
| --- | --- | --- | --- |
| Continue | 在终端中打开 | 工作栏新终端页签,启动原生 CLI | 本地和托管 SSH 项目 |
| OpenCode | 在终端中打开 | 工作栏新终端页签,启动原生 TUI | 本地和托管 SSH 项目 |
-| DeepSeek Harness | 在浏览器中打开 | 系统默认浏览器,访问官方 Web 应用 | 本地项目 |
+| DeepSeek Harness | 打开DSH Web | 系统默认浏览器,访问官方 Web 应用 | 本地项目 |
| 内置模型 | 不显示此快捷入口 | 无 | 保持现状 |
DS 采用官方 `@deepseek-ai/dsh` Web 应用,不开发新的 DS 终端前端,也不在首轮嵌入
@@ -164,7 +164,7 @@ DS 后台的 `read` 和原生 `skill` 可在 Ask 执行;`write`、`edit`、`pw
## 5. 界面设计
-输入区下方从左到右排列“在终端中打开/在浏览器中打开”“压缩上下文”和现有用量信息。
+输入区下方从左到右排列“在终端中打开/打开DSH Web”“压缩上下文”和现有用量信息。
两个按钮独立判断可用性。采用同一轻量操作组,避免两组绝对定位相互覆盖;窄输入区允许
操作组换行,保留模式说明、用量信息和对称边距。稳定预留元信息高度的实现应与
[统一 UI 规范](../../../UI-DESIGN.md)一起更新并验证。
@@ -173,8 +173,9 @@ DS 后台的 `read` 和原生 `skill` 可在 Ask 执行;`write`、`edit`、`pw
进入新终端;外部浏览器由系统处理焦点。启动期间保留按钮宽度并显示“正在打开…”,完成后
恢复原文案。缺少项目目录时在相关位置提供选择目录的操作;其他异步失败进入全局通知视口。
-DS 服务运行后,在入口旁提供轻量菜单“重新打开”“停止此服务”;操作指向当前匹配服务。
-若正在生成,停止前说明会中断该服务中的请求。输入区不常驻显示独立会话和服务生命周期说明。不在每次启动时增加确认弹窗。
+DS 服务运行后,打开入口仍显示“打开DSH Web”,旁边显示“停止DSH服务”;操作指向当前匹配服务。
+点击停止后,在同一操作行追加紧凑的“确认”“取消”按钮,不再展开整行警告。窄窗口下确认和取消作为一组换行,两个按钮不拆行。
+确认按钮的悬停提示和无障碍描述说明网页会断开、进行中的请求会中断;执行时由确认按钮显示“正在停止…”。输入区不常驻显示独立会话和服务生命周期说明。不在每次启动时增加确认弹窗。
按钮快捷入口根据所选 Runtime 显示,不改变工作栏“+”中的普通终端和浏览器入口。已有服务
的停止入口应在切换回来后仍可找到。
diff --git a/docs/features/deepseek-harness/progress.md b/docs/features/deepseek-harness/progress.md
index 1d5b88b1..42824100 100644
--- a/docs/features/deepseek-harness/progress.md
+++ b/docs/features/deepseek-harness/progress.md
@@ -82,7 +82,7 @@ Agent 配置、Shell 执行和系统消息接口。
和 Electron UI 测试失败;未取得全量通过结果,未修改这些并行工作的文件。
本次依赖升级回归的真实供应商模型调用为 0 次;后续真实模型及 Web 实测见上节。上述升级验证不包含官方 Web 应用,
-“在浏览器中打开”入口仍按[原生客户端方案](../assistant-workbar/runtime-native-client-proposal.md)实施。
+“打开DSH Web”入口仍按[原生客户端方案](../assistant-workbar/runtime-native-client-proposal.md)实施。
远程启动契约和资源打包仅支持 Continue、OpenCode,`create-runtime.ts` 拒绝 SSH
执行空间使用 DS,因此本次升级未改变远程 Runtime 路径,也未进行 Linux Host 验证。
diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx
index 10d001d4..a8670d14 100644
--- a/src/renderer/src/App.test.tsx
+++ b/src/renderer/src/App.test.tsx
@@ -10677,7 +10677,7 @@ describe("App", () => {
await screen.findByRole("textbox", { name: "向 GoodBuddy 提问" });
await waitFor(() => expect(api.settings.getRuntime).toHaveBeenCalled());
expect(screen.queryByRole("button", { name: "在终端中打开" })).not.toBeInTheDocument();
- expect(screen.queryByRole("button", { name: "在浏览器中打开" })).not.toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: "打开DSH Web" })).not.toBeInTheDocument();
});
it("opens local DS in the external browser without opening the workbar", async () => {
@@ -10688,13 +10688,13 @@ describe("App", () => {
}]);
vi.mocked(api.openRuntimeNativeClient).mockResolvedValue({ kind: "browser", serviceId: "ds-service" });
render();
- const open = await screen.findByRole("button", { name: "在浏览器中打开" });
+ const open = await screen.findByRole("button", { name: "打开DSH Web" });
await waitFor(() => expect(api.getRuntimeNativeClient).toHaveBeenCalledWith({ conversationId }));
fireEvent.click(open);
await waitFor(() => {
expect(api.openRuntimeNativeClient).toHaveBeenCalledWith({ conversationId });
- expect(screen.getByRole("button", { name: "重新打开" })).toBeEnabled();
- expect(screen.getByRole("button", { name: "停止此服务" })).toBeEnabled();
+ expect(screen.getByRole("button", { name: "打开DSH Web" })).toBeEnabled();
+ expect(screen.getByRole("button", { name: "停止DSH服务" })).toBeEnabled();
});
expect(screen.getByRole("button", { name: "切换助手工作栏" })).toHaveAttribute("aria-expanded", "false");
expect(api.terminal.create).not.toHaveBeenCalled();
diff --git a/src/renderer/src/RuntimeNativeClientActions.test.tsx b/src/renderer/src/RuntimeNativeClientActions.test.tsx
index 45c0e531..b5dcd031 100644
--- a/src/renderer/src/RuntimeNativeClientActions.test.tsx
+++ b/src/renderer/src/RuntimeNativeClientActions.test.tsx
@@ -49,17 +49,17 @@ describe('RuntimeNativeClientActions', () => {
it('refreshes the matching DS service on selection changes and stops only that service', async () => {
api.getRuntimeNativeClient.mockResolvedValueOnce({ serviceId: 'first-service' }).mockResolvedValue({ serviceId: 'second-service' })
const { rerender } = render()
- await screen.findByRole('button', { name: 'Reopen' })
+ await screen.findByRole('button', { name: 'Stop DSH service' })
rerender()
- expect(screen.queryByRole('button', { name: 'Stop this service' })).not.toBeInTheDocument()
- await screen.findByRole('button', { name: 'Reopen' })
- fireEvent.click(screen.getByRole('button', { name: 'Stop this service' }))
+ expect(screen.queryByRole('button', { name: 'Stop DSH service' })).not.toBeInTheDocument()
+ await screen.findByRole('button', { name: 'Stop DSH service' })
+ fireEvent.click(screen.getByRole('button', { name: 'Stop DSH service' }))
expect(api.stopRuntimeNativeClient).not.toHaveBeenCalled()
- fireEvent.click(screen.getByRole('button', { name: 'Stop service' }))
+ fireEvent.click(screen.getByRole('button', { name: 'Confirm' }))
await waitFor(() => {
expect(api.stopRuntimeNativeClient).toHaveBeenCalledWith({ serviceId: 'second-service' })
- expect(screen.getByRole('button', { name: 'Open in browser' })).toBeEnabled()
- expect(screen.queryByRole('button', { name: 'Stop this service' })).not.toBeInTheDocument()
+ expect(screen.getByRole('button', { name: 'Open DSH Web' })).toBeEnabled()
+ expect(screen.queryByRole('button', { name: 'Stop DSH service' })).not.toBeInTheDocument()
})
})
@@ -68,13 +68,14 @@ describe('RuntimeNativeClientActions', () => {
render()
await waitFor(() => expect(api.getRuntimeNativeClient).toHaveBeenCalledTimes(1))
api.getRuntimeNativeClient.mockResolvedValue({ serviceId: 'ready-service' })
- fireEvent.click(screen.getByRole('button', { name: 'Open in browser' }))
+ fireEvent.click(screen.getByRole('button', { name: 'Open DSH Web' }))
await waitFor(() => {
expect(props.notify).toHaveBeenCalledWith({ tone: 'error', message: 'Browser could not open' })
- expect(screen.getByRole('button', { name: 'Reopen' })).toBeEnabled()
+ expect(screen.getByRole('button', { name: 'Open DSH Web' })).toBeEnabled()
+ expect(screen.getByRole('button', { name: 'Stop DSH service' })).toBeEnabled()
})
api.openRuntimeNativeClient.mockResolvedValue({ kind: 'browser', serviceId: 'ready-service' })
- fireEvent.click(screen.getByRole('button', { name: 'Reopen' }))
+ fireEvent.click(screen.getByRole('button', { name: 'Open DSH Web' }))
await waitFor(() => expect(api.openRuntimeNativeClient).toHaveBeenCalledTimes(2))
expect(props.onTerminal).not.toHaveBeenCalled()
})
@@ -87,18 +88,39 @@ describe('RuntimeNativeClientActions', () => {
rerender()
await waitFor(() => expect(api.getRuntimeNativeClient).toHaveBeenCalledTimes(2))
await act(async () => finish({ serviceId: 'old-service' }))
- expect(screen.queryByRole('button', { name: 'Stop this service' })).not.toBeInTheDocument()
+ expect(screen.queryByRole('button', { name: 'Stop DSH service' })).not.toBeInTheDocument()
})
it('retains controls and reports stop errors for retry', async () => {
api.getRuntimeNativeClient.mockResolvedValue({ serviceId: 'service' })
api.stopRuntimeNativeClient.mockRejectedValueOnce(new Error('Stop failed'))
render()
- fireEvent.click(await screen.findByRole('button', { name: 'Stop this service' }))
- fireEvent.click(screen.getByRole('button', { name: 'Stop service' }))
+ fireEvent.click(await screen.findByRole('button', { name: 'Stop DSH service' }))
+ fireEvent.click(screen.getByRole('button', { name: 'Confirm' }))
await waitFor(() => {
expect(props.notify).toHaveBeenCalledWith({ tone: 'error', message: 'Stop failed' })
- expect(screen.getByRole('button', { name: 'Stop service' })).toBeEnabled()
+ expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled()
})
})
+
+ it('cancels inline confirmation and shows stopping on the confirmation button', async () => {
+ await changeUiLocale('zh-CN')
+ api.getRuntimeNativeClient.mockResolvedValue({ serviceId: 'service' })
+ let finish!: () => void
+ api.stopRuntimeNativeClient.mockImplementation(() => new Promise(resolve => { finish = resolve }))
+ render()
+ fireEvent.click(await screen.findByRole('button', { name: '停止DSH服务' }))
+ expect(screen.getByRole('button', { name: '确认' })).toHaveAccessibleDescription('停止后此服务的网页会断开,进行中的请求将中断。')
+ fireEvent.click(screen.getByRole('button', { name: '取消' }))
+ expect(screen.queryByRole('button', { name: '确认' })).not.toBeInTheDocument()
+ expect(api.stopRuntimeNativeClient).not.toHaveBeenCalled()
+ fireEvent.click(screen.getByRole('button', { name: '停止DSH服务' }))
+ fireEvent.click(screen.getByRole('button', { name: '确认' }))
+ expect(screen.getByRole('button', { name: '正在停止…' })).toBeDisabled()
+ expect(screen.getByRole('button', { name: '打开DSH Web' })).toBeDisabled()
+ expect(screen.getByRole('button', { name: '取消' })).toBeDisabled()
+ await act(async () => finish())
+ expect(screen.getByRole('button', { name: '打开DSH Web' })).toBeEnabled()
+ expect(screen.queryByRole('button', { name: '取消' })).not.toBeInTheDocument()
+ })
})
diff --git a/src/renderer/src/RuntimeNativeClientActions.tsx b/src/renderer/src/RuntimeNativeClientActions.tsx
index 879cbfe3..25578bde 100644
--- a/src/renderer/src/RuntimeNativeClientActions.tsx
+++ b/src/renderer/src/RuntimeNativeClientActions.tsx
@@ -86,14 +86,16 @@ export function RuntimeNativeClientActions({ browser, contextKey, conversationId
return <>
{browser && serviceId && }
{serviceId && confirmStop === serviceId &&
- {t('composer.nativeClient.stopWarning')}
-
-
+
+
}
>
}
diff --git a/src/renderer/src/i18n/locales/en-US/app.ts b/src/renderer/src/i18n/locales/en-US/app.ts
index 0d42872e..bdcc1b0c 100644
--- a/src/renderer/src/i18n/locales/en-US/app.ts
+++ b/src/renderer/src/i18n/locales/en-US/app.ts
@@ -631,8 +631,8 @@ export const app = {
compactFailed: 'Context compaction failed'
},
nativeClient: {
- terminal: 'Open in terminal', browser: 'Open in browser', opening: 'Opening...', stopping: 'Stopping...',
- reopen: 'Reopen', stop: 'Stop this service', confirmStop: 'Stop service', cancel: 'Cancel',
+ terminal: 'Open in terminal', browser: 'Open DSH Web', opening: 'Opening...', stopping: 'Stopping...',
+ stop: 'Stop DSH service', confirmStop: 'Confirm', cancel: 'Cancel',
description: 'Independent session. Closing the page keeps the service running; stop it here or quit GoodBuddy.',
stopWarning: 'Stopping disconnects this service and interrupts its requests.',
stopped: 'Native client service stopped', failed: 'Could not open the native client'
diff --git a/src/renderer/src/i18n/locales/zh-CN/app.ts b/src/renderer/src/i18n/locales/zh-CN/app.ts
index 5e51c04f..d2f78101 100644
--- a/src/renderer/src/i18n/locales/zh-CN/app.ts
+++ b/src/renderer/src/i18n/locales/zh-CN/app.ts
@@ -607,8 +607,8 @@ export const app = {
compactFailed: '上下文压缩失败'
},
nativeClient: {
- terminal: '在终端中打开', browser: '在浏览器中打开', opening: '正在打开…', stopping: '正在停止…',
- reopen: '重新打开', stop: '停止此服务', confirmStop: '停止服务', cancel: '取消',
+ terminal: '在终端中打开', browser: '打开DSH Web', opening: '正在打开…', stopping: '正在停止…',
+ stop: '停止DSH服务', confirmStop: '确认', cancel: '取消',
description: '独立会话;关闭网页后服务仍运行,可在此停止,退出 GoodBuddy 时停止。',
stopWarning: '停止后此服务的网页会断开,进行中的请求将中断。',
stopped: '原生客户端服务已停止', failed: '无法打开原生客户端'
diff --git a/src/renderer/src/styles.css b/src/renderer/src/styles.css
index fd3d700a..45ed1cd3 100644
--- a/src/renderer/src/styles.css
+++ b/src/renderer/src/styles.css
@@ -8871,19 +8871,23 @@ button > svg {
min-width: 0;
}
-.composer-native-client-note,
-.composer-native-client-confirm {
+.composer-native-client-note {
flex-basis: 100%;
color: var(--text-muted);
}
.composer-native-client-confirm {
- display: flex;
- flex-wrap: wrap;
+ display: inline-flex;
+ flex-wrap: nowrap;
align-items: center;
gap: var(--space-2);
}
+.composer-native-client-confirm .composer-context-compact {
+ min-width: 0;
+ white-space: nowrap;
+}
+
.composer-meta__error {
margin-left: auto;
}