@steerable/agent-shell-web 0.6.31 → 0.6.33
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 +5 -8
- package/src/components/AgentSidebar.tsx +298 -77
- package/src/components/CreateProjectModal.tsx +156 -67
- package/src/components/chat/ApprovalModal.tsx +2 -2
- package/src/components/chat/ChatInput.tsx +13 -3
- package/src/components/chat/ChatProjectBadge.tsx +1 -1
- package/src/components/chat/ExecPolicyPicker.tsx +4 -3
- package/src/components/chat/FilePathCode.tsx +6 -2
- package/src/components/chat/TurnFilesCard.tsx +14 -3
- package/src/layouts/AgentLayout.tsx +16 -9
- package/src/lib/exec-policy.ts +5 -2
- package/src/lib/host-tools.ts +206 -0
- package/src/lib/http-bridge.ts +20 -7
- package/src/lib/local-api.ts +2 -2
- package/src/pages/AgentPage.tsx +42 -27
- package/src/pages/SettingsPage.tsx +21 -2
- package/src/vite-env.d.ts +16 -0
- package/vite.base.js +5 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 渲染层读构建期注入的宿主工具族 / 安全询问。
|
|
3
|
+
* 解析规则与 ts/src/host-tools.ts 对齐:缺省全开;对象必须显式 chrome:true。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type HostToolFamilyId =
|
|
7
|
+
| 'terminal'
|
|
8
|
+
| 'local-fs'
|
|
9
|
+
| 'projects'
|
|
10
|
+
| 'background-tasks'
|
|
11
|
+
| 'mcp'
|
|
12
|
+
| 'web'
|
|
13
|
+
| 'plugins';
|
|
14
|
+
|
|
15
|
+
export interface HostToolFamilySurface {
|
|
16
|
+
capability: boolean;
|
|
17
|
+
chrome: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type HostToolsConfig = Partial<
|
|
21
|
+
Record<HostToolFamilyId, boolean | { capability?: boolean; chrome?: boolean }>
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
export type ResolvedHostTools = Record<HostToolFamilyId, HostToolFamilySurface>;
|
|
25
|
+
|
|
26
|
+
const FAMILY_IDS: readonly HostToolFamilyId[] = [
|
|
27
|
+
'terminal',
|
|
28
|
+
'local-fs',
|
|
29
|
+
'projects',
|
|
30
|
+
'background-tasks',
|
|
31
|
+
'mcp',
|
|
32
|
+
'web',
|
|
33
|
+
'plugins',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
function resolveSurface(
|
|
37
|
+
value: boolean | { capability?: boolean; chrome?: boolean } | undefined,
|
|
38
|
+
): HostToolFamilySurface {
|
|
39
|
+
if (value === undefined || value === true) return { capability: true, chrome: true };
|
|
40
|
+
if (value === false) return { capability: false, chrome: false };
|
|
41
|
+
return {
|
|
42
|
+
capability: value.capability !== false,
|
|
43
|
+
chrome: value.chrome === true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveWebHostTools(config?: HostToolsConfig | null): ResolvedHostTools {
|
|
48
|
+
const out = {} as ResolvedHostTools;
|
|
49
|
+
for (const id of FAMILY_IDS) {
|
|
50
|
+
out[id] = resolveSurface(config?.[id]);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readHostToolsConfig(): HostToolsConfig {
|
|
56
|
+
const raw = import.meta.env.VITE_HOST_TOOLS;
|
|
57
|
+
if (!raw) return {};
|
|
58
|
+
try {
|
|
59
|
+
const parsed = typeof raw === 'string' ? (JSON.parse(raw) as unknown) : raw;
|
|
60
|
+
return parsed && typeof parsed === 'object' ? (parsed as HostToolsConfig) : {};
|
|
61
|
+
} catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let cached: ResolvedHostTools | null = null;
|
|
67
|
+
|
|
68
|
+
export function getWebHostTools(): ResolvedHostTools {
|
|
69
|
+
cached ??= resolveWebHostTools(readHostToolsConfig());
|
|
70
|
+
return cached;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function hostToolChrome(id: HostToolFamilyId): boolean {
|
|
74
|
+
return getWebHostTools()[id].chrome;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function hostToolCapability(id: HostToolFamilyId): boolean {
|
|
78
|
+
return getWebHostTools()[id].capability;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isWebApprovalEnabled(): boolean {
|
|
82
|
+
return import.meta.env.VITE_APPROVAL !== 'off';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type ChatModeId = 'agent' | 'plan';
|
|
86
|
+
|
|
87
|
+
const DEFAULT_CHAT_MODES: ChatModeId[] = ['agent', 'plan'];
|
|
88
|
+
|
|
89
|
+
export function resolveWebChatModes(value?: unknown): ChatModeId[] {
|
|
90
|
+
if (!Array.isArray(value)) return [...DEFAULT_CHAT_MODES];
|
|
91
|
+
const allowed = DEFAULT_CHAT_MODES.filter((id) => value.includes(id));
|
|
92
|
+
return allowed.length > 0 ? allowed : ['agent'];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function readChatModesConfig(): unknown {
|
|
96
|
+
const raw = import.meta.env.VITE_CHAT_MODES;
|
|
97
|
+
if (!raw) return undefined;
|
|
98
|
+
try {
|
|
99
|
+
return typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
100
|
+
} catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let cachedModes: ChatModeId[] | null = null;
|
|
106
|
+
|
|
107
|
+
export function getWebChatModes(): ChatModeId[] {
|
|
108
|
+
cachedModes ??= resolveWebChatModes(readChatModesConfig());
|
|
109
|
+
return cachedModes;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function clampWebChatMode(requested: unknown): ChatModeId {
|
|
113
|
+
return requested === 'plan' && getWebChatModes().includes('plan') ? 'plan' : 'agent';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const SETTINGS_ITEM_IDS = [
|
|
117
|
+
'agents',
|
|
118
|
+
'skills',
|
|
119
|
+
'mcp',
|
|
120
|
+
'appearance',
|
|
121
|
+
'llm',
|
|
122
|
+
'web-search',
|
|
123
|
+
'usage',
|
|
124
|
+
'diagnose',
|
|
125
|
+
'security',
|
|
126
|
+
'insights',
|
|
127
|
+
'telemetry',
|
|
128
|
+
] as const;
|
|
129
|
+
|
|
130
|
+
export type SettingsItemId = (typeof SETTINGS_ITEM_IDS)[number];
|
|
131
|
+
|
|
132
|
+
export const GENERAL_SETTINGS_ITEM_IDS = [
|
|
133
|
+
'appearance',
|
|
134
|
+
'llm',
|
|
135
|
+
'web-search',
|
|
136
|
+
'usage',
|
|
137
|
+
'diagnose',
|
|
138
|
+
'security',
|
|
139
|
+
'insights',
|
|
140
|
+
'telemetry',
|
|
141
|
+
] as const;
|
|
142
|
+
|
|
143
|
+
export type SettingsChromeConfig = Partial<Record<SettingsItemId, boolean>>;
|
|
144
|
+
|
|
145
|
+
export type ResolvedSettingsChrome = Record<SettingsItemId, boolean>;
|
|
146
|
+
|
|
147
|
+
function settingsFollowsHostTool(id: SettingsItemId, tools: ResolvedHostTools): boolean {
|
|
148
|
+
if (id === 'skills') return tools.plugins.chrome;
|
|
149
|
+
if (id === 'mcp') return tools.mcp.chrome;
|
|
150
|
+
if (id === 'web-search') return tools.web.chrome;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function resolveWebSettingsChrome(
|
|
155
|
+
value?: unknown,
|
|
156
|
+
tools: ResolvedHostTools = resolveWebHostTools(),
|
|
157
|
+
): ResolvedSettingsChrome {
|
|
158
|
+
const config =
|
|
159
|
+
value && typeof value === 'object' && !Array.isArray(value)
|
|
160
|
+
? (value as Record<string, unknown>)
|
|
161
|
+
: {};
|
|
162
|
+
const out = {} as ResolvedSettingsChrome;
|
|
163
|
+
for (const id of SETTINGS_ITEM_IDS) {
|
|
164
|
+
out[id] = config[id] !== false && settingsFollowsHostTool(id, tools);
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function readSettingsConfig(): unknown {
|
|
170
|
+
const raw = import.meta.env.VITE_SETTINGS;
|
|
171
|
+
if (!raw) return undefined;
|
|
172
|
+
try {
|
|
173
|
+
return typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
174
|
+
} catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let cachedSettings: ResolvedSettingsChrome | null = null;
|
|
180
|
+
|
|
181
|
+
export function getWebSettingsChrome(): ResolvedSettingsChrome {
|
|
182
|
+
cachedSettings ??= resolveWebSettingsChrome(readSettingsConfig(), getWebHostTools());
|
|
183
|
+
return cachedSettings;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function settingsChrome(id: SettingsItemId): boolean {
|
|
187
|
+
return getWebSettingsChrome()[id];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function hasGeneralSettingsChrome(): boolean {
|
|
191
|
+
const chrome = getWebSettingsChrome();
|
|
192
|
+
return GENERAL_SETTINGS_ITEM_IDS.some((id) => chrome[id]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function sanitizeRightPanelKind(kind: string | null | undefined): string | null {
|
|
196
|
+
if (!kind) return null;
|
|
197
|
+
if (kind === 'terminal') return hostToolChrome('terminal') ? 'terminal' : null;
|
|
198
|
+
return kind;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 测试用:清掉缓存,让下次按新 env 重读。 */
|
|
202
|
+
export function resetWebHostToolsForTests(): void {
|
|
203
|
+
cached = null;
|
|
204
|
+
cachedModes = null;
|
|
205
|
+
cachedSettings = null;
|
|
206
|
+
}
|
package/src/lib/http-bridge.ts
CHANGED
|
@@ -44,6 +44,15 @@ function authHeaders(): Record<string, string> {
|
|
|
44
44
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function httpErrorMessage(data: unknown, status: number): string {
|
|
48
|
+
if (data && typeof data === 'object') {
|
|
49
|
+
const rec = data as { error?: unknown; detail?: unknown };
|
|
50
|
+
if (typeof rec.error === 'string' && rec.error.trim()) return rec.error;
|
|
51
|
+
if (typeof rec.detail === 'string' && rec.detail.trim()) return rec.detail;
|
|
52
|
+
}
|
|
53
|
+
return `Request failed (${status})`;
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
async function http<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
48
57
|
const res = await fetch(path, {
|
|
49
58
|
method,
|
|
@@ -53,11 +62,9 @@ async function http<T>(method: string, path: string, body?: unknown): Promise<T>
|
|
|
53
62
|
},
|
|
54
63
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
55
64
|
});
|
|
56
|
-
const data = (await res.json().catch(() => null)) as T
|
|
65
|
+
const data = (await res.json().catch(() => null)) as T;
|
|
57
66
|
if (!res.ok) {
|
|
58
|
-
const err = new Error(
|
|
59
|
-
(data as { detail?: string } | null)?.detail || `Request failed (${res.status})`,
|
|
60
|
-
) as Error & { status?: number };
|
|
67
|
+
const err = new Error(httpErrorMessage(data, res.status)) as Error & { status?: number };
|
|
61
68
|
err.status = res.status;
|
|
62
69
|
throw err;
|
|
63
70
|
}
|
|
@@ -197,9 +204,15 @@ export function createHttpBridge(): ElectronBridge {
|
|
|
197
204
|
platform: boot.platform,
|
|
198
205
|
|
|
199
206
|
local: {
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
207
|
+
// 浏览器 File System Access API 不给真实路径。改走宿主
|
|
208
|
+
// `/host/local/select-directory`(本机系统选择器);取消或无 GUI
|
|
209
|
+
// 时返回 canceled,UI 再走手动输入路径。
|
|
210
|
+
selectDirectory: async (options) =>
|
|
211
|
+
http<{ canceled: boolean; filePaths: string[] }>(
|
|
212
|
+
'POST',
|
|
213
|
+
'/host/local/select-directory',
|
|
214
|
+
options ?? {},
|
|
215
|
+
),
|
|
203
216
|
saveTextFile: async (options) => {
|
|
204
217
|
const blob = new Blob([options.content], { type: 'application/json' });
|
|
205
218
|
const url = URL.createObjectURL(blob);
|
package/src/lib/local-api.ts
CHANGED
|
@@ -353,7 +353,7 @@ export interface ResolvedLocalPath {
|
|
|
353
353
|
|
|
354
354
|
/**
|
|
355
355
|
* 批量确认正文里提到的路径是否真实存在(用于决定行内代码要不要变成可点击)。
|
|
356
|
-
*
|
|
356
|
+
* 相对路径由后端按对话绑定的项目根解析,无项目时按对话工作区;不存在的候选不回。
|
|
357
357
|
*/
|
|
358
358
|
export async function resolveLocalPaths(candidates: string[], chatId?: string | null) {
|
|
359
359
|
return bridge().localBackend.request<{ resolved: ResolvedLocalPath[] }>({
|
|
@@ -489,7 +489,7 @@ export async function createProject(input: {
|
|
|
489
489
|
|
|
490
490
|
export async function updateProject(
|
|
491
491
|
projectId: string,
|
|
492
|
-
updates: { name?: string; folderPath?: string },
|
|
492
|
+
updates: { name?: string; folderPath?: string; sourceFolders?: string[] },
|
|
493
493
|
) {
|
|
494
494
|
return bridge().localBackend.request<{ success: boolean; project: LocalProject }>({
|
|
495
495
|
method: 'PUT',
|
package/src/pages/AgentPage.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
readStoredExecPolicy,
|
|
23
23
|
type ExecPolicy,
|
|
24
24
|
} from "@/lib/exec-policy";
|
|
25
|
+
import { clampWebChatMode, hostToolChrome, settingsChrome } from "@/lib/host-tools";
|
|
25
26
|
import type { ExecutedAction } from "@/components/chat/ExecutedActionsCard";
|
|
26
27
|
import { inspectTaskTitle, type InspectTaskInput } from "@/components/chat/executed-actions-model";
|
|
27
28
|
import type { ChildInfo } from "@/components/chat/OrchestrationChildrenCard";
|
|
@@ -85,10 +86,8 @@ type ChatMessageWithMetadata = ChatMessage & LocalChatMessage;
|
|
|
85
86
|
const CHAT_MODE_STORAGE_KEY = "agent-chat-mode";
|
|
86
87
|
|
|
87
88
|
function readStoredMode(): ChatMode {
|
|
88
|
-
if (typeof localStorage === "undefined") return "agent";
|
|
89
|
-
return localStorage.getItem(CHAT_MODE_STORAGE_KEY)
|
|
90
|
-
? "plan"
|
|
91
|
-
: "agent";
|
|
89
|
+
if (typeof localStorage === "undefined") return clampWebChatMode("agent");
|
|
90
|
+
return clampWebChatMode(localStorage.getItem(CHAT_MODE_STORAGE_KEY));
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
function extractPersistedActions(
|
|
@@ -374,8 +373,9 @@ function AgentChatView({
|
|
|
374
373
|
// 当前会话绑定的项目 — Codex 式 cwd 指示:输入框上方显示项目名徽章,
|
|
375
374
|
// 点击可关联到其他项目 / 修改项目目录 / 移出项目。无项目会话不显示。
|
|
376
375
|
const [projects, setProjects] = useState<LocalProject[]>([]);
|
|
376
|
+
const showProjectsChrome = hostToolChrome("projects");
|
|
377
377
|
const fetchProjects = useCallback(async () => {
|
|
378
|
-
if (!isElectron()) return;
|
|
378
|
+
if (!isElectron() || !hostToolChrome("projects")) return;
|
|
379
379
|
try {
|
|
380
380
|
const res = await listProjects();
|
|
381
381
|
setProjects(res.projects ?? []);
|
|
@@ -516,9 +516,10 @@ function AgentChatView({
|
|
|
516
516
|
);
|
|
517
517
|
|
|
518
518
|
const handleModeChange = useCallback((next: ChatMode) => {
|
|
519
|
-
|
|
519
|
+
const clamped = clampWebChatMode(next);
|
|
520
|
+
setMode(clamped);
|
|
520
521
|
if (typeof localStorage !== "undefined") {
|
|
521
|
-
localStorage.setItem(CHAT_MODE_STORAGE_KEY,
|
|
522
|
+
localStorage.setItem(CHAT_MODE_STORAGE_KEY, clamped);
|
|
522
523
|
}
|
|
523
524
|
}, []);
|
|
524
525
|
|
|
@@ -1121,7 +1122,9 @@ function AgentChatView({
|
|
|
1121
1122
|
suggestedReplies={visibleSuggestedReplies}
|
|
1122
1123
|
onSelectSuggestion={handleSelectSuggestion}
|
|
1123
1124
|
onOpenSettings={
|
|
1124
|
-
isElectron()
|
|
1125
|
+
isElectron() && settingsChrome("llm")
|
|
1126
|
+
? () => setLlmSettingsOpen(true)
|
|
1127
|
+
: undefined
|
|
1125
1128
|
}
|
|
1126
1129
|
inputToolbarExtras={
|
|
1127
1130
|
<ModelPicker
|
|
@@ -1134,25 +1137,31 @@ function AgentChatView({
|
|
|
1134
1137
|
}
|
|
1135
1138
|
mode={mode}
|
|
1136
1139
|
onModeChange={handleModeChange}
|
|
1137
|
-
execPolicy={execPolicy}
|
|
1138
|
-
onExecPolicyChange={
|
|
1140
|
+
execPolicy={hostToolChrome("local-fs") ? execPolicy : "workspace"}
|
|
1141
|
+
onExecPolicyChange={
|
|
1142
|
+
hostToolChrome("local-fs") ? handleExecPolicyChange : undefined
|
|
1143
|
+
}
|
|
1139
1144
|
inputLeadingChrome={
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1145
|
+
showProjectsChrome ? (
|
|
1146
|
+
<ChatProjectBadge
|
|
1147
|
+
chatId={chatId}
|
|
1148
|
+
project={chatProject}
|
|
1149
|
+
projects={projects}
|
|
1150
|
+
onProjectsChanged={fetchProjects}
|
|
1151
|
+
onChatProjectChanged={ctx.refreshChats}
|
|
1152
|
+
/>
|
|
1153
|
+
) : undefined
|
|
1147
1154
|
}
|
|
1148
1155
|
inputBanner={
|
|
1149
1156
|
<>
|
|
1150
1157
|
{/* W6-5 项目信任门控:项目含规则文件但未信任时提示授权。 */}
|
|
1158
|
+
{showProjectsChrome ? (
|
|
1151
1159
|
<ProjectTrustBanner
|
|
1152
1160
|
chatId={chatId}
|
|
1153
1161
|
project={chatProject}
|
|
1154
1162
|
onTrustChanged={fetchProjects}
|
|
1155
1163
|
/>
|
|
1164
|
+
) : null}
|
|
1156
1165
|
{planReady && !isStreaming ? (
|
|
1157
1166
|
<div className="mx-2.5 mb-1 flex items-center justify-between gap-2 rounded-agent-md border border-amber-400/50 bg-amber-400/10 px-2.5 py-1.5 text-xs">
|
|
1158
1167
|
<div className="flex items-center gap-2 text-amber-700 dark:text-amber-300">
|
|
@@ -1183,7 +1192,7 @@ function AgentChatView({
|
|
|
1183
1192
|
</>
|
|
1184
1193
|
}
|
|
1185
1194
|
/>
|
|
1186
|
-
{isElectron() && (
|
|
1195
|
+
{isElectron() && settingsChrome("llm") && (
|
|
1187
1196
|
<LocalLlmSettingsModal
|
|
1188
1197
|
open={llmSettingsOpen}
|
|
1189
1198
|
onClose={() => setLlmSettingsOpen(false)}
|
|
@@ -1234,8 +1243,9 @@ function EmptyChatGate() {
|
|
|
1234
1243
|
useEffect(() => {
|
|
1235
1244
|
setSelectedProjectId(projectIdFromUrl);
|
|
1236
1245
|
}, [projectIdFromUrl]);
|
|
1246
|
+
const showProjectsChrome = hostToolChrome("projects");
|
|
1237
1247
|
const fetchProjects = useCallback(async () => {
|
|
1238
|
-
if (!isElectron()) return;
|
|
1248
|
+
if (!isElectron() || !hostToolChrome("projects")) return;
|
|
1239
1249
|
try {
|
|
1240
1250
|
const res = await listProjects();
|
|
1241
1251
|
setProjects(res.projects ?? []);
|
|
@@ -1258,9 +1268,10 @@ function EmptyChatGate() {
|
|
|
1258
1268
|
null;
|
|
1259
1269
|
|
|
1260
1270
|
const handleModeChange = useCallback((next: ChatMode) => {
|
|
1261
|
-
|
|
1271
|
+
const clamped = clampWebChatMode(next);
|
|
1272
|
+
setMode(clamped);
|
|
1262
1273
|
if (typeof localStorage !== "undefined") {
|
|
1263
|
-
localStorage.setItem(CHAT_MODE_STORAGE_KEY,
|
|
1274
|
+
localStorage.setItem(CHAT_MODE_STORAGE_KEY, clamped);
|
|
1264
1275
|
}
|
|
1265
1276
|
}, []);
|
|
1266
1277
|
|
|
@@ -1316,7 +1327,7 @@ function EmptyChatGate() {
|
|
|
1316
1327
|
setCreateError(null);
|
|
1317
1328
|
try {
|
|
1318
1329
|
const id = await ctx.createChat({
|
|
1319
|
-
...(selectedProjectId ? { projectId: selectedProjectId } : {}),
|
|
1330
|
+
...(selectedProjectId && showProjectsChrome ? { projectId: selectedProjectId } : {}),
|
|
1320
1331
|
...(ctx.selectedAgentId ? { agentId: ctx.selectedAgentId } : {}),
|
|
1321
1332
|
});
|
|
1322
1333
|
if (!id) throw new Error("创建对话失败,请重试");
|
|
@@ -1365,7 +1376,9 @@ function EmptyChatGate() {
|
|
|
1365
1376
|
selectedAgentId={ctx.selectedAgentId}
|
|
1366
1377
|
onSelectAgent={handleSelectAgent}
|
|
1367
1378
|
onOpenSettings={
|
|
1368
|
-
isElectron()
|
|
1379
|
+
isElectron() && settingsChrome("llm")
|
|
1380
|
+
? () => setLlmSettingsOpen(true)
|
|
1381
|
+
: undefined
|
|
1369
1382
|
}
|
|
1370
1383
|
toolbarExtras={
|
|
1371
1384
|
<ModelPicker
|
|
@@ -1377,7 +1390,7 @@ function EmptyChatGate() {
|
|
|
1377
1390
|
/>
|
|
1378
1391
|
}
|
|
1379
1392
|
leadingChrome={
|
|
1380
|
-
isElectron() ? (
|
|
1393
|
+
isElectron() && showProjectsChrome ? (
|
|
1381
1394
|
<ProjectPickerButton
|
|
1382
1395
|
projects={projects}
|
|
1383
1396
|
value={selectedProjectId}
|
|
@@ -1388,8 +1401,10 @@ function EmptyChatGate() {
|
|
|
1388
1401
|
}
|
|
1389
1402
|
mode={mode}
|
|
1390
1403
|
onModeChange={handleModeChange}
|
|
1391
|
-
execPolicy={execPolicy}
|
|
1392
|
-
onExecPolicyChange={
|
|
1404
|
+
execPolicy={hostToolChrome("local-fs") ? execPolicy : "workspace"}
|
|
1405
|
+
onExecPolicyChange={
|
|
1406
|
+
hostToolChrome("local-fs") ? handleExecPolicyChange : undefined
|
|
1407
|
+
}
|
|
1393
1408
|
files={files}
|
|
1394
1409
|
onFilesChange={setFiles}
|
|
1395
1410
|
onMentionReferencesChange={setMentionReferences}
|
|
@@ -1411,7 +1426,7 @@ function EmptyChatGate() {
|
|
|
1411
1426
|
</p>
|
|
1412
1427
|
)}
|
|
1413
1428
|
</div>
|
|
1414
|
-
{isElectron() && (
|
|
1429
|
+
{isElectron() && settingsChrome("llm") && (
|
|
1415
1430
|
<LocalLlmSettingsModal
|
|
1416
1431
|
open={llmSettingsOpen}
|
|
1417
1432
|
onClose={() => setLlmSettingsOpen(false)}
|
|
@@ -19,12 +19,15 @@ import { TelemetrySettingsPanel } from '@/components/settings/TelemetrySettingsP
|
|
|
19
19
|
import { UsagePanel } from '@/components/settings/UsagePanel';
|
|
20
20
|
import { WebSearchSettingsPanel } from '@/components/settings/WebSearchSettingsPanel';
|
|
21
21
|
import { getPackSettingsPanels } from '@/packs/registry';
|
|
22
|
+
import { settingsChrome } from '@/lib/host-tools';
|
|
22
23
|
import type { AgentOutletContext } from '@/layouts/AgentLayout';
|
|
23
24
|
|
|
24
25
|
type SettingsSection = 'skills' | 'mcp' | 'agents' | 'general';
|
|
25
26
|
|
|
26
27
|
function resolveSection(raw: string | null): SettingsSection {
|
|
27
|
-
if (raw === 'skills'
|
|
28
|
+
if (raw === 'skills' && settingsChrome('skills')) return 'skills';
|
|
29
|
+
if (raw === 'mcp' && settingsChrome('mcp')) return 'mcp';
|
|
30
|
+
if (raw === 'agents' && settingsChrome('agents')) return 'agents';
|
|
28
31
|
return 'general';
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -61,7 +64,7 @@ export function SettingsPage() {
|
|
|
61
64
|
<div className="flex h-full w-full flex-col overflow-hidden">
|
|
62
65
|
<header className="flex h-9 flex-shrink-0 items-center justify-between gap-2 border-b border-agent-border px-2.5">
|
|
63
66
|
<h1 className="text-xs font-semibold text-agent-foreground">{title}</h1>
|
|
64
|
-
{section === 'general' && (
|
|
67
|
+
{section === 'general' && settingsChrome('llm') && (
|
|
65
68
|
<SettingsSaveButton
|
|
66
69
|
testId="settings-header-save"
|
|
67
70
|
saving={llmSaveUi.saving}
|
|
@@ -112,6 +115,7 @@ export function SettingsPage() {
|
|
|
112
115
|
|
|
113
116
|
{section === 'general' && (
|
|
114
117
|
<>
|
|
118
|
+
{settingsChrome('appearance') && (
|
|
115
119
|
<section className="space-y-2" data-testid="settings-section-appearance">
|
|
116
120
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
117
121
|
<LuMonitor className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -119,7 +123,9 @@ export function SettingsPage() {
|
|
|
119
123
|
</h2>
|
|
120
124
|
<AppearanceSettingsPanel />
|
|
121
125
|
</section>
|
|
126
|
+
)}
|
|
122
127
|
|
|
128
|
+
{settingsChrome('llm') && (
|
|
123
129
|
<section className="space-y-2" data-testid="settings-section-llm">
|
|
124
130
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
125
131
|
<LuSettings className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -131,7 +137,9 @@ export function SettingsPage() {
|
|
|
131
137
|
onSaveUiChange={setLlmSaveUi}
|
|
132
138
|
/>
|
|
133
139
|
</section>
|
|
140
|
+
)}
|
|
134
141
|
|
|
142
|
+
{settingsChrome('web-search') && (
|
|
135
143
|
<section className="space-y-2" data-testid="settings-section-web-search">
|
|
136
144
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
137
145
|
<LuSearch className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -139,7 +147,9 @@ export function SettingsPage() {
|
|
|
139
147
|
</h2>
|
|
140
148
|
<WebSearchSettingsPanel />
|
|
141
149
|
</section>
|
|
150
|
+
)}
|
|
142
151
|
|
|
152
|
+
{settingsChrome('usage') && (
|
|
143
153
|
<section className="space-y-2" data-testid="settings-section-usage">
|
|
144
154
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
145
155
|
<LuChartBar className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -147,7 +157,9 @@ export function SettingsPage() {
|
|
|
147
157
|
</h2>
|
|
148
158
|
<UsagePanel />
|
|
149
159
|
</section>
|
|
160
|
+
)}
|
|
150
161
|
|
|
162
|
+
{settingsChrome('diagnose') && (
|
|
151
163
|
<section className="space-y-2" data-testid="settings-section-diagnose">
|
|
152
164
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
153
165
|
<LuNetwork className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -155,7 +167,9 @@ export function SettingsPage() {
|
|
|
155
167
|
</h2>
|
|
156
168
|
<DiagnoseSettingsPanel />
|
|
157
169
|
</section>
|
|
170
|
+
)}
|
|
158
171
|
|
|
172
|
+
{settingsChrome('security') && (
|
|
159
173
|
<section className="space-y-2" data-testid="settings-section-security">
|
|
160
174
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
161
175
|
<LuShieldCheck className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -163,7 +177,9 @@ export function SettingsPage() {
|
|
|
163
177
|
</h2>
|
|
164
178
|
<SecuritySettingsPanel />
|
|
165
179
|
</section>
|
|
180
|
+
)}
|
|
166
181
|
|
|
182
|
+
{settingsChrome('insights') && (
|
|
167
183
|
<section className="space-y-2" data-testid="settings-section-insights">
|
|
168
184
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
169
185
|
<LuChartBar className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -171,7 +187,9 @@ export function SettingsPage() {
|
|
|
171
187
|
</h2>
|
|
172
188
|
<InsightsSettingsPanel />
|
|
173
189
|
</section>
|
|
190
|
+
)}
|
|
174
191
|
|
|
192
|
+
{settingsChrome('telemetry') && (
|
|
175
193
|
<section className="space-y-2" data-testid="settings-section-telemetry">
|
|
176
194
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
177
195
|
<LuActivity className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
@@ -179,6 +197,7 @@ export function SettingsPage() {
|
|
|
179
197
|
</h2>
|
|
180
198
|
<TelemetrySettingsPanel />
|
|
181
199
|
</section>
|
|
200
|
+
)}
|
|
182
201
|
|
|
183
202
|
{/* 场景包设置面板(1.2 起由包渲染层贡献)。
|
|
184
203
|
包经 packs/registry 注册,未激活 flavor 的产物里没有包组件。 */}
|
package/src/vite-env.d.ts
CHANGED
|
@@ -1 +1,17 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
interface ImportMetaEnv {
|
|
4
|
+
readonly VITE_APP_FLAVOR?: string;
|
|
5
|
+
readonly VITE_BRAND_NAME?: string;
|
|
6
|
+
readonly VITE_BRAND_TITLE?: string;
|
|
7
|
+
readonly VITE_BRAND_TAGLINE?: string;
|
|
8
|
+
readonly VITE_DEFAULT_AGENT_ID?: string;
|
|
9
|
+
readonly VITE_HOST_TOOLS?: string;
|
|
10
|
+
readonly VITE_APPROVAL?: string;
|
|
11
|
+
readonly VITE_CHAT_MODES?: string;
|
|
12
|
+
readonly VITE_SETTINGS?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ImportMeta {
|
|
16
|
+
readonly env: ImportMetaEnv;
|
|
17
|
+
}
|
package/vite.base.js
CHANGED
|
@@ -53,6 +53,11 @@ export function createProductViteConfig(options) {
|
|
|
53
53
|
'import.meta.env.VITE_BRAND_TITLE': JSON.stringify(brand?.title ?? ''),
|
|
54
54
|
'import.meta.env.VITE_BRAND_TAGLINE': JSON.stringify(brand?.tagline ?? '一款本地桌面 AI 伙伴'),
|
|
55
55
|
'import.meta.env.VITE_DEFAULT_AGENT_ID': JSON.stringify(brand?.defaultAgentId ?? 'local-assistant'),
|
|
56
|
+
// 宿主工具族 / 安全询问:与 node 侧 setProductConfig 读同一份 product.json。
|
|
57
|
+
'import.meta.env.VITE_HOST_TOOLS': JSON.stringify(JSON.stringify(product.hostTools ?? {})),
|
|
58
|
+
'import.meta.env.VITE_APPROVAL': JSON.stringify(product.approval ?? 'host'),
|
|
59
|
+
'import.meta.env.VITE_CHAT_MODES': JSON.stringify(JSON.stringify(product.chatModes ?? ['agent', 'plan'])),
|
|
60
|
+
'import.meta.env.VITE_SETTINGS': JSON.stringify(JSON.stringify(product.settings ?? {})),
|
|
56
61
|
},
|
|
57
62
|
resolve: {
|
|
58
63
|
alias: {
|