@steerable/agent-shell-web 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +91 -0
  2. package/README.md +20 -0
  3. package/package.json +69 -0
  4. package/public/favicon.ico +0 -0
  5. package/public/favicon.png +0 -0
  6. package/src/AppShell.tsx +24 -0
  7. package/src/assets/logo-generic.png +0 -0
  8. package/src/brand.test.ts +22 -0
  9. package/src/brand.ts +48 -0
  10. package/src/components/AgentSidebar.test.tsx +127 -0
  11. package/src/components/AgentSidebar.tsx +1010 -0
  12. package/src/components/ChatHeader.test.tsx +230 -0
  13. package/src/components/ChatHeader.tsx +436 -0
  14. package/src/components/DockPanelHeader.tsx +69 -0
  15. package/src/components/LocalLlmSettingsModal.test.tsx +372 -0
  16. package/src/components/LocalLlmSettingsModal.tsx +72 -0
  17. package/src/components/TerminalPanel.tsx +68 -0
  18. package/src/components/TerminalView.tsx +228 -0
  19. package/src/components/chat/ApprovalModal.test.tsx +83 -0
  20. package/src/components/chat/ApprovalModal.tsx +229 -0
  21. package/src/components/chat/AskUserModalHost.test.tsx +176 -0
  22. package/src/components/chat/AskUserModalHost.tsx +61 -0
  23. package/src/components/chat/AskUserQuestionMenu.tsx +621 -0
  24. package/src/components/chat/AssistantMessage.test.tsx +68 -0
  25. package/src/components/chat/AssistantMessage.tsx +469 -0
  26. package/src/components/chat/ChatInput.test.tsx +609 -0
  27. package/src/components/chat/ChatInput.tsx +2068 -0
  28. package/src/components/chat/ChatProjectBadge.tsx +362 -0
  29. package/src/components/chat/EmptyChat.tsx +18 -0
  30. package/src/components/chat/ExecPolicyPicker.test.tsx +26 -0
  31. package/src/components/chat/ExecPolicyPicker.tsx +120 -0
  32. package/src/components/chat/ExecutedActionsCard.tsx +216 -0
  33. package/src/components/chat/InterruptedTurnCard.tsx +53 -0
  34. package/src/components/chat/LocalChatPanel.tsx +408 -0
  35. package/src/components/chat/Markdown.test.tsx +170 -0
  36. package/src/components/chat/Markdown.tsx +432 -0
  37. package/src/components/chat/MessageList.tsx +332 -0
  38. package/src/components/chat/ModelPicker.test.tsx +149 -0
  39. package/src/components/chat/ModelPicker.tsx +289 -0
  40. package/src/components/chat/OrchestrationChildrenCard.tsx +40 -0
  41. package/src/components/chat/PlanTodoList.tsx +121 -0
  42. package/src/components/chat/ProjectTrustBanner.tsx +91 -0
  43. package/src/components/chat/SessionTreeModal.test.tsx +161 -0
  44. package/src/components/chat/SessionTreeModal.tsx +221 -0
  45. package/src/components/chat/StreamingStatus.tsx +95 -0
  46. package/src/components/chat/TaskOutcomeCards.test.tsx +97 -0
  47. package/src/components/chat/TaskOutcomeCards.tsx +128 -0
  48. package/src/components/chat/TaskPanelModal.test.tsx +198 -0
  49. package/src/components/chat/TaskPanelModal.tsx +310 -0
  50. package/src/components/chat/TaskProcessPanel.test.tsx +111 -0
  51. package/src/components/chat/TaskProcessPanel.tsx +208 -0
  52. package/src/components/chat/TurnProcessGroup.test.tsx +161 -0
  53. package/src/components/chat/TurnProcessGroup.tsx +193 -0
  54. package/src/components/chat/UnifiedDiff.tsx +31 -0
  55. package/src/components/chat/UserMessage.tsx +155 -0
  56. package/src/components/chat/elapsed.test.ts +42 -0
  57. package/src/components/chat/elapsed.ts +45 -0
  58. package/src/components/chat/executed-actions-model.test.ts +134 -0
  59. package/src/components/chat/executed-actions-model.ts +143 -0
  60. package/src/components/chat/orchestration-children-model.test.ts +115 -0
  61. package/src/components/chat/orchestration-children-model.ts +104 -0
  62. package/src/components/chat/timestamp.ts +24 -0
  63. package/src/components/chat/turn-timeline.test.ts +97 -0
  64. package/src/components/chat/turn-timeline.ts +132 -0
  65. package/src/components/chat/useChatTasks.test.ts +136 -0
  66. package/src/components/chat/useChatTasks.ts +134 -0
  67. package/src/components/chat/useCopy.ts +62 -0
  68. package/src/components/settings/AgentCapabilityFields.tsx +343 -0
  69. package/src/components/settings/AgentsSettingsPanel.test.tsx +229 -0
  70. package/src/components/settings/AgentsSettingsPanel.tsx +412 -0
  71. package/src/components/settings/DiagnoseSettingsPanel.tsx +119 -0
  72. package/src/components/settings/InsightsSettingsPanel.test.tsx +132 -0
  73. package/src/components/settings/InsightsSettingsPanel.tsx +309 -0
  74. package/src/components/settings/LlmSettingsPanel.tsx +875 -0
  75. package/src/components/settings/McpSettingsPanel.tsx +466 -0
  76. package/src/components/settings/ModelCapabilityChips.tsx +46 -0
  77. package/src/components/settings/ModelIdCombobox.test.tsx +88 -0
  78. package/src/components/settings/ModelIdCombobox.tsx +185 -0
  79. package/src/components/settings/SecuritySettingsPanel.test.tsx +122 -0
  80. package/src/components/settings/SecuritySettingsPanel.tsx +169 -0
  81. package/src/components/settings/SettingsSaveButton.tsx +35 -0
  82. package/src/components/settings/SkillsSettingsPanel.test.tsx +62 -0
  83. package/src/components/settings/SkillsSettingsPanel.tsx +334 -0
  84. package/src/components/settings/TelemetrySettingsPanel.tsx +173 -0
  85. package/src/components/settings/UsagePanel.tsx +196 -0
  86. package/src/components/settings/WebSearchSettingsPanel.test.tsx +90 -0
  87. package/src/components/settings/WebSearchSettingsPanel.tsx +183 -0
  88. package/src/components/settings/compat-flags-model.test.ts +71 -0
  89. package/src/components/settings/compat-flags-model.ts +57 -0
  90. package/src/components/settings/llm-vendors.test.ts +171 -0
  91. package/src/components/settings/llm-vendors.ts +275 -0
  92. package/src/components/settings/model-capabilities.test.ts +88 -0
  93. package/src/components/settings/model-capabilities.ts +123 -0
  94. package/src/components/settings/preset-choice-model.test.ts +96 -0
  95. package/src/components/settings/preset-choice-model.ts +75 -0
  96. package/src/fixtures/browser-dev-data.json +315 -0
  97. package/src/hooks/useChatsAndAgents.ts +182 -0
  98. package/src/layouts/AgentLayout.tsx +574 -0
  99. package/src/lib/attachments.ts +78 -0
  100. package/src/lib/browser-dev-electron-mock.ts +640 -0
  101. package/src/lib/chat-title.ts +33 -0
  102. package/src/lib/chat-transport.ts +346 -0
  103. package/src/lib/date-groups.ts +34 -0
  104. package/src/lib/demo-flag.ts +15 -0
  105. package/src/lib/electron-bridge.ts +270 -0
  106. package/src/lib/exec-policy.ts +25 -0
  107. package/src/lib/http-bridge.ts +293 -0
  108. package/src/lib/insights.ts +17 -0
  109. package/src/lib/local-api.ts +714 -0
  110. package/src/lib/pending-first-message.test.ts +17 -0
  111. package/src/lib/pending-first-message.ts +36 -0
  112. package/src/lib/slash-sources.ts +191 -0
  113. package/src/main.tsx +103 -0
  114. package/src/packs/registry.ts +64 -0
  115. package/src/pages/AgentPage.tsx +1226 -0
  116. package/src/pages/ChatPanelPreviewPage.tsx +264 -0
  117. package/src/pages/SettingsPage.test.tsx +102 -0
  118. package/src/pages/SettingsPage.tsx +197 -0
  119. package/src/styles/index.css +156 -0
  120. package/src/vite-env.d.ts +1 -0
  121. package/vite.base.ts +94 -0
