@corenel/web-ui 0.5.4 → 0.5.5
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/dist/ContextBar.d.ts +5 -1
- package/dist/ContextBar.d.ts.map +1 -1
- package/dist/ContextBar.js +3 -3
- package/dist/ContextBar.js.map +1 -1
- package/dist/ContextManager.d.ts +11 -2
- package/dist/ContextManager.d.ts.map +1 -1
- package/dist/ContextManager.js +42 -4
- package/dist/ContextManager.js.map +1 -1
- package/dist/Transcript.d.ts.map +1 -1
- package/dist/Transcript.js +11 -4
- package/dist/Transcript.js.map +1 -1
- package/dist/agentChat/AgentChat.d.ts +2 -0
- package/dist/agentChat/AgentChat.d.ts.map +1 -1
- package/dist/agentChat/AgentChat.js +24 -2
- package/dist/agentChat/AgentChat.js.map +1 -1
- package/dist/agentChat/OptionsDrawer.d.ts +8 -33
- package/dist/agentChat/OptionsDrawer.d.ts.map +1 -1
- package/dist/agentChat/OptionsDrawer.js +197 -200
- package/dist/agentChat/OptionsDrawer.js.map +1 -1
- package/dist/liveEdit.d.ts +11 -0
- package/dist/liveEdit.d.ts.map +1 -1
- package/dist/liveEdit.js +61 -0
- package/dist/liveEdit.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,37 +1,65 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/* The Corenel
|
|
3
|
-
*
|
|
4
|
-
* from the editor AgentPanel so every agent surface (editor, /agent, /craft) can
|
|
5
|
-
* render the same drawer. Persona/policy selection is CONTROLLED (the host owns
|
|
6
|
-
* the selected id and persists it); everything else — persona/policy LISTS,
|
|
7
|
-
* skills, transcript-view, live-edits — is harness/web-ui-backed and lives here.
|
|
2
|
+
/* The Corenel composer popout — ONE pane answering one question: what does THIS
|
|
3
|
+
* chat run with.
|
|
8
4
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
5
|
+
* WHAT THIS REPLACED, and why none of it survived intact:
|
|
6
|
+
*
|
|
7
|
+
* Two tabs. "Settings" held persona/policy/display/budget; "Capabilities"
|
|
8
|
+
* held a read-only row for every tool, each with an allow/ask/deny badge --
|
|
9
|
+
* and the POLICY that decided those badges lived on the other tab. You read
|
|
10
|
+
* the consequence in one place and changed the cause in another. With
|
|
11
|
+
* Microsoft 365 connected the list was 80+ rows, none of them actionable,
|
|
12
|
+
* with the actual controls (a key form, an add-server form, skill install)
|
|
13
|
+
* buried underneath the whole scroll.
|
|
14
|
+
*
|
|
15
|
+
* Worse, the add-server form POSTed to a CLOUD list while Settings wrote
|
|
16
|
+
* daemon.json on the connected machine. Two stores, two front doors, the same
|
|
17
|
+
* word for both, and neither mentioning the other.
|
|
18
|
+
*
|
|
19
|
+
* WHAT IT IS NOW: sources, not tools. One row per builtin namespace, per
|
|
20
|
+
* connected MCP server, per installed skill -- about a dozen instead of a
|
|
21
|
+
* hundred -- each switchable off FOR THIS CONVERSATION and expandable to see
|
|
22
|
+
* the tools it brings. Policy sits directly above them, so cause and effect are
|
|
23
|
+
* finally in the same pane.
|
|
24
|
+
*
|
|
25
|
+
* MANAGEMENT LIVES IN SETTINGS AND NOWHERE ELSE. Adding a server, storing a
|
|
26
|
+
* key, installing a skill: all moved, so daemon.json has exactly one writer.
|
|
27
|
+
* This drawer reads and scopes; it never mutates what exists.
|
|
28
|
+
*
|
|
29
|
+
* Host couplings still arrive as props so this module never imports the host:
|
|
30
|
+
* `connections` (read-only MCP listing; omitted = those rows hidden), the
|
|
31
|
+
* per-run budget form (`budgetSection`), the Monaco PolicyEditor
|
|
32
|
+
* (`policyEditor`, a render prop), and `toolBadge` for per-tool provenance.
|
|
33
|
+
*/
|
|
15
34
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
16
35
|
import { allTools } from '@corenel/harness/tools/builtins';
|
|
17
36
|
import { resolveNamespace, toolAddress, toolMatchesAnyGlob, NAMESPACE_LABELS } from '@corenel/harness/tools/namespaces';
|
|
37
|
+
import { parseMcpWireName } from '@corenel/protocol';
|
|
18
38
|
import { PermissionService, STANDARD_POLICY, listPolicies, getPolicy } from '@corenel/harness/guardrail';
|
|
19
39
|
import { loadPersonas, createPersona, deletePersona } from '@corenel/harness/prompts/config';
|
|
40
|
+
import { listSkills, subscribeSkills } from '@corenel/harness/skills';
|
|
41
|
+
import { isSourceEnabled, setSourceEnabled, subscribeSessionCapabilities } from '@corenel/tools-web/sessionCapabilities';
|
|
20
42
|
import { useBrand } from '@corenel/ui-kit/host';
|
|
21
|
-
import {
|
|
22
|
-
import { IcPlus, IcTrash, IcX, IcKey, IcCheck, IcSliders, IcZap, IcSearch, IcEdit } from '@corenel/ui-kit/icons';
|
|
43
|
+
import { IcPlus, IcTrash, IcX, IcCheck, IcEdit, IcChevronRight } from '@corenel/ui-kit/icons';
|
|
23
44
|
import { Section, Row, Toggle, MenuBtn } from '@corenel/ui-kit/FieldControls';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
45
|
+
import { sessionSearchableTools } from '@corenel/tools-web/sessionTools';
|
|
46
|
+
import { loadedToolNames, removeLoadedTool, subscribeLoadedTools } from '@corenel/tools-web/sessionLoadedTools';
|
|
47
|
+
/* The source list is the primary control here, and it is worked down with a
|
|
48
|
+
* thumb as often as a mouse. `Toggle` carries its own 44px hit area; the row
|
|
49
|
+
* beside it -- whose left two thirds opens the source -- has to match, or half
|
|
50
|
+
* of every row stays a mouse-sized target on a device with no mouse.
|
|
51
|
+
*
|
|
52
|
+
* A module const on `(pointer: coarse)`, which is what this package already
|
|
53
|
+
* does: primary-pointer coarseness does not change at runtime (Composer.tsx). */
|
|
54
|
+
const COARSE_POINTER = typeof matchMedia !== 'undefined' && matchMedia('(pointer: coarse)').matches;
|
|
27
55
|
const BUILTIN_BLURB = {
|
|
28
56
|
standard: 'Reads run automatically; writes ask first.',
|
|
29
57
|
'read-only': 'Inspection only — writes are refused.',
|
|
30
58
|
autonomous: 'Everything runs without prompts.',
|
|
31
59
|
};
|
|
32
60
|
const policyBlurb = (p) => BUILTIN_BLURB[p.name] || p.description || '';
|
|
33
|
-
/** Effective per-tool status under a policy,
|
|
34
|
-
* namespace address (fs:*) and bare name. */
|
|
61
|
+
/** Effective per-tool status under a policy, shown on an EXPANDED source's tool
|
|
62
|
+
* list. Matches by namespace address (fs:*) and bare name. */
|
|
35
63
|
function toolStatus(policy, tool) {
|
|
36
64
|
const deny = policy.tools?.deny ?? [];
|
|
37
65
|
const allow = policy.tools?.allow ?? [];
|
|
@@ -40,15 +68,12 @@ function toolStatus(policy, tool) {
|
|
|
40
68
|
if (allow.length && !toolMatchesAnyGlob(tool, allow))
|
|
41
69
|
return 'deny';
|
|
42
70
|
return new PermissionService({ policy }).resolveLevel({
|
|
43
|
-
name: tool.name, address: toolAddress(tool), mutates: tool.mutates, permission: tool.permission,
|
|
71
|
+
name: tool.name, address: toolAddress(tool), mutates: tool.mutates, permission: tool.permission, roomSafe: tool.roomSafe,
|
|
44
72
|
});
|
|
45
73
|
}
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* The first fetch (refreshKey's initial value) reads the cache; every later bump
|
|
50
|
-
* forces a re-list, mirroring the old AgentPanel's mount-load + drawer-open
|
|
51
|
-
* refresh + post-mutation `listMcpTools(true)` calls. */
|
|
74
|
+
/** MCP tools for the drawer — mount-load plus a forced refresh each time the
|
|
75
|
+
* drawer opens. Exported because AgentChat builds its "/" command menu from
|
|
76
|
+
* the same list. */
|
|
52
77
|
export function useMcpTools(connections, refreshKey) {
|
|
53
78
|
const [tools, setTools] = useState([]);
|
|
54
79
|
const firstRef = React.useRef(true);
|
|
@@ -65,21 +90,27 @@ export function useMcpTools(connections, refreshKey) {
|
|
|
65
90
|
}
|
|
66
91
|
export function OptionsDrawer(props) {
|
|
67
92
|
const { name: brandName } = useBrand();
|
|
68
|
-
const { open, onClose, personaName, onPersona, policyName, onPolicy, hideIdentity, budgetSection, settingsExtras, connections, toolBadge, policyEditor, registryTick, capabilities,
|
|
69
|
-
const [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
setToolQuery('');
|
|
73
|
-
setDrawerTab('settings');
|
|
74
|
-
} }, [open]);
|
|
75
|
-
const [transcriptView, setTranscriptView] = useTranscriptView();
|
|
76
|
-
const [liveEdits, setLiveEdits] = useLiveEditsSetting();
|
|
93
|
+
const { open, onClose, sessionId, personaName, onPersona, policyName, onPolicy, hideIdentity, budgetSection, settingsExtras, connections, toolBadge, policyEditor, registryTick, capabilities, } = props;
|
|
94
|
+
const [expanded, setExpanded] = useState(null);
|
|
95
|
+
useEffect(() => { if (open)
|
|
96
|
+
setExpanded(null); }, [open]);
|
|
77
97
|
// Persona/policy LISTS stay internal — only the selected ids are the host's.
|
|
78
98
|
const [personas, setPersonas] = useState([]);
|
|
79
99
|
const [personaMenuOpen, setPersonaMenuOpen] = useState(false);
|
|
80
100
|
const [newPersona, setNewPersona] = useState(null);
|
|
81
|
-
|
|
101
|
+
/* CAUGHT, because "not yet" is the ordinary case at mount. Personas live in
|
|
102
|
+
* the config workspace, which lives on the sidecar, and the sidecar is still
|
|
103
|
+
* dialling when this drawer first renders -- `channelFor` throws "that
|
|
104
|
+
* machine is not connected", correctly, and with no catch that became an
|
|
105
|
+
* unhandled rejection on every single app start (twice, under StrictMode). */
|
|
106
|
+
const reloadPersonas = useCallback(() => {
|
|
107
|
+
loadPersonas().then(setPersonas).catch(() => setPersonas([]));
|
|
108
|
+
}, []);
|
|
82
109
|
useEffect(() => { reloadPersonas(); }, [reloadPersonas]);
|
|
110
|
+
/* And read again when the picker is OPENED -- otherwise catching above trades
|
|
111
|
+
* a console error for a permanently empty list. */
|
|
112
|
+
useEffect(() => { if (personaMenuOpen)
|
|
113
|
+
reloadPersonas(); }, [personaMenuOpen, reloadPersonas]);
|
|
83
114
|
const personaLabel = personas.find((p) => p.id === personaName)?.name || personaName;
|
|
84
115
|
const pickPersona = (id) => { onPersona(id); setPersonaMenuOpen(false); };
|
|
85
116
|
const addPersona = async () => {
|
|
@@ -101,103 +132,100 @@ export function OptionsDrawer(props) {
|
|
|
101
132
|
};
|
|
102
133
|
const [policyMenuOpen, setPolicyMenuOpen] = useState(false);
|
|
103
134
|
const [policyEditorOpen, setPolicyEditorOpen] = useState(false);
|
|
104
|
-
const [policyListVersion, setPolicyListVersion] = useState(0);
|
|
135
|
+
const [policyListVersion, setPolicyListVersion] = useState(0);
|
|
105
136
|
const selectPolicy = useCallback((name) => { onPolicy(name); setPolicyMenuOpen(false); }, [onPolicy]);
|
|
106
137
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
138
|
const activePolicy = useMemo(() => getPolicy(policyName) ?? STANDARD_POLICY, [policyName, policyListVersion]);
|
|
108
|
-
// Connections (web-search key + MCP servers) — omitted entirely when
|
|
109
|
-
// `connections` is not supplied (no fake forms for a surface that can't act on them).
|
|
110
|
-
const [toolKeys, setToolKeys] = useState({});
|
|
111
139
|
const [mcpServers, setMcpServers] = useState([]);
|
|
112
|
-
const [connAuthNeeded, setConnAuthNeeded] = useState(false);
|
|
113
|
-
const [connBusy, setConnBusy] = useState(false);
|
|
114
140
|
const [connErr, setConnErr] = useState(null);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const loadConnections = useCallback(() => {
|
|
118
|
-
if (!connections)
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!open || !connections)
|
|
119
143
|
return;
|
|
120
144
|
setConnErr(null);
|
|
121
|
-
|
|
122
|
-
.then(
|
|
123
|
-
.catch((e) => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// MCP tools — mount-load + refresh whenever the drawer opens or a mutation
|
|
131
|
-
// below bumps the key (matching the old `listMcpTools(true)` refresh calls).
|
|
132
|
-
const [mcpRefresh, setMcpRefresh] = useState(0);
|
|
133
|
-
useEffect(() => { if (open)
|
|
134
|
-
setMcpRefresh((k) => k + 1); }, [open]);
|
|
135
|
-
const mcpTools = useMcpTools(connections, mcpRefresh);
|
|
136
|
-
const refreshMcp = useCallback(() => { setMcpRefresh((k) => k + 1); loadConnections(); onConnectionsChanged?.(); }, [loadConnections, onConnectionsChanged]);
|
|
137
|
-
const runConn = async (fn) => {
|
|
138
|
-
setConnBusy(true);
|
|
139
|
-
setConnErr(null);
|
|
140
|
-
try {
|
|
141
|
-
await fn();
|
|
142
|
-
}
|
|
143
|
-
catch (e) {
|
|
144
|
-
setConnErr(String(e?.message || e));
|
|
145
|
-
}
|
|
146
|
-
finally {
|
|
147
|
-
setConnBusy(false);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
const addServer = () => {
|
|
151
|
-
if (!connections)
|
|
152
|
-
return;
|
|
153
|
-
return runConn(async () => {
|
|
154
|
-
if (!serverForm)
|
|
155
|
-
return;
|
|
156
|
-
const label = serverForm.label.trim();
|
|
157
|
-
const url = serverForm.url.trim();
|
|
158
|
-
if (!label || !url) {
|
|
159
|
-
setConnErr('Label and URL are required.');
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
await connections.addMcpServer({ label, url, apiKey: serverForm.apiKey.trim() || undefined });
|
|
163
|
-
setServerForm(null);
|
|
164
|
-
refreshMcp();
|
|
165
|
-
});
|
|
166
|
-
};
|
|
167
|
-
const removeServer = (id) => { if (!connections)
|
|
168
|
-
return; return runConn(async () => { await connections.removeMcpServer(id); refreshMcp(); }); };
|
|
169
|
-
const saveWebKey = () => {
|
|
170
|
-
if (!connections)
|
|
171
|
-
return;
|
|
172
|
-
return runConn(async () => {
|
|
173
|
-
const k = (webKeyEdit ?? '').trim();
|
|
174
|
-
if (k.length < 8) {
|
|
175
|
-
setConnErr('Enter a valid Tavily key.');
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
await connections.setToolKey('tavily', k);
|
|
179
|
-
setWebKeyEdit(null);
|
|
180
|
-
loadConnections();
|
|
145
|
+
connections.listMcpServers()
|
|
146
|
+
.then(setMcpServers)
|
|
147
|
+
.catch((e) => {
|
|
148
|
+
// A sidecar that is not connected is the ordinary case, not an error
|
|
149
|
+
// worth shouting about -- the source list simply has no servers in it.
|
|
150
|
+
const msg = String(e?.message || e);
|
|
151
|
+
if (msg !== 'AUTH_REQUIRED')
|
|
152
|
+
setConnErr(msg);
|
|
153
|
+
setMcpServers([]);
|
|
181
154
|
});
|
|
182
|
-
};
|
|
183
|
-
const
|
|
184
|
-
|
|
155
|
+
}, [open, connections]);
|
|
156
|
+
const [skills, setSkills] = useState([]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const reload = () => { listSkills().then(setSkills).catch(() => setSkills([])); };
|
|
159
|
+
reload();
|
|
160
|
+
return subscribeSkills(reload);
|
|
161
|
+
}, []);
|
|
162
|
+
// Re-render when an override changes so the switches and the run agree
|
|
163
|
+
// without the drawer being closed and reopened.
|
|
164
|
+
const [capTick, setCapTick] = useState(0);
|
|
165
|
+
useEffect(() => subscribeSessionCapabilities(() => setCapTick((t) => t + 1)), []);
|
|
166
|
+
useEffect(() => subscribeLoadedTools(() => setCapTick((t) => t + 1)), []);
|
|
185
167
|
const toolsFn = capabilities?.tools ?? allTools;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
168
|
+
/* THE SOURCE LIST — builtin namespaces, then connected servers, then skills.
|
|
169
|
+
* Built in one pass so the row count and the expanded detail cannot disagree. */
|
|
170
|
+
const sources = useMemo(() => {
|
|
171
|
+
const registry = toolsFn();
|
|
172
|
+
/* ONE SOURCE OF TRUTH: the tool registry the engine actually runs with.
|
|
173
|
+
*
|
|
174
|
+
* The Connected rows used to come from a CLOUD server list while Settings
|
|
175
|
+
* wrote daemon.json on the machine -- two stores, the same word for both.
|
|
176
|
+
* Every MCP tool in the registry already carries its server in its wire
|
|
177
|
+
* name, so grouping them here shows exactly the servers whose tools a run
|
|
178
|
+
* can reach, and needs no second list to drift from. */
|
|
179
|
+
const mcpByServer = new Map();
|
|
180
|
+
for (const t of registry) {
|
|
181
|
+
if (resolveNamespace(t) !== 'mcp')
|
|
182
|
+
continue;
|
|
183
|
+
const parsed = parseMcpWireName(t.name);
|
|
184
|
+
const server = parsed?.server ?? 'mcp';
|
|
185
|
+
(mcpByServer.get(server) ?? mcpByServer.set(server, []).get(server))
|
|
186
|
+
.push({ id: t.name, name: parsed?.tool ?? t.name, description: t.description });
|
|
187
|
+
}
|
|
188
|
+
const builtin = NAMESPACE_LABELS
|
|
189
|
+
// `mcp` becomes one row PER SERVER below; `skill` is not a tool namespace.
|
|
190
|
+
.filter((c) => c.ns !== 'mcp' && c.ns !== 'skill')
|
|
191
|
+
.map((c) => ({
|
|
192
|
+
id: `ns:${c.ns}`,
|
|
193
|
+
label: c.label,
|
|
194
|
+
tools: registry
|
|
195
|
+
.filter((t) => resolveNamespace(t) === c.ns)
|
|
196
|
+
.map((t) => ({ id: t.name, name: t.name, description: t.description, status: toolStatus(activePolicy, t), badge: toolBadge?.(t.name) })),
|
|
197
|
+
}))
|
|
198
|
+
.filter((r) => r.tools.length > 0);
|
|
199
|
+
const labelFor = (id) => mcpServers.find((s2) => s2.id === id)?.label ?? id;
|
|
200
|
+
const connected = [...mcpByServer.entries()].map(([server, tools]) => ({
|
|
201
|
+
id: `mcp:${server}`,
|
|
202
|
+
label: labelFor(server),
|
|
203
|
+
sub: mcpServers.find((s2) => s2.id === server)?.url,
|
|
204
|
+
tools,
|
|
205
|
+
}));
|
|
206
|
+
const skillRows = skills.map((s2) => ({ id: `skill:${s2.name}`, label: s2.name, tools: [] }));
|
|
207
|
+
return [
|
|
208
|
+
{ group: 'Built-in', rows: builtin },
|
|
209
|
+
{ group: 'Connected', rows: connected },
|
|
210
|
+
{ group: 'Skills', rows: skillRows },
|
|
211
|
+
].filter((g) => g.rows.length > 0);
|
|
195
212
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
|
-
}, [
|
|
213
|
+
}, [toolsFn, activePolicy, mcpServers, skills, registryTick, toolBadge]);
|
|
214
|
+
/** How many of a source's tools this conversation still sends. */
|
|
215
|
+
const sentCount = (r) => r.tools.filter((t) => isSourceEnabled(sessionId, `tool:${t.id}`)).length;
|
|
216
|
+
const sourceCount = sources.reduce((n, g) => n + g.rows.length, 0);
|
|
217
|
+
/* LOADED IN THIS CHAT. MCP tools reach the model only once `tool_search`
|
|
218
|
+
* finds them or one is picked from "/", so a connected server no longer means
|
|
219
|
+
* its schemas ride every request -- and the old "over the 128-tool limit"
|
|
220
|
+
* warning has nothing left to warn about. What this conversation DOES carry
|
|
221
|
+
* is listed instead, each removable. Filtered to tools still enabled here,
|
|
222
|
+
* the same rule the run applies, so a switched-off server's tools do not
|
|
223
|
+
* linger in the list. */
|
|
224
|
+
const searchable = new Map(sessionSearchableTools(sessionId, toolsFn()).map((t) => [t.name, t]));
|
|
225
|
+
const loadedHere = loadedToolNames(sessionId).filter((n) => searchable.has(n));
|
|
197
226
|
if (!open) {
|
|
198
|
-
// The PolicyEditor modal is independent of the drawer's open state
|
|
199
|
-
//
|
|
200
|
-
// the drawer's conditional block) — it can stay open after the drawer closes.
|
|
227
|
+
// The PolicyEditor modal is independent of the drawer's open state — it can
|
|
228
|
+
// stay open after the drawer closes.
|
|
201
229
|
return _jsx(_Fragment, { children: policyEditorOpen && policyEditor?.({
|
|
202
230
|
initialName: policyName,
|
|
203
231
|
onClose: () => setPolicyEditorOpen(false),
|
|
@@ -205,101 +233,67 @@ export function OptionsDrawer(props) {
|
|
|
205
233
|
onPolicy(name); },
|
|
206
234
|
}) });
|
|
207
235
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
} } }), toolQuery && _jsx("button", { style: S.toolsClear, onClick: () => setToolQuery(''), "aria-label": "Clear filter", children: _jsx(IcX, { size: 12 }) })] }), connErr && _jsx("div", { style: S.connError, children: connErr }), toolGroups.map((g) => (_jsx(Section, { label: g.label, meta: g.items.length, children: g.items.map((t) => {
|
|
239
|
-
const st = toolStatus(activePolicy, t);
|
|
240
|
-
const badge = toolBadge?.(t.name);
|
|
241
|
-
return (_jsxs("div", { style: S.toolRow, title: t.description, children: [_jsxs("span", { style: S.toolMain, children: [_jsxs("span", { style: S.toolNm, children: [t.name, badge && _jsx("span", { style: S.toolSidecar, children: badge })] }), _jsx("span", { style: S.toolDesc, children: t.description })] }), _jsx("span", { style: st === 'deny' ? S.toolDenied : st === 'ask' ? S.toolAsks : S.toolAuto, children: st })] }, t.name));
|
|
242
|
-
}) }, g.key))), showWeb && (_jsx(Section, { label: "Web search key", meta: "Tavily", children: connAuthNeeded ? (_jsx("div", { style: S.connHint, children: "Sign in to manage your web-search key." })) : webKeyEdit != null ? (_jsxs("div", { style: S.connForm, children: [_jsx("input", { style: S.connInput, type: "password", placeholder: "Tavily API key (tvly-\u2026)", value: webKeyEdit, onChange: (e) => setWebKeyEdit(e.target.value), autoFocus: true }), _jsxs("div", { style: S.connFormRow, children: [_jsx("button", { style: S.connGhost, onClick: () => setWebKeyEdit(null), disabled: connBusy, children: "Cancel" }), _jsxs("button", { style: S.connSave, onClick: saveWebKey, disabled: connBusy, children: [_jsx(IcCheck, { size: 12 }), " Save key"] })] })] })) : (_jsxs("div", { style: S.connRow, children: [_jsxs("span", { style: S.connMain, children: [_jsxs("span", { style: S.connName, children: [_jsx(IcKey, { size: 12 }), " ", toolKeys.tavily?.hasKey ? 'Your key is set' : toolKeys.tavily?.paidFallback ? 'Using the Prompd-provided key' : 'No key set'] }), _jsx("span", { style: S.connSub, children: toolKeys.tavily?.hasKey ? 'web_search uses your Tavily key' : toolKeys.tavily?.paidFallback ? 'add your own key to use your own Tavily quota' : 'web_search is unavailable until a key is added' })] }), _jsx("button", { style: S.connBtn, onClick: () => setWebKeyEdit(''), disabled: connBusy, children: toolKeys.tavily?.hasKey ? 'Change' : 'Add key' }), toolKeys.tavily?.hasKey && _jsx("button", { style: S.connBtnDanger, onClick: clearWebKey, disabled: connBusy, title: "Remove your key", children: _jsx(IcTrash, { size: 12 }) })] })) })), showSkills && _jsx(SkillsSection, {}), showMcp && (_jsx(Section, { label: "MCP servers", meta: connAuthNeeded ? undefined : _jsxs("button", { style: S.catAdd, onClick: () => setServerForm({ label: '', url: '', apiKey: '' }), disabled: connBusy, children: [_jsx(IcPlus, { size: 12 }), " Add"] }), children: connAuthNeeded ? (_jsx("div", { style: S.connHint, children: "Sign in to add and manage MCP servers." })) : (_jsxs(_Fragment, { children: [serverForm && (_jsxs("div", { style: S.connForm, children: [_jsx("input", { style: S.connInput, placeholder: "Label (e.g. GitHub)", value: serverForm.label, onChange: (e) => setServerForm((f) => ({ ...f, label: e.target.value })), autoFocus: true }), _jsx("input", { style: S.connInput, placeholder: "https://server.example.com/mcp", value: serverForm.url, onChange: (e) => setServerForm((f) => ({ ...f, url: e.target.value })) }), _jsx("input", { style: S.connInput, type: "password", placeholder: "Bearer key (optional)", value: serverForm.apiKey, onChange: (e) => setServerForm((f) => ({ ...f, apiKey: e.target.value })) }), _jsxs("div", { style: S.connFormRow, children: [_jsx("button", { style: S.connGhost, onClick: () => setServerForm(null), disabled: connBusy, children: "Cancel" }), _jsxs("button", { style: S.connSave, onClick: addServer, disabled: connBusy, children: [_jsx(IcCheck, { size: 12 }), " Add server"] })] })] })), shownServers.length === 0 && !serverForm && _jsx("div", { style: S.connHint, children: "No MCP servers yet. Add one to give the agent remote tools." }), shownServers.map((s) => {
|
|
243
|
-
const stools = (mcpByServer.get(s.id) || []).filter((t) => matches(t.name || '', t.description || ''));
|
|
244
|
-
const err = mcpErrByServer.get(s.id);
|
|
245
|
-
return (_jsxs("div", { style: S.serverBlock, children: [_jsxs("div", { style: S.serverHead, children: [_jsxs("span", { style: S.connMain, children: [_jsxs("span", { style: S.connName, children: [s.label, s.hasKey && _jsx(IcKey, { size: 11 })] }), _jsx("span", { style: S.connSub, children: s.url })] }), _jsx("span", { style: S.serverCount, children: err ? 'error' : `${stools.length} tool${stools.length === 1 ? '' : 's'}` }), _jsx("button", { style: S.connBtnDanger, onClick: () => removeServer(s.id), disabled: connBusy, title: "Remove server", children: _jsx(IcTrash, { size: 12 }) })] }), err && _jsx("div", { style: S.serverErr, children: err }), stools.map((t, i) => (_jsxs("div", { style: S.toolRow, title: t.description, children: [_jsxs("span", { style: S.toolMain, children: [_jsx("span", { style: S.toolNm, children: t.name }), t.description && _jsx("span", { style: S.toolDesc, children: t.description })] }), _jsx("span", { style: S.toolAuto, children: "mcp" })] }, `${s.id}-${t.name || i}`)))] }, s.id));
|
|
246
|
-
})] })) })), nothing && _jsxs("div", { style: S.toolsEmpty, children: ["No matches for \u201C", toolQuery.trim(), "\u201D."] })] }), _jsxs("div", { style: S.toolsNote, children: ["Status reflects the \u201C", policyName, "\u201D policy: \u201Callow\u201D runs, \u201Cask\u201D needs approval, \u201Cdeny\u201D is blocked."] })] }))] }), policyEditorOpen && policyEditor?.({
|
|
236
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { style: S.drawerBackdrop, onClick: onClose }), _jsxs("aside", { style: S.drawer, role: "dialog", "aria-label": `${brandName} options`, children: [_jsxs("div", { style: S.drawerHead, children: [_jsx("span", { style: S.drawerTitle, children: "This chat" }), _jsx("button", { style: S.iconBtn, onClick: onClose, "aria-label": "Close options", children: _jsx(IcX, { size: 16 }) })] }), _jsxs("div", { style: S.settingsScroll, children: [!hideIdentity && (_jsxs(Section, { label: "Behavior", children: [_jsx(Row, { label: "Persona", children: _jsx(MenuBtn, { value: personaLabel, open: personaMenuOpen, onClick: () => { setPersonaMenuOpen((o) => !o); setPolicyMenuOpen(false); } }) }), personaMenuOpen && (_jsxs("div", { style: S.ddPanel, children: [personas.map((p) => (_jsxs("div", { style: S.personaRow, children: [_jsxs("button", { style: personaPickStyle(p.id === personaName), onClick: () => pickPersona(p.id), children: [_jsxs("span", { style: S.personaPickName, children: [p.name, p.id === personaName && _jsx(IcCheck, { size: 11 })] }), p.description && _jsx("span", { style: S.personaDesc, children: p.description })] }), _jsx("button", { style: S.menuDel, onClick: () => removePersona(p.id), title: "Delete persona", children: _jsx(IcTrash, { size: 12 }) })] }, p.id))), newPersona ? (_jsxs("div", { style: S.personaForm, children: [_jsx("input", { style: S.personaInput, placeholder: "persona name", value: newPersona.name, onChange: (e) => setNewPersona((n) => ({ ...n, name: e.target.value })) }), _jsx("textarea", { style: S.personaTextarea, placeholder: "System prompt \u2014 the voice/role\u2026", value: newPersona.system, onChange: (e) => setNewPersona((n) => ({ ...n, system: e.target.value })) }), _jsxs("div", { style: S.personaFormActions, children: [_jsx("button", { style: S.personaGhost, onClick: () => setNewPersona(null), children: "Cancel" }), _jsxs("button", { style: S.personaSave, onClick: addPersona, children: [_jsx(IcCheck, { size: 12 }), " Save"] })] })] })) : (_jsxs("button", { style: S.menuItem, onClick: () => setNewPersona({ name: '', system: '' }), children: [_jsx(IcPlus, { size: 13 }), " New persona"] }))] })), _jsx(Row, { label: "Policy", children: _jsx(MenuBtn, { value: policyName, open: policyMenuOpen, onClick: () => { setPolicyMenuOpen((o) => !o); setPersonaMenuOpen(false); }, title: policyBlurb(activePolicy) }) }), policyMenuOpen && (_jsxs("div", { style: S.ddPanel, children: [listPolicies().map((e) => (_jsxs("button", { style: policyOptStyle(e.name === policyName), onClick: () => selectPolicy(e.name), children: [_jsxs("span", { style: S.personaPickName, children: [e.name, !e.builtin && _jsx("span", { style: S.policyTag, children: "custom" }), e.name === policyName && _jsx(IcCheck, { size: 11 })] }), _jsx("span", { style: S.personaDesc, children: policyBlurb(e.policy) })] }, e.name))), _jsxs("button", { style: S.policyEditBtn, onClick: () => { setPolicyMenuOpen(false); setPolicyEditorOpen(true); }, children: [_jsx(IcEdit, { size: 12 }), " Edit policies\u2026"] })] }))] })), budgetSection && (_jsx(Section, { label: "Budget", meta: "this chat", children: budgetSection })), connErr && _jsx("div", { style: S.connError, children: connErr }), loadedHere.length > 0 && (_jsx(Section, { label: "Loaded in this chat", meta: loadedHere.length, children: loadedHere.map((n) => {
|
|
237
|
+
const parsed = parseMcpWireName(n);
|
|
238
|
+
return (_jsxs("div", { style: S.loadedRow, "data-loaded-tool": n, children: [_jsx("span", { style: S.toolNm, title: searchable.get(n)?.description, children: parsed ? `${parsed.server} · ${parsed.tool}` : n }), _jsx("button", { style: S.iconBtn, onClick: () => removeLoadedTool(sessionId, n), "aria-label": `Stop sending ${n}`, title: "Stop sending this tool (it stays searchable)", children: _jsx(IcX, { size: 13 }) })] }, n));
|
|
239
|
+
}) })), sources.map((g) => (_jsx(Section, { label: g.group === 'Built-in' ? `Sources · ${g.group}` : g.group, meta: g.group === 'Built-in' ? sourceCount : undefined, children: g.rows.map((r) => {
|
|
240
|
+
const on = isSourceEnabled(sessionId, r.id);
|
|
241
|
+
const isOpen = expanded === r.id;
|
|
242
|
+
return (_jsxs("div", { "data-source-row": r.id, children: [_jsxs("div", { style: S.sourceRow, children: [_jsxs("button", { style: S.sourceMain, onClick: () => setExpanded(isOpen ? null : r.id), "aria-expanded": isOpen, title: r.sub, children: [_jsx("span", { style: { ...S.sourceChevron, transform: isOpen ? 'rotate(90deg)' : 'none' }, children: _jsx(IcChevronRight, { size: 12 }) }), _jsxs("span", { style: S.sourceText, children: [_jsx("span", { style: on ? S.sourceName : S.sourceNameOff, children: r.label }), _jsx("span", { style: S.sourceSub, children: r.error
|
|
243
|
+
? 'error'
|
|
244
|
+
: !r.tools.length
|
|
245
|
+
? 'prompt only'
|
|
246
|
+
/* "N of M" only once they differ. Saying "2 tools"
|
|
247
|
+
* while sending one is the untraceable kind of
|
|
248
|
+
* copy this codebase keeps finding. */
|
|
249
|
+
: sentCount(r) === r.tools.length
|
|
250
|
+
? `${r.tools.length} tool${r.tools.length === 1 ? '' : 's'}`
|
|
251
|
+
: `${sentCount(r)} of ${r.tools.length} tools` })] })] }), _jsx(Toggle, { ariaLabel: `${r.label} in this chat`, checked: on, onChange: (v) => setSourceEnabled(sessionId, r.id, v) })] }), r.error && isOpen && _jsx("div", { style: S.serverErr, children: r.error }), isOpen && r.tools.map((t) => {
|
|
252
|
+
/* A tool is only sent when BOTH its source and it are on.
|
|
253
|
+
* Rendering the conjunction rather than its own flag is
|
|
254
|
+
* what keeps the switch honest while the source is off:
|
|
255
|
+
* a control reading "on" for something being withheld
|
|
256
|
+
* would be a claim the run does not honour. */
|
|
257
|
+
const toolOn = on && isSourceEnabled(sessionId, `tool:${t.id}`);
|
|
258
|
+
return (_jsxs("div", { style: S.toolRow, title: t.description, children: [_jsxs("span", { style: S.toolMain, children: [_jsxs("span", { style: toolOn ? S.toolNm : S.toolNmOff, children: [t.name, t.badge && _jsx("span", { style: S.toolSidecar, children: t.badge })] }), t.description && _jsx("span", { style: S.toolDesc, children: t.description })] }), t.status && (_jsx("span", { style: !toolOn ? S.toolDenied : t.status === 'deny' ? S.toolDenied : t.status === 'ask' ? S.toolAsks : S.toolAuto, children: toolOn ? t.status : 'off' })), _jsx(Toggle, { ariaLabel: `${t.name} in this chat`, checked: toolOn,
|
|
259
|
+
/* Disabled with the source off: the only honest way
|
|
260
|
+
* to turn this one back on is to turn the source on,
|
|
261
|
+
* and a switch that appeared to work while changing
|
|
262
|
+
* nothing visible is worse than one that cannot. */
|
|
263
|
+
disabled: !on, onChange: (v) => setSourceEnabled(sessionId, `tool:${t.id}`, v) })] }, t.id));
|
|
264
|
+
})] }, r.id));
|
|
265
|
+
}) }, g.group))), settingsExtras] }, capTick), _jsx("div", { style: S.toolsNote, children: "Switches apply to this conversation only. Manage sources in Settings \u2192 Tools." })] }), policyEditorOpen && policyEditor?.({
|
|
247
266
|
initialName: policyName,
|
|
248
267
|
onClose: () => setPolicyEditorOpen(false),
|
|
249
268
|
onSaved: (name) => { setPolicyListVersion((v) => v + 1); if (name)
|
|
250
269
|
onPolicy(name); },
|
|
251
270
|
})] }));
|
|
252
271
|
}
|
|
253
|
-
// Style keyed by an "active"/state flag — kept out of S (a flat style map)
|
|
254
|
-
// typed as their own helpers.
|
|
255
|
-
const drawerTabStyle = (active) => ({ display: 'inline-flex', alignItems: 'center', gap: 6, height: 30, padding: '0 11px', border: 0, borderRadius: 'var(--radius-sm)', background: active ? 'var(--accent-soft)' : 'transparent', color: active ? 'var(--accent)' : 'var(--text-secondary)', fontSize: 12.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer' });
|
|
272
|
+
// Style keyed by an "active"/state flag — kept out of S (a flat style map).
|
|
256
273
|
const policyOptStyle = (active) => ({ display: 'flex', flexDirection: 'column', gap: 1, textAlign: 'left', padding: '7px 9px', border: 0, borderRadius: 'var(--radius-sm)', background: active ? 'var(--accent-soft)' : 'transparent', color: active ? 'var(--accent)' : 'var(--text-primary)', cursor: 'pointer', fontFamily: 'inherit' });
|
|
257
274
|
const personaPickStyle = (active) => ({ flex: 1, display: 'flex', flexDirection: 'column', gap: 1, textAlign: 'left', padding: '7px 9px', border: 0, borderRadius: 'var(--radius-sm)', background: active ? 'var(--accent-soft)' : 'transparent', color: active ? 'var(--accent)' : 'var(--text-primary)', cursor: 'pointer', fontFamily: 'inherit' });
|
|
258
275
|
const S = {
|
|
259
276
|
iconBtn: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 30, height: 30, border: 0, borderRadius: 'var(--radius-sm)', background: 'transparent', color: 'var(--text-tertiary)', cursor: 'pointer' },
|
|
260
|
-
// Options drawer — scoped to the panel (absolute, not viewport-fixed) so it
|
|
261
|
-
// slides over the chat, not the whole app.
|
|
262
277
|
drawerBackdrop: { position: 'absolute', inset: 0, background: 'color-mix(in srgb, var(--surface-0, #000) 40%, transparent)', zIndex: 40 },
|
|
263
278
|
drawer: { position: 'absolute', top: 0, right: 0, bottom: 0, width: 340, maxWidth: '92%', display: 'flex', flexDirection: 'column', background: 'var(--surface-1)', borderLeft: '1px solid var(--border-default)', boxShadow: 'var(--shadow-4)', zIndex: 41 },
|
|
264
279
|
drawerHead: { display: 'flex', alignItems: 'center', height: 48, padding: '0 10px 0 14px', borderBottom: '1px solid var(--border-subtle)', flexShrink: 0 },
|
|
265
280
|
drawerTitle: { flex: 1, fontSize: 14, fontWeight: 600, color: 'var(--text-primary)' },
|
|
266
|
-
// Drawer section tabs — a clean segmented row, monochrome with an accent-soft
|
|
267
|
-
// pill on the active tab (gradients stay reserved for the brand mark).
|
|
268
|
-
drawerTabs: { display: 'flex', gap: 4, padding: '6px 8px', borderBottom: '1px solid var(--border-subtle)', flexShrink: 0 },
|
|
269
|
-
drawerTabCount: { fontSize: 10.5, fontFamily: 'var(--font-mono)', fontVariantNumeric: 'tabular-nums', color: 'currentColor', opacity: 0.7 },
|
|
270
|
-
// Right-aligned action button used inside a drawerRow (icon + short label).
|
|
271
|
-
rowBtn: { display: 'inline-flex', alignItems: 'center', gap: 6, height: 28, padding: '0 11px', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-sm)', background: 'var(--surface-2)', color: 'var(--text-secondary)', fontSize: 12.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', flexShrink: 0 },
|
|
272
281
|
ddPanel: { margin: '0 9px 6px', padding: 4, border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', background: 'var(--surface-2)', display: 'flex', flexDirection: 'column', gap: 2 },
|
|
273
282
|
policyTag: { marginLeft: 6, padding: '0 5px', borderRadius: 'var(--radius-pill, 999px)', background: 'var(--surface-3)', color: 'var(--text-tertiary)', fontSize: 9.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.4, verticalAlign: 'middle' },
|
|
274
283
|
policyEditBtn: { display: 'inline-flex', alignItems: 'center', gap: 6, marginTop: 2, padding: '7px 9px', border: 0, borderTop: '1px solid var(--border-subtle)', borderRadius: 0, background: 'transparent', color: 'var(--accent)', fontSize: 12, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', textAlign: 'left' },
|
|
275
284
|
menuItem: { display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 9px', border: 0, borderRadius: 'var(--radius-sm)', background: 'transparent', color: 'var(--text-primary)', cursor: 'pointer', fontSize: 13, fontWeight: 500 },
|
|
276
285
|
menuDel: { display: 'inline-flex', width: 26, height: 26, alignItems: 'center', justifyContent: 'center', border: 0, borderRadius: 'var(--radius-sm)', background: 'transparent', color: 'var(--text-disabled)', cursor: 'pointer' },
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
settingsScroll: { flex: 1, minHeight: 0, overflowY: 'auto', padding: '4px 12px 12px', display: 'flex', flexDirection: 'column' },
|
|
287
|
+
// A source row: a wide click target that expands, plus the per-chat switch.
|
|
288
|
+
sourceRow: { display: 'flex', alignItems: 'center', gap: 8, minHeight: COARSE_POINTER ? 44 : 32, padding: '0 2px' },
|
|
289
|
+
sourceMain: { flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 7, minHeight: COARSE_POINTER ? 44 : 28, padding: '4px 2px', border: 0, background: 'transparent', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit' },
|
|
290
|
+
sourceChevron: { display: 'inline-flex', alignItems: 'center', color: 'var(--text-disabled)', transition: 'transform 120ms ease', flexShrink: 0 },
|
|
291
|
+
sourceText: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 },
|
|
292
|
+
sourceName: { fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
293
|
+
sourceNameOff: { fontSize: 13, fontWeight: 600, color: 'var(--text-disabled)', textDecoration: 'line-through', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
294
|
+
sourceSub: { fontSize: 10.5, color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)', fontVariantNumeric: 'tabular-nums' },
|
|
295
|
+
loadedRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, padding: '2px 3px 2px 9px', minWidth: 0 },
|
|
287
296
|
connError: { margin: '4px 3px 6px', padding: '7px 9px', borderRadius: 'var(--radius-sm)', background: 'var(--danger-soft)', color: 'var(--danger)', fontSize: 12, lineHeight: 1.4 },
|
|
288
|
-
connHint: { padding: '4px 9px 8px', fontSize: 12, lineHeight: 1.45, color: 'var(--text-tertiary)' },
|
|
289
|
-
connRow: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 9px 6px' },
|
|
290
|
-
connMain: { display: 'flex', flexDirection: 'column', gap: 1, flex: 1, minWidth: 0 },
|
|
291
|
-
connName: { display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' },
|
|
292
|
-
connSub: { fontSize: 11, color: 'var(--text-tertiary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
293
|
-
connBtn: { flexShrink: 0, height: 26, padding: '0 10px', border: '1px solid var(--border-default)', borderRadius: 'var(--radius-sm)', background: 'var(--surface-2)', color: 'var(--text-secondary)', fontSize: 12, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer' },
|
|
294
|
-
connBtnDanger: { flexShrink: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 26, height: 26, border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-sm)', background: 'transparent', color: 'var(--text-tertiary)', cursor: 'pointer', padding: 0 },
|
|
295
|
-
connForm: { display: 'flex', flexDirection: 'column', gap: 6, padding: '2px 9px 8px' },
|
|
296
|
-
connInput: { height: 'var(--control-h)', padding: '0 var(--space-3)', border: '1px solid var(--border-default)', borderRadius: 'var(--radius-sm)', background: 'var(--surface-2)', color: 'var(--text-primary)', fontSize: 'var(--fs-body-sm)', fontFamily: 'inherit', outline: 'none' },
|
|
297
|
-
connFormRow: { display: 'flex', justifyContent: 'flex-end', gap: 6 },
|
|
298
|
-
connGhost: { height: 28, padding: '0 10px', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-sm)', background: 'transparent', color: 'var(--text-tertiary)', fontSize: 12, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer' },
|
|
299
|
-
connSave: { display: 'inline-flex', alignItems: 'center', gap: 5, height: 28, padding: '0 12px', border: 0, borderRadius: 'var(--radius-sm)', background: 'var(--grad-brand)', color: 'var(--text-on-accent, #fff)', fontSize: 12, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer' },
|
|
300
|
-
serverBlock: { border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', padding: 6, margin: '0 3px 6px' },
|
|
301
|
-
serverHead: { display: 'flex', alignItems: 'center', gap: 8, padding: '2px 4px 4px' },
|
|
302
|
-
serverCount: { flexShrink: 0, fontSize: 10.5, color: 'var(--text-disabled)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em' },
|
|
303
297
|
serverErr: { padding: '2px 6px 6px', fontSize: 11.5, lineHeight: 1.4, color: 'var(--danger)' },
|
|
304
298
|
personaRow: { display: 'flex', alignItems: 'stretch', gap: 4 },
|
|
305
299
|
personaPickName: { display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13, fontWeight: 600, fontFamily: 'var(--font-mono)' },
|
|
@@ -310,12 +304,15 @@ const S = {
|
|
|
310
304
|
personaFormActions: { display: 'flex', justifyContent: 'flex-end', gap: 6 },
|
|
311
305
|
personaGhost: { height: 28, padding: '0 12px', border: '1px solid var(--border-default)', borderRadius: 'var(--radius-sm)', background: 'var(--surface-1)', color: 'var(--text-secondary)', fontSize: 12.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer' },
|
|
312
306
|
personaSave: { display: 'inline-flex', alignItems: 'center', gap: 5, height: 28, padding: '0 12px', border: 0, borderRadius: 'var(--radius-sm)', background: 'var(--grad-brand)', color: 'var(--text-on-accent, #fff)', fontSize: 12.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer' },
|
|
313
|
-
toolRow: { display: 'flex', alignItems: 'center', gap: 8, minHeight:
|
|
314
|
-
|
|
307
|
+
toolRow: { display: 'flex', alignItems: 'center', gap: 8, minHeight: 26, padding: '0 9px 0 21px', borderRadius: 'var(--radius-sm)', color: 'var(--text-secondary)' },
|
|
308
|
+
toolMain: { display: 'flex', flexDirection: 'column', gap: 1, flex: 1, minWidth: 0 },
|
|
309
|
+
toolNm: { fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
310
|
+
toolNmOff: { fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--text-disabled)', textDecoration: 'line-through', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
311
|
+
toolDesc: { fontSize: 11, lineHeight: 1.35, color: 'var(--text-tertiary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
315
312
|
toolSidecar: { marginLeft: 6, padding: '0 5px', borderRadius: 'var(--radius-sm)', background: 'var(--accent-soft)', color: 'var(--accent)', fontFamily: 'var(--font-sans)', fontSize: 9.5, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', verticalAlign: 'middle' },
|
|
316
313
|
toolAuto: { marginLeft: 'auto', flexShrink: 0, alignSelf: 'center', fontSize: 10.5, color: 'var(--text-disabled)', textTransform: 'uppercase', letterSpacing: '0.06em' },
|
|
317
314
|
toolAsks: { marginLeft: 'auto', flexShrink: 0, alignSelf: 'center', fontSize: 10.5, color: 'var(--warning)', textTransform: 'uppercase', letterSpacing: '0.06em' },
|
|
318
315
|
toolDenied: { marginLeft: 'auto', flexShrink: 0, alignSelf: 'center', fontSize: 10.5, color: 'var(--danger)', textTransform: 'uppercase', letterSpacing: '0.06em' },
|
|
319
|
-
toolsNote: { padding: '8px
|
|
316
|
+
toolsNote: { padding: '8px 12px', fontSize: 11.5, lineHeight: 1.45, color: 'var(--text-tertiary)', borderTop: '1px solid var(--border-subtle)', flexShrink: 0, background: 'var(--surface-1)' },
|
|
320
317
|
};
|
|
321
318
|
//# sourceMappingURL=OptionsDrawer.js.map
|