@hydraharness/harness-client-ui-agent-preset 0.1.1-rc.6
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/LICENSE +21 -0
- package/README.md +73 -0
- package/lib/client.js +1693 -0
- package/lib/index.js +11 -0
- package/lib/invariant.js +23 -0
- package/lib/types/client/AgentPresetLabel.d.ts +30 -0
- package/lib/types/client/AgentPresetRow.d.ts +35 -0
- package/lib/types/client/AgentPresetSeat.d.ts +38 -0
- package/lib/types/client/AgentPresetSection.d.ts +61 -0
- package/lib/types/client/PresetMenu.d.ts +42 -0
- package/lib/types/client/index.d.ts +29 -0
- package/lib/types/client/locales.d.ts +31 -0
- package/lib/types/client/seat-store.d.ts +105 -0
- package/lib/types/client/section-store.d.ts +191 -0
- package/lib/types/client/settings-store.d.ts +165 -0
- package/lib/types/index.d.ts +9 -0
- package/lib/types/invariant.d.ts +16 -0
- package/package.json +84 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1693 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@hydraharness/harness-client-ui-agent-preset",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let _hydraharness_harness_client_ui_primitives = require("@hydraharness/harness-client-ui-primitives");
|
|
10
|
+
let _hydraharness_harness_client_runtime_client = require("@hydraharness/harness-client-runtime/client");
|
|
11
|
+
//#region lib/types/client/locales.js
|
|
12
|
+
/** Locale bundles for the agent-preset settings row, hero chip, header label, and management section. */
|
|
13
|
+
/** English copy. */
|
|
14
|
+
const en = {
|
|
15
|
+
title: "Agent preset",
|
|
16
|
+
description: "Applies to sessions you start from now on. Running sessions keep the preset they began with.",
|
|
17
|
+
loading: "Loading presets…",
|
|
18
|
+
error: "Could not load agent presets.",
|
|
19
|
+
userTrust: "Custom",
|
|
20
|
+
seatHint: "Agent preset for the session you are about to start",
|
|
21
|
+
headerHint: "The agent preset this session runs, fixed when it started",
|
|
22
|
+
nav: "Agent presets",
|
|
23
|
+
sectionIntro: "A preset is the plugin composition one session's agent runs — its tools, prompt, and capabilities. Duplicate an existing one and make it yours, or let the agent draft one for you in Creator mode.",
|
|
24
|
+
builtIn: "Built-in",
|
|
25
|
+
setDefault: "Set as default",
|
|
26
|
+
view: "View",
|
|
27
|
+
presetStandardName: "Standard mode",
|
|
28
|
+
presetStandardDescription: "Full coding agent with file editing, shell, file and web search, skills, planning, goals, subagents, and workflows.",
|
|
29
|
+
presetCodeName: "PTC mode",
|
|
30
|
+
presetCodeDescription: "All Standard mode capabilities, with tools exposed through the Code Mode SDK so the model can combine multi-step operations in one TypeScript program.",
|
|
31
|
+
presetMinimalName: "Minimal mode",
|
|
32
|
+
presetMinimalDescription: "Two-tool coding agent with persistent bash and str_replace_editor.",
|
|
33
|
+
presetCordisName: "Creator mode",
|
|
34
|
+
presetCordisDescription: "Built for creating custom agent presets, with all Standard mode capabilities plus runtime inspection, plugin experiments, and preset-authoring guidance.",
|
|
35
|
+
duplicate: "Duplicate",
|
|
36
|
+
duplicateUnavailable: "This deployment has no writable preset directory",
|
|
37
|
+
delete: "Delete",
|
|
38
|
+
presetId: "Identifier",
|
|
39
|
+
presetIdPlaceholder: "my-agent",
|
|
40
|
+
displayName: "Name",
|
|
41
|
+
displayNamePlaceholder: "Shown in the picker; defaults to the identifier",
|
|
42
|
+
inUse: "Default",
|
|
43
|
+
builtInGroup: "Built-in",
|
|
44
|
+
customGroup: "Custom",
|
|
45
|
+
noDescription: "No description.",
|
|
46
|
+
brokenBadge: "Failed to load",
|
|
47
|
+
brokenNoCopy: "A preset that failed to load cannot be duplicated",
|
|
48
|
+
copyOf: "Copied from",
|
|
49
|
+
composition: "Composition (agent.cordis.yml)",
|
|
50
|
+
cancel: "Cancel",
|
|
51
|
+
close: "Close",
|
|
52
|
+
retry: "Retry",
|
|
53
|
+
copyTitle: "Duplicate preset",
|
|
54
|
+
copyIntro: "The whole preset is copied on this machine. The identifier becomes its directory name and cannot be changed later; everything else is edited in the preset's own files.",
|
|
55
|
+
create: "Create",
|
|
56
|
+
creating: "Creating…",
|
|
57
|
+
creatorDraft: "Draft a custom preset with Creator mode",
|
|
58
|
+
openLocation: "Open folder",
|
|
59
|
+
showLocation: "Show location",
|
|
60
|
+
revealedPathLabel: "Preset files:",
|
|
61
|
+
idRequired: "Give the preset an identifier.",
|
|
62
|
+
idInvalid: "Use lowercase letters, digits, and hyphens, starting with a letter or digit.",
|
|
63
|
+
idTaken: "A preset with this identifier already exists.",
|
|
64
|
+
deleteTitle: "Delete this preset?",
|
|
65
|
+
deleteDescription: "The preset directory is deleted. Sessions already running on it keep working; new sessions cannot select it.",
|
|
66
|
+
deleteConfirm: "Delete",
|
|
67
|
+
deleting: "Deleting…"
|
|
68
|
+
};
|
|
69
|
+
const BUILT_IN_PRESET_KEYS = {
|
|
70
|
+
standard: {
|
|
71
|
+
name: "presetStandardName",
|
|
72
|
+
description: "presetStandardDescription"
|
|
73
|
+
},
|
|
74
|
+
code: {
|
|
75
|
+
name: "presetCodeName",
|
|
76
|
+
description: "presetCodeDescription"
|
|
77
|
+
},
|
|
78
|
+
minimal: {
|
|
79
|
+
name: "presetMinimalName",
|
|
80
|
+
description: "presetMinimalDescription"
|
|
81
|
+
},
|
|
82
|
+
cordis: {
|
|
83
|
+
name: "presetCordisName",
|
|
84
|
+
description: "presetCordisDescription"
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Resolve preset display copy without making user-authored metadata translatable.
|
|
89
|
+
* @param preset - roster row whose copy is being rendered.
|
|
90
|
+
* @param t - active Web locale lookup.
|
|
91
|
+
* @returns localized copy for a known shipped preset, otherwise file metadata.
|
|
92
|
+
*/
|
|
93
|
+
function presetDisplayText(preset, t) {
|
|
94
|
+
const keys = preset.trust === "system" ? BUILT_IN_PRESET_KEYS[preset.id] : void 0;
|
|
95
|
+
if (keys !== void 0) return {
|
|
96
|
+
name: t(keys.name),
|
|
97
|
+
description: t(keys.description)
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
name: preset.name ?? preset.id,
|
|
101
|
+
...preset.description === void 0 ? {} : { description: preset.description }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-agent-preset/src/client/AgentPresetLabel.module.css.mjs
|
|
106
|
+
const css$3 = "._3uTPLa_label{background:var(--dsw-alias-fill-tsp-secondary);max-width:180px;height:22px;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;border-radius:6px;align-items:center;gap:4px;padding:0 2px 0 0;font-size:12px;line-height:22px;display:inline-flex;overflow:hidden}._3uTPLa_icon{opacity:.7;flex:none}";
|
|
107
|
+
const tagId$3 = "@hydraharness/harness-client-ui-agent-preset/AgentPresetLabel.module.css";
|
|
108
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
109
|
+
const tag = document.createElement("style");
|
|
110
|
+
tag.dataset.plugin = "@hydraharness/harness-client-ui-agent-preset";
|
|
111
|
+
tag.dataset.pluginCss = tagId$3;
|
|
112
|
+
tag.textContent = css$3;
|
|
113
|
+
document.head.appendChild(tag);
|
|
114
|
+
}
|
|
115
|
+
var AgentPresetLabel_module_css_default = {
|
|
116
|
+
"icon": "_3uTPLa_icon",
|
|
117
|
+
"label": "_3uTPLa_label"
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region lib/types/client/AgentPresetLabel.js
|
|
121
|
+
/**
|
|
122
|
+
* The session header's agent-preset label.
|
|
123
|
+
*
|
|
124
|
+
* Read-only by construction: a session's composition is fixed once its
|
|
125
|
+
* conversation starts, and a header is only worth reading after that. Offering
|
|
126
|
+
* a control here would promise a switch the host refuses; naming what the
|
|
127
|
+
* session runs is the honest affordance, and the choice itself lives on the
|
|
128
|
+
* new-session screen ({@link AgentPresetSeat}).
|
|
129
|
+
*/
|
|
130
|
+
/**
|
|
131
|
+
* Render this session's agent-preset name beside its title.
|
|
132
|
+
* @param props - composed slot props.
|
|
133
|
+
* @returns the label, or null when the session records no preset.
|
|
134
|
+
*/
|
|
135
|
+
function AgentPresetLabel({ sessionId, useSessions, useAgentPresets, load, t }) {
|
|
136
|
+
const preset = useSessions((state) => state.byId[sessionId]?.agentPreset);
|
|
137
|
+
const options = useAgentPresets((state) => state.options);
|
|
138
|
+
(0, react.useEffect)(() => {
|
|
139
|
+
if (preset !== void 0) load();
|
|
140
|
+
}, [preset, load]);
|
|
141
|
+
if (preset === void 0) return null;
|
|
142
|
+
const option = options.find((entry) => entry.id === preset);
|
|
143
|
+
const text = option === void 0 ? void 0 : presetDisplayText(option, t);
|
|
144
|
+
return (0, react_jsx_runtime.jsxs)("span", {
|
|
145
|
+
className: AgentPresetLabel_module_css_default.label,
|
|
146
|
+
title: text?.description ?? t("headerHint"),
|
|
147
|
+
children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconAgentPresetOutline16, {
|
|
148
|
+
size: 14,
|
|
149
|
+
className: AgentPresetLabel_module_css_default.icon
|
|
150
|
+
}), text?.name ?? preset]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region lib/types/client/PresetMenu.js
|
|
155
|
+
/**
|
|
156
|
+
* The preset picker both surfaces render: a menu of presets over a button
|
|
157
|
+
* naming the current one.
|
|
158
|
+
*
|
|
159
|
+
* The settings row and the composer seat differ in where they sit, what they
|
|
160
|
+
* call the current value, and when they refuse a pick — not in how the picker
|
|
161
|
+
* itself behaves. Trust is the one thing the list always says: a locally
|
|
162
|
+
* authored preset is exactly as privileged as the plugins it names, so the
|
|
163
|
+
* label marks it rather than presenting every preset as shipped and vetted.
|
|
164
|
+
*/
|
|
165
|
+
/**
|
|
166
|
+
* Render the preset picker.
|
|
167
|
+
* @param props - the calling surface's copy, styling, and handlers.
|
|
168
|
+
* @returns the menu and its trigger.
|
|
169
|
+
*/
|
|
170
|
+
function PresetMenu({ options, selectedId, label, t, buttonClassName, chevronClassName, disabled, open, onOpenChange, onSelect }) {
|
|
171
|
+
return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Menu, {
|
|
172
|
+
open,
|
|
173
|
+
onClose: () => {
|
|
174
|
+
onOpenChange(false);
|
|
175
|
+
},
|
|
176
|
+
items: options.map((option) => {
|
|
177
|
+
const name = presetDisplayText(option, t).name;
|
|
178
|
+
return {
|
|
179
|
+
id: option.id,
|
|
180
|
+
label: option.trust === "user" ? `${name} · ${t("userTrust")}` : name
|
|
181
|
+
};
|
|
182
|
+
}),
|
|
183
|
+
selectedId,
|
|
184
|
+
onSelect: (id) => {
|
|
185
|
+
onOpenChange(false);
|
|
186
|
+
onSelect(id);
|
|
187
|
+
},
|
|
188
|
+
align: "end",
|
|
189
|
+
portal: true,
|
|
190
|
+
anchor: (0, react_jsx_runtime.jsxs)("button", {
|
|
191
|
+
"data-hydra-control": "field",
|
|
192
|
+
type: "button",
|
|
193
|
+
className: buttonClassName,
|
|
194
|
+
"aria-haspopup": "menu",
|
|
195
|
+
"aria-expanded": open,
|
|
196
|
+
disabled,
|
|
197
|
+
onClick: () => {
|
|
198
|
+
onOpenChange(!open);
|
|
199
|
+
},
|
|
200
|
+
children: [label, (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconChevronDownOutline14, { className: chevronClassName })]
|
|
201
|
+
})
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-agent-preset/src/client/AgentPresetRow.module.css.mjs
|
|
206
|
+
const css$2 = ".TAlhPa_row{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.TAlhPa_rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.TAlhPa_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.TAlhPa_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.TAlhPa_selector{align-items:center;display:inline-flex}.TAlhPa_chevron{flex:none}";
|
|
207
|
+
const tagId$2 = "@hydraharness/harness-client-ui-agent-preset/AgentPresetRow.module.css";
|
|
208
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
209
|
+
const tag = document.createElement("style");
|
|
210
|
+
tag.dataset.plugin = "@hydraharness/harness-client-ui-agent-preset";
|
|
211
|
+
tag.dataset.pluginCss = tagId$2;
|
|
212
|
+
tag.textContent = css$2;
|
|
213
|
+
document.head.appendChild(tag);
|
|
214
|
+
}
|
|
215
|
+
var AgentPresetRow_module_css_default = {
|
|
216
|
+
"chevron": "TAlhPa_chevron",
|
|
217
|
+
"desc": "TAlhPa_desc",
|
|
218
|
+
"row": "TAlhPa_row",
|
|
219
|
+
"rowText": "TAlhPa_rowText",
|
|
220
|
+
"selector": "TAlhPa_selector",
|
|
221
|
+
"title": "TAlhPa_title"
|
|
222
|
+
};
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region lib/types/client/AgentPresetRow.js
|
|
225
|
+
/**
|
|
226
|
+
* Agent-preset preference row: the preset new sessions are composed from.
|
|
227
|
+
* A running session keeps the composition it began with, so this row never
|
|
228
|
+
* disturbs work in progress.
|
|
229
|
+
*/
|
|
230
|
+
/**
|
|
231
|
+
* Render the new-session agent-preset selector.
|
|
232
|
+
* @param props - composed slot props.
|
|
233
|
+
* @returns the row, or null when the deployment composes no presets.
|
|
234
|
+
*/
|
|
235
|
+
function AgentPresetRow({ load, select, useAgentPreset, t }) {
|
|
236
|
+
const state = useAgentPreset((snapshot) => snapshot);
|
|
237
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
238
|
+
(0, react.useEffect)(() => {
|
|
239
|
+
load();
|
|
240
|
+
}, [load]);
|
|
241
|
+
(0, react.useEffect)(() => {
|
|
242
|
+
if (state.writable && state.status !== "unavailable") return;
|
|
243
|
+
setOpen(false);
|
|
244
|
+
}, [state.status, state.writable]);
|
|
245
|
+
if (state.status === "unavailable") return null;
|
|
246
|
+
const busy = state.status === "loading" || state.status === "saving";
|
|
247
|
+
const chosen = state.options.find((option) => option.id === state.currentValue);
|
|
248
|
+
const chosenText = chosen === void 0 ? void 0 : presetDisplayText(chosen, t);
|
|
249
|
+
const label = state.currentValue === "" ? t("loading") : chosenText?.name ?? state.currentValue;
|
|
250
|
+
const description = state.error ?? t("description");
|
|
251
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
252
|
+
className: AgentPresetRow_module_css_default.row,
|
|
253
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
254
|
+
className: AgentPresetRow_module_css_default.rowText,
|
|
255
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
256
|
+
className: AgentPresetRow_module_css_default.title,
|
|
257
|
+
children: t("title")
|
|
258
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
259
|
+
className: AgentPresetRow_module_css_default.desc,
|
|
260
|
+
role: state.error === null ? void 0 : "alert",
|
|
261
|
+
children: description
|
|
262
|
+
})]
|
|
263
|
+
}), (0, react_jsx_runtime.jsx)(PresetMenu, {
|
|
264
|
+
options: state.options,
|
|
265
|
+
selectedId: state.currentValue,
|
|
266
|
+
label,
|
|
267
|
+
t,
|
|
268
|
+
buttonClassName: AgentPresetRow_module_css_default.selector,
|
|
269
|
+
chevronClassName: AgentPresetRow_module_css_default.chevron,
|
|
270
|
+
disabled: busy || !state.writable || state.options.length === 0,
|
|
271
|
+
open,
|
|
272
|
+
onOpenChange: setOpen,
|
|
273
|
+
onSelect: (id) => {
|
|
274
|
+
select(id);
|
|
275
|
+
}
|
|
276
|
+
})]
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-agent-preset/src/client/AgentPresetSeat.module.css.mjs
|
|
281
|
+
const css$1 = ".X-X6Vq_seat{white-space:nowrap;text-overflow:ellipsis;align-items:center;max-width:min(100%,240px);display:inline-flex;overflow:hidden}.X-X6Vq_seatIcon{color:var(--dsw-alias-label-primary);flex:none}.X-X6Vq_introIcon{animation:.15s cubic-bezier(.16,1,.3,1) both X-X6Vq_seat-icon-in}@keyframes X-X6Vq_seat-icon-in{0%{opacity:0;transform:scale(.5)}to{opacity:1;transform:scale(1)}}.X-X6Vq_introText{white-space:pre;display:inline-block}.X-X6Vq_introChar{white-space:pre;opacity:0;animation:.4s ease-out forwards X-X6Vq_seat-char-in;display:inline-block}@keyframes X-X6Vq_seat-char-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.X-X6Vq_introIcon,.X-X6Vq_introChar{opacity:1;animation:none}}.X-X6Vq_chevron{color:var(--dsw-alias-label-caption);flex:none}.X-X6Vq_item{flex-direction:column;gap:2px;max-width:280px;display:flex}.X-X6Vq_itemName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.X-X6Vq_itemDesc{color:var(--dsw-alias-label-caption);white-space:normal;font-size:12px;line-height:16px}";
|
|
282
|
+
const tagId$1 = "@hydraharness/harness-client-ui-agent-preset/AgentPresetSeat.module.css";
|
|
283
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
284
|
+
const tag = document.createElement("style");
|
|
285
|
+
tag.dataset.plugin = "@hydraharness/harness-client-ui-agent-preset";
|
|
286
|
+
tag.dataset.pluginCss = tagId$1;
|
|
287
|
+
tag.textContent = css$1;
|
|
288
|
+
document.head.appendChild(tag);
|
|
289
|
+
}
|
|
290
|
+
var AgentPresetSeat_module_css_default = {
|
|
291
|
+
"chevron": "X-X6Vq_chevron",
|
|
292
|
+
"introChar": "X-X6Vq_introChar",
|
|
293
|
+
"introIcon": "X-X6Vq_introIcon",
|
|
294
|
+
"introText": "X-X6Vq_introText",
|
|
295
|
+
"item": "X-X6Vq_item",
|
|
296
|
+
"itemDesc": "X-X6Vq_itemDesc",
|
|
297
|
+
"itemName": "X-X6Vq_itemName",
|
|
298
|
+
"seat": "X-X6Vq_seat",
|
|
299
|
+
"seat-char-in": "X-X6Vq_seat-char-in",
|
|
300
|
+
"seat-icon-in": "X-X6Vq_seat-icon-in",
|
|
301
|
+
"seatIcon": "X-X6Vq_seatIcon"
|
|
302
|
+
};
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region lib/types/client/AgentPresetSeat.js
|
|
305
|
+
/**
|
|
306
|
+
* The agent-preset chip on the new-session screen, beside the workspace
|
|
307
|
+
* picker.
|
|
308
|
+
*
|
|
309
|
+
* It lives here rather than in the composer because the choice is only
|
|
310
|
+
* available before a conversation starts: once a turn has run, the session's
|
|
311
|
+
* history was produced under that preset's tools and the host refuses to swap
|
|
312
|
+
* them. A control that spends most of its life disabled belongs on the screen
|
|
313
|
+
* where it still works.
|
|
314
|
+
*
|
|
315
|
+
* The menu opens on the staged choice, which starts as the deployment default.
|
|
316
|
+
* Picking stages; the choice reaches a session when one becomes current.
|
|
317
|
+
*/
|
|
318
|
+
const INTRO_TEXT_DELAY_MS = 150;
|
|
319
|
+
const INTRO_CHAR_STAGGER_MS = 40;
|
|
320
|
+
const INTRO_TEXT_REVEAL_MS = 200;
|
|
321
|
+
const INTRO_CHAR_FADE_MS = 400;
|
|
322
|
+
/**
|
|
323
|
+
* Per-character start offset for the introduce reveal.
|
|
324
|
+
* @param count - character count of the shown preset name.
|
|
325
|
+
* @returns milliseconds between successive character starts.
|
|
326
|
+
*/
|
|
327
|
+
function introStaggerMs(count) {
|
|
328
|
+
if (count <= 1) return 0;
|
|
329
|
+
return Math.min(INTRO_CHAR_STAGGER_MS, INTRO_TEXT_REVEAL_MS / (count - 1));
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Render the new-session agent-preset chip.
|
|
333
|
+
* @param props - composed slot props.
|
|
334
|
+
* @returns the chip, or null when the deployment composes no presets.
|
|
335
|
+
*/
|
|
336
|
+
function AgentPresetSeat({ load, select, introduced, useAgentPresetSeat, t }) {
|
|
337
|
+
const state = useAgentPresetSeat((snapshot) => snapshot);
|
|
338
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
339
|
+
(0, react.useEffect)(() => {
|
|
340
|
+
load();
|
|
341
|
+
}, [load]);
|
|
342
|
+
const chosen = state.options.find((option) => option.id === state.current);
|
|
343
|
+
const label = (chosen === void 0 ? void 0 : presetDisplayText(chosen, t))?.name ?? state.current;
|
|
344
|
+
const ready = state.options.length > 0 && state.current !== "";
|
|
345
|
+
const [introducing, setIntroducing] = (0, react.useState)(false);
|
|
346
|
+
(0, react.useEffect)(() => {
|
|
347
|
+
if (!state.introduce || !ready) return;
|
|
348
|
+
const characters = Array.from(label);
|
|
349
|
+
if (characters.length === 0 || window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
350
|
+
introduced();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
setIntroducing(true);
|
|
354
|
+
const done = window.setTimeout(() => {
|
|
355
|
+
setIntroducing(false);
|
|
356
|
+
introduced();
|
|
357
|
+
}, INTRO_TEXT_DELAY_MS + (characters.length - 1) * introStaggerMs(characters.length) + INTRO_CHAR_FADE_MS);
|
|
358
|
+
return () => {
|
|
359
|
+
window.clearTimeout(done);
|
|
360
|
+
};
|
|
361
|
+
}, [
|
|
362
|
+
state.introduce,
|
|
363
|
+
ready,
|
|
364
|
+
label,
|
|
365
|
+
introduced
|
|
366
|
+
]);
|
|
367
|
+
if (!ready) return null;
|
|
368
|
+
const characters = Array.from(label);
|
|
369
|
+
const stagger = introStaggerMs(characters.length);
|
|
370
|
+
const shownLabel = introducing ? (0, react_jsx_runtime.jsx)("span", {
|
|
371
|
+
className: AgentPresetSeat_module_css_default.introText,
|
|
372
|
+
children: characters.map((character, index) => (0, react_jsx_runtime.jsx)("span", {
|
|
373
|
+
className: AgentPresetSeat_module_css_default.introChar,
|
|
374
|
+
style: { animationDelay: `${INTRO_TEXT_DELAY_MS + index * stagger}ms` },
|
|
375
|
+
children: character
|
|
376
|
+
}, index))
|
|
377
|
+
}) : label;
|
|
378
|
+
return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Menu, {
|
|
379
|
+
open,
|
|
380
|
+
onClose: () => {
|
|
381
|
+
setOpen(false);
|
|
382
|
+
},
|
|
383
|
+
items: state.options.map((option) => {
|
|
384
|
+
const text = presetDisplayText(option, t);
|
|
385
|
+
return {
|
|
386
|
+
id: option.id,
|
|
387
|
+
label: (0, react_jsx_runtime.jsxs)("span", {
|
|
388
|
+
className: AgentPresetSeat_module_css_default.item,
|
|
389
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
390
|
+
className: AgentPresetSeat_module_css_default.itemName,
|
|
391
|
+
children: text.name
|
|
392
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
393
|
+
className: AgentPresetSeat_module_css_default.itemDesc,
|
|
394
|
+
children: text.description ?? t("noDescription")
|
|
395
|
+
})]
|
|
396
|
+
})
|
|
397
|
+
};
|
|
398
|
+
}),
|
|
399
|
+
selectedId: state.current,
|
|
400
|
+
onSelect: (id) => {
|
|
401
|
+
setOpen(false);
|
|
402
|
+
select(id);
|
|
403
|
+
},
|
|
404
|
+
align: "start",
|
|
405
|
+
portal: true,
|
|
406
|
+
anchor: (0, react_jsx_runtime.jsxs)("button", {
|
|
407
|
+
"data-hydra-control": "compact",
|
|
408
|
+
type: "button",
|
|
409
|
+
className: AgentPresetSeat_module_css_default.seat,
|
|
410
|
+
"aria-haspopup": "menu",
|
|
411
|
+
"aria-expanded": open,
|
|
412
|
+
title: state.error ?? t("seatHint"),
|
|
413
|
+
disabled: state.busy,
|
|
414
|
+
onClick: () => {
|
|
415
|
+
setOpen((value) => !value);
|
|
416
|
+
},
|
|
417
|
+
children: [
|
|
418
|
+
(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconAgentPresetOutline16, { className: introducing ? `${AgentPresetSeat_module_css_default.seatIcon} ${AgentPresetSeat_module_css_default.introIcon}` : AgentPresetSeat_module_css_default.seatIcon }),
|
|
419
|
+
shownLabel,
|
|
420
|
+
(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconChevronDownOutline14, { className: AgentPresetSeat_module_css_default.chevron })
|
|
421
|
+
]
|
|
422
|
+
})
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region lib/types/client/settings-store.js
|
|
427
|
+
/**
|
|
428
|
+
* Agent-preset default-settings controller.
|
|
429
|
+
*
|
|
430
|
+
* Options and the current default both come from one `agentPreset.list` call:
|
|
431
|
+
* the roster already reports which id a session with no explicit choice gets,
|
|
432
|
+
* so the row needs no schema introspection. Writes target the settings
|
|
433
|
+
* namespace's `default` field, which is what the host resolves at creation.
|
|
434
|
+
*/
|
|
435
|
+
/** The agent-preset settings namespace on the host wire. */
|
|
436
|
+
const AGENT_PRESET_SETTINGS_NS = "agent-presets";
|
|
437
|
+
/**
|
|
438
|
+
* Human text for a rejected wire call. A transport failure rejects with an
|
|
439
|
+
* Error; a host or a runtime can reject with anything, and the surface still
|
|
440
|
+
* has to say something.
|
|
441
|
+
* @param error - the rejection value.
|
|
442
|
+
* @returns the message to show.
|
|
443
|
+
*/
|
|
444
|
+
function messageOf(error) {
|
|
445
|
+
return error instanceof Error ? error.message : String(error);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Persist one preset as the default for sessions created later.
|
|
449
|
+
*
|
|
450
|
+
* The default is a settings field rather than a preset property, so both the
|
|
451
|
+
* General row and the management section write it here — one home for which
|
|
452
|
+
* namespace and field the host resolves at session creation.
|
|
453
|
+
* @param api - the settings wire face.
|
|
454
|
+
* @param id - the preset to make default.
|
|
455
|
+
* @returns the failure message, or undefined once the write landed.
|
|
456
|
+
*/
|
|
457
|
+
async function writeDefaultPreset(api, id) {
|
|
458
|
+
let response;
|
|
459
|
+
try {
|
|
460
|
+
response = await api.settings.update({
|
|
461
|
+
ns: AGENT_PRESET_SETTINGS_NS,
|
|
462
|
+
patch: { default: id }
|
|
463
|
+
});
|
|
464
|
+
} catch (error) {
|
|
465
|
+
return messageOf(error);
|
|
466
|
+
}
|
|
467
|
+
return response.result.ok ? void 0 : response.result.error.message;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Read the roster, folding both refusal shapes into one message.
|
|
471
|
+
*
|
|
472
|
+
* The wire refuses in two ways — the transport rejects, or it answers an
|
|
473
|
+
* `ok: false` envelope — and every surface treats them identically. Folding
|
|
474
|
+
* them here keeps each store's `load` about what it does with a roster rather
|
|
475
|
+
* than about how the call can fail.
|
|
476
|
+
* @param api - the agent-preset wire face.
|
|
477
|
+
* @returns the roster, or the message to show in its place.
|
|
478
|
+
*/
|
|
479
|
+
async function readRoster(api) {
|
|
480
|
+
try {
|
|
481
|
+
const response = await api.agentPresets.list({});
|
|
482
|
+
return response.result.ok ? {
|
|
483
|
+
ok: true,
|
|
484
|
+
value: response.result.value
|
|
485
|
+
} : {
|
|
486
|
+
ok: false,
|
|
487
|
+
error: response.result.error.message
|
|
488
|
+
};
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return {
|
|
491
|
+
ok: false,
|
|
492
|
+
error: messageOf(error)
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Serialize roster refreshes, coalescing invalidations during a read into a
|
|
498
|
+
* rerun before publishing. Callers settle after the latest requested refresh.
|
|
499
|
+
* @param api - the agent-preset wire face.
|
|
500
|
+
* @param store - the surface's own snapshot store.
|
|
501
|
+
* @param accept - publish a successful roster into the surface's snapshot.
|
|
502
|
+
* @returns the refresh callback owned by this controller.
|
|
503
|
+
*/
|
|
504
|
+
function createRosterLoader(api, store, accept) {
|
|
505
|
+
let inFlight;
|
|
506
|
+
let rerun = false;
|
|
507
|
+
const needsRerun = () => rerun;
|
|
508
|
+
return () => {
|
|
509
|
+
if (inFlight !== void 0) {
|
|
510
|
+
rerun = true;
|
|
511
|
+
return inFlight;
|
|
512
|
+
}
|
|
513
|
+
inFlight = Promise.resolve().then(async () => {
|
|
514
|
+
try {
|
|
515
|
+
do {
|
|
516
|
+
store.set({
|
|
517
|
+
...store.getSnapshot(),
|
|
518
|
+
status: "loading",
|
|
519
|
+
error: null
|
|
520
|
+
});
|
|
521
|
+
rerun = false;
|
|
522
|
+
const roster = await readRoster(api);
|
|
523
|
+
if (needsRerun()) continue;
|
|
524
|
+
if (roster.ok) await accept(roster.value);
|
|
525
|
+
else store.set({
|
|
526
|
+
...store.getSnapshot(),
|
|
527
|
+
status: "error",
|
|
528
|
+
error: roster.error
|
|
529
|
+
});
|
|
530
|
+
} while (needsRerun());
|
|
531
|
+
} finally {
|
|
532
|
+
inFlight = void 0;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
return inFlight;
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* The roster entries as the pickers render them: healthy presets only.
|
|
540
|
+
*
|
|
541
|
+
* The chip and the row exist to choose the NEXT session's composition, and a
|
|
542
|
+
* broken preset cannot compose one — offering it would defer the discovery
|
|
543
|
+
* of that fact to a failed session start. The management section renders the
|
|
544
|
+
* full roster (broken rows included) from its own store instead.
|
|
545
|
+
*
|
|
546
|
+
* The chip, the row, and the management section all show the same facts, and
|
|
547
|
+
* `exactOptionalPropertyTypes` makes "absent" and "present as undefined"
|
|
548
|
+
* different shapes — so the spread dance belongs in one place rather than
|
|
549
|
+
* once per store.
|
|
550
|
+
* @param presets - the roster the host answered with.
|
|
551
|
+
* @returns one option per selectable preset, in roster order.
|
|
552
|
+
*/
|
|
553
|
+
function presetOptions(presets) {
|
|
554
|
+
return presets.filter((preset) => preset.broken === void 0).map((preset) => ({
|
|
555
|
+
id: preset.id,
|
|
556
|
+
trust: preset.trust,
|
|
557
|
+
...preset.name === void 0 ? {} : { name: preset.name },
|
|
558
|
+
...preset.description === void 0 ? {} : { description: preset.description }
|
|
559
|
+
}));
|
|
560
|
+
}
|
|
561
|
+
const INITIAL$2 = {
|
|
562
|
+
status: "idle",
|
|
563
|
+
error: null,
|
|
564
|
+
writable: true,
|
|
565
|
+
currentValue: "",
|
|
566
|
+
options: []
|
|
567
|
+
};
|
|
568
|
+
/** Reads the roster and persists the chosen default. */
|
|
569
|
+
var AgentPresetSettingsController = class {
|
|
570
|
+
api;
|
|
571
|
+
describeFace;
|
|
572
|
+
/** Row snapshot the renderer subscribes to. */
|
|
573
|
+
store = (0, _hydraharness_harness_client_runtime_client.createSnapshotStore)(INITIAL$2);
|
|
574
|
+
refresh;
|
|
575
|
+
/**
|
|
576
|
+
* @param api - the agent-preset and settings wire faces (roster and default write).
|
|
577
|
+
* @param describeFace - the shared mirror's describe face (writability source).
|
|
578
|
+
*/
|
|
579
|
+
constructor(api, describeFace) {
|
|
580
|
+
this.api = api;
|
|
581
|
+
this.describeFace = describeFace;
|
|
582
|
+
this.refresh = createRosterLoader(api, this.store, (roster) => this.acceptRoster(roster));
|
|
583
|
+
}
|
|
584
|
+
set(patch) {
|
|
585
|
+
this.store.set({
|
|
586
|
+
...this.store.getSnapshot(),
|
|
587
|
+
...patch
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Load the roster. An empty roster means the deployment composes no
|
|
592
|
+
* presets, which is a valid deployment rather than a failure — the row
|
|
593
|
+
* reports `unavailable` and renders nothing.
|
|
594
|
+
* @returns once the snapshot reflects the host.
|
|
595
|
+
*/
|
|
596
|
+
load() {
|
|
597
|
+
return this.refresh();
|
|
598
|
+
}
|
|
599
|
+
async acceptRoster(roster) {
|
|
600
|
+
const { presets } = roster;
|
|
601
|
+
const [first] = presets;
|
|
602
|
+
if (first === void 0) {
|
|
603
|
+
this.set({
|
|
604
|
+
status: "unavailable",
|
|
605
|
+
options: [],
|
|
606
|
+
currentValue: ""
|
|
607
|
+
});
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
await this.describeFace.ensure();
|
|
611
|
+
this.set({
|
|
612
|
+
status: "ready",
|
|
613
|
+
error: null,
|
|
614
|
+
writable: this.describeFace.getSnapshot().view?.writable ?? false,
|
|
615
|
+
options: presetOptions(presets),
|
|
616
|
+
currentValue: presets.find((preset) => preset.isDefault)?.id ?? first.id
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Persist one preset as the default for sessions created later. Running
|
|
621
|
+
* sessions keep the composition they were created with, so this never
|
|
622
|
+
* disturbs work in progress.
|
|
623
|
+
* @param id - the preset to make default.
|
|
624
|
+
* @returns once the write settled and the roster was re-read.
|
|
625
|
+
*/
|
|
626
|
+
async select(id) {
|
|
627
|
+
const before = this.store.getSnapshot();
|
|
628
|
+
if (before.status === "saving" || id === before.currentValue) return;
|
|
629
|
+
this.set({
|
|
630
|
+
status: "saving",
|
|
631
|
+
error: null,
|
|
632
|
+
currentValue: id
|
|
633
|
+
});
|
|
634
|
+
const failure = await writeDefaultPreset(this.api, id);
|
|
635
|
+
if (failure !== void 0) {
|
|
636
|
+
this.set({
|
|
637
|
+
status: "ready",
|
|
638
|
+
currentValue: before.currentValue,
|
|
639
|
+
error: failure
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
await this.load();
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
//#endregion
|
|
647
|
+
//#region lib/types/client/section-store.js
|
|
648
|
+
/**
|
|
649
|
+
* Agent-preset management controller: the roster as a list, a copy dialog as
|
|
650
|
+
* the only way a preset is created, and a read-only viewer over the shipped
|
|
651
|
+
* compositions.
|
|
652
|
+
*
|
|
653
|
+
* The browser edits no composition text. A new preset is a host-side copy of
|
|
654
|
+
* an existing one (`{ from, id, name? }` is all that crosses the wire), and
|
|
655
|
+
* everything after creation happens in the preset's own files — which is why
|
|
656
|
+
* the page's other job is getting the user TO those files: open the directory
|
|
657
|
+
* where the host has a desktop, show its path where it does not.
|
|
658
|
+
*
|
|
659
|
+
* The host stays the single fact source. Every mutation writes through the
|
|
660
|
+
* wire and the page re-reads the roster afterwards, because a copy changes
|
|
661
|
+
* more than the row it targeted.
|
|
662
|
+
*/
|
|
663
|
+
/** Ids a preset directory may be named, mirroring the host's own rule. */
|
|
664
|
+
const PRESET_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
665
|
+
const INITIAL$1 = {
|
|
666
|
+
status: "idle",
|
|
667
|
+
error: null,
|
|
668
|
+
authorable: false,
|
|
669
|
+
hasDocument: false,
|
|
670
|
+
rows: [],
|
|
671
|
+
copy: null,
|
|
672
|
+
view: null,
|
|
673
|
+
pendingDelete: null,
|
|
674
|
+
deleting: false,
|
|
675
|
+
revealedPaths: {}
|
|
676
|
+
};
|
|
677
|
+
/**
|
|
678
|
+
* Why this copy cannot be submitted yet, as a locale key, or undefined when
|
|
679
|
+
* it can. Client-side only: the host re-checks the id and its answer is what
|
|
680
|
+
* the dialog reports on failure.
|
|
681
|
+
* @param draft - the open copy dialog.
|
|
682
|
+
* @param rows - the roster, for the collision check.
|
|
683
|
+
* @returns the blocking reason's locale key, or undefined when submittable.
|
|
684
|
+
*/
|
|
685
|
+
function draftBlocker(draft, rows) {
|
|
686
|
+
if (draft.id === "") return "idRequired";
|
|
687
|
+
if (!PRESET_ID.test(draft.id)) return "idInvalid";
|
|
688
|
+
if (rows.some((row) => row.id === draft.id)) return "idTaken";
|
|
689
|
+
}
|
|
690
|
+
/** Reads the roster and drives the copy dialog, viewer, and location reveals. */
|
|
691
|
+
var AgentPresetSectionController = class {
|
|
692
|
+
api;
|
|
693
|
+
rosterChanged;
|
|
694
|
+
/** Page snapshot the renderer subscribes to. */
|
|
695
|
+
store = (0, _hydraharness_harness_client_runtime_client.createSnapshotStore)(INITIAL$1);
|
|
696
|
+
refresh;
|
|
697
|
+
constructor(api, rosterChanged = () => {}) {
|
|
698
|
+
this.api = api;
|
|
699
|
+
this.rosterChanged = rosterChanged;
|
|
700
|
+
this.refresh = createRosterLoader(api, this.store, (roster) => {
|
|
701
|
+
this.acceptRoster(roster);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
set(patch) {
|
|
705
|
+
this.store.set({
|
|
706
|
+
...this.store.getSnapshot(),
|
|
707
|
+
...patch
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
patchCopy(patch) {
|
|
711
|
+
const { copy } = this.store.getSnapshot();
|
|
712
|
+
if (copy === null) return;
|
|
713
|
+
this.set({ copy: {
|
|
714
|
+
...copy,
|
|
715
|
+
...patch
|
|
716
|
+
} });
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Load the roster. An empty roster means the deployment composes no
|
|
720
|
+
* presets, which is a valid deployment rather than a failure — the section
|
|
721
|
+
* reports `unavailable` and renders nothing.
|
|
722
|
+
* @returns once the snapshot reflects the host.
|
|
723
|
+
*/
|
|
724
|
+
load() {
|
|
725
|
+
return this.refresh();
|
|
726
|
+
}
|
|
727
|
+
acceptRoster(roster) {
|
|
728
|
+
const { presets, authorable, hasDocument } = roster;
|
|
729
|
+
if (presets.length === 0) {
|
|
730
|
+
this.set({
|
|
731
|
+
status: "unavailable",
|
|
732
|
+
rows: [],
|
|
733
|
+
authorable,
|
|
734
|
+
hasDocument,
|
|
735
|
+
copy: null,
|
|
736
|
+
view: null
|
|
737
|
+
});
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const revealed = this.store.getSnapshot().revealedPaths;
|
|
741
|
+
const kept = Object.fromEntries(Object.entries(revealed).filter(([id]) => presets.some((preset) => preset.id === id)));
|
|
742
|
+
this.set({
|
|
743
|
+
status: "ready",
|
|
744
|
+
error: null,
|
|
745
|
+
authorable,
|
|
746
|
+
hasDocument,
|
|
747
|
+
rows: presets.map((preset) => ({ ...preset })),
|
|
748
|
+
revealedPaths: kept
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Open one shipped preset's composition in the read-only viewer.
|
|
753
|
+
* @param id - the preset to view.
|
|
754
|
+
* @returns once the composition loaded or the failure is on the page.
|
|
755
|
+
*/
|
|
756
|
+
async view(id) {
|
|
757
|
+
this.set({ error: null });
|
|
758
|
+
try {
|
|
759
|
+
const response = await this.api.agentPresets.read({ agentPreset: id });
|
|
760
|
+
if (!response.result.ok) {
|
|
761
|
+
this.set({ error: response.result.error.message });
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const { name, content } = response.result.value;
|
|
765
|
+
this.set({ view: {
|
|
766
|
+
id,
|
|
767
|
+
title: name ?? id,
|
|
768
|
+
content
|
|
769
|
+
} });
|
|
770
|
+
} catch (error) {
|
|
771
|
+
this.set({ error: messageOf(error) });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/** Close the read-only viewer. */
|
|
775
|
+
closeView() {
|
|
776
|
+
this.set({ view: null });
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Open the copy dialog over one preset.
|
|
780
|
+
* @param from - the preset the copy will start from.
|
|
781
|
+
*/
|
|
782
|
+
beginCopy(from) {
|
|
783
|
+
const row = this.store.getSnapshot().rows.find((candidate) => candidate.id === from);
|
|
784
|
+
this.set({
|
|
785
|
+
error: null,
|
|
786
|
+
copy: {
|
|
787
|
+
from,
|
|
788
|
+
fromTitle: row?.name ?? from,
|
|
789
|
+
id: "",
|
|
790
|
+
name: "",
|
|
791
|
+
saving: false,
|
|
792
|
+
error: null
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
/** Close the copy dialog, discarding whatever was typed. */
|
|
797
|
+
cancelCopy() {
|
|
798
|
+
this.set({ copy: null });
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Name the preset the copy creates.
|
|
802
|
+
* @param id - the id typed into the dialog.
|
|
803
|
+
*/
|
|
804
|
+
setCopyId(id) {
|
|
805
|
+
this.patchCopy({
|
|
806
|
+
id,
|
|
807
|
+
error: null
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Name the copy's display name.
|
|
812
|
+
* @param name - the display name typed into the dialog.
|
|
813
|
+
*/
|
|
814
|
+
setCopyName(name) {
|
|
815
|
+
this.patchCopy({
|
|
816
|
+
name,
|
|
817
|
+
error: null
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Submit the copy, re-read the roster, then take the user to the new
|
|
822
|
+
* preset's files — the directory opens where the host has a desktop, and
|
|
823
|
+
* its path appears on the new row where it does not.
|
|
824
|
+
* @returns once the copy settled and the page reflects it.
|
|
825
|
+
*/
|
|
826
|
+
async confirmCopy() {
|
|
827
|
+
const draft = this.store.getSnapshot().copy;
|
|
828
|
+
if (draft === null || draft.saving) return;
|
|
829
|
+
if (draftBlocker(draft, this.store.getSnapshot().rows) !== void 0) return;
|
|
830
|
+
this.patchCopy({
|
|
831
|
+
saving: true,
|
|
832
|
+
error: null
|
|
833
|
+
});
|
|
834
|
+
try {
|
|
835
|
+
const name = draft.name.trim();
|
|
836
|
+
const response = await this.api.agentPresets.copy({
|
|
837
|
+
from: draft.from,
|
|
838
|
+
agentPreset: draft.id,
|
|
839
|
+
...name === "" ? {} : { name }
|
|
840
|
+
});
|
|
841
|
+
if (!response.result.ok) {
|
|
842
|
+
this.patchCopy({
|
|
843
|
+
saving: false,
|
|
844
|
+
error: response.result.error.message
|
|
845
|
+
});
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
this.set({ copy: null });
|
|
849
|
+
await this.load();
|
|
850
|
+
this.rosterChanged();
|
|
851
|
+
await this.openLocation(draft.id);
|
|
852
|
+
} catch (error) {
|
|
853
|
+
this.patchCopy({
|
|
854
|
+
saving: false,
|
|
855
|
+
error: messageOf(error)
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Open one preset's directory on the host desktop, or reveal its path on
|
|
861
|
+
* the row where the deployment has no opener to hand it to.
|
|
862
|
+
* @param id - the preset whose files the user wants.
|
|
863
|
+
* @returns once the host answered and the page reflects it.
|
|
864
|
+
*/
|
|
865
|
+
async openLocation(id) {
|
|
866
|
+
try {
|
|
867
|
+
const response = await this.api.agentPresets.openDocument({ agentPreset: id });
|
|
868
|
+
if (!response.result.ok) {
|
|
869
|
+
this.set({ error: response.result.error.message });
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (response.result.value.opened) return;
|
|
873
|
+
const { path } = response.result.value;
|
|
874
|
+
this.set({ revealedPaths: {
|
|
875
|
+
...this.store.getSnapshot().revealedPaths,
|
|
876
|
+
[id]: path
|
|
877
|
+
} });
|
|
878
|
+
} catch (error) {
|
|
879
|
+
this.set({ error: messageOf(error) });
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Ask for confirmation before deleting one preset.
|
|
884
|
+
* @param id - the preset to delete, or null to dismiss the confirmation.
|
|
885
|
+
*/
|
|
886
|
+
confirmDelete(id) {
|
|
887
|
+
if (this.store.getSnapshot().deleting) return;
|
|
888
|
+
this.set({ pendingDelete: id });
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Delete the preset awaiting confirmation, then re-read the roster.
|
|
892
|
+
*
|
|
893
|
+
* A session already composed from it keeps running: its composition was
|
|
894
|
+
* mounted at creation and nothing re-reads the file.
|
|
895
|
+
* @returns once the delete settled and the page reflects it.
|
|
896
|
+
*/
|
|
897
|
+
async remove() {
|
|
898
|
+
const { pendingDelete, deleting } = this.store.getSnapshot();
|
|
899
|
+
if (pendingDelete === null || deleting) return;
|
|
900
|
+
this.set({
|
|
901
|
+
deleting: true,
|
|
902
|
+
error: null
|
|
903
|
+
});
|
|
904
|
+
try {
|
|
905
|
+
const response = await this.api.agentPresets.remove({ agentPreset: pendingDelete });
|
|
906
|
+
if (!response.result.ok) {
|
|
907
|
+
this.set({
|
|
908
|
+
deleting: false,
|
|
909
|
+
pendingDelete: null,
|
|
910
|
+
error: response.result.error.message
|
|
911
|
+
});
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
this.set({
|
|
915
|
+
deleting: false,
|
|
916
|
+
pendingDelete: null
|
|
917
|
+
});
|
|
918
|
+
await this.load();
|
|
919
|
+
this.rosterChanged();
|
|
920
|
+
} catch (error) {
|
|
921
|
+
this.set({
|
|
922
|
+
deleting: false,
|
|
923
|
+
pendingDelete: null,
|
|
924
|
+
error: messageOf(error)
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Make one preset the default for sessions created later. Running sessions
|
|
930
|
+
* keep the composition they began with, so this never disturbs work.
|
|
931
|
+
* @param id - the preset to make default.
|
|
932
|
+
* @returns once the write settled and the roster was re-read.
|
|
933
|
+
*/
|
|
934
|
+
async makeDefault(id) {
|
|
935
|
+
const failure = await writeDefaultPreset(this.api, id);
|
|
936
|
+
if (failure !== void 0) {
|
|
937
|
+
this.set({ error: failure });
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
await this.load();
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css.mjs
|
|
945
|
+
const css = ".OMnZtW_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.OMnZtW_title{margin:0;font-size:18px;font-weight:600}.OMnZtW_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px}.OMnZtW_group{flex-direction:column;gap:10px;display:flex}.OMnZtW_group+.OMnZtW_group{margin-top:20px}.OMnZtW_groupHead{letter-spacing:.06em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;font-weight:600}.OMnZtW_cards{grid-template-columns:repeat(auto-fill,minmax(268px,1fr));grid-auto-rows:1fr;gap:12px;margin:0;padding:0;list-style:none;display:grid}.OMnZtW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;transition:border-color .16s,background .16s;display:flex}.OMnZtW_card:hover:not(.OMnZtW_cardActive){border-color:var(--dsw-alias-label-dimmed)}.OMnZtW_cardActive{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-primary)}.OMnZtW_cardBroken,.OMnZtW_cardBroken:hover{border-color:var(--dsw-alias-state-error-primary)}.OMnZtW_brokenBadge{white-space:nowrap;background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-layer-3);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.OMnZtW_cardBrokenReason{color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px;line-height:1.5}.OMnZtW_cardMain{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px 12px 0 0;flex-direction:column;flex:1;gap:8px;padding:14px 16px 12px;display:flex}.OMnZtW_cardMain:disabled{cursor:default}.OMnZtW_cardMain:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.OMnZtW_cardHead{align-items:center;gap:8px;display:flex}.OMnZtW_cardName{font-size:15px;font-weight:600;line-height:1.4}.OMnZtW_badge,.OMnZtW_inUse{white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.OMnZtW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.OMnZtW_inUse{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);margin-left:auto}.OMnZtW_cardDesc{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:4;overflow-wrap:anywhere;-webkit-box-orient:vertical;min-height:42px;font-size:13px;line-height:1.55;display:-webkit-box;overflow:hidden}.OMnZtW_cardId{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-dimmed);margin-top:auto;font-size:11px}.OMnZtW_cardFoot{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;gap:2px;padding:6px 10px;display:flex}.OMnZtW_iconButton{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:7px;align-items:center;padding:6px;display:inline-flex;position:relative}.OMnZtW_iconButton:disabled{opacity:.4;cursor:default}.OMnZtW_iconButton:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.OMnZtW_iconButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.OMnZtW_iconButton:after{content:attr(data-tip);background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);white-space:nowrap;opacity:0;pointer-events:none;border-radius:6px;padding:3px 8px;font-size:11px;line-height:17px;transition:opacity .12s;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%)}.OMnZtW_iconButton:hover:after,.OMnZtW_iconButton:focus-visible:after{opacity:1}.OMnZtW_iconDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.OMnZtW_revealedPath{color:var(--dsw-alias-label-tertiary);align-items:baseline;gap:6px;margin:0;padding:6px 16px 10px;font-size:11px;display:flex}.OMnZtW_revealedPath code{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-secondary);user-select:all;overflow-wrap:anywhere}.OMnZtW_revealedPathLabel{white-space:nowrap}.OMnZtW_secondaryButton{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 8px;font-size:12.5px}.OMnZtW_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1)}.OMnZtW_secondaryButton:disabled{opacity:.5;cursor:default}.OMnZtW_field{flex-direction:column;gap:6px;display:flex}.OMnZtW_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500}.OMnZtW_dialog{width:min(560px,100%)}.OMnZtW_dialogFields{flex-direction:column;gap:12px;display:flex}.OMnZtW_viewerCode{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);max-height:min(52vh,480px);color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);white-space:pre;tab-size:2;--hydra-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--hydra-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:10px;margin:0;padding:12px;font-size:12.5px;line-height:1.5;overflow:auto}.OMnZtW_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}.OMnZtW_deleteDialog{width:min(480px,100%)}.OMnZtW_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.OMnZtW_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.OMnZtW_creatorButton{box-sizing:border-box;border:1px dashed var(--dsw-alias-border-l3);height:44px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:12px;justify-content:center;align-self:stretch;align-items:center;gap:6px;font-size:14px;line-height:22px;display:flex}.OMnZtW_creatorButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.OMnZtW_creatorButton:disabled{opacity:.4;cursor:default}.OMnZtW_input{width:100%}";
|
|
946
|
+
const tagId = "@hydraharness/harness-client-ui-agent-preset/AgentPresetSection.module.css";
|
|
947
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
948
|
+
const tag = document.createElement("style");
|
|
949
|
+
tag.dataset.plugin = "@hydraharness/harness-client-ui-agent-preset";
|
|
950
|
+
tag.dataset.pluginCss = tagId;
|
|
951
|
+
tag.textContent = css;
|
|
952
|
+
document.head.appendChild(tag);
|
|
953
|
+
}
|
|
954
|
+
var AgentPresetSection_module_css_default = {
|
|
955
|
+
"badge": "OMnZtW_badge",
|
|
956
|
+
"brokenBadge": "OMnZtW_brokenBadge",
|
|
957
|
+
"card": "OMnZtW_card",
|
|
958
|
+
"cardActive": "OMnZtW_cardActive",
|
|
959
|
+
"cardBroken": "OMnZtW_cardBroken",
|
|
960
|
+
"cardBrokenReason": "OMnZtW_cardBrokenReason",
|
|
961
|
+
"cardDesc": "OMnZtW_cardDesc",
|
|
962
|
+
"cardFoot": "OMnZtW_cardFoot",
|
|
963
|
+
"cardHead": "OMnZtW_cardHead",
|
|
964
|
+
"cardId": "OMnZtW_cardId",
|
|
965
|
+
"cardMain": "OMnZtW_cardMain",
|
|
966
|
+
"cardName": "OMnZtW_cardName",
|
|
967
|
+
"cards": "OMnZtW_cards",
|
|
968
|
+
"creatorButton": "OMnZtW_creatorButton",
|
|
969
|
+
"deleteConfirm": "OMnZtW_deleteConfirm",
|
|
970
|
+
"deleteDialog": "OMnZtW_deleteDialog",
|
|
971
|
+
"dialog": "OMnZtW_dialog",
|
|
972
|
+
"dialogFields": "OMnZtW_dialogFields",
|
|
973
|
+
"error": "OMnZtW_error",
|
|
974
|
+
"field": "OMnZtW_field",
|
|
975
|
+
"fieldLabel": "OMnZtW_fieldLabel",
|
|
976
|
+
"group": "OMnZtW_group",
|
|
977
|
+
"groupHead": "OMnZtW_groupHead",
|
|
978
|
+
"iconButton": "OMnZtW_iconButton",
|
|
979
|
+
"iconDanger": "OMnZtW_iconDanger",
|
|
980
|
+
"inUse": "OMnZtW_inUse",
|
|
981
|
+
"input": "OMnZtW_input",
|
|
982
|
+
"intro": "OMnZtW_intro",
|
|
983
|
+
"revealedPath": "OMnZtW_revealedPath",
|
|
984
|
+
"revealedPathLabel": "OMnZtW_revealedPathLabel",
|
|
985
|
+
"secondaryButton": "OMnZtW_secondaryButton",
|
|
986
|
+
"section": "OMnZtW_section",
|
|
987
|
+
"title": "OMnZtW_title",
|
|
988
|
+
"viewerCode": "OMnZtW_viewerCode"
|
|
989
|
+
};
|
|
990
|
+
//#endregion
|
|
991
|
+
//#region lib/types/client/AgentPresetSection.js
|
|
992
|
+
/**
|
|
993
|
+
* Agent-presets settings section: the roster as cards, a copy dialog as the
|
|
994
|
+
* only way a preset is created, and a read-only viewer over the shipped
|
|
995
|
+
* compositions.
|
|
996
|
+
*
|
|
997
|
+
* The browser edits no composition text — a shipped preset opens read-only to
|
|
998
|
+
* be READ (it is the known-good composition a copy starts from), and a custom
|
|
999
|
+
* preset is edited in its own files, which is what the location action leads
|
|
1000
|
+
* to. Deleting a preset leaves running sessions alone: a composition is
|
|
1001
|
+
* mounted once at session creation and nothing re-reads the file.
|
|
1002
|
+
*/
|
|
1003
|
+
function CopyDialog({ state, t, actions }) {
|
|
1004
|
+
const draft = state.copy;
|
|
1005
|
+
const blocker = draft === null ? void 0 : draftBlocker(draft, state.rows);
|
|
1006
|
+
const message = draft === null ? null : draft.error ?? (blocker === void 0 ? null : t(blocker));
|
|
1007
|
+
const source = draft === null ? void 0 : state.rows.find((row) => row.id === draft.from);
|
|
1008
|
+
const sourceTitle = source === void 0 ? draft?.fromTitle : presetDisplayText(source, t).name;
|
|
1009
|
+
return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Modal, {
|
|
1010
|
+
open: draft !== null,
|
|
1011
|
+
onClose: () => {
|
|
1012
|
+
actions.cancelCopy();
|
|
1013
|
+
},
|
|
1014
|
+
title: draft === null ? t("copyTitle") : `${t("copyTitle")} · ${t("copyOf")} ${sourceTitle}`,
|
|
1015
|
+
closeLabel: t("close"),
|
|
1016
|
+
description: t("copyIntro"),
|
|
1017
|
+
className: AgentPresetSection_module_css_default.dialog,
|
|
1018
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Button, {
|
|
1019
|
+
variant: "outline",
|
|
1020
|
+
disabled: draft?.saving === true,
|
|
1021
|
+
onClick: () => {
|
|
1022
|
+
actions.cancelCopy();
|
|
1023
|
+
},
|
|
1024
|
+
children: t("cancel")
|
|
1025
|
+
}), (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Button, {
|
|
1026
|
+
disabled: draft === null || draft.saving || blocker !== void 0,
|
|
1027
|
+
onClick: () => {
|
|
1028
|
+
actions.confirmCopy();
|
|
1029
|
+
},
|
|
1030
|
+
children: draft?.saving === true ? t("creating") : t("create")
|
|
1031
|
+
})] }),
|
|
1032
|
+
children: draft === null ? null : (0, react_jsx_runtime.jsxs)("div", {
|
|
1033
|
+
className: AgentPresetSection_module_css_default.dialogFields,
|
|
1034
|
+
children: [
|
|
1035
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
1036
|
+
className: AgentPresetSection_module_css_default.field,
|
|
1037
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1038
|
+
className: AgentPresetSection_module_css_default.fieldLabel,
|
|
1039
|
+
children: t("presetId")
|
|
1040
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
1041
|
+
"data-hydra-control": "field",
|
|
1042
|
+
className: AgentPresetSection_module_css_default.input,
|
|
1043
|
+
value: draft.id,
|
|
1044
|
+
autoFocus: true,
|
|
1045
|
+
spellCheck: false,
|
|
1046
|
+
placeholder: t("presetIdPlaceholder"),
|
|
1047
|
+
onChange: (event) => {
|
|
1048
|
+
actions.setCopyId(event.target.value);
|
|
1049
|
+
}
|
|
1050
|
+
})]
|
|
1051
|
+
}),
|
|
1052
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
1053
|
+
className: AgentPresetSection_module_css_default.field,
|
|
1054
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1055
|
+
className: AgentPresetSection_module_css_default.fieldLabel,
|
|
1056
|
+
children: t("displayName")
|
|
1057
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
1058
|
+
"data-hydra-control": "field",
|
|
1059
|
+
className: AgentPresetSection_module_css_default.input,
|
|
1060
|
+
value: draft.name,
|
|
1061
|
+
spellCheck: false,
|
|
1062
|
+
placeholder: t("displayNamePlaceholder"),
|
|
1063
|
+
onChange: (event) => {
|
|
1064
|
+
actions.setCopyName(event.target.value);
|
|
1065
|
+
}
|
|
1066
|
+
})]
|
|
1067
|
+
}),
|
|
1068
|
+
message === null ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1069
|
+
className: AgentPresetSection_module_css_default.error,
|
|
1070
|
+
role: "alert",
|
|
1071
|
+
children: message
|
|
1072
|
+
})
|
|
1073
|
+
]
|
|
1074
|
+
})
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Render one card's description, clamped by CSS and offered in full on hover.
|
|
1079
|
+
* The tooltip is attached only while the text is actually cut off, so a short
|
|
1080
|
+
* description does not answer a hover with a bubble repeating the card.
|
|
1081
|
+
* @param props.text - the description as rendered, already localized.
|
|
1082
|
+
* @returns the description element, tooltip-anchored while it overflows.
|
|
1083
|
+
*/
|
|
1084
|
+
function CardDescription({ text }) {
|
|
1085
|
+
const ref = (0, react.useRef)(null);
|
|
1086
|
+
const [truncated, setTruncated] = (0, react.useState)(false);
|
|
1087
|
+
(0, react.useLayoutEffect)(() => {
|
|
1088
|
+
const el = ref.current;
|
|
1089
|
+
/* v8 ignore next -- the ref is attached before layout effects run. */
|
|
1090
|
+
if (el === null) return;
|
|
1091
|
+
const measure = () => {
|
|
1092
|
+
setTruncated(el.scrollHeight > el.clientHeight);
|
|
1093
|
+
};
|
|
1094
|
+
measure();
|
|
1095
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
1096
|
+
const observer = new ResizeObserver(measure);
|
|
1097
|
+
observer.observe(el);
|
|
1098
|
+
return () => {
|
|
1099
|
+
observer.disconnect();
|
|
1100
|
+
};
|
|
1101
|
+
}, [text]);
|
|
1102
|
+
return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Tooltip, {
|
|
1103
|
+
label: text,
|
|
1104
|
+
side: "bottom",
|
|
1105
|
+
delayMs: 400,
|
|
1106
|
+
disabled: !truncated,
|
|
1107
|
+
maxWidth: 360,
|
|
1108
|
+
children: (0, react_jsx_runtime.jsx)("span", {
|
|
1109
|
+
ref,
|
|
1110
|
+
className: AgentPresetSection_module_css_default.cardDesc,
|
|
1111
|
+
title: "",
|
|
1112
|
+
children: text
|
|
1113
|
+
})
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Render the Agent presets section content column.
|
|
1118
|
+
* @param props - composed slot props.
|
|
1119
|
+
* @returns the section, or null when the deployment composes no presets.
|
|
1120
|
+
*/
|
|
1121
|
+
function AgentPresetSection(props) {
|
|
1122
|
+
const { useAgentPresetSection, t, load } = props;
|
|
1123
|
+
const state = useAgentPresetSection((snapshot) => snapshot);
|
|
1124
|
+
const viewedId = state.view?.id;
|
|
1125
|
+
const viewedRow = viewedId === void 0 ? void 0 : state.rows.find((row) => row.id === viewedId);
|
|
1126
|
+
const viewedTitle = state.view === null ? "" : viewedRow === void 0 ? state.view.title : presetDisplayText(viewedRow, t).name;
|
|
1127
|
+
(0, react.useEffect)(() => {
|
|
1128
|
+
load();
|
|
1129
|
+
}, [load]);
|
|
1130
|
+
if (state.status === "unavailable") return null;
|
|
1131
|
+
if (state.status === "error") {
|
|
1132
|
+
/* v8 ignore next -- an error status always carries text; the fallback satisfies the nullable type */
|
|
1133
|
+
const detail = state.error ?? "";
|
|
1134
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1135
|
+
className: AgentPresetSection_module_css_default.section,
|
|
1136
|
+
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1137
|
+
className: AgentPresetSection_module_css_default.error,
|
|
1138
|
+
role: "alert",
|
|
1139
|
+
children: `${t("error")} ${detail}`
|
|
1140
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1141
|
+
type: "button",
|
|
1142
|
+
className: AgentPresetSection_module_css_default.secondaryButton,
|
|
1143
|
+
onClick: () => {
|
|
1144
|
+
load();
|
|
1145
|
+
},
|
|
1146
|
+
children: t("retry")
|
|
1147
|
+
})]
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
const creatorButton = props.startCreatorDraft !== void 0 && state.rows.some((row) => row.id === "cordis") ? (0, react_jsx_runtime.jsxs)("button", {
|
|
1151
|
+
type: "button",
|
|
1152
|
+
className: AgentPresetSection_module_css_default.creatorButton,
|
|
1153
|
+
disabled: !state.authorable,
|
|
1154
|
+
title: state.authorable ? void 0 : t("duplicateUnavailable"),
|
|
1155
|
+
onClick: () => {
|
|
1156
|
+
props.startCreatorDraft?.();
|
|
1157
|
+
props.close();
|
|
1158
|
+
},
|
|
1159
|
+
children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconPlusOutline16, { size: 14 }), t("creatorDraft")]
|
|
1160
|
+
}) : null;
|
|
1161
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1162
|
+
className: AgentPresetSection_module_css_default.section,
|
|
1163
|
+
children: [
|
|
1164
|
+
(0, react_jsx_runtime.jsx)("h2", {
|
|
1165
|
+
className: AgentPresetSection_module_css_default.title,
|
|
1166
|
+
children: t("nav")
|
|
1167
|
+
}),
|
|
1168
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
1169
|
+
className: AgentPresetSection_module_css_default.intro,
|
|
1170
|
+
children: t("sectionIntro")
|
|
1171
|
+
}),
|
|
1172
|
+
state.error === null ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1173
|
+
className: AgentPresetSection_module_css_default.error,
|
|
1174
|
+
role: "alert",
|
|
1175
|
+
children: state.error
|
|
1176
|
+
}),
|
|
1177
|
+
[["system", t("builtInGroup")], ["user", t("customGroup")]].map(([trust, heading]) => {
|
|
1178
|
+
const group = state.rows.filter((row) => row.trust === trust).map((row) => ({
|
|
1179
|
+
row,
|
|
1180
|
+
text: presetDisplayText(row, t)
|
|
1181
|
+
}));
|
|
1182
|
+
const tail = trust === "user" ? creatorButton : null;
|
|
1183
|
+
if (group.length === 0 && tail === null) return null;
|
|
1184
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
1185
|
+
className: AgentPresetSection_module_css_default.group,
|
|
1186
|
+
children: [
|
|
1187
|
+
(0, react_jsx_runtime.jsx)("h3", {
|
|
1188
|
+
className: AgentPresetSection_module_css_default.groupHead,
|
|
1189
|
+
children: heading
|
|
1190
|
+
}),
|
|
1191
|
+
group.length === 0 ? null : (0, react_jsx_runtime.jsx)("ul", {
|
|
1192
|
+
className: AgentPresetSection_module_css_default.cards,
|
|
1193
|
+
children: group.map(({ row, text }) => (0, react_jsx_runtime.jsxs)("li", {
|
|
1194
|
+
className: row.broken !== void 0 ? `${AgentPresetSection_module_css_default.card} ${AgentPresetSection_module_css_default.cardBroken}` : row.isDefault ? `${AgentPresetSection_module_css_default.card} ${AgentPresetSection_module_css_default.cardActive}` : AgentPresetSection_module_css_default.card,
|
|
1195
|
+
children: [
|
|
1196
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1197
|
+
type: "button",
|
|
1198
|
+
className: AgentPresetSection_module_css_default.cardMain,
|
|
1199
|
+
"aria-pressed": row.isDefault,
|
|
1200
|
+
disabled: row.isDefault || row.broken !== void 0,
|
|
1201
|
+
"aria-label": `${row.broken !== void 0 ? t("brokenBadge") : row.isDefault ? t("inUse") : t("setDefault")}: ${text.name}`,
|
|
1202
|
+
title: row.broken ?? (row.isDefault ? t("inUse") : t("setDefault")),
|
|
1203
|
+
onClick: () => {
|
|
1204
|
+
props.makeDefault(row.id);
|
|
1205
|
+
},
|
|
1206
|
+
children: [
|
|
1207
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
1208
|
+
className: AgentPresetSection_module_css_default.cardHead,
|
|
1209
|
+
children: [
|
|
1210
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1211
|
+
className: AgentPresetSection_module_css_default.cardName,
|
|
1212
|
+
children: text.name
|
|
1213
|
+
}),
|
|
1214
|
+
row.broken !== void 0 ? (0, react_jsx_runtime.jsx)("span", {
|
|
1215
|
+
className: AgentPresetSection_module_css_default.brokenBadge,
|
|
1216
|
+
children: t("brokenBadge")
|
|
1217
|
+
}) : null,
|
|
1218
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1219
|
+
className: AgentPresetSection_module_css_default.badge,
|
|
1220
|
+
children: row.trust === "user" ? t("userTrust") : t("builtIn")
|
|
1221
|
+
}),
|
|
1222
|
+
row.isDefault ? (0, react_jsx_runtime.jsx)("span", {
|
|
1223
|
+
className: AgentPresetSection_module_css_default.inUse,
|
|
1224
|
+
children: t("inUse")
|
|
1225
|
+
}) : null
|
|
1226
|
+
]
|
|
1227
|
+
}),
|
|
1228
|
+
(0, react_jsx_runtime.jsx)(CardDescription, { text: text.description ?? t("noDescription") }),
|
|
1229
|
+
row.broken === void 0 ? null : (0, react_jsx_runtime.jsx)("span", {
|
|
1230
|
+
className: AgentPresetSection_module_css_default.cardBrokenReason,
|
|
1231
|
+
role: "alert",
|
|
1232
|
+
children: row.broken
|
|
1233
|
+
}),
|
|
1234
|
+
(0, react_jsx_runtime.jsx)("code", {
|
|
1235
|
+
className: AgentPresetSection_module_css_default.cardId,
|
|
1236
|
+
children: row.id
|
|
1237
|
+
})
|
|
1238
|
+
]
|
|
1239
|
+
}),
|
|
1240
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1241
|
+
className: AgentPresetSection_module_css_default.cardFoot,
|
|
1242
|
+
children: [
|
|
1243
|
+
row.trust === "system" ? row.broken === void 0 ? (0, react_jsx_runtime.jsx)("button", {
|
|
1244
|
+
type: "button",
|
|
1245
|
+
className: AgentPresetSection_module_css_default.iconButton,
|
|
1246
|
+
"data-tip": t("view"),
|
|
1247
|
+
"aria-label": `${t("view")}: ${text.name}`,
|
|
1248
|
+
onClick: () => {
|
|
1249
|
+
props.view(row.id);
|
|
1250
|
+
},
|
|
1251
|
+
children: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconBrowseOutline16, {})
|
|
1252
|
+
}) : null : (0, react_jsx_runtime.jsx)("button", {
|
|
1253
|
+
type: "button",
|
|
1254
|
+
className: AgentPresetSection_module_css_default.iconButton,
|
|
1255
|
+
"data-tip": state.hasDocument ? t("openLocation") : t("showLocation"),
|
|
1256
|
+
"aria-label": `${state.hasDocument ? t("openLocation") : t("showLocation")}: ${text.name}`,
|
|
1257
|
+
onClick: () => {
|
|
1258
|
+
props.openLocation(row.id);
|
|
1259
|
+
},
|
|
1260
|
+
children: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconFolderOpenOutline16, {})
|
|
1261
|
+
}),
|
|
1262
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1263
|
+
type: "button",
|
|
1264
|
+
className: AgentPresetSection_module_css_default.iconButton,
|
|
1265
|
+
disabled: !state.authorable || row.broken !== void 0,
|
|
1266
|
+
"data-tip": row.broken !== void 0 ? t("brokenNoCopy") : state.authorable ? t("duplicate") : t("duplicateUnavailable"),
|
|
1267
|
+
"aria-label": `${t("duplicate")}: ${text.name}`,
|
|
1268
|
+
onClick: () => {
|
|
1269
|
+
props.beginCopy(row.id);
|
|
1270
|
+
},
|
|
1271
|
+
children: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconCopyOutline16, {})
|
|
1272
|
+
}),
|
|
1273
|
+
row.trust === "user" ? (0, react_jsx_runtime.jsx)("button", {
|
|
1274
|
+
type: "button",
|
|
1275
|
+
className: `${AgentPresetSection_module_css_default.iconButton} ${AgentPresetSection_module_css_default.iconDanger}`,
|
|
1276
|
+
"data-tip": t("delete"),
|
|
1277
|
+
"aria-label": `${t("delete")}: ${text.name}`,
|
|
1278
|
+
onClick: () => {
|
|
1279
|
+
props.confirmDelete(row.id);
|
|
1280
|
+
},
|
|
1281
|
+
children: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconTrashOutline16, {})
|
|
1282
|
+
}) : null
|
|
1283
|
+
]
|
|
1284
|
+
}),
|
|
1285
|
+
state.revealedPaths[row.id] === void 0 ? null : (0, react_jsx_runtime.jsxs)("p", {
|
|
1286
|
+
className: AgentPresetSection_module_css_default.revealedPath,
|
|
1287
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1288
|
+
className: AgentPresetSection_module_css_default.revealedPathLabel,
|
|
1289
|
+
children: t("revealedPathLabel")
|
|
1290
|
+
}), (0, react_jsx_runtime.jsx)("code", { children: state.revealedPaths[row.id] })]
|
|
1291
|
+
})
|
|
1292
|
+
]
|
|
1293
|
+
}, row.id))
|
|
1294
|
+
}),
|
|
1295
|
+
tail
|
|
1296
|
+
]
|
|
1297
|
+
}, trust);
|
|
1298
|
+
}),
|
|
1299
|
+
(0, react_jsx_runtime.jsx)(CopyDialog, {
|
|
1300
|
+
state,
|
|
1301
|
+
t,
|
|
1302
|
+
actions: {
|
|
1303
|
+
cancelCopy: props.cancelCopy,
|
|
1304
|
+
confirmCopy: props.confirmCopy,
|
|
1305
|
+
setCopyId: props.setCopyId,
|
|
1306
|
+
setCopyName: props.setCopyName
|
|
1307
|
+
}
|
|
1308
|
+
}),
|
|
1309
|
+
(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Modal, {
|
|
1310
|
+
open: state.view !== null,
|
|
1311
|
+
onClose: () => {
|
|
1312
|
+
props.closeView();
|
|
1313
|
+
},
|
|
1314
|
+
title: state.view === null ? "" : `${t("view")} · ${viewedTitle}`,
|
|
1315
|
+
closeLabel: t("close"),
|
|
1316
|
+
description: t("composition"),
|
|
1317
|
+
className: AgentPresetSection_module_css_default.dialog,
|
|
1318
|
+
footer: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Button, {
|
|
1319
|
+
variant: "outline",
|
|
1320
|
+
autoFocus: true,
|
|
1321
|
+
onClick: () => {
|
|
1322
|
+
props.closeView();
|
|
1323
|
+
},
|
|
1324
|
+
children: t("close")
|
|
1325
|
+
}),
|
|
1326
|
+
children: state.view === null ? null : (0, react_jsx_runtime.jsx)("pre", {
|
|
1327
|
+
className: AgentPresetSection_module_css_default.viewerCode,
|
|
1328
|
+
children: state.view.content
|
|
1329
|
+
})
|
|
1330
|
+
}),
|
|
1331
|
+
(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Modal, {
|
|
1332
|
+
open: state.pendingDelete !== null,
|
|
1333
|
+
onClose: () => {
|
|
1334
|
+
props.confirmDelete(null);
|
|
1335
|
+
},
|
|
1336
|
+
title: t("deleteTitle"),
|
|
1337
|
+
closeLabel: t("close"),
|
|
1338
|
+
description: t("deleteDescription"),
|
|
1339
|
+
className: AgentPresetSection_module_css_default.deleteDialog,
|
|
1340
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Button, {
|
|
1341
|
+
variant: "outline",
|
|
1342
|
+
autoFocus: true,
|
|
1343
|
+
disabled: state.deleting,
|
|
1344
|
+
onClick: () => {
|
|
1345
|
+
props.confirmDelete(null);
|
|
1346
|
+
},
|
|
1347
|
+
children: t("cancel")
|
|
1348
|
+
}), (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.Button, {
|
|
1349
|
+
variant: "outline",
|
|
1350
|
+
className: AgentPresetSection_module_css_default.deleteConfirm,
|
|
1351
|
+
disabled: state.deleting,
|
|
1352
|
+
onClick: () => {
|
|
1353
|
+
props.remove();
|
|
1354
|
+
},
|
|
1355
|
+
children: state.deleting ? t("deleting") : t("deleteConfirm")
|
|
1356
|
+
})] })
|
|
1357
|
+
})
|
|
1358
|
+
]
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
//#endregion
|
|
1362
|
+
//#region lib/types/client/seat-store.js
|
|
1363
|
+
/**
|
|
1364
|
+
* Hero-chip controller: which preset the NEXT session gets.
|
|
1365
|
+
*
|
|
1366
|
+
* The new-session screen has no session, so a pick is staged rather than
|
|
1367
|
+
* applied. It reaches a session when one becomes current and is still blank —
|
|
1368
|
+
* whether the workspace connect created it or reused an existing blank one,
|
|
1369
|
+
* which is why staging cannot simply ride along on `sessions.create`.
|
|
1370
|
+
*
|
|
1371
|
+
* The stage is forgotten once applied: the next new session starts from the
|
|
1372
|
+
* deployment default again, matching the workspace picker beside it.
|
|
1373
|
+
*/
|
|
1374
|
+
const INITIAL = {
|
|
1375
|
+
options: [],
|
|
1376
|
+
current: "",
|
|
1377
|
+
error: null,
|
|
1378
|
+
busy: false,
|
|
1379
|
+
introduce: false
|
|
1380
|
+
};
|
|
1381
|
+
/** Stages the next session's preset and applies it when one appears. */
|
|
1382
|
+
var AgentPresetSeatController = class {
|
|
1383
|
+
api;
|
|
1384
|
+
currentSession;
|
|
1385
|
+
onApplied;
|
|
1386
|
+
/** Chip snapshot the renderer subscribes to. */
|
|
1387
|
+
store = (0, _hydraharness_harness_client_runtime_client.createSnapshotStore)(INITIAL);
|
|
1388
|
+
/**
|
|
1389
|
+
* The deployment default, so a consumed stage can fall back to it without
|
|
1390
|
+
* re-reading the roster.
|
|
1391
|
+
*/
|
|
1392
|
+
fallback = "";
|
|
1393
|
+
/** Set while a pick is waiting for a session; cleared once applied. */
|
|
1394
|
+
staged;
|
|
1395
|
+
constructor(api, currentSession, onApplied) {
|
|
1396
|
+
this.api = api;
|
|
1397
|
+
this.currentSession = currentSession;
|
|
1398
|
+
this.onApplied = onApplied;
|
|
1399
|
+
}
|
|
1400
|
+
set(patch) {
|
|
1401
|
+
this.store.set({
|
|
1402
|
+
...this.store.getSnapshot(),
|
|
1403
|
+
...patch
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Read the roster and open the chip on the deployment default.
|
|
1408
|
+
* @returns once the snapshot reflects the host.
|
|
1409
|
+
*/
|
|
1410
|
+
async load() {
|
|
1411
|
+
try {
|
|
1412
|
+
const response = await this.api.agentPresets.list({});
|
|
1413
|
+
if (!response.result.ok) {
|
|
1414
|
+
this.set({ error: response.result.error.message });
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
const { presets } = response.result.value;
|
|
1418
|
+
this.fallback = presets.find((preset) => preset.isDefault)?.id ?? presets[0]?.id ?? "";
|
|
1419
|
+
this.set({
|
|
1420
|
+
options: presetOptions(presets),
|
|
1421
|
+
current: this.staged ?? this.currentSession()?.agentPreset ?? this.fallback,
|
|
1422
|
+
error: null
|
|
1423
|
+
});
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
this.set({ error: messageOf(error) });
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Stage one preset for the next session, applying it immediately when a
|
|
1430
|
+
* blank session is already current.
|
|
1431
|
+
* @param id - the preset to stage.
|
|
1432
|
+
* @returns once the stage settled, and the apply too when one happened.
|
|
1433
|
+
*/
|
|
1434
|
+
async select(id) {
|
|
1435
|
+
if (this.store.getSnapshot().busy) return;
|
|
1436
|
+
this.stage(id);
|
|
1437
|
+
await this.apply();
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Stage a pick WITHOUT the immediate apply, for a flow that starts the
|
|
1441
|
+
* receiving session after the pick (the settings section's creator entry).
|
|
1442
|
+
* `select()`'s immediate apply would meet the still-current running session
|
|
1443
|
+
* and drop the stage as unservable; staging alone leaves it for the
|
|
1444
|
+
* list-change applier, which fires when the started session becomes
|
|
1445
|
+
* current.
|
|
1446
|
+
* @param id - the preset to stage.
|
|
1447
|
+
* @param introduce - true when the stage came from another screen and the
|
|
1448
|
+
* chip should announce itself on the session it lands on.
|
|
1449
|
+
*/
|
|
1450
|
+
stage(id, introduce = false) {
|
|
1451
|
+
this.staged = id;
|
|
1452
|
+
this.set({
|
|
1453
|
+
current: id,
|
|
1454
|
+
error: null,
|
|
1455
|
+
introduce
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
/** Acknowledge the introduction cue once the chip has played it. */
|
|
1459
|
+
introduced() {
|
|
1460
|
+
if (!this.store.getSnapshot().introduce) return;
|
|
1461
|
+
this.set({ introduce: false });
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Hand the staged choice to the current session, if there is one to take it.
|
|
1465
|
+
*
|
|
1466
|
+
* Called both by `select()` and by whoever observes the current session
|
|
1467
|
+
* changing, because the session may appear either before or after the pick.
|
|
1468
|
+
* @returns once the switch settled, or immediately when there is nothing to do.
|
|
1469
|
+
*/
|
|
1470
|
+
async apply() {
|
|
1471
|
+
const staged = this.staged;
|
|
1472
|
+
const session = this.currentSession();
|
|
1473
|
+
if (staged === void 0 || session === void 0) return;
|
|
1474
|
+
if (!session.blank || session.agentPreset === staged) {
|
|
1475
|
+
this.staged = void 0;
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
this.set({
|
|
1479
|
+
busy: true,
|
|
1480
|
+
error: null
|
|
1481
|
+
});
|
|
1482
|
+
try {
|
|
1483
|
+
const response = await this.api.agentPresets.select({
|
|
1484
|
+
sessionId: session.id,
|
|
1485
|
+
agentPreset: staged
|
|
1486
|
+
});
|
|
1487
|
+
this.staged = void 0;
|
|
1488
|
+
if (!response.result.ok) {
|
|
1489
|
+
this.set({
|
|
1490
|
+
busy: false,
|
|
1491
|
+
error: response.result.error.message,
|
|
1492
|
+
current: this.fallback
|
|
1493
|
+
});
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
this.set({
|
|
1497
|
+
busy: false,
|
|
1498
|
+
current: response.result.value.agentPreset
|
|
1499
|
+
});
|
|
1500
|
+
this.onApplied?.(session.id, response.result.value.agentPreset);
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
this.staged = void 0;
|
|
1503
|
+
this.set({
|
|
1504
|
+
busy: false,
|
|
1505
|
+
error: messageOf(error),
|
|
1506
|
+
current: this.fallback
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
//#endregion
|
|
1512
|
+
//#region lib/types/client/index.js
|
|
1513
|
+
/**
|
|
1514
|
+
* Agent-preset surface plugin, browser half — four surfaces over one roster:
|
|
1515
|
+
* a General-settings row for the default preset, a chip on the new-session
|
|
1516
|
+
* screen for the session about to start, a read-only label in the session
|
|
1517
|
+
* header, and a settings section that manages the roster (copy, delete,
|
|
1518
|
+
* default, and the way into a preset's own files).
|
|
1519
|
+
*
|
|
1520
|
+
* A running session keeps the composition it began with (the host refuses to
|
|
1521
|
+
* adopt an existing session under a different preset). That is what splits
|
|
1522
|
+
* the choice from the display: the General row and the hero chip are both
|
|
1523
|
+
* before-the-fact, while the header only reports what a session already runs.
|
|
1524
|
+
*/
|
|
1525
|
+
/** Required services (cordis fiber inject). */
|
|
1526
|
+
const inject = [
|
|
1527
|
+
"slots",
|
|
1528
|
+
"locale",
|
|
1529
|
+
"connection",
|
|
1530
|
+
"remote",
|
|
1531
|
+
"settingsScope"
|
|
1532
|
+
];
|
|
1533
|
+
/**
|
|
1534
|
+
* Mount the General-settings row.
|
|
1535
|
+
* @param ctx - the browser plugin context.
|
|
1536
|
+
*/
|
|
1537
|
+
function apply(ctx) {
|
|
1538
|
+
const { api } = ctx.get("connection");
|
|
1539
|
+
const controller = new AgentPresetSettingsController(api, ctx.settingsScope.describe());
|
|
1540
|
+
const rosterReaders = /* @__PURE__ */ new Set();
|
|
1541
|
+
const section = new AgentPresetSectionController(api, () => {
|
|
1542
|
+
controller.load();
|
|
1543
|
+
for (const read of rosterReaders) read();
|
|
1544
|
+
});
|
|
1545
|
+
ctx.effect(() => ctx.locale.register("settings.agentPreset", { en }), "ui-agent-preset: settings row dictionaries");
|
|
1546
|
+
const injected = () => ({
|
|
1547
|
+
hooks: { agentPreset: controller.store },
|
|
1548
|
+
load: () => controller.load(),
|
|
1549
|
+
select: (id) => controller.select(id)
|
|
1550
|
+
});
|
|
1551
|
+
ctx.effect(() => {
|
|
1552
|
+
const refresh = () => {
|
|
1553
|
+
controller.load();
|
|
1554
|
+
if (section.store.getSnapshot().status !== "idle") section.load();
|
|
1555
|
+
};
|
|
1556
|
+
const disposers = [ctx.remote.$on("settings/document-updated", (ns) => {
|
|
1557
|
+
if (ns !== "agent-presets") return;
|
|
1558
|
+
refresh();
|
|
1559
|
+
}), ctx.on("connection/reset", () => {
|
|
1560
|
+
refresh();
|
|
1561
|
+
})];
|
|
1562
|
+
return () => {
|
|
1563
|
+
for (const dispose of disposers) dispose();
|
|
1564
|
+
};
|
|
1565
|
+
}, "ui-agent-preset: settings refresh");
|
|
1566
|
+
let creatorDraft;
|
|
1567
|
+
ctx.inject([
|
|
1568
|
+
"slots",
|
|
1569
|
+
"conversation",
|
|
1570
|
+
"sessions",
|
|
1571
|
+
"workspaces"
|
|
1572
|
+
], (scope) => {
|
|
1573
|
+
const api = scope.get("connection").api;
|
|
1574
|
+
const seat = new AgentPresetSeatController(api, () => {
|
|
1575
|
+
const state = scope.sessions.list.getSnapshot();
|
|
1576
|
+
const summary = state.current === void 0 ? void 0 : state.byId[state.current];
|
|
1577
|
+
return summary === void 0 ? void 0 : {
|
|
1578
|
+
id: summary.id,
|
|
1579
|
+
blank: summary.blank,
|
|
1580
|
+
...summary.agentPreset === void 0 ? {} : { agentPreset: summary.agentPreset }
|
|
1581
|
+
};
|
|
1582
|
+
}, (sessionId, agentPreset) => {
|
|
1583
|
+
scope.sessions.noteAgentPreset(sessionId, agentPreset);
|
|
1584
|
+
});
|
|
1585
|
+
const seatInjected = () => ({
|
|
1586
|
+
hooks: { agentPresetSeat: seat.store },
|
|
1587
|
+
load: () => seat.load(),
|
|
1588
|
+
select: (id) => seat.select(id),
|
|
1589
|
+
introduced: () => {
|
|
1590
|
+
seat.introduced();
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
const labelInjected = () => ({
|
|
1594
|
+
hooks: { agentPresets: controller.store },
|
|
1595
|
+
load: () => controller.load()
|
|
1596
|
+
});
|
|
1597
|
+
scope.effect(() => {
|
|
1598
|
+
const stop = scope.sessions.list.subscribe(() => {
|
|
1599
|
+
seat.apply();
|
|
1600
|
+
});
|
|
1601
|
+
const settingsMoved = scope.remote.$on("settings/document-updated", (ns) => {
|
|
1602
|
+
if (ns !== "agent-presets") return;
|
|
1603
|
+
seat.load();
|
|
1604
|
+
});
|
|
1605
|
+
const presetSelected = scope.remote.$on("agent-preset/selected", (sessionId, agentPreset) => {
|
|
1606
|
+
scope.sessions.noteAgentPreset(sessionId, agentPreset);
|
|
1607
|
+
});
|
|
1608
|
+
const readRoster = () => {
|
|
1609
|
+
seat.load();
|
|
1610
|
+
};
|
|
1611
|
+
rosterReaders.add(readRoster);
|
|
1612
|
+
creatorDraft = () => {
|
|
1613
|
+
seat.stage("cordis", true);
|
|
1614
|
+
scope.workspaces.startSession();
|
|
1615
|
+
};
|
|
1616
|
+
const chip = scope.slots.register({
|
|
1617
|
+
name: "conversation.hero.agentPreset",
|
|
1618
|
+
locale: "settings.agentPreset",
|
|
1619
|
+
inject: seatInjected
|
|
1620
|
+
}, AgentPresetSeat);
|
|
1621
|
+
const label = scope.slots.register({
|
|
1622
|
+
name: "conversation.session.header.actions",
|
|
1623
|
+
id: "agent-preset",
|
|
1624
|
+
order: -10,
|
|
1625
|
+
locale: "settings.agentPreset",
|
|
1626
|
+
inject: labelInjected
|
|
1627
|
+
}, AgentPresetLabel);
|
|
1628
|
+
return () => {
|
|
1629
|
+
stop();
|
|
1630
|
+
settingsMoved();
|
|
1631
|
+
presetSelected();
|
|
1632
|
+
rosterReaders.delete(readRoster);
|
|
1633
|
+
creatorDraft = void 0;
|
|
1634
|
+
chip();
|
|
1635
|
+
label();
|
|
1636
|
+
};
|
|
1637
|
+
}, "ui-agent-preset: new-session chip and header label");
|
|
1638
|
+
});
|
|
1639
|
+
const sectionInjected = () => ({
|
|
1640
|
+
hooks: { agentPresetSection: section.store },
|
|
1641
|
+
load: () => section.load(),
|
|
1642
|
+
view: (id) => section.view(id),
|
|
1643
|
+
closeView: () => {
|
|
1644
|
+
section.closeView();
|
|
1645
|
+
},
|
|
1646
|
+
beginCopy: (from) => {
|
|
1647
|
+
section.beginCopy(from);
|
|
1648
|
+
},
|
|
1649
|
+
cancelCopy: () => {
|
|
1650
|
+
section.cancelCopy();
|
|
1651
|
+
},
|
|
1652
|
+
setCopyId: (id) => {
|
|
1653
|
+
section.setCopyId(id);
|
|
1654
|
+
},
|
|
1655
|
+
setCopyName: (name) => {
|
|
1656
|
+
section.setCopyName(name);
|
|
1657
|
+
},
|
|
1658
|
+
confirmCopy: () => section.confirmCopy(),
|
|
1659
|
+
openLocation: (id) => section.openLocation(id),
|
|
1660
|
+
...creatorDraft === void 0 ? {} : { startCreatorDraft: creatorDraft },
|
|
1661
|
+
confirmDelete: (id) => {
|
|
1662
|
+
section.confirmDelete(id);
|
|
1663
|
+
},
|
|
1664
|
+
remove: () => section.remove(),
|
|
1665
|
+
makeDefault: (id) => section.makeDefault(id)
|
|
1666
|
+
});
|
|
1667
|
+
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
1668
|
+
name: "settings.general.item",
|
|
1669
|
+
id: "agent-preset",
|
|
1670
|
+
order: -25,
|
|
1671
|
+
locale: "settings.agentPreset",
|
|
1672
|
+
inject: injected
|
|
1673
|
+
}, AgentPresetRow));
|
|
1674
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1675
|
+
name: "settings.section",
|
|
1676
|
+
id: "agent-presets",
|
|
1677
|
+
order: 20,
|
|
1678
|
+
label: () => ctx.locale.bind("settings.agentPreset")("nav"),
|
|
1679
|
+
locale: "settings.agentPreset",
|
|
1680
|
+
inject: sectionInjected
|
|
1681
|
+
}, AgentPresetSection));
|
|
1682
|
+
}
|
|
1683
|
+
//#endregion
|
|
1684
|
+
exports.AGENT_PRESET_SETTINGS_NS = AGENT_PRESET_SETTINGS_NS;
|
|
1685
|
+
exports.apply = apply;
|
|
1686
|
+
exports.draftBlocker = draftBlocker;
|
|
1687
|
+
exports.inject = inject;
|
|
1688
|
+
exports.writeDefaultPreset = writeDefaultPreset;
|
|
1689
|
+
return module.exports;
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
//# sourceMappingURL=client.js.map
|