package/LICENSE ADDED
@@ -0,0 +1,91 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity.
18
+
19
+ "You" (or "Your") shall mean an individual or Legal Entity
20
+ exercising permissions granted by this License.
21
+
22
+ "Source" form shall mean the preferred form for making modifications.
23
+
24
+ "Object" form shall mean any form resulting from mechanical
25
+ transformation or translation of a Source form.
26
+
27
+ "Work" shall mean the work of authorship made available under the
28
+ License.
29
+
30
+ "Derivative Works" shall mean any work based on the Work.
31
+
32
+ "Contribution" shall mean any work of authorship intentionally
33
+ submitted to Licensor for inclusion in the Work.
34
+
35
+ "Contributor" shall mean Licensor and any individual or Legal Entity
36
+ on behalf of whom a Contribution has been received by Licensor.
37
+
38
+ 2. Grant of Copyright License.
39
+ Subject to the terms and conditions of this License, each Contributor
40
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
41
+ royalty-free, irrevocable copyright license to reproduce, prepare
42
+ Derivative Works of, publicly display, publicly perform, sublicense,
43
+ and distribute the Work and such Derivative Works in Source or Object
44
+ form.
45
+
46
+ 3. Grant of Patent License.
47
+ Subject to the terms and conditions of this License, each Contributor
48
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
49
+ royalty-free, irrevocable patent license to make, have made, use,
50
+ offer to sell, sell, import, and otherwise transfer the Work.
51
+
52
+ 4. Redistribution.
53
+ You may reproduce and distribute copies of the Work or Derivative
54
+ Works thereof in any medium, with or without modifications, and in
55
+ Source or Object form, provided that You meet the following conditions:
56
+
57
+ (a) You must give any other recipients of the Work or Derivative
58
+ Works a copy of this License; and
59
+ (b) You must cause any modified files to carry prominent notices
60
+ stating that You changed the files; and
61
+ (c) You must retain, in the Source form of any Derivative Works that
62
+ You distribute, all copyright, patent, trademark, and attribution
63
+ notices from the Source form of the Work; and
64
+ (d) If the Work includes a "NOTICE" text file as part of its
65
+ distribution, then any Derivative Works that You distribute must
66
+ include a readable copy of the attribution notices contained within
67
+ such NOTICE file.
68
+
69
+ 5. Submission of Contributions.
70
+ Unless You explicitly state otherwise, any Contribution intentionally
71
+ submitted for inclusion in the Work shall be under the terms and
72
+ conditions of this License.
73
+
74
+ 6. Trademarks.
75
+ This License does not grant permission to use the trade names,
76
+ trademarks, service marks, or product names of the Licensor.
77
+
78
+ 7. Disclaimer of Warranty.
79
+ Unless required by applicable law or agreed to in writing, Licensor
80
+ provides the Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
81
+ OF ANY KIND.
82
+
83
+ 8. Limitation of Liability.
84
+ In no event and under no legal theory shall any Contributor be liable
85
+ to You for damages arising as a result of this License or out of the
86
+ use or inability to use the Work.
87
+
88
+ 9. Accepting Warranty or Additional Liability.
89
+ While redistributing the Work or Derivative Works thereof, You may
90
+ choose to offer, and charge a fee for, acceptance of support, warranty,
91
+ indemnity, or other liability obligations.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @steerable/agent-shell-web
2
+
3
+ Product-neutral renderer SPA source for `@steerable/agent-shell` (React + Vite).
4
+
5
+ This is a **source package**, not a build artifact. Product web entry points
6
+ consume it via the `@/` alias (anchored at this package's `src/`) and the
7
+ `createProductViteConfig` factory exported from `./vite.base`:
8
+
9
+ ```ts
10
+ import { createProductViteConfig } from '@steerable/agent-shell-web/vite.base';
11
+
12
+ export default createProductViteConfig({ productDir, flavor });
13
+ ```
14
+
15
+ The product's Vite build compiles this package's `src/` into the product's own
16
+ bundle; there is no prebuilt `dist/` here. See
17
+ [`docs/spec/architecture.md`](https://github.com/pathlyapp/steerable-framework/blob/develop/docs/spec/architecture.md)
18
+ for the tier model.
19
+
20
+ License: Apache-2.0.
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@steerable/agent-shell-web",
3
+ "version": "0.6.15",
4
+ "type": "module",
5
+ "description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/ + vite.base.ts), not a build artifact.",
6
+ "license": "Apache-2.0",
7
+ "homepage": "https://steerableframework.com/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/pathlyapp/steerable-framework.git",
11
+ "directory": "packages/agent-shell/web"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/pathlyapp/steerable-framework/issues"
15
+ },
16
+ "exports": {
17
+ "./vite.base": "./vite.base.ts",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "src",
22
+ "public",
23
+ "vite.base.ts",
24
+ "README.md"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "provenance": true
29
+ },
30
+ "dependencies": {
31
+ "@xterm/addon-fit": "^0.11.0",
32
+ "@xterm/addon-web-links": "^0.12.0",
33
+ "@xterm/xterm": "^6.0.0",
34
+ "framer-motion": "^12.38.0",
35
+ "react": "^18.3.1",
36
+ "react-dom": "^18.3.1",
37
+ "react-icons": "^5.5.0",
38
+ "react-markdown": "^10.1.0",
39
+ "react-router-dom": "^6.30.0",
40
+ "rehype-raw": "^7.0.0",
41
+ "remark-gfm": "^4.0.1",
42
+ "@steerable/agent-protocol": "0.6.15",
43
+ "@steerable/agent-ui": "0.6.15"
44
+ },
45
+ "devDependencies": {
46
+ "@tailwindcss/vite": "^4.3.0",
47
+ "@testing-library/react": "^16.1.0",
48
+ "@types/react": "^18.3.12",
49
+ "@types/react-dom": "^18.3.1",
50
+ "@vitejs/plugin-react": "^4.7.0",
51
+ "happy-dom": "^15.11.7",
52
+ "tailwindcss": "^4.3.0",
53
+ "typescript": "^5.8.3",
54
+ "vite": "^5.4.21",
55
+ "vitest": "^3.2.4",
56
+ "@steerable/pack-sdk": "0.6.15"
57
+ },
58
+ "scripts": {
59
+ "gen": "echo agent-shell-web",
60
+ "build": "tsc --noEmit",
61
+ "typecheck": "tsc --noEmit",
62
+ "test": "vitest run",
63
+ "lint": "tsc --noEmit",
64
+ "check:drift": "echo ok",
65
+ "app:build": "vite build --config vite.app.config.ts",
66
+ "app:build:demo": "vite build --config vite.demo.config.ts",
67
+ "app:dev": "vite --config vite.app.config.ts"
68
+ }
69
+ }
Binary file
Binary file
@@ -0,0 +1,24 @@
1
+ import { Outlet } from 'react-router-dom';
2
+ import { isElectron } from './lib/electron-bridge';
3
+ import { isDemoMode } from './lib/demo-flag';
4
+
5
+ export function AppShell() {
6
+ return (
7
+ <div className="flex h-full w-full flex-col bg-agent-canvas text-agent-foreground">
8
+ <main className="flex flex-1 overflow-hidden">
9
+ <Outlet />
10
+ </main>
11
+ {isDemoMode() ? (
12
+ <div className="border-t border-agent-border bg-agent-muted px-3 py-1 text-xs text-agent-muted-foreground">
13
+ Demo &middot; simulated data, no live model
14
+ </div>
15
+ ) : (
16
+ !isElectron() && (
17
+ <div className="border-t border-agent-border bg-agent-muted px-3 py-1 text-xs text-agent-muted-foreground">
18
+ Browser preview mode &middot; running outside Electron, IPC bridge unavailable
19
+ </div>
20
+ )
21
+ )}
22
+ </div>
23
+ );
24
+ }
Binary file
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { LOCAL_ASSISTANT_AGENT_ID, pickDefaultAgentId } from './brand';
3
+
4
+ describe('pickDefaultAgentId', () => {
5
+ const catalog = [
6
+ { id: LOCAL_ASSISTANT_AGENT_ID },
7
+ { id: 'data-analyst' },
8
+ { id: 'all-round-assistant' },
9
+ ];
10
+
11
+ it('returns null when the catalog is empty', () => {
12
+ expect(pickDefaultAgentId([])).toBeNull();
13
+ });
14
+
15
+ it('prefers the build-time default (shell: 电脑操作员) even when it is not first', () => {
16
+ expect(pickDefaultAgentId(catalog)).toBe(LOCAL_ASSISTANT_AGENT_ID);
17
+ });
18
+
19
+ it('falls back to the first visible agent when the build default is absent', () => {
20
+ expect(pickDefaultAgentId([{ id: 'writer' }, { id: 'reader' }])).toBe('writer');
21
+ });
22
+ });
package/src/brand.ts ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * 渲染进程品牌单一真源。品牌文案在 web 构建时由产品 vite 配置注入
3
+ * (VITE_BRAND_* define:product.json brand 优先,其次激活包 pack.json
4
+ * brand,3.1);logo 资产由激活包的 web 模块在注册时经
5
+ * setBrandLogoUrl() 注入(资产静态 import 在包内,只进本产品 bundle)。
6
+ * 未注入时一律回落到 shell 中性默认(Steerable Shell / generic logo)。
7
+ * 主进程侧对应物见 src/brand.ts。
8
+ */
9
+ import shellLogoUrl from '@/assets/logo-generic.png';
10
+
11
+ /** 当前构建的 flavor(开放字符串,0.3g 起不再是二值联合)。 */
12
+ export const APP_FLAVOR: string = import.meta.env.VITE_APP_FLAVOR ?? 'generic';
13
+
14
+ export const LOCAL_ASSISTANT_AGENT_ID = 'local-assistant';
15
+
16
+ export const BRAND_NAME: string = import.meta.env.VITE_BRAND_NAME ?? 'Steerable Shell';
17
+
18
+ /** shell 默认 logo(中性通用图标);包品牌 logo 由包 web 模块注册覆盖。 */
19
+ let brandLogoUrl: string = shellLogoUrl;
20
+
21
+ /**
22
+ * 注册产品品牌 logo(包的 register*Renderer() 在 bootstrap 前调用)。
23
+ * 重复注册抛错(组装期笔误,fail fast——与 node 侧 setProductBrand 同语义)。
24
+ */
25
+ export function setBrandLogoUrl(url: string): void {
26
+ if (brandLogoUrl !== shellLogoUrl && brandLogoUrl !== url) {
27
+ throw new Error('[brand] logo already set');
28
+ }
29
+ brandLogoUrl = url;
30
+ }
31
+
32
+ /** 当前产品品牌 logo(bootstrap 之后读取——包的注册已完成)。 */
33
+ export function getBrandLogoUrl(): string {
34
+ return brandLogoUrl;
35
+ }
36
+
37
+ /** 首页 / 新对话未选手动专家时绑定的内置智能体(包品牌可覆盖)。 */
38
+ export const DEFAULT_AGENT_ID: string =
39
+ import.meta.env.VITE_DEFAULT_AGENT_ID ?? LOCAL_ASSISTANT_AGENT_ID;
40
+
41
+ /**
42
+ * 从当前可见专家目录里挑默认 id:优先当前构建的默认智能体(包品牌经
43
+ * VITE_DEFAULT_AGENT_ID 注入),缺席则退回列表第一项。
44
+ */
45
+ export function pickDefaultAgentId(agents: ReadonlyArray<{ id: string }>): string | null {
46
+ if (agents.length === 0) return null;
47
+ return agents.some((agent) => agent.id === DEFAULT_AGENT_ID) ? DEFAULT_AGENT_ID : agents[0].id;
48
+ }
@@ -0,0 +1,127 @@
1
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
2
+ import { MemoryRouter, Outlet, Route, Routes, useLocation } from 'react-router-dom';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+ import type { UseChatsAndAgentsResult } from '@/hooks/useChatsAndAgents';
5
+ import type { LocalChat, LocalChatAgent } from '@/lib/local-api';
6
+
7
+ vi.mock('@/lib/electron-bridge', () => ({
8
+ isElectron: () => false,
9
+ getElectronBridge: () => null,
10
+ }));
11
+
12
+ vi.mock('@/brand', () => ({
13
+ BRAND_NAME: '测试助手',
14
+ getBrandLogoUrl: () => 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"/>',
15
+ }));
16
+
17
+ const { AgentSidebar } = await import('./AgentSidebar');
18
+
19
+ afterEach(() => cleanup());
20
+
21
+ const agent: LocalChatAgent = {
22
+ id: 'local-assistant',
23
+ slug: 'local-assistant',
24
+ name: '电脑操作员',
25
+ icon: null,
26
+ color: '#4f46e5',
27
+ description: null,
28
+ rolePrompt: null,
29
+ isBuiltin: true,
30
+ };
31
+
32
+ const existingChat: LocalChat = {
33
+ id: 'chat-with-content',
34
+ projectId: null,
35
+ userId: 'local',
36
+ title: '已经聊过的对话',
37
+ agentId: agent.id,
38
+ createdAt: '2026-01-01T00:00:00.000Z',
39
+ updatedAt: '2026-01-01T00:01:00.000Z',
40
+ isPinned: false,
41
+ systemPrompt: null,
42
+ pinnedRefs: null,
43
+ };
44
+
45
+ function LocationProbe() {
46
+ const loc = useLocation();
47
+ return (
48
+ <div data-testid="loc">
49
+ {loc.pathname}
50
+ {loc.search}
51
+ </div>
52
+ );
53
+ }
54
+
55
+ function renderSidebar(
56
+ initialEntry: string,
57
+ createChat = vi.fn(),
58
+ ) {
59
+ const data: UseChatsAndAgentsResult = {
60
+ chats: [existingChat],
61
+ agents: [agent],
62
+ isLoading: false,
63
+ error: null,
64
+ selectedAgentId: agent.id,
65
+ setSelectedAgentId: vi.fn(),
66
+ refreshChats: vi.fn(async () => {}),
67
+ refreshAgents: vi.fn(async () => {}),
68
+ createChat,
69
+ deleteChat: vi.fn(async () => true),
70
+ patchChatTitle: vi.fn(),
71
+ isLoadingMoreChats: false,
72
+ hasMoreChats: false,
73
+ loadMoreChats: vi.fn(async () => {}),
74
+ };
75
+
76
+ render(
77
+ <MemoryRouter initialEntries={[initialEntry]}>
78
+ <Routes>
79
+ <Route
80
+ element={
81
+ <>
82
+ <AgentSidebar
83
+ data={data}
84
+ rightPanel={null}
85
+ onToggleRightPanel={() => {}}
86
+ chatSlots={[]}
87
+ onCollapse={() => {}}
88
+ />
89
+ <LocationProbe />
90
+ <Outlet />
91
+ </>
92
+ }
93
+ >
94
+ <Route path="/" element={<div />} />
95
+ <Route path="/agent" element={<div />} />
96
+ <Route path="/agent/:chatId" element={<div />} />
97
+ <Route path="/settings" element={<div />} />
98
+ </Route>
99
+ </Routes>
100
+ </MemoryRouter>,
101
+ );
102
+
103
+ return { createChat };
104
+ }
105
+
106
+ describe('AgentSidebar 新对话不落库', () => {
107
+ it('打开落地页且不调用 createChat', () => {
108
+ const { createChat } = renderSidebar('/agent/chat-with-content');
109
+ fireEvent.click(screen.getByTestId('sidebar-new-chat'));
110
+ expect(createChat).not.toHaveBeenCalled();
111
+ expect(screen.getByTestId('loc').textContent).toBe('/agent');
112
+ });
113
+
114
+ it('连点两次也不会多出侧栏行', () => {
115
+ renderSidebar('/agent');
116
+ const before = screen.getAllByTestId('sidebar-chat-row').length;
117
+ fireEvent.click(screen.getByTestId('sidebar-new-chat'));
118
+ fireEvent.click(screen.getByTestId('sidebar-new-chat'));
119
+ expect(screen.getAllByTestId('sidebar-chat-row')).toHaveLength(before);
120
+ });
121
+
122
+ it('打开智能体管理页', () => {
123
+ renderSidebar('/agent');
124
+ fireEvent.click(screen.getByTestId('sidebar-agent-settings'));
125
+ expect(screen.getByTestId('loc').textContent).toBe('/settings?section=agents');
126
+ });
127
+ });