@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/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